diff --git a/crypto/openssh/.github/ci-status.md b/crypto/openssh/.github/ci-status.md index f3e088fd6043..8d4cea10dba4 100644 --- a/crypto/openssh/.github/ci-status.md +++ b/crypto/openssh/.github/ci-status.md @@ -1,11 +1,15 @@ master : [![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:master) [![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:master) [![Upstream self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/upstream.yml/badge.svg)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/upstream.yml?query=branch:master) [![CIFuzz](https://github.com/openssh/openssh-portable/actions/workflows/cifuzz.yml/badge.svg)](https://github.com/openssh/openssh-portable/actions/workflows/cifuzz.yml) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/openssh.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:openssh) [![Coverity Status](https://scan.coverity.com/projects/21341/badge.svg)](https://scan.coverity.com/projects/openssh-portable) +9.4 : +[![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg?branch=V_9_4)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_4) +[![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg?branch=V_9_4)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_4) + 9.3 : [![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg?branch=V_9_3)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_3) [![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg?branch=V_9_3)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_3) diff --git a/crypto/openssh/.github/configs b/crypto/openssh/.github/configs index e054eb3196b5..c7d6a55ab962 100755 --- a/crypto/openssh/.github/configs +++ b/crypto/openssh/.github/configs @@ -1,340 +1,351 @@ #!/bin/sh # # usage: configs vmname test_config (or '' for default) # # Sets the following variables: # CONFIGFLAGS options to ./configure # SSHD_CONFOPTS sshd_config options # TEST_TARGET make target used when testing. defaults to "tests". # LTESTS config=$1 if [ "$config" = "" ]; then config="default" fi unset CC CFLAGS CPPFLAGS LDFLAGS LTESTS SUDO TEST_TARGET="tests compat-tests" LTESTS="" SKIP_LTESTS="" SUDO=sudo # run with sudo by default TEST_SSH_UNSAFE_PERMISSIONS=1 # Stop on first test failure to minimize logs TEST_SSH_FAIL_FATAL=yes CONFIGFLAGS="" LIBCRYPTOFLAGS="" case "$config" in default|sol64) ;; c89) + # If we don't have LLONG_MAX, configure will figure out that it can + # get it by setting -std=gnu99, at which point we won't be testing + # C89 any more. To avoid this, feed it in via CFLAGS. + llong_max=`gcc -E -dM - /dev/null)" ]; then REGRESS_INTEROP_PUTTY=yes export REGRESS_INTEROP_PUTTY fi export CC CFLAGS CPPFLAGS LDFLAGS LTESTS SUDO export TEST_TARGET TEST_SSH_UNSAFE_PERMISSIONS TEST_SSH_FAIL_FATAL diff --git a/crypto/openssh/.github/setup_ci.sh b/crypto/openssh/.github/setup_ci.sh index 154f51bdc205..010a333a6642 100755 --- a/crypto/openssh/.github/setup_ci.sh +++ b/crypto/openssh/.github/setup_ci.sh @@ -1,216 +1,224 @@ #!/bin/sh PACKAGES="" . .github/configs $@ host=`./config.guess` echo "config.guess: $host" case "$host" in *cygwin) PACKAGER=setup echo Setting CYGWIN system environment variable. setx CYGWIN "binmode" echo Removing extended ACLs so umask works as expected. setfacl -b . regress PACKAGES="$PACKAGES,autoconf,automake,cygwin-devel,gcc-core" PACKAGES="$PACKAGES,make,openssl-devel,zlib-devel" ;; *-darwin*) PACKAGER=brew brew install automake exit 0 ;; *) PACKAGER=apt esac TARGETS=$@ INSTALL_FIDO_PPA="no" export DEBIAN_FRONTEND=noninteractive #echo "Setting up for '$TARGETS'" set -ex if [ -x "`which lsb_release 2>&1`" ]; then lsb_release -a fi # Ubuntu 22.04 defaults to private home dirs which prevent the # agent-getpeerid test from running ssh-add as nobody. See # https://github.com/actions/runner-images/issues/6106 if [ ! -z "$SUDO" ] && ! "$SUDO" -u nobody test -x ~; then echo ~ is not executable by nobody, adding perms. chmod go+x ~ fi if [ "${TARGETS}" = "kitchensink" ]; then TARGETS="krb5 libedit pam sk selinux" fi for flag in $CONFIGFLAGS; do case "$flag" in --with-pam) TARGETS="${TARGETS} pam" ;; --with-libedit) TARGETS="${TARGETS} libedit" ;; esac done for TARGET in $TARGETS; do case $TARGET in default|without-openssl|without-zlib|c89) # nothing to do ;; clang-sanitize*) PACKAGES="$PACKAGES clang-12" ;; cygwin-release) PACKAGES="$PACKAGES libcrypt-devel libfido2-devel libkrb5-devel" ;; gcc-sanitize*) ;; clang-*|gcc-*) compiler=$(echo $TARGET | sed 's/-Werror//') PACKAGES="$PACKAGES $compiler" ;; krb5) PACKAGES="$PACKAGES libkrb5-dev" ;; heimdal) PACKAGES="$PACKAGES heimdal-dev" ;; libedit) case "$PACKAGER" in setup) PACKAGES="$PACKAGES libedit-devel" ;; apt) PACKAGES="$PACKAGES libedit-dev" ;; esac ;; *pam) PACKAGES="$PACKAGES libpam0g-dev" ;; sk) INSTALL_FIDO_PPA="yes" PACKAGES="$PACKAGES libfido2-dev libu2f-host-dev libcbor-dev" ;; selinux) PACKAGES="$PACKAGES libselinux1-dev selinux-policy-dev" ;; hardenedmalloc) INSTALL_HARDENED_MALLOC=yes ;; musl) PACKAGES="$PACKAGES musl-tools" ;; tcmalloc) PACKAGES="$PACKAGES libgoogle-perftools-dev" ;; openssl-noec) INSTALL_OPENSSL=OpenSSL_1_1_1k SSLCONFOPTS="no-ec" ;; openssl-*) INSTALL_OPENSSL=$(echo ${TARGET} | cut -f2 -d-) case ${INSTALL_OPENSSL} in 1.1.1_stable) INSTALL_OPENSSL="OpenSSL_1_1_1-stable" ;; 1.*) INSTALL_OPENSSL="OpenSSL_$(echo ${INSTALL_OPENSSL} | tr . _)" ;; 3.*) INSTALL_OPENSSL="openssl-${INSTALL_OPENSSL}" ;; esac PACKAGES="${PACKAGES} putty-tools" ;; libressl-*) INSTALL_LIBRESSL=$(echo ${TARGET} | cut -f2 -d-) case ${INSTALL_LIBRESSL} in master) ;; *) INSTALL_LIBRESSL="$(echo ${TARGET} | cut -f2 -d-)" ;; esac PACKAGES="${PACKAGES} putty-tools" ;; boringssl) INSTALL_BORINGSSL=1 PACKAGES="${PACKAGES} cmake ninja-build" ;; valgrind*) PACKAGES="$PACKAGES valgrind" ;; + zlib-*) + ;; *) echo "Invalid option '${TARGET}'" exit 1 ;; esac done if [ "yes" = "$INSTALL_FIDO_PPA" ]; then sudo apt update -qq sudo apt install -qy software-properties-common sudo apt-add-repository -y ppa:yubico/stable fi tries=3 while [ ! -z "$PACKAGES" ] && [ "$tries" -gt "0" ]; do case "$PACKAGER" in apt) sudo apt update -qq if sudo apt install -qy $PACKAGES; then PACKAGES="" fi ;; setup) if /cygdrive/c/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then PACKAGES="" fi ;; esac if [ ! -z "$PACKAGES" ]; then sleep 90 fi tries=$(($tries - 1)) done if [ ! -z "$PACKAGES" ]; then echo "Package installation failed." exit 1 fi if [ "${INSTALL_HARDENED_MALLOC}" = "yes" ]; then (cd ${HOME} && git clone https://github.com/GrapheneOS/hardened_malloc.git && cd ${HOME}/hardened_malloc && make -j2 && sudo cp out/libhardened_malloc.so /usr/lib/) fi if [ ! -z "${INSTALL_OPENSSL}" ]; then (cd ${HOME} && git clone https://github.com/openssl/openssl.git && cd ${HOME}/openssl && git checkout ${INSTALL_OPENSSL} && ./config no-threads shared ${SSLCONFOPTS} \ --prefix=/opt/openssl && make && sudo make install_sw) fi if [ ! -z "${INSTALL_LIBRESSL}" ]; then if [ "${INSTALL_LIBRESSL}" = "master" ]; then (mkdir -p ${HOME}/libressl && cd ${HOME}/libressl && git clone https://github.com/libressl-portable/portable.git && cd ${HOME}/libressl/portable && git checkout ${INSTALL_LIBRESSL} && sh update.sh && sh autogen.sh && ./configure --prefix=/opt/libressl && make -j2 && sudo make install) else LIBRESSL_URLBASE=https://cdn.openbsd.org/pub/OpenBSD/LibreSSL (cd ${HOME} && wget ${LIBRESSL_URLBASE}/libressl-${INSTALL_LIBRESSL}.tar.gz && tar xfz libressl-${INSTALL_LIBRESSL}.tar.gz && cd libressl-${INSTALL_LIBRESSL} && ./configure --prefix=/opt/libressl && make -j2 && sudo make install) fi fi if [ ! -z "${INSTALL_BORINGSSL}" ]; then (cd ${HOME} && git clone https://boringssl.googlesource.com/boringssl && cd ${HOME}/boringssl && mkdir build && cd build && cmake -GNinja -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && ninja && mkdir -p /opt/boringssl/lib && cp ${HOME}/boringssl/build/crypto/libcrypto.a /opt/boringssl/lib && cp -r ${HOME}/boringssl/include /opt/boringssl) fi + +if [ ! -z "${INSTALL_ZLIB}" ]; then + (cd ${HOME} && git clone https://github.com/madler/zlib.git && + cd ${HOME}/zlib && ./configure && make && + sudo make install prefix=/opt/zlib) +fi diff --git a/crypto/openssh/.github/workflows/c-cpp.yml b/crypto/openssh/.github/workflows/c-cpp.yml index e4e2a64e05d2..be0c97f84cfd 100644 --- a/crypto/openssh/.github/workflows/c-cpp.yml +++ b/crypto/openssh/.github/workflows/c-cpp.yml @@ -1,128 +1,129 @@ name: C/C++ CI on: push: paths: [ '**.c', '**.h', '**.m4', '**.sh', '.github/**', '**/Makefile.in', 'configure.ac' ] pull_request: paths: [ '**.c', '**.h', '**.m4', '**.sh', '.github/**', '**/Makefile.in', 'configure.ac' ] jobs: ci: if: github.repository != 'openssh/openssh-portable-selfhosted' strategy: fail-fast: false matrix: # First we test all OSes in the default configuration. target: - ubuntu-20.04 - ubuntu-22.04 - macos-11 - macos-12 - macos-13 - windows-2019 - windows-2022 config: [default] # Then we include any extra configs we want to test for specific VMs. # Valgrind slows things down quite a bit, so start them first. include: - { target: windows-2019, config: cygwin-release } - { target: windows-2022, config: cygwin-release } - { target: ubuntu-20.04, config: valgrind-1 } - { target: ubuntu-20.04, config: valgrind-2 } - { target: ubuntu-20.04, config: valgrind-3 } - { target: ubuntu-20.04, config: valgrind-4 } - { target: ubuntu-20.04, config: valgrind-5 } - { target: ubuntu-20.04, config: valgrind-unit } - { target: ubuntu-20.04, config: c89 } - { target: ubuntu-20.04, config: clang-6.0 } - { target: ubuntu-20.04, config: clang-8 } - { target: ubuntu-20.04, config: clang-9 } - { target: ubuntu-20.04, config: clang-10 } - { target: ubuntu-20.04, config: clang-11 } - { target: ubuntu-20.04, config: clang-12-Werror } - { target: ubuntu-20.04, config: clang-sanitize-address } - { target: ubuntu-20.04, config: clang-sanitize-undefined } - { target: ubuntu-20.04, config: gcc-sanitize-address } - { target: ubuntu-20.04, config: gcc-sanitize-undefined } - { target: ubuntu-20.04, config: gcc-7 } - { target: ubuntu-20.04, config: gcc-8 } - { target: ubuntu-20.04, config: gcc-10 } - { target: ubuntu-20.04, config: gcc-11-Werror } - { target: ubuntu-20.04, config: pam } - { target: ubuntu-20.04, config: kitchensink } - { target: ubuntu-22.04, config: hardenedmalloc } - { target: ubuntu-20.04, config: tcmalloc } - { target: ubuntu-20.04, config: musl } - { target: ubuntu-latest, config: boringssl } - { target: ubuntu-latest, config: libressl-master } - { target: ubuntu-latest, config: libressl-3.2.6 } - { target: ubuntu-latest, config: libressl-3.3.6 } - { target: ubuntu-latest, config: libressl-3.4.3 } - { target: ubuntu-latest, config: libressl-3.5.3 } - { target: ubuntu-latest, config: libressl-3.6.1 } - { target: ubuntu-latest, config: libressl-3.7.2 } - { target: ubuntu-latest, config: openssl-master } - { target: ubuntu-latest, config: openssl-noec } - { target: ubuntu-latest, config: openssl-1.1.1 } - { target: ubuntu-latest, config: openssl-1.1.1k } - { target: ubuntu-latest, config: openssl-1.1.1n } - { target: ubuntu-latest, config: openssl-1.1.1q } - { target: ubuntu-latest, config: openssl-1.1.1t } - { target: ubuntu-latest, config: openssl-3.0.0 } - { target: ubuntu-latest, config: openssl-3.0.7 } - { target: ubuntu-latest, config: openssl-3.1.0 } - { target: ubuntu-latest, config: openssl-1.1.1_stable } - { target: ubuntu-latest, config: openssl-3.0 } # stable branch + - { target: ubuntu-latest, config: zlib-develop } - { target: ubuntu-22.04, config: pam } - { target: ubuntu-22.04, config: krb5 } - { target: ubuntu-22.04, config: heimdal } - { target: ubuntu-22.04, config: libedit } - { target: ubuntu-22.04, config: sk } - { target: ubuntu-22.04, config: selinux } - { target: ubuntu-22.04, config: kitchensink } - { target: ubuntu-22.04, config: without-openssl } - { target: macos-11, config: pam } - { target: macos-12, config: pam } - { target: macos-13, config: pam } runs-on: ${{ matrix.target }} steps: - name: set cygwin git params if: ${{ startsWith(matrix.target, 'windows') }} run: git config --global core.autocrlf input - name: install cygwin if: ${{ startsWith(matrix.target, 'windows') }} uses: cygwin/cygwin-install-action@master - uses: actions/checkout@main - name: setup CI system run: sh ./.github/setup_ci.sh ${{ matrix.config }} - name: autoreconf run: sh -c autoreconf - name: configure run: sh ./.github/configure.sh ${{ matrix.config }} - name: save config uses: actions/upload-artifact@main with: name: ${{ matrix.target }}-${{ matrix.config }}-config path: config.h - name: make clean run: make clean - name: make run: make -j2 - name: make tests run: sh ./.github/run_test.sh ${{ matrix.config }} env: TEST_SSH_UNSAFE_PERMISSIONS: 1 TEST_SSH_HOSTBASED_AUTH: yes - name: save logs if: failure() uses: actions/upload-artifact@main with: name: ${{ matrix.target }}-${{ matrix.config }}-logs path: | config.h config.log regress/*.log regress/valgrind-out/ regress/asan.log.* regress/msan.log.* regress/log/* diff --git a/crypto/openssh/.github/workflows/selfhosted.yml b/crypto/openssh/.github/workflows/selfhosted.yml index e84db699ea31..de0a4125bf08 100644 --- a/crypto/openssh/.github/workflows/selfhosted.yml +++ b/crypto/openssh/.github/workflows/selfhosted.yml @@ -1,119 +1,122 @@ name: C/C++ CI self-hosted on: push: paths: [ '**.c', '**.h', '**.m4', '**.sh', '.github/**', '**/Makefile.in', 'configure.ac' ] jobs: selfhosted: if: github.repository == 'openssh/openssh-portable-selfhosted' runs-on: ${{ matrix.host }} timeout-minutes: 600 env: HOST: ${{ matrix.host }} TARGET_HOST: ${{ matrix.target }} TARGET_CONFIG: ${{ matrix.config }} strategy: fail-fast: false # We use a matrix in two parts: firstly all of the VMs are tested with the # default config. "target" corresponds to a label associated with the # worker. The default is an ephemeral VM running under libvirt. matrix: target: - alpine - centos7 - debian-i386 - dfly30 - dfly48 - dfly60 - dfly62 - dfly64 - fbsd10 - fbsd12 - fbsd13 - minix3 - nbsd3 - nbsd4 - nbsd8 - nbsd9 - obsd51 - obsd67 - obsd69 - obsd70 + - obsd72 + - obsd73 - obsdsnap - obsdsnap-i386 - openindiana - sol10 - sol11 config: - default host: - libvirt include: # Then we include extra libvirt test configs. - { target: aix51, config: default, host: libvirt } - { target: centos7, config: pam, host: libvirt } - { target: debian-i386, config: pam, host: libvirt } - { target: dfly30, config: without-openssl, host: libvirt} - { target: dfly48, config: pam ,host: libvirt } - { target: dfly58, config: pam, host: libvirt } - { target: dfly60, config: pam, host: libvirt } - { target: dfly62, config: pam, host: libvirt } - { target: fbsd10, config: pam, host: libvirt } - { target: fbsd12, config: pam, host: libvirt } - { target: fbsd13, config: pam, host: libvirt } - { target: nbsd8, config: pam, host: libvirt } - { target: nbsd9, config: pam, host: libvirt } - { target: openindiana, config: pam, host: libvirt } - { target: sol10, config: pam, host: libvirt } - { target: sol11, config: pam-krb5, host: libvirt } - { target: sol11, config: sol64, host: libvirt } # VMs with persistent disks that have their own runner. - { target: win10, config: default, host: win10 } - { target: win10, config: cygwin-release, host: win10 } # Physical hosts, with either native runners or remote via ssh. - { target: ARM, config: default, host: ARM } - { target: ARM64, config: default, host: ARM64 } - { target: ARM64, config: pam, host: ARM64 } - { target: debian-riscv64, config: default, host: debian-riscv64 } + - { target: obsd-arm64, config: default, host: obsd-arm64 } - { target: openwrt-mips, config: default, host: openwrt-mips } - { target: openwrt-mipsel, config: default, host: openwrt-mipsel } steps: - name: shutdown VM if running run: vmshutdown working-directory: ${{ runner.temp }} - uses: actions/checkout@main - name: autoreconf run: autoreconf - name: startup VM run: vmstartup working-directory: ${{ runner.temp }} - name: configure run: vmrun ./.github/configure.sh ${{ matrix.config }} - name: save config uses: actions/upload-artifact@main with: name: ${{ matrix.target }}-${{ matrix.config }}-config path: config.h - name: make clean run: vmrun make clean - name: make run: vmrun make - name: make tests run: vmrun ./.github/run_test.sh ${{ matrix.config }} timeout-minutes: 600 - name: save logs if: failure() uses: actions/upload-artifact@main with: name: ${{ matrix.target }}-${{ matrix.config }}-logs path: | config.h config.log regress/*.log regress/log/* regress/valgrind-out/ - name: shutdown VM if: always() run: vmshutdown working-directory: ${{ runner.temp }} diff --git a/crypto/openssh/ChangeLog b/crypto/openssh/ChangeLog index 3e16fbfd346d..61725d3a136b 100644 --- a/crypto/openssh/ChangeLog +++ b/crypto/openssh/ChangeLog @@ -1,10174 +1,9557 @@ -commit daa5b2d869ee5a16f3ef9035aa0ad3c70cf4028e +commit 80a2f64b8c1d27383cc83d182b73920d1e6a91f1 +Author: Damien Miller +Date: Wed Oct 4 15:34:10 2023 +1100 + + crank version numbers + +commit f65f187b105d9b5c12fd750a211397d08c17c6d4 +Author: djm@openbsd.org +Date: Wed Oct 4 04:04:09 2023 +0000 + + upstream: openssh-9.5 + + OpenBSD-Commit-ID: 5e0af680480bd3b6f5560cf840ad032d48fd6b16 + +commit ffe27e54a4bb18d5d3bbd3f4cc93a41b8d94dfd2 +Author: djm@openbsd.org +Date: Wed Oct 4 04:03:50 2023 +0000 + + upstream: add some cautionary text about % token expansion and + + shell metacharacters; based on report from vinci AT protonmail.ch + + OpenBSD-Commit-ID: aa1450a54fcee2f153ef70368d90edb1e7019113 + +commit 60ec3d54fd1ebfe2dda75893fa1e870b8dffbb0d +Author: djm@openbsd.org +Date: Tue Oct 3 23:56:10 2023 +0000 + + upstream: fix link to agent draft; spotted by Jann Horn + + OpenBSD-Commit-ID: ff5bda21a83ec013db683e282256a85201d2dc4b + +commit 12e2d4b13f6f63ce2de13cbfcc9e4d0d4b4ab231 +Author: Damien Miller +Date: Wed Oct 4 10:54:04 2023 +1100 + + use portable provider allowlist path in manpage + + spotted by Jann Horn + +commit 6c2c6ffde75df95fd838039850d3dd3d84956d87 +Author: deraadt@openbsd.org +Date: Tue Sep 19 20:37:07 2023 +0000 + + upstream: typo; from Jim Spath + + OpenBSD-Commit-ID: 2f5fba917b5d4fcf93d9e0b0756c7f63189e228e + +commit b6b49130a0089b297245ee39e769231d7c763014 +Author: djm@openbsd.org +Date: Sun Sep 10 23:12:32 2023 +0000 + + upstream: rename remote_glob() -> sftp_glob() to match other API + + OpenBSD-Commit-ID: d9dfb3708d824ec02970a84d96cf5937e0887229 + +commit 21b79af6c8d2357c822c84cef3fbdb8001ed263b +Author: djm@openbsd.org +Date: Sun Sep 10 03:51:55 2023 +0000 + + upstream: typo in comment + + OpenBSD-Commit-ID: 69285e0ce962a7c6b0ab5f17a293c60a0a360a18 + +commit 41232d25532b4d2ef6c5db62efc0cf50a79d26ca +Author: Darren Tucker +Date: Sun Sep 10 15:45:38 2023 +1000 + + Use zero-call-used-regs=used with Apple compilers. + + Apple's versions of clang have version numbers that do not match the + corresponding upstream clang versions. Unfortunately, they do still + have the clang-15 zero-call-used-regs=all bug, so for now use the value + that doesn't result in segfaults. We could allowlist future versions + that are known to work. bz#3584 (and probably also our github CI + failures). + +commit 90ccc5918ea505bf156c31148b6b59a1bf5d6dc6 +Author: djm@openbsd.org +Date: Sun Sep 10 03:25:53 2023 +0000 + + upstream: randomise keystroke obfuscation intervals and average + + interval rate. ok dtucker@ + + OpenBSD-Commit-ID: 05f61d051ab418fcfc4857ff306e420037502382 + +commit bd1b9e52f5fa94d87223c90905c5fdc1a7c32aa6 +Author: djm@openbsd.org +Date: Fri Sep 8 06:34:24 2023 +0000 + + upstream: fix sizeof(*ptr) instead sizeof(ptr) in realloc (pointer here + + is char**, so harmless); spotted in CID 416964 + + OpenBSD-Commit-ID: c61caa4a5a667ee20bb1042098861e6c72c69002 + +commit c4f966482983e18601eec70a1563115de836616f +Author: djm@openbsd.org +Date: Fri Sep 8 06:10:57 2023 +0000 + + upstream: regress test recursive remote-remote directories copies where + + the directory contains a symlink to another directory. + + also remove errant `set -x` that snuck in at some point + + OpenBSD-Regress-ID: 1c94a48bdbd633ef2285954ee257725cd7bc456f + +commit 5e1dfe5014ebc194641678303e22ab3bba15f4e5 +Author: djm@openbsd.org +Date: Fri Sep 8 06:10:02 2023 +0000 + + upstream: fix recursive remote-remote copies of directories that + + contain symlinks to other directories (similar to bz3611) + + OpenBSD-Commit-ID: 7e19d2ae09b4f941bf8eecc3955c9120171da37f + +commit 7c0ce2bf98b303b6ad91493ee3247d96c18ba1f6 +Author: djm@openbsd.org +Date: Fri Sep 8 05:50:57 2023 +0000 + + upstream: regress test for recursive copies of directories containing + + symlinks to other directories. bz3611, ok dtucker@ + + OpenBSD-Regress-ID: eaa4c29cc5cddff4e72a16bcce14aeb1ecfc94b9 + +commit 2de990142a83bf60ef694378b8598706bc654b08 +Author: djm@openbsd.org +Date: Fri Sep 8 05:56:13 2023 +0000 + + upstream: the sftp code was one of my first contributions to + + OpenSSH and it shows - the function names are terrible. + + Rename do_blah() to sftp_blah() to make them less so. + + Completely mechanical except for sftp_stat() and sftp_lstat() which + change from returning a pointer to a static variable (error-prone) to + taking a pointer to a caller-provided receiver. + + OpenBSD-Commit-ID: eb54d6a72d0bbba4d623e2175cf5cc4c75dc2ba4 + +commit 249d8bd0472b53e3a2a0e138b4c030a31e83346a +Author: djm@openbsd.org +Date: Fri Sep 8 05:50:12 2023 +0000 + + upstream: fix scp in SFTP mode recursive upload and download of + + directories that contain symlinks to other directories. In scp mode, the + links would be followed, but in SFTP mode they were not. bz3611, ok dtucker@ + + OpenBSD-Commit-ID: 9760fda668eaa94a992250d7670dfbc62a45197c + +commit 0e1f4401c466fa4fdaea81b6dadc8dd1fc4cf0af +Author: djm@openbsd.org +Date: Wed Sep 6 23:36:09 2023 +0000 + + upstream: regression test for override of subsystem in match blocks + + OpenBSD-Regress-ID: 5f8135da3bfda71067084c048d717b0e8793e87c + +commit 8a1450c62035e834d8a79a5d0d1c904236f9dcfe +Author: djm@openbsd.org +Date: Wed Sep 6 23:35:35 2023 +0000 + + upstream: allow override of Sybsystem directives in sshd Match + + blocks + + OpenBSD-Commit-ID: 3911d18a826a2d2fe7e4519075cf3e57af439722 + +commit 6e52826e2a74d077147a82ead8d4fbd5b54f4e3b +Author: djm@openbsd.org +Date: Wed Sep 6 23:26:37 2023 +0000 + + upstream: allocate the subsystems array as necessary and remove the + + fixed limit of subsystems. Saves a few kb of memory in the server and makes + it more like the other options. + + OpenBSD-Commit-ID: e683dfca6bdcbc3cc339bb6c6517c0c4736a547f + +commit e19069c9fac4c111d6496b19c7f7db43b4f07b4f +Author: djm@openbsd.org +Date: Wed Sep 6 23:23:53 2023 +0000 + + upstream: preserve quoting of Subsystem commands and arguments. + + This may change behaviour of exotic configurations, but the most common + subsystem configuration (sftp-server) is unlikely to be affected. + + OpenBSD-Commit-ID: 8ffa296aeca981de5b0945242ce75aa6dee479bf + +commit 52dfe3c72d98503d8b7c6f64fc7e19d685636c0b +Author: djm@openbsd.org +Date: Wed Sep 6 23:21:36 2023 +0000 + + upstream: downgrade duplicate Subsystem directives from being a + + fatal error to being a debug message to match behaviour with just about all + other directives. + + OpenBSD-Commit-ID: fc90ed2cc0c18d4eb8e33d2c5e98d25f282588ce + +commit 1ee0a16e07b6f0847ff463d7b5221c4bf1876e25 +Author: djm@openbsd.org +Date: Wed Sep 6 23:18:15 2023 +0000 + + upstream: handle cr+lf (instead of just cr) in sshsig signature + + files + + OpenBSD-Commit-ID: 647460a212b916540016d066568816507375fd7f + +commit e1c284d60a928bcdd60bc575c6f9604663502770 +Author: job@openbsd.org +Date: Mon Sep 4 10:29:58 2023 +0000 + + upstream: Generate Ed25519 keys when invoked without arguments + + Ed25519 public keys are very convenient due to their small size. + OpenSSH has supported Ed25519 since version 6.5 (January 2014). + + OK djm@ markus@ sthen@ deraadt@ + + OpenBSD-Commit-ID: f498beaad19c8cdcc357381a60df4a9c69858b3f + +commit 694150ad92765574ff82a18f4e86322bd3231e68 +Author: djm@openbsd.org +Date: Mon Sep 4 00:08:14 2023 +0000 + + upstream: trigger keystroke timing obfucation only if the channels + + layer enqueud some data in the last poll() cycle; this avoids triggering the + obfuscatior for non-channels data like ClientAlive probes and also fixes a + related problem were the obfucations would be triggered on fully quiescent + connections. + + Based on / tested by naddy@ + + OpenBSD-Commit-ID: d98f32dc62d7663ff4660e4556e184032a0db123 + +commit b5fd97896b59a3a46245cf438cc8b16c795d9f74 +Author: djm@openbsd.org +Date: Mon Sep 4 00:04:02 2023 +0000 + + upstream: avoid bogus "obfuscate_keystroke_timing: stopping ..." + + debug messages when keystroke timing obfuscation was never started; spotted + by naddy@ + + OpenBSD-Commit-ID: 5c270d35f7d2974db5c1646e9c64188f9393be31 + +commit ccf7d913db34e49b7a6db1b8331bd402004c840d +Author: djm@openbsd.org +Date: Mon Sep 4 00:01:46 2023 +0000 + + upstream: make channel_output_poll() return a flag indicating + + whether channel data was enqueued. Will be used to improve keystroke timing + obfuscation. Problem spotted by / tested by naddy@ + + OpenBSD-Commit-ID: f9776c7b0065ba7c3bbe50431fd3b629f44314d0 + +commit 43254b326ac6e2131dbd750f9464dc62c14bd5a7 +Author: djm@openbsd.org +Date: Sun Sep 3 23:59:32 2023 +0000 + + upstream: set interactive mode for ControlPersist sessions if they + + originally requested a tty; enables keystroke timing obfuscation for most + ControlPersist sessions. Spotted by naddy@ + + OpenBSD-Commit-ID: 72783a26254202e2f3f41a2818a19956fe49a772 + +commit ff3eda68ceb2e2bb8f48e3faceb96076c3e85c20 +Author: Darren Tucker +Date: Thu Aug 31 23:02:35 2023 +1000 + + Set LLONG_MAX for C89 test. + + If we don't have LLONG_MAX, configure will figure out that it can get it + by setting -std=gnu99, at which point we won't be testing C89 any more. + To avoid this, feed it in via CFLAGS. + +commit f98031773db361424d59e3301aa92aacf423d920 +Author: djm@openbsd.org +Date: Tue Aug 29 02:50:10 2023 +0000 + + upstream: make PerSourceMaxStartups first-match-wins; ok dtucker@ + + OpenBSD-Commit-ID: dac0c24cb709e3c595b8b4f422a0355dc5a3b4e7 + +commit cfa66857db90cd908de131e0041a50ffc17c7df8 +Author: djm@openbsd.org +Date: Mon Aug 28 09:52:09 2023 +0000 + + upstream: descriptive text shouldn't be under .Cm + + OpenBSD-Commit-ID: b1afaeb456a52bc8a58f4f9f8b2f9fa8f6bf651b + +commit 01dbf3d46651b7d6ddf5e45d233839bbfffaeaec +Author: djm@openbsd.org +Date: Mon Aug 28 09:48:11 2023 +0000 + + upstream: limit artificial login delay to a reasonable maximum (5s) + + and don't delay at all for the "none" authentication mechanism. Patch by + Dmitry Belyavskiy in bz3602 with polish/ok dtucker@ + + OpenBSD-Commit-ID: 85b364676dd84cf1de0e98fc2fbdcb1a844ce515 + +commit 528da5b9d7c5da01ed7a73ff21c722e1b5326006 +Author: jmc@openbsd.org +Date: Mon Aug 28 05:32:28 2023 +0000 + + upstream: add spacing for punctuation when macro args; + + OpenBSD-Commit-ID: e80343c16ce0420b2aec98701527cf90371bd0db + +commit 3867361ca691d0956ef7d5fb8181cf554a91d84a +Author: djm@openbsd.org +Date: Mon Aug 28 04:06:52 2023 +0000 + + upstream: explicit long long type in timing calculations (doesn't + + matter, since the range is pre-clamped) + + OpenBSD-Commit-ID: f786ed902d04a5b8ecc581d068fea1a79aa772de + +commit 7603ba71264e7fa938325c37eca993e2fa61272f +Author: djm@openbsd.org +Date: Mon Aug 28 03:31:16 2023 +0000 + + upstream: Add keystroke timing obfuscation to the client. + + This attempts to hide inter-keystroke timings by sending interactive + traffic at fixed intervals (default: every 20ms) when there is only a + small amount of data being sent. It also sends fake "chaff" keystrokes + for a random interval after the last real keystroke. These are + controlled by a new ssh_config ObscureKeystrokeTiming keyword/ + + feedback/ok markus@ + + OpenBSD-Commit-ID: 02231ddd4f442212820976068c34a36e3c1b15be + +commit dce6d80d2ed3cad2c516082682d5f6ca877ef714 +Author: djm@openbsd.org +Date: Mon Aug 28 03:28:43 2023 +0000 + + upstream: Introduce a transport-level ping facility + + This adds a pair of SSH transport protocol messages SSH2_MSG_PING/PONG + to implement a ping capability. These messages use numbers in the "local + extensions" number space and are advertised using a "ping@openssh.com" + ext-info message with a string version number of "0". + + ok markus@ + + OpenBSD-Commit-ID: b6b3c4cb2084c62f85a8dc67cf74954015eb547f + +commit d2d247938b38b928f8a6e1a47a330c5584d3a358 +Author: tobhe@openbsd.org +Date: Mon Aug 21 21:16:18 2023 +0000 + + upstream: Log errors in kex_exchange_identification() with level + + verbose instead of error to reduce preauth log spam. All of those get logged + with a more generic error message by sshpkt_fatal(). + + feedback from sthen@ + ok djm@ + + OpenBSD-Commit-ID: bd47dab4695b134a44c379f0e9a39eed33047809 + +commit 9d7193a8359639801193ad661a59d1ae4dc3d302 +Author: djm@openbsd.org +Date: Mon Aug 21 04:59:54 2023 +0000 + + upstream: correct math for ClientAliveInterval that caused the + + probes to be sent less frequently than configured; from Dawid Majchrzak + + OpenBSD-Commit-ID: 641153e7c05117436ddfc58267aa267ca8b80038 + +commit 3c6ab63b383b0b7630da175941e01de9db32a256 +Author: Darren Tucker +Date: Fri Aug 25 14:48:02 2023 +1000 + + Include Portable version in sshd version string. + + bz#3608, ok djm@ + +commit 17fa6cd10a26e193bb6f65d21264d2fe553bcd87 +Author: Darren Tucker +Date: Mon Aug 21 19:47:58 2023 +1000 + + obsd-arm64 host is real hardware... + + so put in the correct config location. + +commit 598ca75c85acaaacee5ef954251e489cc20d7be9 +Author: Darren Tucker +Date: Mon Aug 21 18:38:36 2023 +1000 + + Add OpenBSD ARM64 test host. + +commit 1acac79bfbe207e8db639e8043524962037c8feb +Author: Darren Tucker +Date: Mon Aug 21 18:05:26 2023 +1000 + + Add test for zlib development branch. + +commit 84efebf352fc700e9040c8065707c63caedd36a3 +Author: djm@openbsd.org +Date: Mon Aug 21 04:36:46 2023 +0000 + + upstream: want stdlib.h for free(3) + + OpenBSD-Commit-ID: 743af3c6e3ce5e6cecd051668f0327a01f44af29 + +commit cb4ed12ffc332d1f72d054ed92655b5f1c38f621 +Author: Darren Tucker +Date: Sat Aug 19 07:39:08 2023 +1000 + + Fix zlib version check for 1.3 and future version. + + bz#3604. + +commit 25b75e21f16bccdaa472ea1889b293c9bd51a87b +Author: Darren Tucker +Date: Mon Aug 14 11:10:08 2023 +1000 + + Add 9.4 branch to CI status page. + +commit 803e22eabd3ba75485eedd8b7b44d6ace79f2052 +Author: djm@openbsd.org +Date: Fri Aug 18 01:37:41 2023 +0000 + + upstream: fix regression in OpenSSH 9.4 (mux.c r1.99) that caused + + multiplexed sessions to ignore SIGINT under some circumstances. Reported by / + feedback naddy@, ok dtucker@ + + OpenBSD-Commit-ID: 4d5c6c894664f50149153fd4764f21f43e7d7e5a + +commit e706bca324a70f68dadfd0ec69edfdd486eed23a +Author: djm@openbsd.org +Date: Wed Aug 16 16:14:11 2023 +0000 + + upstream: defence-in-depth MaxAuthTries check in monitor; ok markus + + OpenBSD-Commit-ID: 65a4225dc708e2dae71315adf93677edace46c21 + +commit d1ab7eb90474df656d5e9935bae6df0bd000d343 +Author: djm@openbsd.org +Date: Mon Aug 14 03:37:00 2023 +0000 + + upstream: add message number of SSH2_MSG_NEWCOMPRESS defined in RFC8308 + + OpenBSD-Commit-ID: 6c984171c96ed67effd7b5092f3d3975d55d6028 + +commit fa8da52934cb7dff6f660a143276bdb28bb9bbe1 +Author: Darren Tucker +Date: Sun Aug 13 15:01:27 2023 +1000 + + Add obsd72 and obsd73 test targets. + +commit f9f18006678d2eac8b0c5a5dddf17ab7c50d1e9f +Author: djm@openbsd.org +Date: Thu Aug 10 23:05:48 2023 +0000 + + upstream: better debug logging of sessions' exit status + + OpenBSD-Commit-ID: 82237567fcd4098797cbdd17efa6ade08e1a36b0 + +commit a8c57bcb077f0cfdffcf9f23866bf73bb93e185c +Author: naddy@openbsd.org +Date: Thu Aug 10 14:37:32 2023 +0000 + + upstream: drop a wayward comma, ok jmc@ + + OpenBSD-Commit-ID: 5c11fbb9592a29b37bbf36f66df50db9d38182c6 + +commit e962f9b318a238db1becc53c2bf79dd3a49095b4 Author: Damien Miller Date: Thu Aug 10 11:10:22 2023 +1000 depend -commit 41bfb63f5101fbacde9d8d2ada863f9ee16df194 +commit 0fcb60bf83130dfa428bc4422b3a3ac20fb528af Author: Damien Miller Date: Thu Aug 10 11:05:42 2023 +1000 update versions in RPM specs -commit e598b92b1eecedac21667edf1fe92078eaf8f2b1 +commit d0cee4298491314f09afa1c4383a66d913150b26 Author: Damien Miller Date: Thu Aug 10 11:05:14 2023 +1000 update version in README -commit e797e5ffa74377c8696e3b0559a258d836479239 +commit 78b4dc6684f4d35943b46b24ee645edfdb9974f5 Author: djm@openbsd.org Date: Thu Aug 10 01:01:07 2023 +0000 upstream: openssh-9.4 OpenBSD-Commit-ID: 71fc1e01a4c4ea061b252bd399cda7be757e6e35 +commit 58ca4f0aa8c4306ac0a629c9a85fb1efaf4ff092 +Author: Darren Tucker +Date: Thu Aug 10 11:30:24 2023 +1000 + + Only include unistd.h once. + commit 3961ed02dc578517a9d2535128cff5c3a5460d28 Author: Damien Miller Date: Thu Aug 10 09:08:49 2023 +1000 wrap poll.h include in HAVE_POLL_H commit e535fbe2af893046c28adfcd787c1fdbae36a24a Author: dtucker@openbsd.org Date: Fri Aug 4 06:32:40 2023 +0000 upstream: Apply ConnectTimeout to multiplexing local socket connections. If the multiplex socket exists but the connection times out, ssh will fall back to a direct connection the same way it would if the socket did not exist at all. ok djm@ OpenBSD-Commit-ID: 2fbe1a36d4a24b98531b2d298a6557c8285dc1b4 commit 9d92e7b24848fcc605945f7c2e3460c7c31832ce Author: Darren Tucker Date: Thu Aug 3 19:35:33 2023 +1000 Fix RNG seeding for OpenSSL w/out self seeding. When sshd is built with an OpenSSL that does not self-seed, it would fail in the preauth privsep process while handling a new connection. Sanity checked by djm@ commit f70010d9b0b3e7e95de8aa0b961e1d74362cfb5d Author: djm@openbsd.org Date: Wed Aug 2 23:04:38 2023 +0000 upstream: CheckHostIP has defaulted to 'no' for a while; make the commented- out config option match. From Ed Maste OpenBSD-Commit-ID: e66e934c45a9077cb1d51fc4f8d3df4505db58d9 commit c88a8788f9865d02b986d00405b9f0be65ad0b5a Author: dtucker@openbsd.org Date: Tue Aug 1 08:15:04 2023 +0000 upstream: remove unnecessary if statement. github PR#422 from eyalasulin999, ok djm@ OpenBSD-Commit-ID: 2b6b0dde4407e039f58f86c8d2ff584a8205ea55 commit 77b8b865cd5a8c79a47605c0c5b2bacf4692c4d5 Author: jmc@openbsd.org Date: Fri Jul 28 05:42:36 2023 +0000 upstream: %C is a callable macro in mdoc(7) so, as we do for %D, escape it; OpenBSD-Commit-ID: 538cfcddbbb59dc3a8739604319491dcb8e0c0c9 commit e0f91aa9c2fbfc951e9ced7e1305455fc614d3f2 Author: djm@openbsd.org Date: Fri Jul 28 05:33:15 2023 +0000 upstream: don't need to start a command here; use ssh -N instead. Fixes failure on cygwin spotted by Darren OpenBSD-Regress-ID: ff678a8cc69160a3b862733d935ec4a383f93cfb commit f446a44f30bc680e0d026a4204844b02646c1c2d Author: djm@openbsd.org Date: Wed May 17 05:52:01 2023 +0000 upstream: add LTESTS_FROM variable to allow skipping of tests up to a specific point. e.g. "make LTESTS_FROM=t-sftp" will only run the sftp.sh test and subsequent ones. ok dtucker@ OpenBSD-Regress-ID: 07f653de731def074b29293db946042706fcead3 commit 8eb8899d612440a9b608bee7f916081d3d0b7812 Author: djm@openbsd.org Date: Fri May 12 06:37:42 2023 +0000 upstream: test ChrootDirectory in Match block OpenBSD-Regress-ID: a6150262f39065939f025e546af2a346ffe674c1 commit e43f43d3f19516222e9a143468ea0dc1b3ab67b6 Author: djm@openbsd.org Date: Fri May 12 06:36:27 2023 +0000 upstream: better error messages OpenBSD-Regress-ID: 55e4186604e80259496d841e690ea2090981bc7a commit 6958f00acf3b9e0b3730f7287e69996bcf3ceda4 Author: djm@openbsd.org Date: Thu Jul 27 22:26:49 2023 +0000 upstream: don't incorrectly truncate logged strings retrieved from PKCS#11 modules; based on GHPR406 by Jakub Jelen; ok markus OpenBSD-Commit-ID: 7ed1082f23a13b38c373008f856fd301d50012f9 commit d1ffde6b55170cd4b9a72bfd9a3f17508e6cf714 Author: djm@openbsd.org Date: Thu Jul 27 22:25:17 2023 +0000 upstream: make sshd_config AuthorizedPrincipalsCommand and AuthorizedKeysCommand accept the %D (routing domain) and a new %C (connection address/port 4-tuple) as expansion sequences; ok markus OpenBSD-Commit-ID: ee9a48bf1a74c4ace71b69de69cfdaa2a7388565 commit 999a2886ca1844a7a74b905e5f2c8c701f9838cd Author: djm@openbsd.org Date: Thu Jul 27 22:23:05 2023 +0000 upstream: increase default KDF work-factor for OpenSSH format private keys from 16 to 24; { feedback ok } x { deraadt markus } OpenBSD-Commit-ID: a3afb1383f8ff0a49613d449f02395d9e8d4a9ec commit 0fa803a1dd1c7b546c166000e23a869cf6c4ec10 Author: Darren Tucker Date: Thu Jul 27 02:25:09 2023 +1000 Prefer OpenSSL's SHA256 in sk-dummy.so Previously sk-dummy.so used libc's (or compat's) SHA256 since it may be built without OpenSSL. In many cases, however, including both libc's and OpenSSL's headers together caused conflicting definitions. We tried working around this (on OpenSSL <1.1 you could define OPENSSL_NO_SHA, NetBSD had USE_LIBC_SHA2, various #define hacks) with varying levels of success. Since OpenSSL >=1.1 removed OPENSSL_NO_SHA and including most OpenSSL headers would bring sha.h in, even if it wasn't used directly this was a constant hassle. Admit defeat and use OpenSSL's SHA256 unless we aren't using OpenSSL at all. ok djm@ commit 36cdb5dbf55c99c0faad06066f56a7c341258c1f Author: Darren Tucker Date: Thu Jul 27 10:29:44 2023 +1000 Retire dfly58 test VM. Add dfly64. commit 2d34205dab08ede9b0676efa57647fc49e6decbe Author: djm@openbsd.org Date: Wed Jul 26 23:06:00 2023 +0000 upstream: make ssh -f (fork after authentication) work properly in multiplexed cases (inc. ControlPersist). bz3589 bz3589 Based on patches by Peter Chubb; ok dtucker@ OpenBSD-Commit-ID: a7a2976a54b93e6767dc846b85647e6ec26969ac commit 076aeda86a7ee9be8fd2f0181ec7b9729a6ceb37 Author: naddy@openbsd.org Date: Sun Jul 23 20:04:45 2023 +0000 upstream: man page typos; ok jmc@ OpenBSD-Commit-ID: e6ddfef94b0eb867ad88abe07cedc8ed581c07f0 commit 135e7d5fe31f700e6dfc61ce914970c5ee7175ba Author: jmc@openbsd.org Date: Thu Jul 20 05:43:39 2023 +0000 upstream: tweak the allow-remote-pkcs11 text; OpenBSD-Commit-ID: bc965460a89edf76865b7279b45cf9cbdebd558a commit 5f83342b61d1f76c141de608ed2bd293990416bd Author: Darren Tucker Date: Tue Jul 25 13:00:22 2023 +1000 Handle a couple more OpenSSL no-ecc cases. ok djm@ commit edc2ef4e418e514c99701451fae4428ec04ce538 Author: Damien Miller Date: Thu Jul 20 12:53:44 2023 +1000 depend commit 51fda734e0d3c2df256fc03e8b060c4305be6e59 Author: Damien Miller Date: Thu Jul 20 12:53:21 2023 +1000 Bring back OPENSSL_HAS_ECC to ssh-pkcs11-client commit 099cdf59ce1e72f55d421c8445bf6321b3004755 Author: djm@openbsd.org Date: Wed Jul 19 14:03:45 2023 +0000 upstream: Separate ssh-pkcs11-helpers for each p11 module Make ssh-pkcs11-client start an independent helper for each provider, providing better isolation between modules and reliability if a single module misbehaves. This also implements reference counting of PKCS#11-hosted keys, allowing ssh-pkcs11-helper subprocesses to be automatically reaped when no remaining keys reference them. This fixes some bugs we have that make PKCS11 keys unusable after they have been deleted, e.g. https://bugzilla.mindrot.org/show_bug.cgi?id=3125 ok markus@ OpenBSD-Commit-ID: 0ce188b14fe271ab0568f4500070d96c5657244e commit 29ef8a04866ca14688d5b7fed7b8b9deab851f77 Author: djm@openbsd.org Date: Wed Jul 19 14:02:27 2023 +0000 upstream: Ensure FIDO/PKCS11 libraries contain expected symbols This checks via nlist(3) that candidate provider libraries contain one of the symbols that we will require prior to dlopen(), which can cause a number of side effects, including execution of constructors. Feedback deraadt; ok markus OpenBSD-Commit-ID: 1508a5fbd74e329e69a55b56c453c292029aefbe commit 1f2731f5d7a8f8a8385c6031667ed29072c0d92a Author: djm@openbsd.org Date: Wed Jul 19 13:56:33 2023 +0000 upstream: Disallow remote addition of FIDO/PKCS11 provider libraries to ssh-agent by default. The old behaviour of allowing remote clients from loading providers can be restored using `ssh-agent -O allow-remote-pkcs11`. Detection of local/remote clients requires a ssh(1) that supports the `session-bind@openssh.com` extension. Forwarding access to a ssh-agent socket using non-OpenSSH tools may circumvent this control. ok markus@ OpenBSD-Commit-ID: 4c2bdf79b214ae7e60cc8c39a45501344fa7bd7c commit 892506b13654301f69f9545f48213fc210e5c5cc Author: djm@openbsd.org Date: Wed Jul 19 13:55:53 2023 +0000 upstream: terminate process if requested to load a PKCS#11 provider that isn't a PKCS#11 provider; from / ok markus@ OpenBSD-Commit-ID: 39532cf18b115881bb4cfaee32084497aadfa05c commit f3f56df8ec476b2de6cbdbdfdb77a2a61087829d Author: Damien Miller Date: Wed Jul 19 12:07:18 2023 +1000 agent_fuzz doesn't want stdint.h conditionalised commit 750911fd31d307a767cc86e3bfa90bbbb77b1a25 Author: Damien Miller Date: Tue Jul 18 15:41:12 2023 +1000 conditionalise stdint.h inclusion on HAVE_STDINT_H fixes build on AIX5 at least commit ff047504fa6e008c4092f8929881816b8993bea0 Author: Damien Miller Date: Tue Jul 18 15:30:45 2023 +1000 conditionalise match localnetwork on ifaddrs.h Fixes build breakage on platforms that lack getifaddrs() commit b87b03282e466ca2927954ce93f5dbf0bfdc68f6 Author: djm@openbsd.org Date: Mon Jul 17 06:16:33 2023 +0000 upstream: missing match localnetwork negation check OpenBSD-Commit-ID: 9a08ed8dae27d3f38cf280f1b28d4e0ff41a737a commit 6d6e185ba29ef4274164b77eab4dc763907f8821 Author: jmc@openbsd.org Date: Mon Jul 17 05:41:53 2023 +0000 upstream: - add -P to usage() - sync the arg name to -J in usage() with that in ssh.1 - reformat usage() to match what "man ssh" does on 80width OpenBSD-Commit-ID: 5235dd7aa42e5bf90ae54579d519f92fc107036e commit f1a9898283a0638667b587ee4a950afd61ab51b0 Author: jmc@openbsd.org Date: Mon Jul 17 05:38:10 2023 +0000 upstream: -P before -p in SYNOPSIS; OpenBSD-Commit-ID: 535f5257c779e26c6a662a038d241b017f8cab7c commit eef4d7e873568e1c84c36bb4034e2c3378250a61 Author: jsg@openbsd.org Date: Mon Jul 17 05:36:14 2023 +0000 upstream: configuation -> configuration OpenBSD-Commit-ID: 4776ced33b780f1db0b2902faec99312f26a726b commit dc1dbe94cf6532bd546a3373ad436404f8850e5f Author: djm@openbsd.org Date: Mon Jul 17 05:26:38 2023 +0000 upstream: move other RCSIDs to before their respective license blocks too no code change OpenBSD-Commit-ID: ef5bf46b57726e4260a63b032b0b5ac3b4fe9cd4 commit ebe11044681caff78834ca6b78311ad19c1860b8 Author: djm@openbsd.org Date: Mon Jul 17 05:22:30 2023 +0000 upstream: Move RCSID to before license block and away from #includes, where it caused merge conflict in -portable for each commit :( OpenBSD-Commit-ID: 756ebac963df3245258b962e88150ebab9d5fc20 commit 05c08e5f628de3ecf6f7ea20947735bcfa3201e0 Author: djm@openbsd.org Date: Mon Jul 17 05:20:15 2023 +0000 upstream: return SSH_ERR_KRL_BAD_MAGIC when a KRL doesn't contain a valid magic number and not SSH_ERR_MESSAGE_INCOMPLETE; the former is needed to fall back to text revocation lists in some cases; fixes t-cert-hostkey. OpenBSD-Commit-ID: 5c670a6c0f027e99b7774ef29f18ba088549c7e1 commit c6fad2c3d19b74f0bd0af1ef040fc74f3a1d9ebb Author: Damien Miller Date: Mon Jul 17 14:56:14 2023 +1000 avoid AF_LINK on platforms that don't define it commit 919bc3d3b712c920de1ae6be5ac6561c98886d7e Author: djm@openbsd.org Date: Mon Jul 17 04:08:31 2023 +0000 upstream: Add support for configuration tags to ssh(1). This adds a ssh_config(5) "Tag" directive and corresponding "Match tag" predicate that may be used to select blocks of configuration similar to the pf.conf(5) keywords of the same name. ok markus OpenBSD-Commit-ID: dc08358e70e702b59ac3e591827e5a96141b06a3 commit 3071d85a47061c1bdaf11a0ac233b501ecba862c Author: djm@openbsd.org Date: Mon Jul 17 04:04:36 2023 +0000 upstream: add a "match localnetwork" predicate. This allows matching on the addresses of available network interfaces and may be used to vary the effective client configuration based on network location (e.g. to use a ProxyJump when not on a particular network). ok markus@ OpenBSD-Commit-ID: cffb6ff9a3803abfc52b5cad0aa190c5e424c139 commit beec17bb311365b75a0a5941418d4b96df7d7888 Author: djm@openbsd.org Date: Mon Jul 17 04:01:10 2023 +0000 upstream: remove vestigal support for KRL signatures When the KRL format was originally defined, it included support for signing of KRL objects. However, the code to sign KRLs and verify KRL signatues was never completed in OpenSSH. Now, some years later, we have SSHSIG support in ssh-keygen that is more general, well tested and actually works. So this removes the semi-finished KRL signing/verification support from OpenSSH and refactors the remaining code to realise the benefit - primarily, we no longer need to perform multiple parsing passes over KRL objects. ok markus@ OpenBSD-Commit-ID: 517437bab3d8180f695c775410c052340e038804 commit 449566f64c21b4578d5c0c431badd0328adc53ed Author: djm@openbsd.org Date: Mon Jul 17 03:57:21 2023 +0000 upstream: Support for KRL extensions. This defines wire formats for optional KRL extensions and implements parsing of the new submessages. No actual extensions are supported at this point. ok markus OpenBSD-Commit-ID: ae2fcde9a22a9ba7f765bd4f36b3f5901d8c3fa7 commit 18ea857770e84825a3a6238bb37f54864487b59f Author: dtucker@openbsd.org Date: Fri Jul 14 07:44:21 2023 +0000 upstream: Include stdint.h for SIZE_MAX. Fixes OPENSSL=no build. OpenBSD-Commit-ID: e7c31034a5434f2ead3579b13a7892960651e6b0 commit 20b768fcd13effe0f2d3619661b6c8592c773553 Author: Darren Tucker Date: Fri Jul 14 17:07:32 2023 +1000 Fix typo in declaration of nmesg. commit 4b94d09542e36ebde2eb9ad89bc68431609932de Author: Damien Miller Date: Fri Jul 14 15:34:47 2023 +1000 portable-specific int overflow defence-in-depth These too are unreachable, but we want the code to be safe regardless of context. Reported by Yair Mizrahi @ JFrog commit 2ee48adb9fc8692e8d6ac679dcc9f35e89ad68f0 Author: djm@openbsd.org Date: Fri Jul 14 05:31:44 2023 +0000 upstream: add defence-in-depth checks for some unreachable integer overflows reported by Yair Mizrahi @ JFrog; feedback/ok millert@ OpenBSD-Commit-ID: 52af085f4e7ef9f9d8423d8c1840a6a88bda90bd commit 4b43bc358ae6f6b19a973679246dc5172f6ac41b Author: djm@openbsd.org Date: Mon Jul 10 04:51:26 2023 +0000 upstream: misplaced debug message OpenBSD-Commit-ID: d0f12af0a5067a756aa707bc39a83fa6f58bf7e5 commit 8c7203bcee4c4f98a22487b4631fe068b992099b Author: Damien Miller Date: Wed Jul 12 11:41:19 2023 +1000 replace deprecate selinux matchpathcon function This function is apparently deprecated. Documentation on what is the supposed replacement is is non-existent, so this follows the approach glibc used https://sourceware.org/git/?p=glibc.git;a=patch;h=f278835f59 ok dtucker@ commit 7e8800f5d701efffa39ccb63ca1e095ea777c31a Author: dtucker@openbsd.org Date: Thu Jul 6 22:17:59 2023 +0000 upstream: minleft and maxsign are u_int so cast appropriately. Prompted by github PR#410, ok deraadt. OpenBSD-Commit-ID: 0514cd51db3ec60239966622a0d3495b15406ddd commit 94842bfe9b09fc93189c6ed0dc9bbebc1d44a426 Author: dlg@openbsd.org Date: Tue Jul 4 03:59:21 2023 +0000 upstream: add support for unix domain sockets to ssh -W ok djm@ dtucker@ OpenBSD-Commit-ID: 3e6d47567b895c7c28855c7bd614e106c987a6d8 commit a95fc5eed09a0238fb127b6c50e8498432b79dae Author: David Seifert Date: Fri May 12 14:06:01 2023 +0200 gss-serv.c: `MAXHOSTNAMELEN` -> `HOST_NAME_MAX` `MAXHOSTNAMELEN` is not defined in POSIX, which breaks on musl: https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html Bug: https://bugs.gentoo.org/834044 commit 8a6cd08850f576e7527c52a1b086cae82fab290e Author: Darren Tucker Date: Fri Jun 23 09:49:02 2023 +1000 Update runner OS version for hardenedmalloc test. Hardenedmalloc dropped support for "legacy glibc" versions in their 64dad0a69 so use a newer Ubuntu version for the runner for that test. commit cfca6f17e64baed6822bb927ed9f372ce64d9c5b Author: Damien Miller Date: Thu Jun 22 15:04:03 2023 +1000 handle sysconf(SC_OPEN_MAX) returning > INT_MAX; bz3581; ok dtucker commit c1c2ca1365b3f7b626683690bd2c68265f6d8ffd Author: djm@openbsd.org Date: Wed Jun 21 05:10:26 2023 +0000 upstream: better validate CASignatureAlgorithms in ssh_config and sshd_config. Previously this directive would accept certificate algorithm names, but these were unusable in practice as OpenSSH does not support CA chains. part of bz3577; ok dtucker@ OpenBSD-Commit-ID: a992d410c8a78ec982701bc3f91043dbdb359912 commit 4e73cd0f4ab3e5b576c56cac9732da62c8fc0565 Author: djm@openbsd.org Date: Wed Jun 21 05:08:32 2023 +0000 upstream: make `ssh -Q CASignatureAlgorithms` only list signature algorithms that are valid for CA signing. Previous behaviour was to list all signing algorithms, including certificate algorithms (OpenSSH certificates do not support CA chains). part of bz3577; ok dtucker@ OpenBSD-Commit-ID: 99c2b072dbac0f44fd1f2269e3ff6c1b5d7d3e59 commit a69062f1695ac9c3c3dea29d3044c72aaa6af0ea Author: djm@openbsd.org Date: Wed Jun 21 05:06:04 2023 +0000 upstream: handle rlimits > INT_MAX (rlim_t is u64); ok dtucker bz3581 OpenBSD-Commit-ID: 31cf59c041becc0e5ccb0a77106f812c4cd1cd74 commit 8d33f2aa6bb895a7f85a47189913639086347b75 Author: djm@openbsd.org Date: Tue Jun 20 23:59:33 2023 +0000 upstream: prepare for support for connecting to unix domain sockets using ssh -W by explicitly decoding PORT_STREAMLOCAL (a negative number) from the u32 that's passed over the multiplexing socket; previously code would just cast, which is UB. OpenBSD-Commit-ID: e5ac5f40d354096c51e8c118a5c1b2d2b7a31384 commit b4ac435b4e67f8eb5932d8f59eb5b3cf7dc38df0 Author: djm@openbsd.org Date: Tue Jun 20 00:05:09 2023 +0000 upstream: reset comment=NULL for each key in do_fingerprint(); fixes "no comment" not showing on when running `ssh-keygen -l` on multiple keys where one has a comment and other following keys do not. Patch from Markus Kuhn via GHPR407, bz3580 OpenBSD-Commit-ID: 3cce84456fdcd67dc6b84e369f92c6686d111d9b commit b53a809a549dcd4fbde554c6aa283e597b15ea33 Author: millert@openbsd.org Date: Mon Jun 5 13:24:36 2023 +0000 upstream: Store timeouts as int, not u_int as they are limited to INT_MAX. Fixes sign compare warnings systems with 32-bit time_t due to type promotion. OK djm@ OpenBSD-Commit-ID: 48081e9ad35705c5f1705711704a4c2ff94e87b7 commit 2709809fd616a0991dc18e3a58dea10fb383c3f0 Author: Philip Hands Date: Wed May 24 19:41:14 2023 +0200 fixup! if -s & -p specified, mention 'sftp -P' on success SSH-Copy-ID-Upstream: 32686e7c65b4fa2846e474d3315102dfa0f043b0 commit 204e0bf05161b7641500d7ab266c21217412379f Author: Darren Tucker Date: Tue Aug 3 21:25:48 2021 +1000 Make ssh-copy-id(1) consistent with OpenSSH. This makes the ssh-copy-id man page more consistent with the rest of the OpenSSH man pages: - new sentence, new line - no sentences >80 - N.B. -> NB - zap unused .Pp - zap trailing whitespace Report from Debian via mindrot bz#3331, diff from jmc at openbsd.org. SSH-Copy-ID-Upstream: d8974cfb6242316460ed22a1ccc662800a50c5d3 commit 9de79df66d1430d290fab670bb4b18612875e518 Author: Philip Hands Date: Wed May 24 11:45:43 2023 +0200 if -s & -p specified, mention 'sftp -P' on success This was inspired by this: https://github.com/openssh/openssh-portable/pull/321 but I thought that it was better to not do the sed patching. BTW the reason one can get away with using $SSH_OPTS throughout, despite the lowercase -p in there, even if sftp is in use, is that the sftp call is using the already-established ssh master connection, so the port was passed to the earlier ssh. SSH-Copy-ID-Upstream: 1c124d9bfafdbe28a00b683367ebf5750ce12eb2 commit 801cda54c00e0f4e7d89345a90874c8d05dc233a Author: Philip Hands Date: Tue May 23 23:07:11 2023 +0200 drop whitespace SSH-Copy-ID-Upstream: e604fae1cdee35c18055d35dcec530cf12ef00ad commit 288482f53613f3e74544eb92deeb24f7c7f1f371 Author: Philip Hands Date: Tue May 23 20:52:13 2023 +0200 make -x also apply to the target script SSH-Copy-ID-Upstream: 3c4214704f427bd0654adf9b0fc079253db21cf4 commit b79e7b88ed44f0e4339f0ff35c96c78a92175a8d Author: Philip Hands Date: Tue May 23 16:46:42 2023 +0200 add -t option to specify the target path Allow the default target path (.ssh/authorized_files) to be over-riden This was inspired by this MR from Panagiotis Cheilaris https://gitlab.com/phil_hands/ssh-copy-id/-/merge_requests/8 SSH-Copy-ID-Upstream: a942a0e076874adb6d8b2f0fb76d6c7918190fcd commit 914f4ad138714c471ba72fb6d5496b6235320edd Author: Carlos Rodríguez Gili Date: Tue Apr 20 19:23:57 2021 +0200 Fix test error for /bin/sh on Solaris 10 and older On Solaris 10 and older targets /bin/sh is not POSIX-compliant. Test -z `...` fails with error 'sh: test: argument expected'. Using quotes around backticks fixes this and doesn't break POSIX compatibility. SSH-Copy-ID-Upstream: 98394072a3f985b2650c1e8eab2fef84e38cc065 commit bd382dca316c721aed1e45edcf4c4e0f6374afb0 Author: Jakub Jelen Date: Tue Mar 2 21:34:05 2021 +0000 Remove outdated comment The commit b068122 removed the code dropping the trailing colon, but the comment stayed leaving the code confusing for future readers SSH-Copy-ID-Upstream: 930d39f238117cd53810240ec989d0356aa1c1f6 commit bdcaf7939029433635d63aade8f9ac762aca2bbe Author: Darren Tucker Date: Wed May 10 18:50:46 2023 +1000 Special case OpenWrt instead of Dropbear. OpenWrt overrides the location of authorized_keys for root. Currently we assume that all Dropbear installations behave this way, which is not the case. Check for OpenWrt and root user before using that location instead of assuming that for all Dropbear servers. Prompted by Github PR#250. SSH-Copy-ID-Upstream: 0e1f5d443a9967483c33945793107ae3f3e4af2d commit cf84498f67abe93f813a296167b406a0db7b288e Author: Philip Hands Date: Thu May 18 18:20:55 2023 +0200 ssh-copy-id: add -x option (for debugging) This option causes the ssh-copy-id to run with set -x SSH-Copy-ID-Upstream: a0ee367ea8c0a29c8b4515245e408d2d349e7844 commit b4a1efdcb88f03394c08e7f68ed4e11676830002 Author: Philip Hands Date: Thu May 18 17:14:41 2023 +0200 update copyright notices SSH-Copy-ID-Upstream: c284ed33b361814ea48ff68cbd01ca525b2bf117 commit fcd78e31cdd45a7e69ccfe6d8a3b1037dc1de290 Author: djm@openbsd.org Date: Wed May 24 23:01:06 2023 +0000 upstream: fix AuthorizedPrincipalsCommand when AuthorizedKeysCommand appears previously in configuration. Reported by John Meyers in bz3574 ok dtucker@ OpenBSD-Commit-ID: 1c92e4517284386703936e1d3abaa36cfacf1951 commit 5ec5504f1d328d5bfa64280cd617c3efec4f78f3 Author: dtucker@openbsd.org Date: Wed May 10 10:04:20 2023 +0000 upstream: Remove unused prototypes for ssh1 RSA functions. From lengyijun via github PR#396. OpenBSD-Commit-ID: 379a5afa8b7a0f3cba0c8a9bcceb4e5e33a5c1ef commit fbf362b3891ae4b36052d1b39f37fc618b41c476 Author: Darren Tucker Date: Tue May 9 19:26:56 2023 +1000 main(void) to prevent unused variable warning. commit baf854c8bb0a6d0af5c696c801e631a48dabbaba Author: Darren Tucker Date: Tue May 9 19:25:45 2023 +1000 Remove warning pragma since clang doesn't like it. commit 5fbb7a1349fbbb48ccb1b8cafff2c1854370d87d Author: Darren Tucker Date: Tue May 9 17:13:33 2023 +1000 Suppress warning for snprintf truncation test. commit 47742c513e4e045ecc985c6483fc5c8b050acda2 Author: Darren Tucker Date: Tue May 9 17:12:50 2023 +1000 Update OpenSSL compat test for 3.x. commit 86ad25d455a2313126125540e61e0f9314283f88 Author: Darren Tucker Date: Mon May 8 20:23:08 2023 +1000 Add macos13 PAM test target. commit 77cca2c4b13bc6e5f389565583b6202b0d1bccc2 Author: Darren Tucker Date: Mon May 8 20:14:46 2023 +1000 Skip agent-peereid test on macos13. sudo -S nobody doesn't work on the github runners (probably a permission issue) so skip that test. commit b356b8e91678ea295bcf44df5248c3fbf499fdcf Author: Darren Tucker Date: Mon May 8 20:14:28 2023 +1000 Include config.guess in debug output. commit b7afd8a4ecaca8afd3179b55e9db79c0ff210237 Author: Darren Tucker Date: Mon May 8 20:12:59 2023 +1000 Handle OpenSSL >=3 ABI compatibility. Beyond OpenSSL 3.0, the ABI compatibility guarantees are wider (only major must match instead of major and minor in earlier versions). bz#3548, ok djm@ commit 0e9e2663eb2c6e9c3e10d15d70418312ae67e542 Author: dtucker@openbsd.org Date: Mon May 1 08:57:29 2023 +0000 upstream: Import regenerated moduli. OpenBSD-Commit-ID: 3d5f811cfcaed8cc4a97e1db49ac61bdf118113c commit d9687f49682e1e93383fc15ab2018850b2ef38c3 Author: Darren Tucker Date: Mon May 1 11:45:14 2023 +1000 Add macos-13 test target. Also flatten OS list for clarity. commit aacfd6767497b8fa6d41ecdd3f8e265d1e9ef1f6 Author: djm@openbsd.org Date: Sun Apr 30 22:54:22 2023 +0000 upstream: adjust ftruncate() logic to handle servers that reorder requests. sftp/scp will ftruncate the destination file after a transfer completes, to deal with the case where a longer destination file already existed. We tracked the highest contiguous block transferred to deal with this case, but our naive tracking doesn't deal with servers that reorder requests - a misfeature strictly permitted by the protocol but seldom implemented. Adjust the logic to ftruncate() at the highest absolute block received when the transfer is successful. feedback deraadt@ ok markus@ prompted by https://github.com/openssh/openssh-portable/commit/9b733#commitcomment-110679778 OpenBSD-Commit-ID: 4af7fac75958ad8507b4fea58706f3ff0cfddb1b commit c8eb3941758615c8284a48fff47872db926da63c Author: djm@openbsd.org Date: Wed Apr 26 01:36:03 2023 +0000 upstream: Check for ProxyJump=none in CanonicalizeHostname logic. Previously ssh would incorrectly refuse to canonicalise the hostname if ProxyJump was explicitly set to "none" when CanonicalizeHostname=yes bz3567; ok dtucker OpenBSD-Commit-ID: 80a58e43c3a32f97361282f756ec8d3f37989efd commit ac383f3a5c6f529a2e8a5bc44af79a08c7da294e Author: jsg@openbsd.org Date: Wed Apr 12 14:22:04 2023 +0000 upstream: remove duplicate signal.h include OpenBSD-Commit-ID: 30c0a34d74d91ddd0e6992525da70d3293392f70 commit 740dafa20f3f3d325f6f5d44e990b8c8a6d3d816 Author: jsg@openbsd.org Date: Wed Apr 12 08:53:54 2023 +0000 upstream: fix double words ok dtucker@ OpenBSD-Commit-ID: 44d3223902fbce5276422bdc8063ab72a4078489 commit 6452f89577ec4f22440c31b8e19b061d1a7c4b2a Author: Darren Tucker Date: Tue Apr 11 16:49:19 2023 +1000 Test against LibreSSL 3.7.2. commit 2138f6be595ca106fe4805a1e3ab9c4d8acc697b Author: Damien Miller Date: Thu Apr 6 14:33:10 2023 +1000 remove unused upper-case const strings in fmtfp no float format that uses upper-case is supported nor are hex floats. ok dtucker commit 484c5e6168fdb22cbcd73c4ff987cf9ca47989ca Author: djm@openbsd.org Date: Thu Apr 6 03:56:02 2023 +0000 upstream: simplify sshsig_find_principals() similar to what happened to sshsig_check_allowed_keys() in r1.31, removing some dead code OpenBSD-Commit-ID: a493e628d4d6c08f878c276d998f4313ba61702d commit 3a7b110fbc7e096423f8f7b459deffe4c65d70f4 Author: djm@openbsd.org Date: Thu Apr 6 03:21:31 2023 +0000 upstream: remove redundant ssh!=NULL check; we'd already dereferenced it OpenBSD-Commit-ID: 852bf12591ec5a9fb12dcbde9b1fd3945ad0df3c commit 2519110659a1efac6c976895a86659d1b341c91b Author: djm@openbsd.org Date: Thu Apr 6 03:19:32 2023 +0000 upstream: match_user() shouldn't be called with user==NULL unless host and ipaddr are also NULL OpenBSD-Commit-ID: fa3518346c21483e9e01a2e4b9436ae501daf8ea commit 3b9ceaad7ad63c1c03c2a89e148340ad3a62a482 Author: djm@openbsd.org Date: Thu Apr 6 03:12:32 2023 +0000 upstream: don't care about glob() return value here. OpenBSD-Commit-ID: 85bb82fea90478a482e9f65a1bec0aa24227fd66 commit 09d8da0849e2791b2500267cda333cd238f38754 Author: dtucker@openbsd.org Date: Mon Apr 3 08:10:54 2023 +0000 upstream: Move up null check and simplify process_escapes. Based on Coverity CID 291863 which points out we check the channel pointer for NULLness after dereferencing it. Move this to the start of the function, and while there simplify initialization of efc a bit. ok djm@ OpenBSD-Commit-ID: de36e5ad6fde0fe263ca134e986b9095dc59380a commit b36b162be5e6206f12b734222b7bc517c13a6bc8 Author: Damien Miller Date: Fri Mar 31 14:51:20 2023 +1100 need va_end() after va_copy(); ok dtucker spotted by Coverity commit f703757234a5c585553e72bba279b255a272750a Author: dtucker@openbsd.org Date: Fri Mar 31 05:56:36 2023 +0000 upstream: Explicitly ignore return from waitpid here too. OpenBSD-Commit-ID: eef2403df083c61028969fc679ee370373eacacb commit 6b73aa29035991d1448a1a76f63ac152a6bf931c Author: dtucker@openbsd.org Date: Fri Mar 31 04:45:08 2023 +0000 upstream: Explictly ignore return codes where we don't check them. OpenBSD-Commit-ID: 1ffb03038ba1b6b72667be50cf5e5e396b5f2740 commit 6f0308a3e717ebe68eeb3f95253612fab5dbf20e Author: dtucker@openbsd.org Date: Fri Mar 31 04:42:29 2023 +0000 upstream: Return immediately from get_sock_port if sock <0 so we don't call getsockname on a negative FD. From Coverity CID 291840, ok djm@ OpenBSD-Commit-ID: de1c1130646230c2eda559831fc6bfd1b61d9618 commit 1c1124dc901fca1ea2cb762044b8f1a5793a2bed Author: djm@openbsd.org Date: Fri Mar 31 04:23:02 2023 +0000 upstream: don't leak arg2 on parse_pubkey_algos error path; ok dtucker@ OpenBSD-Commit-ID: 7d0270ad3dd102412ca76add2b3760518abdef75 commit 8ba2d4764bb6a4701cd447d8b52604622ffe65f4 Author: djm@openbsd.org Date: Fri Mar 31 04:22:27 2023 +0000 upstream: clamp max number of GSSAPI mechanisms to 2048; ok dtucker OpenBSD-Commit-ID: ce66db603a913d3dd57063e330cb5494d70722c4 commit 1883841fc13d0eada8743cac5d3abe142ee2efa7 Author: djm@openbsd.org Date: Fri Mar 31 04:21:56 2023 +0000 upstream: don't print key if printing hostname failed; with/ok dtucker@ OpenBSD-Commit-ID: ad42971a6ee5a46feab2d79f7f656f8cf4b119f3 commit c6011129cafe4c411f6ef670a4cf271314708eb8 Author: djm@openbsd.org Date: Fri Mar 31 04:04:15 2023 +0000 upstream: remove redundant test OpenBSD-Commit-ID: 6a0b719f9b1ae9d42ad8c5b144c7962c93792f7c commit 4fb29eeafb40a2076c0dbe54e46b687c318f87aa Author: djm@openbsd.org Date: Fri Mar 31 04:00:37 2023 +0000 upstream: don't attempt to decode a ridiculous number of attributes; harmless because of bounds elsewhere, but better to be explicit OpenBSD-Commit-ID: 1a34f4b6896155b80327d15dc7ccf294b538a9f2 commit fc437c154ef724621a4af236de9bc7e51a8381ae Author: djm@openbsd.org Date: Fri Mar 31 03:22:49 2023 +0000 upstream: remove unused variable; prompted by Coverity CID 291879 OpenBSD-Commit-ID: 4c7d20ef776887b0ba1aabcfc1b14690e4ad0a40 commit 0eb8131e4a53b33a8fc9b9ab694e6b6778b87ade Author: dtucker@openbsd.org Date: Fri Mar 31 00:44:29 2023 +0000 upstream: Check fd against >=0 instead of >0 in error path. The dup could in theory return fd 0 although currently it doesn't in practice. From Dmitry Belyavskiy vi github PR#238. OpenBSD-Commit-ID: 4a95f3f7330394dffee5c749d52713cbf3b54846 commit 7174ba6f8a431ca4257767a260fc50e204068242 Author: dtucker@openbsd.org Date: Thu Mar 30 07:19:50 2023 +0000 upstream: Ignore return value from muxclient(). It normally loops without returning, but it if returns on failure we immediately exit. Coverity CID 405050. OpenBSD-Commit-ID: ab3fde6da384ea588226037c38635a6b2e015295 commit a4c1c2513e36f111eeaa1322c510067930e5e51e Author: Damien Miller Date: Fri Mar 31 14:17:22 2023 +1100 don't call connect() on negative socket Coverity CID 405037 commit 34ee842cdd981a759fe8f0d4a37521f9a1c63170 Author: djm@openbsd.org Date: Thu Mar 30 03:05:01 2023 +0000 upstream: return SSH_ERR_KEY_NOT_FOUND if the allowed_signers file is empty, not SSH_ERR_INTERNAL_ERROR. Also remove some dead code spotted by Coverity; with/ok dtucker@ OpenBSD-Commit-ID: 898a1e817cda9869554b1f586a434f67bcc3b650 commit f108e77a9dc9852e72215af1bf27731c48434557 Author: dtucker@openbsd.org Date: Thu Mar 30 00:49:37 2023 +0000 upstream: Remove dead code from inside if block. The only way the if statement can be true is if both dup()s fail, and in that case the tmp2 can never be set. Coverity CID 291805, ok djm@ OpenBSD-Commit-ID: c0d6089b3fb725015462040cd94e23237449f0c8 commit 05b8e88ebe23db690abbfb1a91111abea09cde08 Author: Darren Tucker Date: Thu Mar 30 13:53:29 2023 +1100 child_set_eng: verify both env pointer and count. If child_set env was called with a NULL env pointer and a non-zero count it would end up in a null deref, although we don't currently do this. Prompted by Coverity CID 291850, tweak & ok djm@ commit 28f1b8ef9b84b8cd2f6c9889a0c60aa4a90dadfa Author: dtucker@openbsd.org Date: Wed Mar 29 01:07:48 2023 +0000 upstream: Ignore return from sshpkt_disconnect since we set our own return value for the function. Coverity CID 291797, ok djm@ OpenBSD-Commit-ID: 710b57ba954c139240895e23feea41f203201f04 commit c3da05d95922f5550bcc7815e799474d6a160175 Author: dtucker@openbsd.org Date: Wed Mar 29 00:59:08 2023 +0000 upstream: Plug potential mem leak in process_put. It allocates abs_dst inside a loop but only frees it on exit, so free inside the loop if necessary. Coverity CID 291837, ok djm@ OpenBSD-Commit-ID: a01616503a185519b16f00dde25d34ceaf4ae1a3 commit 13ae327eae598b1043e5ec30e4b170edb3c898a5 Author: djm@openbsd.org Date: Wed Mar 29 00:18:35 2023 +0000 upstream: fix memory leak; Coverity CID 291848 with/ok dtucker@ OpenBSD-Commit-ID: 37f80cb5d075ead5a00ad1b74175684ab1156ff8 commit 9ffa76e1284c85bf459c3dcb8e995733a8967e1b Author: dtucker@openbsd.org Date: Tue Mar 28 07:44:32 2023 +0000 upstream: Plug more mem leaks in sftp by making make_absolute_pwd_glob work in the same way as make_absolute: you pass it a dynamically allocated string and it either returns it, or frees it and allocates a new one. Patch from emaste at freebsd.org and https://reviews.freebsd.org/D37253 ok djm@ OpenBSD-Commit-ID: 85f7404e9d47fd28b222fbc412678f3361d2dffc commit 82b2b8326962b1a98af279bc5bbbbbcab15b3e45 Author: dtucker@openbsd.org Date: Tue Mar 28 06:12:38 2023 +0000 upstream: Remove compat code for OpenSSL < 1.1.* since -portable no longer supports them. OpenBSD-Commit-ID: ea2893783331947cd29a67612b4e56f818f185ff commit b500afcf00ae1b6b73b2ccf171111dfbfeaef74d Author: dtucker@openbsd.org Date: Mon Mar 27 23:56:54 2023 +0000 upstream: Remove compat code for OpenSSL 1.0.* versions now that -portable has dropped support for those versions. OpenBSD-Regress-ID: 82a8eacd87aec28e4aa19f17246ddde9d5ce7fe7 commit 727560e6011efcb36d2f3ac6910444bc775abaa1 Author: Darren Tucker Date: Tue Mar 28 18:06:42 2023 +1100 Prevent conflicts between Solaris SHA2 and OpenSSL. We used to prevent conflicts between native SHA2 headers and OpenSSL's by setting OPENSSL_NO_SHA but that was removed prior to OpenSSL 1.1.0 commit 46db8e14b7f186d32173dcdecd5b785334429b8b Author: Darren Tucker Date: Tue Mar 28 12:44:03 2023 +1100 Remove HEADER_SHA_H from previous... since it causes more problems than it solves. commit 72bd68d37387aa5f81da928f6e82f1c88ed8f674 Author: Darren Tucker Date: Tue Mar 28 10:35:18 2023 +1100 Replace OPENSSL_NO_SHA with HEADER_SHA_H. Since this test doesn't use OpenSSL's SHA2 and may cause conflicts we don't want to include it, but OPENSSL_NO_SHA was removed beginning in OpenSSL's 1.1 series. commit 99668f2e6e0deb833e46cfab56db59ff0fc28c7e Author: Darren Tucker Date: Tue Mar 28 09:50:06 2023 +1100 Configure with --target instead of deprecated form. commit f751d9306c62cd1061f966e6a7483d9bab9c379b Author: Darren Tucker Date: Mon Mar 27 22:05:29 2023 +1100 Pass rpath when building 64bit Solaris. commit a64b935cd450ee8d04c26c9cd728629cf9ca5c91 Author: Darren Tucker Date: Mon Mar 27 19:21:19 2023 +1100 Explicitly disable OpenSSL on AIX test VM. commit 7ebc6f060fc2f70495a56e16d210baae6424cd96 Author: dtucker@openbsd.org Date: Mon Mar 27 03:56:50 2023 +0000 upstream: Add RevokedHostKeys to percent expansion test. OpenBSD-Regress-ID: c077fd12a38005dd53d878c5b944154dec88d2ff commit f1a17de150f8d309d0c52f9abfaebf11c51a8537 Author: dtucker@openbsd.org Date: Mon Mar 27 03:56:11 2023 +0000 upstream: Add tilde and environment variable expansion to RevokedHostKeys. bz#3552, ok djm@ OpenBSD-Commit-ID: ce5d8e0219b63cded594c17d4c2958c06918ec0d commit 009eb4cb48a9708ab9174684dcbcc0f942907abe Author: djm@openbsd.org Date: Mon Mar 27 03:31:05 2023 +0000 upstream: fix test: getnameinfo returns a non-zero value on error, not (neccessarily) -1. From GHPR#384 OpenBSD-Commit-ID: d35e2b71268f66f5543a7ea68751972b3ae22b25 commit 4f0a676486700f10a4788f7e9426e94e39c1c89e Author: djm@openbsd.org Date: Mon Mar 27 03:25:08 2023 +0000 upstream: scp: when copying local->remote, check that source file exists before opening SFTP connection to the server. Based on GHPR#370 ok dtucker, markus OpenBSD-Commit-ID: b4dd68e15bfe22ce4fac9960a1066a2b721e54fb commit 154d8baf631327163571760c2c524bc93c37567c Author: Darren Tucker Date: Mon Mar 27 12:22:30 2023 +1100 Also look for gdb error message from OpenIndiana. commit fbd3811ddb2b6ce2e6dba91fde7352c8978e5412 Author: Darren Tucker Date: Mon Mar 27 11:08:00 2023 +1100 Explicitly disable security key test on aix51 VM. We don't know how to build the shared objects required for the security key tests so skip them. commit 4922ac3be8a996780ef3dc220411da2e27c29d9c Author: Darren Tucker Date: Sun Mar 26 14:49:43 2023 +1100 Split libcrypto and other config flags. This should allow the automatic OpenSSL version selection in the tests to work better. commit 4a948b1469f185e871160a2d70e2a0fce2858f9e Author: Darren Tucker Date: Sun Mar 26 14:39:45 2023 +1100 Specify test target if we build without OpenSSL. When we decide we can't use the versions of OpenSSL available, also restrict the tests we run to avoid the ones that need OpenSSL. commit b308c636f5b5d89eecb98be00b3d56306a005a09 Author: Darren Tucker Date: Sun Mar 26 14:22:53 2023 +1100 Find suitable OpenSSL version. Check the installed OpenSSL versions for a suitable one, and if there isn't (and we don't have a specific version configured) then build without OpenSSL. commit 021ea5c2860f133f44790970968e0e73208b3a87 Author: Damien Miller Date: Fri Mar 24 15:02:52 2023 +1100 Github testing support for BoringSSL commit 9a97cd106466a2a9bda2bfaa4c48c4f1b2cc9c1b Author: Damien Miller Date: Fri Mar 24 15:34:29 2023 +1100 BoringSSL doesn't support EC_POINT_point2bn() so don't invoke it in unittest commit cc5969c033a032d126ff78e5d95cf20abbede4c7 Author: Damien Miller Date: Fri Mar 24 15:34:05 2023 +1100 another ERR_load_CRYPTO_strings() vestige commit 4974293899a068133e976f81d6693670d2b576ca Author: Damien Miller Date: Fri Mar 24 15:24:05 2023 +1100 don't use obsolete ERR_load_CRYPTO_strings() OpenSSL (and elsewhere in OpenSSH) uses ERR_load_crypto_strings() commit 3c527d55f906e6970d17c4cab6db90ae9e013235 Author: Damien Miller Date: Fri Mar 24 15:23:05 2023 +1100 Allow building with BoringSSL commit b7e27cfd7f163fc16b4c5d041cc28ee488a5eeec Author: Damien Miller Date: Fri Mar 24 15:21:18 2023 +1100 put back SSLeay_version compat in configure test Needed to detect old versions and give good "your version is bad" messages at configure time; spotted by dtucker@ commit 7280401bdd77ca54be6867a154cc01e0d72612e0 Author: Damien Miller Date: Fri Mar 24 13:56:25 2023 +1100 remove support for old libcrypto OpenSSH now requires LibreSSL 3.1.0 or greater or OpenSSL 1.1.1 or greater with/ok dtucker@ commit abda22fb48302f2142233f71d27c74040288c518 Author: Darren Tucker Date: Sun Mar 19 15:36:13 2023 +1100 Test latest OpenSSL 1.1, 3.0 and LibreSSL 3.7. commit 610ac1cb077cd5a1ebfc21612154bfa13d2ec825 Author: Darren Tucker Date: Thu Mar 16 21:38:04 2023 +1100 Show 9.3 branch instead of 9.2. commit cb30fbdbee869f1ce11f06aa97e1cb8717a0b645 Author: Damien Miller Date: Thu Mar 16 08:28:19 2023 +1100 depend commit 1dba63eb10c40b6fda9f5012ed6ae87e2d3d028e Author: Damien Miller Date: Thu Mar 16 08:27:54 2023 +1100 crank version commit ba7532d0dac9aaf0ad7270664c43837fc9f64a5f Author: djm@openbsd.org Date: Wed Mar 15 21:19:57 2023 +0000 upstream: openssh-9.3 OpenBSD-Commit-ID: 8011495f2449c1029bb316bd015eab2e00509848 commit 6fd4daafb949b66bf555f3100f715a9ec64c3390 Author: dtucker@openbsd.org Date: Tue Mar 14 07:28:47 2023 +0000 upstream: Free KRL ptr in addition to its contents. From Coverity CID 291841, ok djm@ OpenBSD-Commit-ID: f146ba08b1b43af4e0d7ad8c4dae3748b4fa31b6 commit 1d270bd303afaf6d94e9098cbbf18e5e539e2088 Author: dtucker@openbsd.org Date: Tue Mar 14 07:26:25 2023 +0000 upstream: Check pointer for NULL before deref. None of the existing callers seem to do that, but it's worth checking. From Coverity CID 291834, ok djm@ OpenBSD-Commit-ID: a0a97113f192a7cb1a2c97b932f677f573cda7a4 commit d95af508e78c0cd3dce56b83853baaa59ae295cf Author: dtucker@openbsd.org Date: Sun Mar 12 10:40:39 2023 +0000 upstream: Limit number of entries in SSH2_MSG_EXT_INFO request. This is already constrained by the maximum SSH packet size but this makes it explicit. Prompted by Coverity CID 291868, ok djm@ markus@ OpenBSD-Commit-ID: aea023819aa44a2dcb9dd0fbec10561896fc3a09 commit 8f287ba60d342b3e2f750e7332d2131e3ec7ecd0 Author: dtucker@openbsd.org Date: Sun Mar 12 09:41:18 2023 +0000 upstream: calloc can return NULL but xcalloc can't. From Coverity CID 291881, ok djm@ OpenBSD-Commit-ID: 50204b755f66b2ec7ac3cfe379d07d85ca161d2b commit 83a56a49fd50f4acf900f934279482e4ef329715 Author: dtucker@openbsd.org Date: Fri Mar 10 07:17:08 2023 +0000 upstream: Explicitly ignore return from fcntl (... FD_CLOEXEC) here too. Coverity CID 291853. OpenBSD-Commit-ID: 99d8b3da9d0be1d07ca8dd8e98800a890349e9b5 commit 0fda9d704d3bbf54a5e64ce02a6fecb11fe7f047 Author: Damien Miller Date: Fri Mar 10 15:59:46 2023 +1100 bounds checking for getrrsetbyname() replacement; Spotted by Coverity in CID 405033; ok millert@ commit 89b8df518f21677045599df0ad3e5dd0f39909b5 Author: dtucker@openbsd.org Date: Fri Mar 10 04:06:21 2023 +0000 upstream: Plug mem leak on error path. Coverity CID 405026, ok djm@. OpenBSD-Commit-ID: 8212ca05d01966fb5e72205c592b2257708a2aac commit bf4dae0ad192c3e2f03f7223834b00d88ace3d3e Author: Darren Tucker Date: Fri Mar 10 14:46:57 2023 +1100 Add prototypes for mkstemp replacements. Should prevent warnings due to our wrapper function. commit 4e04d68d6a33cdc73b831fd4b5e6124175555d3d Author: dtucker@openbsd.org Date: Fri Mar 10 03:01:51 2023 +0000 upstream: Expliticly ignore return code from fcntl(.. FD_CLOEXEC) since there's not much we can do anyway. From Coverity CID 291857, ok djm@ OpenBSD-Commit-ID: 051429dd07af8db3fec10d82cdc78d90bb051729 commit d6d38fd77cbe091c59e1bb720c3a494df4990640 Author: djm@openbsd.org Date: Fri Mar 10 02:32:04 2023 +0000 upstream: Like sshd_config, some ssh_config options are not first-match-wins. sshd_config.5 was fixed in r1.348, this is the same for this file OpenBSD-Commit-ID: 7be55b9351cde449b136afcc52d07aa4113b215e commit 7187d3f86bf8f2066cc9941f217d23b0cacae25e Author: dtucker@openbsd.org Date: Fri Mar 10 02:24:56 2023 +0000 upstream: Remove no-op (int) > INT_MAX checks since they can never be true. From Coverity CID 405031, ok djm@ OpenBSD-Commit-ID: 9df3783b181e056595e2bb9edf7ed41d61cf8e84 commit 77adde4305542ebe3005dd456122624fe2347b01 Author: Darren Tucker Date: Fri Mar 10 13:27:29 2023 +1100 Wrap mkstemp calls with umask set/restore. glibc versions 2.06 and earlier did not set a umask on files created by mkstemp created the world-writable. Wrap mkstemp to set and restore the umask. From Coverity (CIDs 291826 291886 291891), ok djm@ commit 633d3dc2a1e9e2a013d019a0576a0771c8423713 Author: jcs@openbsd.org Date: Thu Mar 9 21:06:24 2023 +0000 upstream: modify parentheses in conditionals to make it clearer what is being assigned and what is being checked ok djm dtucker OpenBSD-Commit-ID: 19c10baa46ae559474409f75a5cb3d0eade7a9b8 commit 733030840c4772f858de95d5940ec0c37663e8b0 Author: dtucker@openbsd.org Date: Thu Mar 9 07:11:05 2023 +0000 upstream: Re-split the merge of the reorder-hostkeys test. In the kex_proposal_populate_entries change I merged the the check for reordering hostkeys with the actual reordering, but kex_assemble_names mutates options.hostkeyalgorithms which renders the check ineffective. Put the check back where it was. Spotted and tested by jsg@, ok djm@ OpenBSD-Commit-ID: a7469f25a738db5567395d1881e32479a7ffc9de commit 54ac4ab2b53ce9fcb66b8250dee91c070e4167ed Author: djm@openbsd.org Date: Thu Mar 9 06:58:26 2023 +0000 upstream: include destination constraints for smartcard keys too. Spotted by Luci Stanescu; ok deraadt@ markus@ OpenBSD-Commit-ID: add879fac6903a1cb1d1e42c4309e5359c3d870f commit bfd1ad01d974a316b60622759ad17537fa2d92b4 Author: Darren Tucker Date: Thu Mar 9 18:24:54 2023 +1100 Limit the number of PAM environment variables. xcalloc has its own limits, but these are specific to PAM. From Coverity CID 405198, ok djm@ commit a231414970e01a35f45a295d5f93698fa1249b28 Author: Darren Tucker Date: Thu Mar 9 18:19:44 2023 +1100 Limit the number of PAM environment variables. From Coverity CID 405194, tweaks and ok djm@ commit 36c6c3eff5e4a669ff414b9daf85f919666e8e03 Author: dtucker@openbsd.org Date: Wed Mar 8 06:21:32 2023 +0000 upstream: Plug mem leak. Coverity CID 405196, ok djm@ OpenBSD-Commit-ID: 175f09349387c292f626da68f65f334faaa085f2 commit dfb9b736e1ccf9e6b03eea21cd961f4fd0634c98 Author: tb@openbsd.org Date: Wed Mar 8 05:33:53 2023 +0000 upstream: ssh-pkcs11: synchronize error messages with errors A handful of error messages contained incorrect function names or otherwise inaccurate descriptions. Fix them to match reality. input/ok djm OpenBSD-Commit-ID: 165a15db52f75b31e1804b043480c36af09f3411 commit 51875897b81b5c21b80c256a29597916edbde454 Author: guenther@openbsd.org Date: Wed Mar 8 04:43:12 2023 +0000 upstream: Delete obsolete /* ARGSUSED */ lint comments. ok miod@ millert@ OpenBSD-Commit-ID: 7be168a570264d59e96a7d2d22e927d45fee0e4c commit a76085bda883c2104afb33ab0334eca190927362 Author: Darren Tucker Date: Wed Mar 8 17:25:37 2023 +1100 Extra brackets to prevent warning. commit 147ae57d4dfa0508109f93b78a7d8b92819e1f83 Author: djm@openbsd.org Date: Wed Mar 8 00:05:58 2023 +0000 upstream: use RSA/SHA256 when testing usability of private key in agent; with/ok dtucker OpenBSD-Commit-ID: fe1382e2fdf23fcae631308e72342bad56066a56 commit 27fd251bc906a763e70ce0f27c8abdf8bbd1e416 Author: djm@openbsd.org Date: Wed Mar 8 00:05:37 2023 +0000 upstream: use RSA/SHA256 when testing usability of private key; based on fix in bz3546 by Dmitry Belyavskiy; with/ok dtucker OpenBSD-Commit-ID: 0ef414cc363a832f9fab92a5da0234448bce2eba commit eee9f3fc3d52ae7d2106929bb06b7f291fb0b81a Author: djm@openbsd.org Date: Tue Mar 7 21:47:42 2023 +0000 upstream: refactor to be more readable top to bottom. Prompted by Coverity CID 405048 which was a false-positive fd leak; ok dtucker@ OpenBSD-Commit-ID: fc55ec2af622a017defb9b768bf26faefc792c00 commit 42a06b29a4c99272bf690f9b3be520b08b448dc5 Author: Darren Tucker Date: Tue Mar 7 18:34:41 2023 +1100 Add header changes missed in previous. commit 4710077096edff2e6926dd5b15bf586491d317db Author: dtucker@openbsd.org Date: Tue Mar 7 06:09:14 2023 +0000 upstream: Fix mem leak in environment setup. From jjelen at redhat.com via bz#2687, ok djm@ OpenBSD-Commit-ID: 9f9e4ba3cac003e6f81da3bcebd1b9ec43e7f353 commit 03acc50d0ccb78fc91d1570de1cd0fdfea646028 Author: dtucker@openbsd.org Date: Mon Mar 6 12:15:47 2023 +0000 upstream: Unit test for kex_proposal_populate_entries. OpenBSD-Regress-ID: bdb211d80d572a08bf14b49fe2a58b9ff265c006 commit 3f9231c2e1f374ebb08016ba00ea97b47c0ed20b Author: djm@openbsd.org Date: Tue Mar 7 05:37:26 2023 +0000 upstream: fix memory leak in process_read() path; Spotted by James Robinson in GHPR363; ok markus@ OpenBSD-Commit-ID: cdc2d98e6478b7e7f3a36976845adae3820429d8 commit c5e6e890839ec520ab9301a92cba56303749dea2 Author: djm@openbsd.org Date: Tue Mar 7 01:30:52 2023 +0000 upstream: correct size for array argument when changing UMAC_OUTPUT_LEN Coverity CID 291845; ok dtucker@ OpenBSD-Commit-ID: 2eb017d10705bb623d4418691f961c930eafaec0 commit 9641753e0fd146204d57b2a4165f552a81afade4 Author: dtucker@openbsd.org Date: Mon Mar 6 12:14:48 2023 +0000 upstream: Refactor creation of KEX proposal. This adds kex_proposal_populate_entries (and corresponding free) which populates the KEX proposal array with dynamically allocated strings. This replaces the previous mix of static and dynamic that has been the source of previous leaks and bugs. Remove unused compat functions. With & ok djm@. OpenBSD-Commit-ID: f2f99da4aae2233cb18bf9c749320c5e040a9c7b commit aa59d6a489fb20973fa461d0fdb1110db412947b Author: dtucker@openbsd.org Date: Sun Mar 5 09:24:35 2023 +0000 upstream: Fix mem and FILE leaks in moduli screening. If multiple -Ocheckpoint= options are passed, the earlier ones would be overwritten and leaked. If we use an input file that wasn't stdin, close that. From Coverity CIDs 291884 and 291894. OpenBSD-Commit-ID: a4d9d15f572926f841788912e2b282485ad09e8b commit 23b8cb41767af99a1aac24589d1882d9c8c2c205 Author: dtucker@openbsd.org Date: Sun Mar 5 08:18:58 2023 +0000 upstream: Plug mem leak in moduli checkpoint option parsing. From Coverity CID 291894. OpenBSD-Commit-ID: 9b1aba2d049741ae21c8dc4560a7e29ab17310f4 commit fc7f8f2188d4a4fc8ba77eddbe863c7665666db5 Author: dtucker@openbsd.org Date: Sun Mar 5 05:34:09 2023 +0000 upstream: Remove unused compat.h includes. We've previously removed a lot of the really old compatibility code, and with it went the need to include compat.h in most of the files that have it. OpenBSD-Commit-ID: 5af8baa194be00a3092d17598e88a5b29f7ea2b4 commit 6c165c36246d8004c20e1df5cec4961a5ac422d6 Author: dtucker@openbsd.org Date: Sat Mar 4 03:22:59 2023 +0000 upstream: Use time_t for x11 timeout. Use time_t instead of u_int for remaining x11 timeout checks for 64bit time_t safety. From Coverity CIDs 405197 and 405028, ok djm@ OpenBSD-Commit-ID: 356685bfa1fc3d81bd95722d3fc47101cc1a4972 commit 4a3918f51bd2d968387e7aa87e33b32c78077fb4 Author: dtucker@openbsd.org Date: Fri Mar 3 10:23:42 2023 +0000 upstream: Ensure ms_remain is always initialized similar to what we do in ssh_packet_write_wait. bz#2687, from jjelen at redhat.com. OpenBSD-Commit-ID: a50e0541cf823f8d1c72f71ccde925d3dbe6dfac commit e44846a4487d2885ac7f2610be09b1e2bf52249b Author: dtucker@openbsd.org Date: Fri Mar 3 09:48:51 2023 +0000 upstream: Check for non-NULL before string comparison. From jjelen at redhat.com via bz#2687. OpenBSD-Commit-ID: 0d9b2e0cac88a311b5766b1aef737082583c285f commit 1842d523fae63b862ce8e60725c9b606cddb86a6 Author: djm@openbsd.org Date: Fri Mar 3 05:00:34 2023 +0000 upstream: guard against getsockname(-1, ...) from Coverity CID 291832 OpenBSD-Commit-ID: e58d5227327917d189229b7f0b37d2780f360d5f commit 78571a5fe9847d40d7f220c92b707574ae9ec4ce Author: djm@openbsd.org Date: Fri Mar 3 04:36:20 2023 +0000 upstream: some options are not first-match-wins. Mention that there are exceptions at the start of the manpage and label some of them in the option description. OpenBSD-Commit-ID: 3b74728446fa6fc8742769eeb8c3674e233e84c4 commit d1c1b3272e8895a96c4f5889bd6e07a8525bd9f1 Author: djm@openbsd.org Date: Fri Mar 3 04:34:49 2023 +0000 upstream: actually print "channeltimeout none" in config dump mode; spotted via Coverity CID 405022 OpenBSD-Commit-ID: b074b52bf138b75f08264e8da15880b29c7a630f commit 8bf61e95610b48192d4e1720cc15d9004617301d Author: Darren Tucker Date: Fri Mar 3 14:50:03 2023 +1100 Add Coverity badges. commit 93291bd723959adf462b1df958106cf07a7734dd Author: dtucker@openbsd.org Date: Fri Mar 3 03:12:24 2023 +0000 upstream: Check return values of dup2. Spotted by Coverity, ok djm@ OpenBSD-Commit-ID: 19fb1b53072826d00c67df677731d2f6c1dd602b commit e37261dff33af23f37202cfce0848d36f5c1055c Author: dtucker@openbsd.org Date: Fri Mar 3 02:37:58 2023 +0000 upstream: Use time_t for x11_refuse_time timeout. We need SSH_TIME_T_MAX for this, so move from misc.c to misc.h so it's available. Fixes a Coverity warning for 64bit time_t safety, ok djm@ OpenBSD-Commit-ID: c69c4c3152cdaab953706db4ccf4d5fd682f7d8d commit 32755a98c29114b13f4c9d47454bbb265b932ad7 Author: dtucker@openbsd.org Date: Fri Mar 3 02:34:29 2023 +0000 upstream: Check return value from fctnl and warn on failure. Spotted by Coverity, ok djm@ OpenBSD-Commit-ID: 2097c7db3cf657f1e3a6c5077041bacc63143cab commit 5fc60e8246c36b8255f72a937ebe9787b39648c6 Author: dtucker@openbsd.org Date: Thu Mar 2 11:10:27 2023 +0000 upstream: Remove SUDO in proxy command wrapper. Anything that needs sudo is already run by it, and it breaks if root isn't in sudoers. OpenBSD-Regress-ID: 6cf22fda32a89c16915f31a6ed9bbdbef2a3bac9 commit 0d514659b23a257247491179cfbb53a6dd64e164 Author: dtucker@openbsd.org Date: Thu Mar 2 08:24:41 2023 +0000 upstream: Fix breakage on dhgex test. This was due to the sshd logs being written to the wrong log file. While there, make save_debug_logs less verbose, write the name of the tarball to regress.log and use $SUDO to remove the old symlinks (which shouldn't be needed, but won't hurt). Initial problem spotted by anton@. OpenBSD-Regress-ID: 9c44fb9cd418e6ff31165e7a6c1f9f11a6d19f5b commit 860201201d4ae655702807966901682cff30a171 Author: dtucker@openbsd.org Date: Thu Mar 2 08:14:52 2023 +0000 upstream: Quote grep and log message better. OpenBSD-Regress-ID: 3823d9063127169736aa274b1784cb28e15b64d4 commit 03a03c6002525f5ad9c8fc874a5d5826a35d9858 Author: dtucker@openbsd.org Date: Thu Mar 2 06:41:56 2023 +0000 upstream: Always call fclose on checkpoints. In the case of an fprintf failure we would not call fclose which would leak the FILE pointer. While we're there, try to clean up the temp file on failure. Spotted by Coverity, ok djm@ OpenBSD-Commit-ID: 73c7ccc5d4fcc235f54c6b20767a2815408525ef commit 13fe8f9785e6d90400ce548939a0b0ddc11fcb3c Author: dtucker@openbsd.org Date: Wed Mar 1 21:54:50 2023 +0000 upstream: Remove old log symlinks before creating new ones. In -portable some platforms don't like overwriting existing symlinks. OpenBSD-Regress-ID: 7e7ddc0beb73e945e1c4c58d51c8a125b518120f commit 131fcbcaffd1e3bcf5ab766ec497b5d768955310 Author: Darren Tucker Date: Wed Mar 1 23:23:02 2023 +1100 Adjust test jobs for new log directory. commit a6f4ac8a2baf77e5361cfa017d0dc250d1409bec Author: dtucker@openbsd.org Date: Wed Mar 1 09:29:32 2023 +0000 upstream: Rework logging for the regression tests. Previously we would log to ssh.log and sshd.log, but that is insufficient for tests that have more than one concurent ssh/sshd. Instead, we'll log to separate datestamped files in a $OBJ/log/ and leave a symlink at the previous location pointing at the most recent instance with an entry in regress.log showing which files were created at each point. This should be sufficient to reconstruct what happened even for tests that use multiple instances of each program. If the test fails, tar up all of the logs for later analysis. This will let us also capture the output from some of the other tools which was previously sent to /dev/null although most of those will be in future commits. OpenBSD-Regress-ID: f802aa9e7fa51d1a01225c05fb0412d015c33e24 commit 8ead62ed5e86c7df597d8604f332f49cd1527b85 Author: dtucker@openbsd.org Date: Tue Feb 28 21:31:50 2023 +0000 upstream: fatal out if allocating banner string fails to avoid potential null deref later in sscanf. Spotted by Coverity, ok deraadt@ OpenBSD-Commit-ID: 74e8d228ac00552e96e9e968dfcccf8dd1f46ad5 commit 44ca56ba0b3f531f1d85730cc701097cd49e6868 Author: dtucker@openbsd.org Date: Tue Feb 28 08:45:24 2023 +0000 upstream: Explicitly ignore return from fchmod similar to other calls to prevent warning. OpenBSD-Commit-ID: fdc5287dcee0860b5a493186414226c655b0eb0a commit 803392933a3a6f09f834aa5f0c2aab06a3b382f4 Author: dtucker@openbsd.org Date: Mon Feb 27 22:12:40 2023 +0000 upstream: Plug mem leak on globbed ls error path. Spotted by Coverity, ok deraadt@ OpenBSD-Commit-ID: de28476025db29820a9a2e56e98b964d8a02861c commit aa33b4d396abf47a2a45f982f28d054fb1dcb5c3 Author: Darren Tucker Date: Mon Feb 27 21:04:22 2023 +1100 Cast time_t's in debug output to long long. Should fix Coverity warning about truncation of 64bit time_t. commit b0fd60a9de62a03189ad57d0c07f0ac51dc00e95 Author: Darren Tucker Date: Mon Feb 27 17:28:59 2023 +1100 Do shadow expiry calcs using "long long". Coverity flags these as potentially not 64bit time_t safe so use long long for the calculations and debug output. ok djm@ commit 01dbeb3084d714bbd001ff9d03b9de542e8cdf58 Author: Damien Miller Date: Mon Feb 27 17:07:52 2023 +1100 avoid clash between for getopt's struct option Since we don't use getopt_long() nothing outside the getopt() implementation itself uses this structure, so move it into the source to remove it from visibility and clashes with libc's ok dtucker@ commit eb88d07c43afe407094e7d609248d85a15e148ef Author: Darren Tucker Date: Sat Feb 25 14:45:41 2023 +1100 Revert explicit chmods on private keys. This should no longer be needed on Cygwin test runners due to previous commit. commit 52b75db61030a6c8baf66b73644380cf3f58e26a Author: Darren Tucker Date: Sat Feb 25 14:43:28 2023 +1100 Remove extended ACLs from working dirs. This should allow umask to work as expected and prevent tests from failing due to excessive permissions on private keys. commit 0c5d4c843df5605b043a758d69f9a611ef63c479 Author: Darren Tucker Date: Fri Feb 24 13:44:13 2023 +1100 Explicitly set permissions on user and host keys. On cygwin, the umask might not be sufficient. Should fix tests on Github runners. commit 6c9fc9d7a9f7abf82c3294d74e6d4a25735862ce Author: djm@openbsd.org Date: Wed Feb 22 03:56:43 2023 +0000 upstream: fix progressmeter corruption on wide displays; bz3534 feedback/ok dtucker@ OpenBSD-Commit-ID: f4affee067cec7c182f3e0b307d758e0472762a3 commit fe0bd3cde9665d364e5eedd2c2c2e60d4cdc3786 Author: dtucker@openbsd.org Date: Tue Feb 21 06:48:18 2023 +0000 upstream: fseek to end of known_hosts before writing to it. POSIX and ANSI C require that applications call fseek or similar between read and writing to a RW file. OpenBSD doesn't enforce this, but some (System V derived) platforms need this to prevent it from writing a spurious extra byte (in this case, a newline). ok djm@ deraadt@ OpenBSD-Commit-ID: 33e680dcd8110582a93a40a8491024e961f45137 commit 357fb8ae14c07cd025eeed66e73de91bab569849 Author: Darren Tucker Date: Tue Feb 21 17:51:09 2023 +1100 Also run unit tests on AIX VMs. In the past these tests took too long, but these days it only adds about 5 min to the run. commit 17781aaa5188ee1477f7779b280d105512e3dbed Author: Darren Tucker Date: Tue Feb 21 17:38:55 2023 +1100 Wrap stdint.h inside ifdef. commit ef798bad38505f7bf1b5fa5c0843dfc5a2b192b9 Author: Mayank Sharma Date: Mon Feb 20 17:37:15 2023 +0530 Add includes to ptimeout test. Fixes test failures on AIX due to type mismatches. commit ab69dda05d5268454209f529fa80f477e60d846a Author: Darren Tucker Date: Mon Feb 20 18:24:39 2023 +1100 Always use the openssl binary configure tells us. This fixes tests on platforms that do not have the openssl tool installed at all. commit 2a7e3449908571af601a4c2d12ab140096442e47 Author: dtucker@openbsd.org Date: Fri Feb 17 04:22:50 2023 +0000 upstream: Remove now-unused compat bit SSH_BUG_RSASIGMD5. The code to set this was removed in OpenSSH 7.7 when support for SSH implementations dating back to before RFC standardization were removed. "burn it all" djm@ OpenBSD-Commit-ID: 6330935fbe23dd00be79891505e06d1ffdac7cda commit 0833ccf2c8b7ae08b296c06f17bd53e3ab94b0b0 Author: dtucker@openbsd.org Date: Fri Feb 17 03:06:18 2023 +0000 upstream: Remove now-unused compat bit SSH_BUG_BIGENDIANAES. This was previously set for OpenSSH 2.3 (released in 2000) but this check was removed in OpenSSH 7.7 (2018). ok djm@ deraadt@ OpenBSD-Commit-ID: 326426ea328707fc9e83305291ab135c87f678af commit c81c2bea6e828d52b62b448b4ffdd3c163177975 Author: Damien Miller Date: Fri Feb 17 10:12:40 2023 +1100 whitespace fixes commit 500f90b39db5f0014e6b0c49ff1f45c994b69293 Author: Damien Miller Date: Fri Feb 17 10:02:08 2023 +1100 whitespace at EOL commit 68350152406339170721c15e97afdf827a5e4001 Author: dtucker@openbsd.org Date: Thu Feb 16 10:10:00 2023 +0000 upstream: Remove SSH_BUG_PASSWORDPAD compat bit since it's no longer used. ok markus@ OpenBSD-Commit-ID: b92c21f56fe4b7f9a54790d6a9650725c226820b commit 537cccd804eaf65f32bdce037cc31db4e0ab0f44 Author: dtucker@openbsd.org Date: Thu Feb 16 07:55:15 2023 +0000 upstream: Remove SSH_BUG_IGNOREMSG compat flag since it's only applicable to SSH1 and thus no longer used. ok markus@ "kill it with fire" djm@ OpenBSD-Commit-ID: ea13318b1937795d9db4790d3ce0a6ed01584dab commit 285cf6cd4b91a0a0ce33193c358c99085af33e43 Author: jmc@openbsd.org Date: Fri Feb 10 06:41:53 2023 +0000 upstream: space between macro and punctuation; sort usage(); OpenBSD-Commit-ID: 6141610cfca037700730e41f868d1d9124958f8c commit d39a96f70f81878c77336ed35f5c648c1804b71a Author: jmc@openbsd.org Date: Fri Feb 10 06:40:48 2023 +0000 upstream: space between macro and punctuation; OpenBSD-Commit-ID: abc95e550be9e6d9a7ff64b65c104c7be21ab19e commit 16e82bf53fc34e43e3b948d43b68d5b27a7335e6 Author: jmc@openbsd.org Date: Fri Feb 10 06:39:27 2023 +0000 upstream: sort SYNOPSIS; OpenBSD-Commit-ID: dacd9da33277d5669a51213d880632599c890c1e commit d9685121ff6d57b8797411f3cb123884a4b96e30 Author: Darren Tucker Date: Sat Feb 11 12:32:19 2023 +1100 Improve seccomp compat on older systems. Check if flags to mmap and madvise are defined before using them. Should fix problems building on older Linux systems that don't have these. bz#3537, with & ok djm@. commit 6180b0fa4f7996687678702806257e661fd5931e Author: djm@openbsd.org Date: Fri Feb 10 05:06:03 2023 +0000 upstream: test -Ohashalg=... and that the default output contains both specified hash algorithms; prompted by dtucker@ OpenBSD-Regress-ID: 26f309208c8d8b8fa9c5f419767b85f1e9b22f51 commit d651f5c9fe37e61491eee46c49ba9fa03dbc0e6a Author: djm@openbsd.org Date: Fri Feb 10 04:56:30 2023 +0000 upstream: let ssh-keygen and ssh-keyscan accept -Ohashalg=sha1|sha256 when outputting SSHFP fingerprints to allow algorithm selection. bz3493 ok dtucker@ OpenBSD-Commit-ID: e6e07fe21318a873bd877f333e189eb963a11b3d commit 18938d11a90b74d63c20b2d3c965d5bd64786ab1 Author: djm@openbsd.org Date: Fri Feb 10 04:47:19 2023 +0000 upstream: add a `sshd -G` option that parses and prints the effective configuration without attempting to load private keys and perform other checks. This allows usage of the option before keys have been generated. bz3460 feedback/ok dtucker@ OpenBSD-Commit-ID: 774504f629023fc25a559ab1d95401adb3a7fb29 commit df7d3dbf7194db8e97730ee0425d4d9d7bdb8b10 Author: djm@openbsd.org Date: Fri Feb 10 04:40:28 2023 +0000 upstream: make `ssh -Q CASignatureAlgorithms` work as the manpage says it should bz3532 OpenBSD-Commit-ID: 0ddb17b3fcbd99bfb5baea4ac5e449620cbd3adc commit d3b8d4198b6595f23b5859d43dc8fc701f97429b Author: Darren Tucker Date: Fri Feb 10 14:26:44 2023 +1100 Add CentOS 7 test targets. commit 22efb01e355bba4755b730ed417f91c081445bfc Author: dtucker@openbsd.org Date: Thu Feb 9 09:55:33 2023 +0000 upstream: Test adding terminating newline to known_hosts. OpenBSD-Regress-ID: 5fc3010ac450195b3fbdeb68e875564968800365 commit caec6da1a583ed8c32c6ad3b81bbcaab46ac8b61 Author: dtucker@openbsd.org Date: Wed Feb 8 08:06:03 2023 +0000 upstream: ssh-agent doesn't actually take -v, so the recently-added ones will result in the test not cleaning up after itself. Patch from cjwatson at debian.org vi bz#3536. OpenBSD-Regress-ID: 1fc8283568f5bf2f918517c2c1e778072cf61b1a commit 3c379c9a849a635cc7f05cbe49fe473ccf469ef9 Author: dtucker@openbsd.org Date: Thu Feb 9 09:54:11 2023 +0000 upstream: Ensure that there is a terminating newline when adding a new entry to known_hosts. bz#3529, with git+openssh at limpsquid.nl, ok deraadt@ markus@ OpenBSD-Commit-ID: fa8d90698da1886570512b96f051e266eac105e0 commit 95b6bbd2553547260b324b39d602061c88b774bc Author: Darren Tucker Date: Tue Feb 7 08:43:47 2023 +1100 Replace 9.1 with 9.2 on CI status page. commit 195313dfe10a23c82e9d56d5fdd2f59beee1bdcf Author: Damien Miller Date: Fri Feb 3 16:33:09 2023 +1100 harden Linux seccomp sandbox Linux mmap(2) and madvise(2) syscalls support quite a number of funky flags that we don't expect that sshd/libc will ever need. We can exclude this kernel attack surface by filtering the mmap(2) flags and the madvise(2) advice arguments. Similarly, the sandboxed process in sshd is a single-threaded program that does not use shared memory for synchronisation or communication. Therefore, there should be no reason for the advanced priority inheritance futex(2) operations to be necessary. These can also be excluded. Motivated by Jann Horn pointing out that there have been kernel bugs in nearby Linux kernel code, e.g. CVE-2020-29368, CVE-2020-29374 and CVE-2022-42703. Feedback Jann Horn, ok dtucker@ commit 6dfb65de949cdd0a5d198edee9a118f265924f33 Author: Damien Miller Date: Thu Feb 2 23:21:54 2023 +1100 crank versions in RPM specs commit d07cfb11a0ca574eb68a3931d8c46fbe862a2021 Author: Damien Miller Date: Thu Feb 2 23:21:45 2023 +1100 update version in README commit 9fe207565b4ab0fe5d1ac5bb85e39188d96fb214 Author: Damien Miller Date: Thu Feb 2 23:17:49 2023 +1100 adapt compat_kex_proposal() test to portable commit 903c556b938fff2d7bff8da2cc460254430963c5 Author: djm@openbsd.org Date: Thu Feb 2 12:12:52 2023 +0000 upstream: test compat_kex_proposal(); by dtucker@ OpenBSD-Regress-ID: 0e404ee264db546f9fdbf53390689ab5f8d38bf2 commit 405fba71962dec8409c0c962408e09049e5624b5 Author: dtucker@openbsd.org Date: Thu Jan 19 07:53:45 2023 +0000 upstream: Check if we can copy sshd or need to use sudo to do so during reexec test. Skip test if neither can work. Patch from anton@, tweaks from me. OpenBSD-Regress-ID: 731b96ae74d02d5744e1f1a8e51d09877ffd9b6d commit b2a2a8f69fd7737ea17dc044353c514f2f962f35 Author: djm@openbsd.org Date: Thu Feb 2 12:10:22 2023 +0000 upstream: openssh-9.2 OpenBSD-Commit-ID: f7389f32413c74d6e2055f05cf65e7082de03923 commit 12da7823336434a403f25c7cc0c2c6aed0737a35 Author: djm@openbsd.org Date: Thu Feb 2 12:10:05 2023 +0000 upstream: fix double-free caused by compat_kex_proposal(); bz3522 by dtucker@, ok me OpenBSD-Commit-ID: 2bfc37cd2d41f67dad64c17a64cf2cd3806a5c80 commit 79efd95ab5ff99f4cb3a955e2d713b3f54fb807e Author: Darren Tucker Date: Wed Feb 1 17:17:26 2023 +1100 Skip connection-timeout test on minix3. Minix 3's Unix domain sockets don't seem to work the way we expect, so skip connection-timeout test on that platform. While there, group together all similarly skipped tests and explicitly comment. commit 6b508c4e039619842bcf5a16f8a6b08dd6bec44a Author: Damien Miller Date: Wed Feb 1 12:12:05 2023 +1100 fix libfido2 detection without pkg-config Place libfido2 before additional libraries (that it may depend upon) and not after. bz3530 from James Zhang; ok dtucker@ commit 358e300fed5e6def233a2c06326e51e20ebed621 Author: deraadt@openbsd.org Date: Wed Jan 18 20:56:36 2023 +0000 upstream: delete useless dependency OpenBSD-Commit-ID: e1dc11143f83082e3154d6094f9136d0dc2637ad commit a4cb9be1b021b511e281ee55c356f964487d9e82 Author: deraadt@openbsd.org Date: Wed Jan 18 20:43:15 2023 +0000 upstream: Create and install sshd random relink kit. ../Makefile.inc and Makfile are concatenated for reuse, which hopefully won't be too fragile, we'll see if we need a different approach. The resulting sshd binary is tested with the new sshd -V option before installation. As the binary layout is now semi-unknown (meaning relative, fixed, and gadget offsets are not precisely known), change the filesystem permissions to 511 to prevent what I call "logged in BROP". I have ideas for improving this further but this is a first step ok djm OpenBSD-Commit-ID: 1e0a2692b7e20b126dda60bf04999d1d30d959d8 commit bc7de6f91a9a0ae2f148a9d31a4027d441a51999 Author: jmc@openbsd.org Date: Wed Jan 18 06:55:32 2023 +0000 upstream: tweak previous; ok djm OpenBSD-Commit-ID: df71ce4180c58202dfdc1d92626cfe900b91b7c3 commit a20b7e999773e6333c8aa9b0a7fa41966e63b037 Author: Darren Tucker Date: Tue Jan 31 19:35:44 2023 +1100 Skip connection-timeout test under Valgrind. Valgrind slows things down so much that the timeout test fails. Skip this test until we figure out if we can make it work. commit c3ffb54b4fc5e608206037921db6ccbc2f5ab25f Author: Darren Tucker Date: Wed Jan 25 21:58:40 2023 +1100 Skip connection-timeout when missing FD passing. This tests uses multiplexing which uses file descriptor passing, so skip it if we don't have that. Fixes test failures on Cygwin. commit 35253af01d8c0ab444c8377402121816e71c71f5 Author: djm@openbsd.org Date: Wed Jan 18 02:00:10 2023 +0000 upstream: when restoring non-blocking mode to stdio fds, restore exactly the flags that ssh started with and don't just clobber them with zero, as this could also remove the append flag from the set; bz3523; ok dtucker@ OpenBSD-Commit-ID: 1336b03e881db7564a4b66014eb24c5230e9a0c0 commit 7d17ea151c0b2519f023bd9cc7f141128833ac47 Author: millert@openbsd.org Date: Wed Jan 18 01:50:21 2023 +0000 upstream: Add a -V (version) option to sshd like the ssh client has. OK markus@ deraadt@ OpenBSD-Commit-ID: abe990ec3e636fb040132aab8cbbede98f0c413e commit 62360feb7f08f2a4c6fc36f3b3449309203c42c9 Author: millert@openbsd.org Date: Tue Jan 17 18:52:44 2023 +0000 upstream: For "ssh -V" always exit 0, there is no need to check opt again. This was missed when the fallthrough in the switch case above it was removed. OK deraadt@ OpenBSD-Commit-ID: 5583e5d8f6d62a8a4215cfa95a69932f344c8120 commit 12492c0abf1eb415d08a897cc1d8b9e789888230 Author: djm@openbsd.org Date: Tue Jan 17 10:15:10 2023 +0000 upstream: also check that an active session inhibits UnusedConnectionTimeout idea markus@ OpenBSD-Regress-ID: 55c0fb61f3bf9e092b0a53f9041d3d2012f14003 commit cef2593c33ac46a58238ff998818754eabdf64ff Author: djm@openbsd.org Date: Tue Jan 17 10:02:34 2023 +0000 upstream: regression test for UnusedConnectionTimeout OpenBSD-Regress-ID: 7f29001374a68e71e5e078f69e4520cf4bcca084 commit aff9493a89c71d6a080419b49ac64eead9730491 Author: djm@openbsd.org Date: Mon Jan 16 04:11:29 2023 +0000 upstream: unbreak test: cannot access shell positional parameters past $9 without wrapping the position in braces (i.e. need ${10}, etc.) OpenBSD-Regress-ID: 3750ec98d5d409ce6a93406fedde6f220d2ea2ac commit 0293c19807f83141cdf33b443154459f9ee471f6 Author: djm@openbsd.org Date: Tue Jan 17 09:44:48 2023 +0000 upstream: Add a sshd_config UnusedConnectionTimeout option to terminate client connections that have no open channels for some length of time. This complements the recently-added ChannelTimeout option that terminates inactive channels after a timeout. ok markus@ OpenBSD-Commit-ID: ca983be74c0350364c11f8ba3bd692f6f24f5da9 commit 8ec2e3123802d2beeca06c1644b0b647f6d36dab Author: djm@openbsd.org Date: Sun Jan 15 23:35:10 2023 +0000 upstream: adapt to ed25519 changes in src/usr.bin/ssh OpenBSD-Regress-ID: 4b3e7ba7ee486ae8a0b4790f8112eded2bb7dcd5 commit 9fbbfeca1ce4c7ec0001c827bbf4189a3ba0964b Author: djm@openbsd.org Date: Sun Jan 15 23:05:32 2023 +0000 upstream: update OpenSSH's Ed25519 code to the last version of SUPERCOP (20221122) and change the import approach to the same one we use for Streamlined NTRUPrime: use a shell script to extract the bits we need from SUPERCOP, make some minor adjustments and squish them all into a single file. ok tb@ tobhe@ OpenBSD-Commit-ID: 1bc0fd624cb6af440905b8ba74ac7c03311b8e3b commit 6283f4bd83eee714d0f5fc55802eff836b06fea8 Author: Darren Tucker Date: Sat Jan 14 22:02:44 2023 +1100 Allow writev is seccomp sandbox. This seems to be used by recent glibcs at least in some configurations. From bz#3512, ok djm@ commit 923c3f437f439cfca238fba37e97a7041782f615 Author: dtucker@openbsd.org Date: Sat Jan 14 10:05:54 2023 +0000 upstream: Shell syntax fix. From ren mingshuai vi github PR#369. OpenBSD-Regress-ID: 6696b2eeefe128099fc3d7ea9f23252cc35156f9 commit 4d87a00f704e0365e11c3c38b170c1275ec461fc Author: dtucker@openbsd.org Date: Sat Jan 14 09:57:08 2023 +0000 upstream: Instead of skipping the all-tokens test if we don't have OpenSSL (since we use it to compute the hash), put the hash at the end and just omit it if we don't have it. Prompted by bz#3521. OpenBSD-Regress-ID: c79ecba64250ed3b6417294b6c965e6b12ca5eea commit b05406d6f93b8c8ec11ec8b27e7c76cc7a5a55fb Author: jmc@openbsd.org Date: Fri Jan 13 07:13:40 2023 +0000 upstream: fix double phrase in previous; OpenBSD-Commit-ID: 671e6c8dc5e9230518b2bbfa143daaa88adc66c2 commit 40564812b659c530eb1f4b62d09e85612aef3107 Author: dtucker@openbsd.org Date: Fri Jan 13 03:16:29 2023 +0000 upstream: Document "UserKnownHostsFile none". ok djm@ OpenBSD-Commit-ID: f695742d39e34ecdcc3c861c3739a84648a4bce5 commit d03e245e034019a37388f6f5f893ce848ab6d2e2 Author: Darren Tucker Date: Fri Jan 13 23:02:34 2023 +1100 Retry package installation 3 times. When setting up the CI environment, retry package installation 3 times before going up. Should help prevent spurious failures during infrastructure issues. commit 625f6bc39840167dafb3bf5b6a3e18503ac986e8 Author: dtucker@openbsd.org Date: Fri Jan 13 04:47:34 2023 +0000 upstream: Move scp path setting to a helper function. The previous commit to add scp to the test sshd's path causes the t-envpass test to fail when the test scp is given using a fully qualified path. Put this in a helper function and only call it from the scp tests. OpenBSD-Regress-ID: 7533dc1c4265c1de716abb062957994195b36df4 commit 6e6f88647042b3cde54a628545c2f5fb656a9327 Author: dtucker@openbsd.org Date: Fri Jan 13 04:23:00 2023 +0000 upstream: Add scp's path to test sshd's PATH. If the scp we're testing is fully qualified (eg it's not in the system PATH) then add its path to the under-test sshd's PATH so we can find it. Prompted by bz#3518. OpenBSD-Regress-ID: 7df4f5a0be3aa135495b7e5a6719d3cbc26cc4c0 commit 8a5e99a70fcf9b022a8aa175ebf6a71f58511da3 Author: Darren Tucker Date: Fri Jan 13 15:49:48 2023 +1100 Remove skipping test when scp not in path. An upcoming change renders this obsolete by adding scp's path to the test sshd's PATH, and removing this first will make the subsequent sync easier. commit 41f36dd896c8fb8337d403fcf476762986976e9d Author: dtucker@openbsd.org Date: Fri Jan 13 02:58:20 2023 +0000 upstream: Add a "Host" line to the output of ssh -G showing the original host arg. Inspired by patch from vincent at bernat.ch via bz#3343, ok djm@ OpenBSD-Commit-ID: 59c0f60a222113a44d0650cd394376e3beecc883 commit f673b49f3be3eb51074fbb8a405beb6cd0f7d93e Author: djm@openbsd.org Date: Fri Jan 13 02:44:02 2023 +0000 upstream: avoid printf("%s", NULL) if using ssh -oUserKnownHostsFile=none and a hostkey in one of the system known hosts file changes; ok dtucker@ OpenBSD-Commit-ID: 7ca87614bfc6da491315536a7f2301434a9fe614 commit 93fc7c576563e3d88a1dc019dd213f65607784cc Author: djm@openbsd.org Date: Wed Jan 11 05:39:38 2023 +0000 upstream: clamp the minimum buffer lengths and number of inflight requests too OpenBSD-Commit-ID: c4965f62fa0ba850940fd66ae3f60cf516bbcd56 commit 48bf234322e639d279c5a28435eae50155e9b514 Author: djm@openbsd.org Date: Wed Jan 11 05:36:50 2023 +0000 upstream: ignore bogus upload/download buffer lengths in the limits extension OpenBSD-Commit-ID: c5b023e0954693ba9a5376e4280c739b5db575f8 commit 36b00d31833ca74cb0f7c7d8eda1bde55700f929 Author: djm@openbsd.org Date: Wed Jan 11 02:13:52 2023 +0000 upstream: remove whitespace at EOL from code extracted from SUPERCOP OpenBSD-Commit-ID: 1ec524ff2fbb9387d731601437c82008f35a60f4 commit d888de06c5e4d7dbf2f2b85f2b5bf028c570cf78 Author: djm@openbsd.org Date: Wed Jan 11 00:51:27 2023 +0000 upstream: rewrite this test to use a multiplexed ssh session so we can control its lifecycle without risk of race conditions; fixes some of the Github integration tests for openssh-portable OpenBSD-Regress-ID: 5451cad59ba0d43ae9eeda48ec80f54405fee969 commit 4bcc737a35fdd9cc4af7423d6c23dfd0c7ef4786 Author: Damien Miller Date: Wed Jan 11 11:45:17 2023 +1100 remove buffer len workaround for NetBSD 4.x Switching to from pipes to a socketpair for communicating with the ssh process avoids the (kernel bug?) problem. commit f5154d2aac3e6a32a1b13dec23a701a087850cdc Author: Damien Miller Date: Wed Jan 11 11:44:19 2023 +1100 add back use of pipes in scp.c under USE_PIPES This matches sftp.c which prefers socketpair but uses pipes on some older platforms. commit eec737b59cf13841de46134967a206607000acd4 Author: millert@openbsd.org Date: Tue Jan 10 23:22:15 2023 +0000 upstream: Switch scp from using pipes to a socketpair for communication with it's ssh sub-processes. We no longer need to reserve two descriptors to ensure that we don't end up using fd 0-2 unexpectedly, that is handled by sanitise_stdfd() in main(). Based on an original diff from djm@. OK deraadt@ djm@ OpenBSD-Commit-ID: b80c372faac462471e955ddeab9480d668a2e48d commit d213d126a4a343abd3a1eb13687d39c1891fe5c8 Author: jmc@openbsd.org Date: Fri Jan 6 08:44:11 2023 +0000 upstream: tweak previous; ok djm OpenBSD-Commit-ID: 229c493452766d70a78b0f02f6ff9894f9028858 commit 4a5590a5ee47b7dfd49773e9fdba48ad3089fe64 Author: Damien Miller Date: Mon Jan 9 16:33:56 2023 +1100 try to improve logging for dynamic-forward test previously the logs from the ssh used to exercise the forwarding channel would clobber the logs from the ssh actually doing the forwarding commit 715bc25dcfccf9fb2bee820155fe071d01a618db Author: Darren Tucker Date: Sat Jan 7 23:24:50 2023 +1100 Skip dynamic-forward test on minix3. This test relies on loopback addresses which minix does not have. Previously the test would not run at all since it also doesn't have netcat, but now we use our own netcat it tries and fails. commit dd1249bd5c45128a908395c61b26996a70f82205 Author: Damien Miller Date: Sun Jan 8 12:08:59 2023 +1100 don't test IPv6 addresses if platform lacks support commit d77fc611a62f2dfee0b654c31a50a814b13310dd Author: dtucker@openbsd.org Date: Fri Jan 6 12:33:33 2023 +0000 upstream: When OpenSSL is not available, skip parts of percent test that require it. Based on github pr#368 from ren mingshuai. OpenBSD-Regress-ID: 49a375b2cf61ccb95b52e75e2e025cd10988ebb2 commit 1cd2aac312af9172f1b5cb06c2e1cd090abb83cf Author: Darren Tucker Date: Sat Jan 7 23:01:11 2023 +1100 Use our own netcat for dynamic-forward test. That way we can be surer about its behaviour rather than trying to second-guess the behaviour of various netcat implementations. commit 26cab41c05d7b0859d2a1ea5b6ed253d91848a80 Author: Darren Tucker Date: Sat Jan 7 14:30:43 2023 +1100 Use autoconf to find openssl binary. It's possible to install an OpenSSL in a path not in the system's default library search path. OpenSSH can still use this (eg if you specify an rpath) but the openssl binary there may not work. If one is available on the system path just use that. commit 5532e010a0eeb6aa264396514f9aed7948471538 Author: Darren Tucker Date: Sat Jan 7 10:34:18 2023 +1100 Check openssl_bin path is executable before using. commit 5d7b16cff48598d5908db970bfdc9ff9326142c8 Author: Darren Tucker Date: Fri Jan 6 23:19:07 2023 +1100 Set OPENSSL_BIN from OpenSSL directory. commit 344a0e8240eaf08da5d46a5e3a9ecad6e4f64c35 Author: dtucker@openbsd.org Date: Fri Jan 6 08:50:33 2023 +0000 upstream: Save debug logs from ssh for debugging purposes. OpenBSD-Regress-ID: 109e40b06de1c006a3b8e0d8745b790b2c5870a0 commit e1ef172646f7f49c80807eea90225ef5e0be55a8 Author: djm@openbsd.org Date: Fri Jan 6 08:07:39 2023 +0000 upstream: regression test for ChannelTimeout OpenBSD-Regress-ID: 280bfbefcfa415428ad744e43f69a8dede8ad685 commit 2393ea8daf25853459eb07a528d7577688847777 Author: djm@openbsd.org Date: Fri Jan 6 07:18:18 2023 +0000 upstream: fix typo in verbose logging OpenBSD-Regress-ID: 0497cdb66e003b2f50ed77291a9104fba2e017e9 commit 161a5378a3cc2e7aa3f9674cb7f4686ae6ce9586 Author: djm@openbsd.org Date: Fri Jan 6 02:59:50 2023 +0000 upstream: unit tests for misc.c:ptimeout_* API OpenBSD-Regress-ID: 01f8fb12d08e5aaadd4bd4e71f456b6588be9a94 commit 018d671d78145f03d6f07ae9d64d51321da70325 Author: tb@openbsd.org Date: Wed Jan 4 22:48:57 2023 +0000 upstream: Copy bytes from the_banana[] rather than banana() Fixes test failure due to segfault seen on arm64 with xonly snap. ok djm OpenBSD-Regress-ID: 86e2aa4bbd1dff1bc4ebb2969c0d6474485be046 commit ab6bb69e251faa8b24f81b25c72ec0120f20cad4 Author: Damien Miller Date: Fri Jan 6 19:13:36 2023 +1100 unbreak scp on NetBSD 4.x e555d5cad5 effectively increased the default copy buffer size for SFTP transfers. This caused NetBSD 4.x to hang during the "copy local file to remote file in place" scp.sh regression test. This puts back the original 32KB copy buffer size until we can properly figure out why. lots of debugging assistance from dtucker@ commit 2d1ff2b9431393ad99ef496d5e3b9dd0d4f5ac8c Author: djm@openbsd.org Date: Fri Jan 6 02:47:18 2023 +0000 upstream: Implement channel inactivity timeouts This adds a sshd_config ChannelTimeouts directive that allows channels that have not seen traffic in a configurable interval to be automatically closed. Different timeouts may be applied to session, X11, agent and TCP forwarding channels. Note: this only affects channels over an opened SSH connection and not the connection itself. Most clients close the connection when their channels go away, with a notable exception being ssh(1) in multiplexing mode. ok markus dtucker OpenBSD-Commit-ID: ae8bba3ed9d9f95ff2e2dc8dcadfa36b48e6c0b8 commit 0e34348d0bc0b1522f75d6212a53d6d1d1367980 Author: djm@openbsd.org Date: Fri Jan 6 02:42:34 2023 +0000 upstream: Add channel_set_xtype() This sets an "extended" channel type after channel creation (e.g. "session:subsystem:sftp") that will be used for setting channel inactivity timeouts. ok markus dtucker OpenBSD-Commit-ID: 42564aa92345045b4a74300528f960416a15d4ca commit ceedf09b2977f3a756c759a6e7eb8f8e9db86a18 Author: djm@openbsd.org Date: Fri Jan 6 02:41:49 2023 +0000 upstream: tweak channel ctype names These are now used by sshd_config:ChannelTimeouts to specify timeouts by channel type, so force them all to use a similar format without whitespace. ok dtucker markus OpenBSD-Commit-ID: 66834765bb4ae14f96d2bb981ac98a7dae361b65 commit c60438158ad4b2f83d8504257aba1be7d0b0bb4b Author: djm@openbsd.org Date: Fri Jan 6 02:39:59 2023 +0000 upstream: Add channel_force_close() This will forcibly close an open channel by simulating read/write errors, draining the IO buffers and calling the detach function. Previously the detach function was only ever called during channel garbage collection, but there was no way to signal the user of a channel (e.g. session.c) that its channel was being closed deliberately (vs. by the usual state-machine logic). So this adds an extra "force" argument to the channel cleanup callback to indicate this condition. ok markus dtucker OpenBSD-Commit-ID: 23052707a42bdc62fda2508636e624afd466324b commit d478cdc7ad6edd4b1bcd1e86fb2f23194ff33d5a Author: djm@openbsd.org Date: Fri Jan 6 02:38:23 2023 +0000 upstream: replace manual poll/ppoll timeout math with ptimeout API feedback markus / ok markus dtucker OpenBSD-Commit-ID: c5ec4f2d52684cdb788cd9cbc1bcf89464014be2 commit 4adf3817a24efe99b06e62630577d683c7cd8065 Author: djm@openbsd.org Date: Fri Jan 6 02:37:04 2023 +0000 upstream: add ptimeout API for keeping track of poll/ppoll timeouts; ok dtucker markus OpenBSD-Commit-ID: 3335268ca135b3ec15a947547d7cfbb8ff929ead commit 8c7c69d32375d2f3ce9da0109c9bffc560842316 Author: djm@openbsd.org Date: Thu Jan 5 05:49:13 2023 +0000 upstream: suppress "Connection closed" message when in quiet mode OpenBSD-Commit-ID: 8a3ab7176764da55f60bfacfeae9b82d84e3908f commit 845ceecea2ac311b0c267f9ecbd34862e1876fc6 Author: djm@openbsd.org Date: Mon Jan 2 07:03:57 2023 +0000 upstream: regression test for PermitRemoteOpen OpenBSD-Regress-ID: 8271aafbf5c21950cd5bf966f08e585cebfe630c commit b3daa8dc582348d6ab8150bc1e571b7aa08c5388 Author: djm@openbsd.org Date: Mon Jan 2 07:03:30 2023 +0000 upstream: fix bug in PermitRemoteOpen which caused it to ignore its first argument unless it was one of the special keywords "any" or "none". Reported by Georges Chaudy in bz3515; ok dtucker@ OpenBSD-Commit-ID: c5678a39f1ff79993d5ae3cfac5746a4ae148ea5 commit 0872663a7be0301bcc3d49acdbc9b740a3d972d4 Author: jmc@openbsd.org Date: Mon Dec 26 19:16:03 2022 +0000 upstream: spelling fixes; from paul tagliamonte amendments to his diff are noted on tech OpenBSD-Commit-ID: d776dd03d0b882ca9c83b84f6b384f6f9bd7de4a commit 797da2812a71785b34890bb6eb44767a7d09cd34 Author: djm@openbsd.org Date: Fri Dec 16 07:13:22 2022 +0000 upstream: Mention that scp uses the SFTP protocol and remove reference to legacy flag. Spotted by, feedback and ok jmc@ OpenBSD-Commit-ID: 9dfe04966f52e941966b46c7a2972147f95281b3 commit 93f2ce8c050a7a2a628646c00b40b9b53fef93ef Author: djm@openbsd.org Date: Fri Dec 16 06:56:47 2022 +0000 upstream: Clear signal mask early in main(); sshd may have been started with one or more signals masked (sigprocmask(2) is not cleared on fork/exec) and this could interfere with various things, e.g. the login grace timer. Execution environments that fail to clear the signal mask before running sshd are clearly broken, but apparently they do exist. Reported by Sreedhar Balasubramanian; ok dtucker@ OpenBSD-Commit-ID: 77078c0b1c53c780269fc0c416f121d05e3010ae commit 4acfaabfae41badb9d334a2ee88c5c6ad041c0d5 Author: jmc@openbsd.org Date: Fri Dec 16 06:52:48 2022 +0000 upstream: add -X to usage(); OpenBSD-Commit-ID: 1bdc3df7de11d766587b0428318336dbffe4a9d0 commit e555d5cad5afae7d5ef2bbc02ca591178fe16fed Author: djm@openbsd.org Date: Fri Dec 16 03:40:03 2022 +0000 upstream: add a -X option to both scp(1) and sftp(1) to allow control over some SFTP protocol knobs: the copy buffer length and the number of inflight requests, both of which are used during upload/download. Previously these could be controlled in sftp(1) using the -b/-R options. This makes them available in both SFTP protocol clients using the same option character sequence. ok dtucker@ OpenBSD-Commit-ID: 27502bffc589776f5da1f31df8cb51abe9a15f1c commit 5a7a7acab2f466dc1d7467b5d05d35268c3137aa Author: deraadt@openbsd.org Date: Thu Dec 15 18:20:39 2022 +0000 upstream: The idiomatic way of coping with signed char vs unsigned char (which did not come from stdio read functions) in the presence of ctype macros, is to always cast to (unsigned char). casting to (int) for a "macro" which is documented to take int, is weird. And sadly wrong, because of the sing extension risk.. same diff from florian OpenBSD-Commit-ID: 65b9a49a68e22ff3a0ebd593f363e9f22dd73fea commit b0b58222c7cc62efd8212c4fb65a545f58ebb22d Author: Darren Tucker Date: Mon Dec 19 18:49:51 2022 +1100 Simply handling of SSH_CONNECTION PAM env var. Prompted by bz#3508: there's no need to cache the value of sshpam_conninfo so remove the global. While there, add check of return value from pam_putenv. ok djm@ commit ed8444572ae684fdb892f97bae342c6cb6456f04 Author: Darren Tucker Date: Mon Dec 19 18:42:34 2022 +1100 Add tests for LibreSSL 3.7.0 and OpenSSL 1.1.1s. commit abb9a8aaddfcacbd12641f6e4f203da0fa85a287 Author: Darren Tucker Date: Sun Dec 18 21:36:25 2022 +1100 Use sudo when resetting perms on directories. commit 2f5664c5908d84697cbe91302d5d5c4d83cb2121 Author: Darren Tucker Date: Sun Dec 18 21:19:33 2022 +1100 Set group perms on regress dir. This ensures that the tests don't fail due to StrictMode checks. commit 137196300fc1540affadde880210f02ba6cb4abf Author: Darren Tucker Date: Sun Dec 18 21:13:42 2022 +1100 Fetch regress logs from obj dir. commit 5f93c4836527d9fda05de8944a1c7b4a205080c7 Author: Darren Tucker Date: Tue Dec 13 20:59:54 2022 +1100 obsdsnap test VMs runs-on libvirt too. commit 8386886fb1ab7fda73069fb0db1dbe0e5a52f758 Author: Darren Tucker Date: Tue Dec 13 20:55:37 2022 +1100 Run upstream obsdsnap tests on ephemeral runners. commit b6e01459b55ece85d7f296b2bc719d1841e1009e Author: Darren Tucker Date: Tue Dec 13 20:48:56 2022 +1100 Move obsdsnap test VMs to ephemeral runners. commit ea6fdf9a1aa71a411f7db218a986392c4fb55693 Author: Damien Miller Date: Fri Dec 9 18:00:21 2022 +1100 use calloc for allocating arc4random structs ok dtucker commit 4403b62f5548e91389cb3339d26a9d0c4bb07b34 Author: dtucker@openbsd.org Date: Fri Dec 9 00:22:29 2022 +0000 upstream: Warn if no host keys for hostbased auth can be loaded. OpenBSD-Commit-ID: 2a0a13132000cf8d3593133c1b49768aa3c95977 commit a6183e25e3f1842e21999fe88bc40bb99b121dc3 Author: dtucker@openbsd.org Date: Fri Dec 9 00:17:40 2022 +0000 upstream: Add server debugging for hostbased auth. auth_debug_add queues messages about the auth process which is sent to the client after successful authentication. This also sends those to the server debug log to aid in debugging. From bz#3507, ok djm@ OpenBSD-Commit-ID: 46ff67518cccf9caf47e06393e2a121ee5aa258a commit b85c3581c16aaf6e83b9a797c80705a56b1f312e Author: cheloha@openbsd.org Date: Sun Dec 4 23:50:49 2022 +0000 upstream: remove '?' from getopt(3) loops userspace: remove vestigial '?' cases from top-level getopt(3) loops getopt(3) returns '?' when it encounters a flag not present in the in the optstring or if a flag is missing its option argument. We can handle this case with the "default" failure case with no loss of legibility. Hence, remove all the redundant "case '?':" lines. Prompted by dlg@. With help from dlg@ and millert@. Link: https://marc.info/?l=openbsd-tech&m=167011979726449&w=2 ok naddy@ millert@ dlg@ OpenBSD-Commit-ID: b2f89346538ce4f5b33ab8011a23e0626a67e66e commit 9a067e8d28a2249fd73f004961e30c113ee85e5d Author: dtucker@openbsd.org Date: Wed Dec 7 11:45:43 2022 +0000 upstream: Fix comment typo. OpenBSD-Regress-ID: 3b04faced6511bb5e74648c6a4ef4bf2c4decf03 commit ce3c3e78ce45d68a82c7c8dc89895f297a67f225 Author: Darren Tucker Date: Wed Dec 7 18:58:25 2022 +1100 Add SANDBOX_DEBUG to the kitchensink test build. commit bc234605fa3eb10f56bf0d74c8ecb0d91ada9d05 Author: Damien Miller Date: Wed Dec 7 18:38:25 2022 +1100 disable SANDBOX_SECCOMP_FILTER_DEBUG It was mistakenly enabled in 2580916e4872 Reported by Peter sec-openssh-com.22.fichtner AT 0sg.net commit b087c5cfa011b27992e01589314fec830266f99d Author: Rose <83477269+AtariDreams@users.noreply.github.com> Date: Tue Nov 29 15:12:54 2022 -0500 Update autotools Regenerate config files using latest autotools commit d63f5494978a185c7421d492b9c2f6f05bb54138 Author: Darren Tucker Date: Tue Dec 6 12:22:36 2022 +1100 Fix typo in comment. Spotted by tim@ commit 73dcca12115aa12ed0d123b914d473c384e52651 Author: dtucker@openbsd.org Date: Sun Dec 4 11:03:11 2022 +0000 upstream: Remove duplicate includes. Patch from AtariDreams via github PR#364. OpenBSD-Commit-ID: b9186638a05cb8b56ef7c0de521922b6723644ea commit 3cec15543010bc8d6997d896b1717a650afb7e92 Author: djm@openbsd.org Date: Fri Dec 2 04:40:27 2022 +0000 upstream: make struct sshbuf private and remove an unused field; ok dtucker OpenBSD-Commit-ID: c7a3d77c0b8c153d463398606a8d57569186a0c3 commit 5796bf8ca9535f9fa7d01829a540d2550e05c860 Author: Darren Tucker Date: Fri Dec 2 11:43:36 2022 +1100 Restore ssh-agent permissions on exit. ...enough that subsequent builds can overwrite ssh-agent if necessary. commit ccf5a13868cbb4659107458cac1e017c98abcbda Author: dtucker@openbsd.org Date: Thu Dec 1 02:22:13 2022 +0000 upstream: Clean up ssh-add and ssh-agent logs. OpenBSD-Regress-ID: 9eda8e4c3714d7f943ab2e73ed58a233bd29cd2c commit 7a8b40cf6a5eda80173140cc6750a6db8412fa87 Author: dtucker@openbsd.org Date: Thu Dec 1 02:19:29 2022 +0000 upstream: Log output of ssh-agent and ssh-add This should make debugging easier. OpenBSD-Regress-ID: 5974b02651f428d7e1079b41304c498ca7e306c8 commit 4a1805d532616233dd6072e5cd273b96dd3062e6 Author: dtucker@openbsd.org Date: Tue Nov 29 22:41:14 2022 +0000 upstream: Add void to client_repledge args to fix compiler warning. ok djm@ OpenBSD-Commit-ID: 7e964a641ce4a0a0a11f047953b29929d7a4b866 commit 815c4704930aa449edf6e812e99d69e9ffd31f01 Author: djm@openbsd.org Date: Mon Nov 28 01:38:22 2022 +0000 upstream: tighten pledge(2) after session establishment feedback, ok & testing in snaps deraadt@ OpenBSD-Commit-ID: aecf4d49d28586dfbcc74328d9333398fef9eb58 commit f7cebbbf407d772ed71403d314343766782fe540 Author: djm@openbsd.org Date: Mon Nov 28 01:37:36 2022 +0000 upstream: New EnableEscapeCommandline ssh_config(5) option This option (default "no") controls whether the ~C escape is available. Turning it off by default means we will soon be able to use a stricter default pledge(2) in the client. feedback deraadt@ dtucker@; tested in snaps for a while OpenBSD-Commit-ID: 7e277595d60acb8263118dcb66554472257b387a commit d323f7ecf52e3d4ec1f4939bf31693e02f891dca Author: mbuhl@openbsd.org Date: Fri Nov 18 19:47:40 2022 +0000 upstream: In channel_request_remote_forwarding the parameters for permission_set_add are leaked as they are also duplicated in the call. Found by CodeChecker. ok djm OpenBSD-Commit-ID: 4aef50fa9be7c0b138188814c8fe3dccc196f61e commit 62cc33e6eed847aafdc29e34aa69e9bd82a0ee16 Author: Darren Tucker Date: Wed Nov 30 11:23:11 2022 +1100 Use -fzero-call-used-regs=used on clang 15. clang 15 seems to have a problem with -fzero-call-used-reg=all which causes spurious "incorrect signature" failures with ED25519. On those versions, use -fzero-call-used-regs=used instead. (We may add exceptions later if specific versions prove to be OK). Also move the GCC version check to match. Initial investigation by Daniel Pouzzner (douzzer at mega nu), workaround suggested by Bill Wendling (morbo at google com). bz#3475, ok djm@ commit f84b9cffd52c9c5c359a54a1929f9948e803ab1d Author: Darren Tucker Date: Mon Nov 28 21:09:28 2022 +1100 Skip unit tests on slow riscv64 hardware. commit 9f2747e0bed3faca92679eae69aef10c95dc82f5 Author: Darren Tucker Date: Sun Nov 27 15:26:22 2022 +1100 Rework how selfhosted tests interact with runners. Previously there was one runner per test target (mostly VMs). This had a few limitations: - multiple tests that ran on the same target (eg multiple build configs) were serialized on availability or that runner. - it needed manual balancing of VMs over host machines. To address this, make VMs that use ephemeral disks (ie most of them) all use a pool of runners with the "libvirt" label. This requires that we distinguish between "host" and "target" for those. Native runners and VMs with persistent disks (eg the constantly-updated snapshot ones) specify the same host and target. This should improve test throughput. commit d664ddaec87bdc7385be8ef7f1337793e1679d48 Author: Darren Tucker Date: Sun Nov 27 12:19:37 2022 +1100 Run vmstartup from temp dir. This will allow us to create ephemeral disk images per-runner. commit 0fa16e952b1fc1c4cf65e3dd138b0e87003e2e45 Author: Darren Tucker Date: Sun Nov 27 12:14:00 2022 +1100 Make "config" in matrix singular and pass in env. This will allow the startup scripts to adapt their behaviour based on the type and config. commit e8857043af54809187be1e8b06749db61112899f Author: Darren Tucker Date: Sun Nov 27 11:42:22 2022 +1100 Add "libvirt" label to dfly30. commit 9775473d84902dc37753686cd10ae71fbe67efda Author: Darren Tucker Date: Sun Nov 27 09:28:20 2022 +1100 Rename "os" in matrix to "target". This is in preparation to distinguish this from the host that the runner runs on in case where they are separate (eg VMs). commit 04fd00ceff39f4544ced6f5342060abe584835d0 Author: Darren Tucker Date: Sun Nov 27 09:23:04 2022 +1100 Remove unused self-hosted test targets. commit c9d9fcad2a11c1cd1550a541f44091d65f0b5584 Author: Darren Tucker Date: Sun Nov 27 09:16:15 2022 +1100 Remove explicit "default" test config argument. Not specifying the test config implicitly selects default args. commit 15a01cf15f396f87c6d221c5a6af98331c818962 Author: Darren Tucker Date: Wed Nov 23 13:18:54 2022 +1100 Add fallback for old platforms w/out MAP_ANON. commit 6b9bbbfe8b26db6e9a30a7e08c223e85421aed98 Author: Darren Tucker Date: Wed Nov 23 13:09:11 2022 +1100 If we haven't found it yet, recheck for sys/stat.h. On some very old platforms, sys/stat.h needs sys/types.h, however autoconf 2.71's AC_CHECK_INCLUDES_DEFAULT checks for them in the opposite order, which in combination with modern autoconf's "present but cannot be compiled" behaviour causes it to not be detected. commit 8926956f22639132a9f2433fcd25224e01b900f5 Author: Darren Tucker Date: Fri Nov 11 11:25:37 2022 +1100 Add dfly62 test target. commit 650de7ecd3567b5a5dbf16dd1eb598bd8c20bca8 Author: dtucker@openbsd.org Date: Thu Nov 10 23:03:10 2022 +0000 upstream: Handle dynamic remote port forwarding in escape commandline's -R processing. bz#3499, ok djm@ OpenBSD-Commit-ID: 194ee4cfe7ed0e2b8ad0727f493c798a50454208 commit 5372db7e7985ba2c00f20fdff8942145ca99e033 Author: Darren Tucker Date: Thu Nov 10 12:44:51 2022 +1100 Remove seed passing over reexec. This was added for the benefit of platforms using ssh-rand-helper to prevent a delay on each connection as sshd reseeded itself. ssh-random-helper is long gone, and since the re-exec happens before the chroot the re-execed sshd can reseed itself normally. ok djm@ commit ca98d3f8c64cfc51af81e1b01c36a919d5947ec2 Author: Darren Tucker Date: Wed Nov 9 20:59:20 2022 +1100 Skip reexec test on OpenSSL 1.1.1 specifically. OpenSSL 1.1.1 has a bug in its RNG that breaks reexec fallback, so skip that test. See bz#3483 for details. commit 5ec4ebc2548e5f7f1b55b2a5cef5b67bdca8146f Author: dtucker@openbsd.org Date: Wed Nov 9 09:04:12 2022 +0000 upstream: Fix typo in fatal error message. Patch from vapier at chromium.org. OpenBSD-Commit-ID: 8a0c164a6a25eef0eedfc30df95bfa27644e35cf commit e6abafe9a6d809422d3432b95b3f9747b0acaa71 Author: dtucker@openbsd.org Date: Wed Nov 9 09:01:52 2022 +0000 upstream: Remove errant colon and simplify format string in error messages. Patch from vapier at chromium.org. OpenBSD-Commit-ID: fc28466ebc7b74e0072331947a89bdd239c160d3 commit db2027a687516f87c3fb141e87154bb3d8a7807c Author: djm@openbsd.org Date: Wed Nov 9 01:37:44 2022 +0000 upstream: rename client_global_hostkeys_private_confirm() to client_global_hostkeys_prove_confirm(), as it handles the "hostkeys-prove00@openssh.com" message; no functional change OpenBSD-Commit-ID: 31e09bd3cca6eed26855b88fb8beed18e9bd026d commit 1c2be7c2004cf1abcd172fee9fe3eab57cd4c426 Author: djm@openbsd.org Date: Wed Nov 9 00:15:59 2022 +0000 upstream: typo in comment OpenBSD-Commit-ID: 39c58f41e0f32d1ff31731fa6f5bbbc3ad25084a commit cf1a9852d7fc93e4abc4168aed09529a57427cdc Author: Darren Tucker Date: Wed Nov 9 09:23:47 2022 +1100 Defer seed_rng until after closefrom call. seed_rng will initialize OpenSSL, and some engine providers (eg Intel's QAT) will open descriptors for their own use. bz#3483, patch from joel.d.schuetze at intel.com, ok djm@ commit dffa64480163fbf76af7e4fb62c26bb0dd6642aa Author: Darren Tucker Date: Wed Nov 9 08:27:47 2022 +1100 Fix comment text. From emaste at freebsd.org. commit d9df5689c29823ab830ec4f54c83c6cc3c0077ad Author: Pierre Ossman Date: Wed Jul 6 13:52:10 2022 +0200 Avoid assuming layout of fd_set POSIX doesn't specify the internal layout of the fd_set object, so let's not assume it is just a bit mask. This increases compatibility with systems that have a different layout. The assumption is also worthless as we already refuse to use file descriptors over FD_SETSIZE anyway. Meaning that the default size of fd_set is quite sufficient. commit 419aa8a312e8d8f491933ca3d5933e602cb05aae Author: Darren Tucker Date: Tue Nov 8 12:42:52 2022 +1100 Shutdown any VM before trying to check out repo. In the case where the previous run did not clean up, the checkout will fail as it'll leave a stale mount. commit a32c07cbb78f65d8527642b96474a83b413f8108 Author: Darren Tucker Date: Tue Nov 8 11:33:25 2022 +1100 Run vm startup and shutdown from runner temp dir. Should work even if the github workspace dir is on a stale sshfs mount. commit 2b40a7dfcdb8e616155b9504145aa52b271455aa Author: Darren Tucker Date: Tue Nov 8 11:03:31 2022 +1100 Add valrind-5 test here too. commit 2ea03d1f6d0a05ee2b63ed2dc0f2d54f1e4655a1 Author: Darren Tucker Date: Tue Nov 8 09:21:10 2022 +1100 Update checkout and upload actions. Update actions/checkout and actions/upload-artifact to main branch for compatibility with node.js v16. commit 4e316ff0f18a118232bb9ac6512ee62773a9e8ea Author: Darren Tucker Date: Tue Nov 8 09:17:04 2022 +1100 Split out rekey test since it runs the longest. commit 21625a6424258a92a96a3bb73ae6aabc5ed8a6b4 Author: dtucker@openbsd.org Date: Mon Nov 7 10:09:28 2022 +0000 upstream: The IdentityFile option in ssh_config can also be used to specify a public key file, as documented in ssh.1 for the -i option. Document this also for IdentityFile in ssh_config.5, for documentation completeness. From laalsaas at systemli.org via portable github PR#352, ok jmc@ djm@ OpenBSD-Commit-ID: 2f943be9f96e60ef81a9a4faa25b009999f9883b commit 747691604d3325ed2b62bad85b6fd8563ad32f6c Author: dtucker@openbsd.org Date: Mon Nov 7 10:05:38 2022 +0000 upstream: Remove some set but otherwise unused variables, spotted in -portable by clang 16's -Wunused-but-set-variable. ok djm@ OpenBSD-Commit-ID: 3d943ddf2369b38fbf89f5f19728e7dc1daf3982 commit 1d78d25653805aefc7a8dd9d86cd7359ada3823c Author: dtucker@openbsd.org Date: Mon Nov 7 10:02:59 2022 +0000 upstream: Check for and disallow MaxStartups values less than or equal to zero during config parsing, rather than faling later at runtime. bz#3489, ok djm@ OpenBSD-Commit-ID: d79c2b7a8601eb9be493629a91245d761154308b commit a00f59a645072e5f5a8d207af15916a7b23e2642 Author: djm@openbsd.org Date: Mon Nov 7 04:04:40 2022 +0000 upstream: fix parsing of hex cert expiry time; was checking whether the start time began with "0x", not the expiry time. from Ed Maste OpenBSD-Commit-ID: 6269242c3e1a130b47c92cfca4d661df15f05739 commit f58acaf8c7315483f4ac87d46a1aa2142a713cd8 Author: Darren Tucker Date: Mon Nov 7 15:10:59 2022 +1100 Fix merge conflict. commit 162e5741020a8d996c0c12b988b118e71ed728e6 Author: Darren Tucker Date: Mon Nov 7 15:04:33 2022 +1100 Branch-specific links for master status badges. commit e4b7c12ab24579312aa3ed38ce7041a439ec2d56 Author: Darren Tucker Date: Mon Nov 7 14:46:38 2022 +1100 Add CIFuzz status badge. commit b496b9f831acd1e5bcd875e26e797488beef494a Author: Darren Tucker Date: Mon Nov 7 14:45:16 2022 +1100 Do not run CIFuzz on selfhosted tree. We already run it on the regular tree, no need to double up. commit 2138b1c4ddb300129a41a5104627b0d561184c7b Author: Darren Tucker Date: Mon Nov 7 14:41:58 2022 +1100 Whitespace change to trigger CIFuzz workflow. commit 4670b97ef87c7b0f21283c9b07c7191be88dda05 Author: Darren Tucker Date: Mon Nov 7 14:34:04 2022 +1100 Run cifuzz workflow on the actions as regular CI. commit 79391e66ce851ace1baf3c6a35e83a23f08ec2ba Author: David Korczynski Date: Tue Nov 30 11:45:20 2021 +0000 Add CIFuzz integration commit c1893364a0be243270014d7d34362a8101d55112 Author: dtucker@openbsd.org Date: Mon Nov 7 02:21:22 2022 +0000 upstream: Import regenerated moduli. OpenBSD-Commit-ID: b0e54ee4d703bd6929bbc624068666a7a42ecb1f commit 5c3f18fb994ef27e685b205ee2351851b80fdbd1 Author: dtucker@openbsd.org Date: Mon Nov 7 01:53:01 2022 +0000 upstream: Fix typo. From pablomh via -portable github PR#344. OpenBSD-Commit-ID: d056ee2e73691dc3ecdb44a6de68e6b88cd93827 commit e1c6fcc142066417c9832e634463faa3dd5d116c Author: Darren Tucker Date: Mon Nov 7 12:46:58 2022 +1100 Link to branch-specific queries for V_9_1 status. commit 4f4a5fad6d8892c3f8ee9cd81ec7de6458210c9f Author: Darren Tucker Date: Sun Nov 6 10:55:59 2022 +1100 Use "prohibit-password" in -portable comments. "without-password" is the deprecated alias for "prohibit-password", so we should reference the latter. From emaste at freebsd.org. commit 0f7e1eba55259ec037f515000b4c4afbf446230a Author: Darren Tucker Date: Sun Nov 6 10:50:01 2022 +1100 Fix tracing disable on FreeBSD. Some versions of FreeBSD do not support using id 0 to refer to the current pid for procctl, so pass getpid() explicitly. From emaste at freebsd.org. commit 32fddb982fd61b11a2f218a115975a87ab126d43 Author: Darren Tucker Date: Mon Nov 7 10:39:01 2022 +1100 Fix setres*id checks to work with clang-16. glibc has the prototypes for setresuid and setresgid behind _GNU_SOURCE, and clang 16 will error out on implicit function definitions, so add _GNU_SOURCE and the required headers to the configure checks. From sam at @gentoo.org via bz#3497. commit 12af712d116f42164bcfa56db901d06e4fa27199 Author: Sam James Date: Sun Nov 6 04:52:38 2022 +0000 configure.ac: Fix -Wstrict-prototypes Clang 16 now warns on this and it'll be removed in C23, so let's just be future proof. It also reduces noise when doing general Clang 16 porting work (which is a big job as it is). github PR#355. Signed-off-by: Sam James commit 40b0a5eb6e3edfa2886b60c09c7803353b0cc7f5 Author: Sam James Date: Sun Nov 6 04:47:35 2022 +0000 configure.ac: Add include for openpty Another Clang 16ish fix (which makes -Wimplicit-function-declaration an error by default). github PR#355. See: 2efd71da49b9cfeab7987058cf5919e473ff466b See: be197635329feb839865fdc738e34e24afd1fca8 commit 6b17e128879ec6cc32ca2c28b5d894b4aa72e32d Author: Rochdi Nassah Date: Fri Oct 28 01:26:31 2022 +0100 Fix broken zlib link. commit 99500df246ccb736ddbdd04160dcc82165d81a77 Author: Darren Tucker Date: Fri Nov 4 16:59:26 2022 +1100 Don't run openbsd-compat tests on Cygwin. Add "compat-tests" to the default TEST_TARGET so we can override as necessary. Override TEST_TARGET for Cygwin as the tests don't currently compile there. commit 3cae9f92a31897409666aa1e6f696f779759332b Author: djm@openbsd.org Date: Thu Nov 3 21:59:20 2022 +0000 upstream: replace recently-added valid_domain() check for hostnames going to known_hosts with a more relaxed check for bad characters; previous commit broke address literals. Reported by/feedback from florian@ OpenBSD-Commit-ID: 10b86dc6a4b206adaa0c11b58b6d5933898d43e0 commit 9655217231c9056200bea7ae2dffcc9c0c3eb265 Author: Darren Tucker Date: Thu Nov 3 23:07:50 2022 +1100 Rerun tests on changes to Makefile.in in any dir. commit 3500f0405a3ab16b59a26f3508c4257a3fc3bce6 Author: Darren Tucker Date: Thu Nov 3 23:04:08 2022 +1100 Link libssh into compat tests. The cygwin compat code uses xmalloc, so add libssh.a so pick up that. commit ec59effcf65b8a4c85d47ff5a271123259dd0ab8 Author: Darren Tucker Date: Thu Nov 3 21:44:23 2022 +1100 Fix compat regress to work with non-GNU make. commit 73550a218e7dfbbd599534cbf856309bc924f6fd Author: Darren Tucker Date: Thu Nov 3 13:41:16 2022 +1100 Increase selfhosted job timeout. The default job timeout of 360 (6h) is not enough to complete the regress tests for some of the slow VMs depending on the load on the host. Increase to 600 (10h). commit db97d8d0b90c6ce52b94b153d6f8f5f7d3b11777 Author: Darren Tucker Date: Thu Nov 3 10:00:43 2022 +1100 Only run opensslver tests if built with OpenSSL. commit ba053709638dff2f6603df0c1f340352261d63ea Author: Darren Tucker Date: Wed Nov 2 14:16:04 2022 +1100 Add tests for OpenSSL 3.0.7 and LibreSSL 3.6.1. commit edd24101c7e17d1a8f6576e1aaf62233b47ad6f5 Author: Darren Tucker Date: Thu Nov 3 08:17:39 2022 +1100 Run compat regress tests too. commit fe88d67e7599b0bc73f6e4524add28d743e7f977 Author: Darren Tucker Date: Thu Nov 3 08:14:05 2022 +1100 Compat tests need libcrypto. This was moved to CHANNELLIBS during the libs refactor. Spotted by rapier at psc.edu. commit 96b519726b7944eee3c23a54eee3d5c031ba1533 Author: Darren Tucker Date: Thu Nov 3 04:24:39 2022 +1100 Include time.h when defining timegm. Fixes build on some platforms eg recent AIX. commit da6038bd5cd55eb212eb2aec1fc8ae79bbf76156 Author: Darren Tucker Date: Tue Nov 1 19:10:30 2022 +1100 Always use compat getentropy. Have it call native getentropy and fall back as required. Should fix issues of platforms where libc has getentropy but it is not implemented in the kernel. Based on github PR#354 from simsergey. commit 5ebe18cab6be3247b44c807ac145164010465b82 Author: Darren Tucker Date: Wed Nov 2 10:51:48 2022 +1100 Check for sockaddr_in.sin_len. If found, set SOCK_HAS_LEN which is used in addr.c. Should fix keyscan tests on platforms with this (eg old NetBSD). commit a1febadf426536612c2734168d409147c392e7cf Author: dtucker@openbsd.org Date: Sun Oct 30 18:42:07 2022 +0000 upstream: Use variable for diff options instead of unconditionally specifying "-rN". This will make life easier in -portable where not all diff's understand -N. OpenBSD-Regress-ID: 8b8a407115546be1c6d72d350b1e4f1f960d3cd3 commit f6d3ed9a8a9280cbb68d6a499850cfe810e92bd0 Author: Darren Tucker Date: Mon Oct 31 05:13:02 2022 +1100 OpenSSL dev branch is 302 not 320. While there, also accept 301 which it shat it was previously. commit 25c8a2bbcc10c493d27faea57c42a6bf13fa51f2 Author: djm@openbsd.org Date: Fri Oct 28 02:47:04 2022 +0000 upstream: put sshkey_check_rsa_length() back in sshkey.c to unbreak OPENSSL=no builds OpenBSD-Commit-ID: 99eec58abe382ecd14b14043b195ee1babb9cf6e commit 1192588546c29ceec10775125f396555ea71850f Author: djm@openbsd.org Date: Fri Oct 28 02:29:34 2022 +0000 upstream: allow ssh-keyscan(1) to accept CIDR address ranges, e.g. ssh-keyscan 192.168.0.0/24 If a CIDR range is passed, then it will be expanded to all possible addresses in the range including the all-0s and all-1s addresses. bz#976 feedback/ok markus@ OpenBSD-Commit-ID: ce6c5211f936ac0053fd4a2ddb415277931e6c4b commit 64af4209309461c79c39eda2d13f9d77816c6398 Author: Damien Miller Date: Fri Oct 28 12:54:35 2022 +1100 fix merge botch commit 27267642699342412964aa785b98afd69d952c88 Author: djm@openbsd.org Date: Fri Oct 28 00:44:44 2022 +0000 upstream: refactor sshkey_private_deserialize feedback/ok markus@ OpenBSD-Commit-ID: f5ca6932fdaf840a5e8250becb38315a29b5fc9f commit 2519a7077a9332f70935e5242ba91ee670ed6b87 Author: djm@openbsd.org Date: Fri Oct 28 00:44:17 2022 +0000 upstream: refactor sshkey_private_serialize_opt() feedback/ok markus@ OpenBSD-Commit-ID: 61e0fe989897901294efe7c3b6d670cefaf44cbd commit 11a768adf98371fe4e43f3b06014024c033385d5 Author: djm@openbsd.org Date: Fri Oct 28 00:43:30 2022 +0000 upstream: refactor certify feedback/ok markus@ OpenBSD-Commit-ID: 35d742992e223eaca3537e6fb3d3002c08eed4f6 commit 3fbc58bb249d967cc43ebdc554f6781bb73d4a58 Author: djm@openbsd.org Date: Fri Oct 28 00:43:08 2022 +0000 upstream: refactor sshkey_sign() and sshkey_verify() feedback/ok markus@ OpenBSD-Commit-ID: 368e662c128c99d05cc043b1308d2b6c71a4d3cc commit a1deb6cdbbe6afaab74ecb08fcb62db5739267be Author: djm@openbsd.org Date: Fri Oct 28 00:41:52 2022 +0000 upstream: refactor sshkey_from_blob_internal() feedback/ok markus@ OpenBSD-Commit-ID: 1f46c0cbb8060ee9666a02749594ad6658c8e283 commit 7d00799c935271ce89300494c5677190779f6453 Author: djm@openbsd.org Date: Fri Oct 28 00:41:17 2022 +0000 upstream: refactor sshkey_from_private() feedback/ok markus@ OpenBSD-Commit-ID: e5dbe7a3545930c50f70ee75c867a1e08b382b53 commit 262647c2e920492ca57f1b9320d74f4a0f6e482b Author: djm@openbsd.org Date: Fri Oct 28 00:39:29 2022 +0000 upstream: factor out key generation feedback/ok markus@ OpenBSD-Commit-ID: 5b4211bff4de8d9adb84bc72857a8c42c44e7ceb commit 401c74e7dc15eab60540653d2f94d9306a927bab Author: djm@openbsd.org Date: Fri Oct 28 00:38:58 2022 +0000 upstream: refactor and simplify sshkey_read() feedback/ok markus@ OpenBSD-Commit-ID: 0d93b7a56e31cd06a8bb0d2191d084ce254b0971 commit 591fed94e66a016acf87f4b7cd416ce812f2abe8 Author: djm@openbsd.org Date: Fri Oct 28 00:37:24 2022 +0000 upstream: factor out public key serialization feedback/ok markus@ OpenBSD-Commit-ID: a3570c4b97290c5662890aea7328d87f55939033 commit 1e78844ae2b2dc01ba735d5ae740904c57e13685 Author: djm@openbsd.org Date: Fri Oct 28 00:36:31 2022 +0000 upstream: factor out sshkey_equal_public() feedback/ok markus@ OpenBSD-Commit-ID: 1368ba114cb37732fe6ec3d89c7e6d27ea6fdc94 commit 25de1c01a8b9a2c8ab9b1da22444a03e89c982de Author: djm@openbsd.org Date: Fri Oct 28 00:35:40 2022 +0000 upstream: begin big refactor of sshkey Move keytype data and some of the type-specific code (allocation, cleanup, etc) out into each key type's implementation. Subsequent commits will move more, with the goal of having each key-*.c file owning as much of its keytype's implementation as possible. lots of feedback + ok markus@ OpenBSD-Commit-ID: 0f2b4334f73914344e9e5b3d33522d41762a57ec commit 445363433ba20b8a3e655b113858c836da46a1cb Author: djm@openbsd.org Date: Mon Oct 24 22:43:36 2022 +0000 upstream: Be more paranoid with host/domain names coming from the never write a name with bad characters to a known_hosts file. reported by David Leadbeater, ok deraadt@ OpenBSD-Commit-ID: ba9b25fa8b5490b49398471e0c9657b0cbc7a5ad commit 7190154de2c9fe135f0cc1ad349cb2fa45152b89 Author: djm@openbsd.org Date: Mon Oct 24 21:52:50 2022 +0000 upstream: regress test for unmatched glob characters; fails before previous commit but passes now. bz3488; prodded by dtucker@ OpenBSD-Regress-ID: 0cc5cc9ea4a6fd170dc61b9212f15badaafb3bbd commit a4821a592456c3add3cd325db433110cdaaa3e5c Author: djm@openbsd.org Date: Mon Oct 24 21:51:55 2022 +0000 upstream: when scp(1) is using the SFTP protocol for transport (the default), better match scp/rcp's handling of globs that don't match the globbed characters but do match literally (e.g. trying to transfer "foo.[1]"). Previously scp(1) in SFTP mode would not match these pathnames but legacy scp/rcp mode would. Reported by Michael Yagliyan in bz3488; ok dtucker@ OpenBSD-Commit-ID: d8a3773f53015ba811fddba7473769a2fd343e11 commit 18376847b8043ba967eabbe23692ef74c9a3fddc Author: jsg@openbsd.org Date: Thu Oct 13 09:09:28 2022 +0000 upstream: use correct type with sizeof ok djm@ OpenBSD-Commit-ID: d6c882c2e8a42ff831a5b3cbc2c961ecb2dd6143 commit 4a4883664d6b4e9e4e459a8cdc16bd8d4b735de9 Author: jmc@openbsd.org Date: Fri Oct 7 06:00:58 2022 +0000 upstream: ssh-agent.1: - use Nm not Xr for self-ref - while here, wrap a long line ssh-agent.c: - add -O to usage() OpenBSD-Commit-ID: 855dac4695cef22e96d69c53436496bc408ca389 commit 9fd2441113fce2a83fc7470968c3b27809cc7f10 Author: djm@openbsd.org Date: Fri Oct 7 04:06:26 2022 +0000 upstream: document "-O no-restrict-websafe"; spotted by Ross L Richardson OpenBSD-Commit-ID: fe9eaa50237693a14ebe5b5614bf32a02145fe8b commit 614252b05d70f798a0929b1cd3d213030ad4d007 Author: Darren Tucker Date: Tue Oct 18 06:29:16 2022 +1100 OpenSSL dev branch now identifies as 3.2.0. commit 195e5a65fd793a738ea8451ebfdd1919db5aff3e Author: Damien Miller Date: Mon Oct 17 09:41:47 2022 +1100 revert c64b62338b4 and guard POLL* defines instead c64b62338b4 broke OSX builds, which do have poll.h but lack ppoll(2) Spotted by dtucker commit bc2e480d99613bd59720edae244d1764636544c4 Author: Damien Miller Date: Fri Oct 14 14:52:22 2022 +1100 undef _get{short,long} before redefining commit 5eb796a369c64f18d55a6ae9b1fa9b35eea237fb Author: Harmen Stoppels Date: Thu Oct 13 16:08:46 2022 +0200 Fix snprintf configure test for clang 15 Clang 15 -Wimplicit-int defaults to an error in C99 mode and above. A handful of tests have "main(..." and not "int main(..." which caused the tests to produce incorrect results. commit c64b62338b46ffa08839f05f21ad69fa6234dc17 Author: Damien Miller Date: Mon Oct 10 12:32:43 2022 +1100 skip bsd-poll.h if poll.h found; ok dtucker commit 5ee2b8ccfcf4b606f450eb0ff2305e311f68b0be Author: djm@openbsd.org Date: Thu Oct 6 22:42:37 2022 +0000 upstream: honour user's umask if it is more restrictive then the ssh default (022); based on patch from Alex Henrie, ok dtucker@ deraadt@ OpenBSD-Commit-ID: fe1b9e15fc9a4f49fc338e848ce14d8727abe82d commit a75cffc2700cebd3e2dd9093f7f7388d2be95cb7 Author: Darren Tucker Date: Fri Oct 7 03:54:56 2022 +1100 Add LibreSSL 3.6.0 to test suite. While there, bump OpenSSL to latest 1.1.1q release. commit fcc0f0c0e96a30076683fea9a7c9eedc72931742 Author: Darren Tucker Date: Thu Oct 6 21:18:16 2022 +1100 Add 9.1 branch to CI status page. commit ef211eee63821d894a8bf81f22bfba9f6899d0fe Author: Darren Tucker Date: Tue Oct 4 23:20:23 2022 +1100 Test commits to all branches of portable. Only test OpenBSD upstream on commits to master since that's what it tracks. commit fe646de03cafb6593ff4e4954bca9ec4b4b753a8 Author: Damien Miller Date: Wed Oct 5 03:47:26 2022 +1100 whitespace at EOL commit a6e1852d10c63a830196e82168dadd957aaf28ec Author: Damien Miller Date: Wed Oct 5 03:40:01 2022 +1100 mention libfido2 autodetection commit 7360c2c206f33d309edbaf64036c96fadf74d640 Author: Damien Miller Date: Wed Oct 5 03:37:36 2022 +1100 remove mention of --with-security-key-builtin it is enabled by default when libfido2 is installed commit 0ffb46f2ee2ffcc4daf45ee679e484da8fcf338c Author: Damien Miller Date: Tue Oct 4 01:51:42 2022 +1100 update .depend commit 657e676ff696c7bb787bffb0e249ea1be3b474e1 Author: Damien Miller Date: Tue Oct 4 01:45:52 2022 +1100 update release notes URL commit f059da2b29840c0f048448809c317ce2ae014da7 Author: Damien Miller Date: Tue Oct 4 01:45:41 2022 +1100 crank versions in RPM spec files commit b51f3f172d87cbdb80ca4eb7b2149e56a7647557 Author: djm@openbsd.org Date: Mon Sep 26 22:18:40 2022 +0000 upstream: openssh-9.1 OpenBSD-Commit-ID: 5a467b2ee81da01a86adf1ad93b62b1728494e56 commit 4cf8d0c0f3030f594a238bab21a0695735515487 Author: dtucker@openbsd.org Date: Wed Sep 21 22:26:50 2022 +0000 upstream: Fix typo. From AlexanderStohr via github PR#343. OpenBSD-Commit-ID: a134c9b4039e48803fc6a87f955b0f4a03181497 commit 8179fed3264d5919899900ed8881d5f9bb57ca33 Author: djm@openbsd.org Date: Mon Sep 19 21:39:16 2022 +0000 upstream: add RequiredRSASize to the list of keywords accepted by -o; spotted by jmc@ OpenBSD-Commit-ID: fe871408cf6f9d3699afeda876f8adbac86a035e commit 5f954929e9f173dd1e279e07d0e8b14fa845814d Author: Damien Miller Date: Mon Sep 19 20:59:34 2022 +1000 no need for glob.h here it also causes portability problems commit 03d94a47207d58b3db37eba4f87eb6ae5a63168a Author: Damien Miller Date: Mon Sep 19 20:59:04 2022 +1000 avoid Wuninitialized false positive in gcc-12ish commit 9d952529113831fb3071ab6e408d2726fd72e771 Author: djm@openbsd.org Date: Mon Sep 19 10:46:00 2022 +0000 upstream: use users-groups-by-id@openssh.com sftp-server extension (when available) to fill in user/group names for directory listings. Implement a client-side cache of see uid/gid=>user/group names. ok markus@ OpenBSD-Commit-ID: f239aeeadfa925a37ceee36ee8b256b8ccf4466e commit 8ff680368b0bccf88ae85d4c99de69387fbad7a6 Author: djm@openbsd.org Date: Mon Sep 19 10:43:12 2022 +0000 upstream: sftp client library support for users-groups-by-id@openssh.com; ok markus@ OpenBSD-Commit-ID: ddb2f33a2da6349a9a89a8b5bcb9ca7c999394de commit 488f6e1c582212c2374a4bf8cd1b703d2e70fb8b Author: djm@openbsd.org Date: Mon Sep 19 10:41:58 2022 +0000 upstream: extend sftp-common.c:extend ls_file() to support supplied user/group names; ok markus@ OpenBSD-Commit-ID: c70c70498b1fdcf158531117e405b6245863bfb0 commit 74b77f7497dba3a58315c8f308883de448078057 Author: djm@openbsd.org Date: Mon Sep 19 10:40:52 2022 +0000 upstream: sftp-server(8): add a "users-groups-by-id@openssh.com" extension request that allows the client to obtain user/group names that correspond to a set of uids/gids. Will be used to make directory listings more useful and consistent in sftp(1). ok markus@ OpenBSD-Commit-ID: 7ebabde0bcb95ef949c4840fe89e697e30df47d3 commit 231a346c0c67cc7ca098360f9a554fa7d4f1eddb Author: djm@openbsd.org Date: Mon Sep 19 08:49:50 2022 +0000 upstream: better debugging for connect_next() OpenBSD-Commit-ID: d16a307a0711499c971807f324484ed3a6036640 commit 1875042c52a3b950ae5963c9ca3774a4cc7f0380 Author: djm@openbsd.org Date: Sat Sep 17 10:34:29 2022 +0000 upstream: Add RequiredRSASize for sshd(8); RSA keys that fall beneath this limit will be ignored for user and host-based authentication. Feedback deraadt@ ok markus@ OpenBSD-Commit-ID: 187931dfc19d51873df5930a04f2d972adf1f7f1 commit 54b333d12e55e6560b328c737d514ff3511f1afd Author: djm@openbsd.org Date: Sat Sep 17 10:33:18 2022 +0000 upstream: add a RequiredRSASize for checking RSA key length in ssh(1). User authentication keys that fall beneath this limit will be ignored. If a host presents a host key beneath this limit then the connection will be terminated (unfortunately there are no fallbacks in the protocol for host authentication). feedback deraadt, Dmitry Belyavskiy; ok markus@ OpenBSD-Commit-ID: 430e339b2a79fa9ecc63f2837b06fdd88a7da13a commit 07d8771bacfefbcfb37fa8a6dc6103bcc097e0ab Author: djm@openbsd.org Date: Sat Sep 17 10:30:45 2022 +0000 upstream: Add a sshkey_check_rsa_length() call for checking the length of an RSA key; ok markus@ OpenBSD-Commit-ID: de77cd5b11594297eda82edc594b0d32b8535134 commit 3991a0cf947cf3ae0f0373bcec5a90e86a7152f5 Author: djm@openbsd.org Date: Sat Sep 17 10:11:29 2022 +0000 upstream: actually hook up restrict_websafe; the command-line flag was never actually used. Spotted by Matthew Garrett OpenBSD-Commit-ID: 0b363518ac4c2819dbaa3dfad4028633ab9cdff1 commit 30b2a7e4291fb9e357f80a237931ff008d686d3b Author: djm@openbsd.org Date: Fri Sep 16 06:55:37 2022 +0000 upstream: correct error value OpenBSD-Commit-ID: 780efcbad76281f11f14b2a5ff04eb6db3dfdad4 commit ac1ec9545947d9f9657259f55d04cb49d3a94c8a Author: djm@openbsd.org Date: Fri Sep 16 03:33:14 2022 +0000 upstream: sftp: Be a bit more clever about completions There are commands (e.g. "get" or "put") that accept two arguments, a local path and a remote path. However, the way current completion is written doesn't take this distinction into account and always completes remote or local paths. By expanding CMD struct and "cmds" array this distinction can be reflected and with small adjustment to completer code the correct path can be completed. By Michal Privoznik, ok dtucker@ OpenBSD-Commit-ID: 1396d921c4eb1befd531f5c4a8ab47e7a74b610b commit 590db83384f9d99fc51c84505792d26d1ef60df9 Author: djm@openbsd.org Date: Fri Sep 16 03:13:34 2022 +0000 upstream: sftp: Don't attempt to complete arguments for non-existent commands If user entered a non-existent command (e.g. because they made a typo) there is no point in trying to complete its arguments. Skip calling complete_match() if that's the case. From Michal Privoznik OpenBSD-Commit-ID: cf39c811a68cde2aeb98fc85addea4000ef6b07a commit ff9809fdfd1d9a91067bb14a77d176002edb153c Author: djm@openbsd.org Date: Wed Sep 14 00:14:37 2022 +0000 upstream: sk_enroll: never drop SSH_SK_USER_VERIFICATION_REQD flag from response Now that all FIDO signing calls attempt first without PIN and then fall back to trying PIN only if that attempt fails, we can remove the hack^wtrick that removed the UV flag from the keys returned during enroll. By Corinna Vinschen OpenBSD-Commit-ID: 684517608c8491503bf80cd175425f0178d91d7f commit 940dc10729cb5a95b7ee82c10184e2b9621c8a1d Author: djm@openbsd.org Date: Wed Sep 14 00:13:13 2022 +0000 upstream: a little extra debugging OpenBSD-Commit-ID: edf1601c1d0905f6da4c713f4d9cecc7d1c0295a commit 4b5f91cb959358141181b934156513fcb8a6c1e3 Author: djm@openbsd.org Date: Wed Sep 14 00:02:03 2022 +0000 upstream: ssh-agent: attempt FIDO key signing without PIN and use the error to determine whether a PIN is required and prompt only if necessary. from Corinna Vinschen OpenBSD-Commit-ID: dd6be6a0b7148608e834ee737c3479b3270b00dd commit 113523bf0bc33600b07ebb083572c8c346b6fdf4 Author: jmc@openbsd.org Date: Sun Sep 11 06:38:11 2022 +0000 upstream: .Li -> .Vt where appropriate; from josiah frentsos, tweaked by schwarze ok schwarze OpenBSD-Commit-ID: 565046e3ce68b46c2f440a93d67c2a92726de8ed commit 86af013b56cecb5ee58ae0bd9d495cd586fc5918 Author: jsg@openbsd.org Date: Sat Sep 10 08:50:53 2022 +0000 upstream: fix repeated words ok miod@ jmc@ OpenBSD-Commit-ID: 6765daefe26a6b648cc15cadbbe337596af709b7 commit 0ba39b93b326a7d5dfab776cc9b9d326161a9b16 Author: djm@openbsd.org Date: Fri Sep 9 03:31:42 2022 +0000 upstream: notifier_complete(NULL, ...) is a noop, so no need to test that ctx!=NULL; from Corinna Vinschen OpenBSD-Commit-ID: ade2f2e9cc519d01a586800c25621d910bce384a commit be197635329feb839865fdc738e34e24afd1fca8 Author: Sam James Date: Thu Sep 8 02:49:29 2022 +0100 openbsd-compat/bsd-asprintf: add include for vsnprintf Fixes the following build failure with Clang 15 on musl: ``` bsd-asprintf.c:51:8: error: call to undeclared library function 'vsnprintf' with type 'int (char *, unsigned long, const char *, struct __va_list_tag *)'; ISO C99 and laterclang -O2 -pipe -fdiagnostics-color=always -frecord-gcc-switches -pipe -Wunknown-warning-option -Qunused-arguments -Wall -Wpointer-arith -Wuninitialized -Wsign-compare -Wformat-security -Wsizeof-pointer-memaccess -Wno-pointer-sign -Wno-unused-result -Wmisleading-indentation -Wbitwise-instead-of-logical -fno-strict-aliasing -mretpoline -ftrapv -fzero-call-used-regs=all -fno-builtin-memset -fstack-protector-strong -fPIE -I. -I. -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_DEFAULT_SOURCE -DSSHDIR=\"/etc/ssh\" -D_PATH_SSH_PROGRAM=\"/usr/bin/ssh\" -D_PATH_SSH_ASKPASS_DEFAULT=\"/usr/lib/misc/ssh-askpass\" -D_PATH_SFTP_SERVER=\"/usr/lib/misc/sftp-server\" -D_PATH_SSH_KEY_SIGN=\"/usr/lib/misc/ssh-keysign\" -D_PATH_SSH_PKCS11_HELPER=\"/usr/lib/misc/ssh-pkcs11-helper\" -D_PATH_SSH_SK_HELPER=\"/usr/lib/misc/ssh-sk-helper\" -D_PATH_SSH_PIDDIR=\"/run\" -D_PATH_PRIVSEP_CHROOT_DIR=\"/var/empty\" -DHAVE_CONFIG_H -c cipher-aes.c -o cipher-aes.o do not support implicit function declarations [-Wimplicit-function-declaration] ret = vsnprintf(string, INIT_SZ, fmt, ap2); ^ bsd-asprintf.c:51:8: note: include the header or explicitly provide a declaration for 'vsnprintf' 1 error generated. ``` commit 6cb6f660bb35f77a0456dd2581ddf39c29398a5e Author: Darren Tucker Date: Fri Sep 2 16:43:27 2022 +1000 Remove DEF_WEAK, it's already in defines.h. commit ce39e7d8b70c4726defde5d3bc4cb7d40d131153 Author: Darren Tucker Date: Fri Sep 2 14:28:14 2022 +1000 Resync arc4random with OpenBSD. This brings us up to current, including djm's random-reseeding change, as prompted by logan at cyberstorm.mu in bz#3467. It brings the platform-specific hooks from LibreSSL Portable, simplified to match our use case. ok djm@. commit beaddde26f30e2195b8aa4f3193970e140e17305 Author: Darren Tucker Date: Fri Sep 2 14:20:04 2022 +1000 Move OPENBSD ORIGINAL marker. Putting this after the copyright statement (which doesn't change) instead of before the version identifier (which does) prevents merge conflicts when resyncing changes. commit c83e467ead67a8cb48ef4bec8085d6fb880a2ff4 Author: Darren Tucker Date: Fri Sep 2 14:17:28 2022 +1000 Remove arc4random_uniform from arc4random.c This was previously moved into its own file (matching OpenBSD) which prematurely committed in commit 73541f2. commit 5f45c2395c60865e59fa44152ff1d003a128c5bc Author: djm@openbsd.org Date: Fri Sep 2 04:20:02 2022 +0000 upstream: sk-usbhid: fix key_lookup() on tokens with built-in UV explicitly test whether the token performs built-in UV (e.g. biometric tokens) and enable UV in that case. From Pedro Martelletto via GHPR#388 OpenBSD-Commit-ID: 007eb7e387d27cf3029ab06b88224e03eca62ccd commit 03277a4aa49b80af541a3e691f264c0c0d8f9cec Author: Darren Tucker Date: Wed Aug 31 20:26:30 2022 +1000 Move sftp from valgrind-2 to 3 to rebalance. commit fcf5365da69c516817321ba89c3a91df98d098df Author: djm@openbsd.org Date: Wed Aug 31 02:56:40 2022 +0000 upstream: whitespace OpenBSD-Commit-ID: c2bcbf93610d3d62ed206cdf9bf9ff98c6aaf232 commit e60136a3d7a223dd8e84ba8a6895bc3142360993 Author: Damien Miller Date: Mon Aug 29 13:27:45 2022 +1000 additional keys commit 2b02dcb505288c462d1b5dd1ac04e603d01340eb Author: Damien Miller Date: Mon Aug 29 13:23:43 2022 +1000 cross-sign allowed_signers with PGP key Provides continuity of trust from legacy PGP release key to the SSHSIG signing keys that we will use henceforth for git signing. commit 51b345f177ae981b8755f6bdf8358b1cc5e83d67 Author: Darren Tucker Date: Sat Aug 27 21:49:27 2022 +1000 Add libcrypt-devel to cygwin-release deps. Based on feedback from vinschen at redhat.com. commit 9f81736cf16dd8dda1c8942f1973a5f80b8cd78c Author: Darren Tucker Date: Sat Aug 27 09:37:40 2022 +1000 Add Windows 2022 test targets. commit 85e1a69243f12be8520438ad6a3cfdc0b7fcbb2d Author: Darren Tucker Date: Fri Aug 26 16:26:06 2022 +1000 Add cygwin-release test target. This also moves the cygwin package install from the workflow file to setup_ci.sh so that we can install different sets of Cygwin packages for different test configs. commit 92382dbe8bf9ea1225b16858f9b9b208c15c7e8d Author: djm@openbsd.org Date: Fri Aug 26 08:16:27 2022 +0000 upstream: whitespace OpenBSD-Commit-ID: a5d015efbfd228dc598ffdef612d2da3a579e5d8 commit 70a5de0a50e84d7250eb4e4537f765599f64c4af Author: djm@openbsd.org Date: Fri Aug 26 08:12:56 2022 +0000 upstream: whitespace OpenBSD-Commit-ID: d297e4387935d4aef091c5e9432578c2e513f538 commit 3a683a19fd116ea15ebf8aa13d02646cceb302a9 Author: Damien Miller Date: Fri Aug 26 14:23:55 2022 +1000 initial list of allowed signers commit 6851f4b8c3fc1b3e1114c56106e4dc31369c8513 Author: Darren Tucker Date: Fri Aug 19 17:22:18 2022 +1000 Install Cygwin packages based on OS not config. commit f96480906893ed93665df8cdf9065865c51c1475 Author: djm@openbsd.org Date: Fri Aug 19 06:07:47 2022 +0000 upstream: attemp FIDO key signing without PIN and use the error code returned to fall back only if necessary. Avoids PIN prompts for FIDO tokens that don't require them; part of GHPR#302 OpenBSD-Commit-ID: 4f752aaf9f2e7c28bcaaf3d4f8fc290131bd038e commit 5453333b5d28e313284cb9aae82899704103f98d Author: djm@openbsd.org Date: Fri Aug 19 05:53:28 2022 +0000 upstream: remove incorrect check that can break enrolling a resident key (introduced in r1.40) OpenBSD-Commit-ID: 4cab364d518470e29e624af3d3f9ffa9c92b6f01 commit ff89b1bed80721295555bd083b173247a9c0484e Author: dtucker@openbsd.org Date: Fri Aug 19 04:02:46 2022 +0000 upstream: Strictly enforce the maximum allowed SSH2 banner size in ssh-keyscan and prevent a one-byte buffer overflow. Patch from Qualys, ok djm@ OpenBSD-Commit-ID: 6ae664f9f4db6e8a0589425f74cd0bbf3aeef4e4 commit 1b470b9036639cef4f32fb303bb35ea0b711178d Author: Darren Tucker Date: Fri Aug 19 15:18:09 2022 +1000 Fix cygwin conditional steps. commit fd6ee741ab16714b7035d60aca924123ba28135a Author: Darren Tucker Date: Fri Aug 19 15:12:57 2022 +1000 Add a bit more debug output. commit a9305c4c739f4d91a3d3a92c0b6d4949404a36c5 Author: Darren Tucker Date: Fri Aug 12 15:08:47 2022 +1000 Add Cygwin (on windows-2019) test target. In addition to installing the requisite Cygwin packages, we also need to explicitly invoke "sh" for steps that run other scripts since the runner environment doesn't understand #! paths. commit 5062ad48814b06162511c4f5924a33d97b6b2566 Author: djm@openbsd.org Date: Fri Aug 19 03:06:30 2022 +0000 upstream: double free() in error path; from Eusgor via GHPR333 OpenBSD-Commit-ID: 39f35e16ba878c8d02b4d01d8826d9b321be26d4 commit 5a5c580b48fc6006bdfa731fc2f6d4945c2c0e4e Author: Darren Tucker Date: Thu Aug 18 21:36:39 2022 +1000 Check for perms to run agent-getpeereid test. Ubuntu 22.04 defaults to private home dirs which prevents "nobody" running ssh-add during the agent-getpeereid test. Check for this and add the necessary permissions. commit cd06a76b7ccc706e2bb4f1cc4aa9e9796a28a812 Author: Damien Miller Date: Wed Aug 17 16:04:16 2022 +1000 on Cygwin, prefer WinHello FIDO device If no FIDO device was explictly specified, then prefer the windows://hello FIDO device. An exception to this is when probing resident FIDO keys, in which case hardware FIDO devices are preferred. commit 47f72f534ac5cc2cd3027675a3df7b00a8f77575 Author: djm@openbsd.org Date: Wed Aug 17 06:01:57 2022 +0000 upstream: add an extra flag to sk_probe() to indicate whether we're probing for a FIDO resident key or not. Unused here, but will make like easier for portable OpenBSD-Commit-ID: 432c8ff70e270378df9dbceb9bdeaa5b43b5a832 commit edb0bcb3c79b16031dc87a8e57aecc3c4a3414f0 Author: jmc@openbsd.org Date: Tue Aug 16 20:24:08 2022 +0000 upstream: use .Cm for "sign"; from josiah frentsos OpenBSD-Commit-ID: 7f80a53d54857ac6ae49ea6ad93c5bd12231d1e4 commit cccb011e130cbbac538b1689d10e4a067298df8b Author: Corinna Vinschen Date: Thu Aug 11 20:19:35 2022 +0200 Revert "check_sk_options: add temporary WinHello workaround" Cygwin now comes with libfido2 1.11.0, so this workaround isn't required anymore. This reverts commit 242c044ab111a37aad3b0775727c36a4c5f0102c. Signed-off-by: Corinna Vinschen commit 9468cd7cf9d989dfa2ac20e2a0268ba6e93bfa5a Author: Corinna Vinschen Date: Thu Aug 11 20:18:17 2022 +0200 fido_dev_is_winhello: return 0, not "false" "false" is not used anywhere in OpenSSH, so return 0 like everywhere else. Signed-off-by: Corinna Vinschen commit 730a80609472ee0451c99482d75c9c41f3ebc42d Author: djm@openbsd.org Date: Fri Aug 12 05:20:28 2022 +0000 upstream: sftp-server: support home-directory request Add support to the sftp-server for the home-directory extension defined in draft-ietf-secsh-filexfer-extensions-00. This overlaps a bit with the existing expand-path@openssh.com, but uses a more official protocol name, and so is a bit more likely to be implemented by non-OpenSSH clients. From Mike Frysinger, ok dtucker@ OpenBSD-Commit-ID: bfc580d05cc0c817831ae7ecbac4a481c23566ab commit 5e820bf79ce3ce99ef7e98b0ab642b0a0a4f396c Author: Darren Tucker Date: Fri Aug 12 14:56:55 2022 +1000 Replace deprecated ubuntu-18.04 runners with 22.04 commit 87b0d9c1b789d3ff958ec45df2ac912e24461bae Author: Darren Tucker Date: Thu Aug 11 22:48:23 2022 +1000 Add a timegm implementation from Heimdal via Samba. Fixes build on (at least Solaris 10). commit d0c4fa58594577994921b593f10037c5282597ca Author: Darren Tucker Date: Thu Aug 11 14:23:58 2022 +1000 Rerun tests if any .github config file changes. commit 113fe6c77ab43769fc61e953d07cb619fd7ea54b Author: Darren Tucker Date: Thu Aug 11 13:33:51 2022 +1000 Skip hostbased during Valgrind tests. Valgrind doesn't let ssh exec ssh-keysign (because it's setuid) so skip it during the Valgrind based tests. See https://bugs.kde.org/show_bug.cgi?id=119404 for a discussion of this (ironically there the problematic binary was ssh(1) back when it could still be setuid). commit b98a42afb69d60891eb0488935990df6ee571c4d Author: djm@openbsd.org Date: Thu Aug 11 01:57:50 2022 +0000 upstream: add some tests for parse_absolute_time(), including cases where it is forced to the UTC timezone. bz3468 ok dtucker OpenBSD-Regress-ID: ea07ca31c2f3847a38df028ca632763ae44e8759 commit ec1ddb72a146fd66d18df9cd423517453a5d8044 Author: djm@openbsd.org Date: Thu Aug 11 01:56:51 2022 +0000 upstream: allow certificate validity intervals, sshsig verification times and authorized_keys expiry-time options to accept dates in the UTC time zone in addition to the default of interpreting them in the system time zone. YYYYMMDD and YYMMDDHHMM[SS] dates/times will be interpreted as UTC if suffixed with a 'Z' character. Also allow certificate validity intervals to be specified in raw seconds-since-epoch as hex value, e.g. -V 0x1234:0x4567890. This is intended for use by regress tests and other tools that call ssh-keygen as part of a CA workflow. bz3468 ok dtucker OpenBSD-Commit-ID: 454db1cdffa9fa346aea5211223a2ce0588dfe13 commit 4df246ec75751da7eb925e1880498300d8bda187 Author: Darren Tucker Date: Thu Aug 11 10:23:55 2022 +1000 Fix conditional for running hostbased tests. commit 2580916e48721802220c61ce9e0df1297c00bc07 Author: Damien Miller Date: Thu Aug 11 08:58:28 2022 +1000 fix SANDBOX_SECCOMP_FILTER_DEBUG commit fdbd5bf507fc271ff813714fab8a72ff2c6cb5ca Author: Darren Tucker Date: Wed Aug 10 17:35:52 2022 +1000 Test hostbased auth on github runners. commit 7e2f51940ba48a1c0fae1107801ea643fa83c971 Author: Darren Tucker Date: Wed Aug 10 17:25:24 2022 +1000 Rename our getentropy to prevent possible loops. Since arc4random seeds from getentropy, and we use OpenSSL for that if enabled, there's the possibility that if we build on a system that does not have getentropy then run on a system that does have it, then OpenSSL could end up calling our getentropy and getting stuck in a loop. Pointed out by deraadt@, ok djm@ commit 7a01f61be8d0aca0e975e7417f26371495fe7674 Author: Darren Tucker Date: Mon Aug 8 12:17:04 2022 +1000 Actually put HAVE_STDINT_H around the stdint.h. commit 73541f29f0b50480da6c20dceb7a7191bd8ea7d3 Author: Darren Tucker Date: Mon Aug 8 10:30:34 2022 +1000 Give unused param a name. Fixes builds on platforms that do have fido2 but don't have fido_dev_is_winhello. commit 2a108c0ea960381bd9b14ee0d84e818a23df4482 Author: djm@openbsd.org Date: Fri Aug 5 05:01:40 2022 +0000 upstream: don't prompt for FIDO passphrase before attempting to enroll the credential, just let the enroll operating fail and we'll attempt to get a PIN anyway. Might avoid some unneccessary PIN prompts. Part of GHPR#302 from Corinna Vinschen; ok dtucker@ OpenBSD-Commit-ID: bd5342ffc353ee37d39617906867c305564d1ce2 commit 2886975c0ad9244e60dc5e4be34fde3aa573a4b5 Author: Corinna Vinschen Date: Fri Feb 11 14:33:41 2022 +0100 sk_sign: set FIDO2 uv attribute explicitely for WinHello WinHello via libfido2 performs user verification by default. However, if we stick to that, there's no way to differentiate between keys created with or without "-O verify-required". Set FIDO2 uv attribute explicitely to FIDO_OPT_FALSE, then check if user verification has been requested. Signed-off-by: Corinna Vinschen commit 242c044ab111a37aad3b0775727c36a4c5f0102c Author: Corinna Vinschen Date: Tue Feb 15 11:28:08 2022 +0100 check_sk_options: add temporary WinHello workaround Up to libfido 1.10.0, WinHello advertises "clientPin" rather than "uv" capability. This is fixed in 1.11.0. For the time being, workaround it here. Signed-off-by: Corinna Vinschen commit 78774c08cc4b4997382975b0f414a86e06b6780c Author: Corinna Vinschen Date: Thu Feb 10 18:19:29 2022 +0100 compat code for fido_dev_is_winhello() Signed-off-by: Corinna Vinschen commit 3d3a932a019aedfb891e0779bb4990cd5008a390 Author: Darren Tucker Date: Fri Aug 5 13:12:27 2022 +1000 Factor out getrnd() and rename to getentropy(). Factor out the arc4random seeding into its own file and change the interface to match getentropy. Use native getentropy if available. This will make it easier to resync OpenBSD changes to arc4random. Prompted by bz#3467, ok djm@. commit 9385d277b787403be9dfcb229cf372202496d2f3 Author: Darren Tucker Date: Thu Aug 4 18:55:48 2022 +1000 Include CHANNEL and FIDO2 libs in configure output commit 141535b904b6fba01724444f38193a8599201f82 Author: djm@openbsd.org Date: Mon Aug 1 11:09:26 2022 +0000 upstream: avoid double-free in error path introduced in r1.70; report and fix based on GHPR#332 by v-rzh ok dtucker@ OpenBSD-Commit-ID: 3d21aa127b1f37cfc5bdc21461db369a663a951f commit dba7099ffcba3ca07b3946f017ba6a4c3158d9b1 Author: Darren Tucker Date: Wed Jul 27 18:40:12 2022 +1000 Remove deprecated MacOS 10.15 runners. commit 722a56439aa5972c830e4a9a724cf52aff4a950a Author: Darren Tucker Date: Wed Jul 27 18:31:14 2022 +1000 Move stale-configure check as early as possible. We added a check in Makefile to catch the case where configure needs to be rebuilt, however this did not happen until a build was attempted in which case all of the work done by configure was wasted. Move this check to the start of configure to catch it as early as possible. ok djm@ commit 099d6b56288b421ba38531d26dc1bd6bb685e311 Author: Darren Tucker Date: Fri Jul 22 10:47:19 2022 +1000 Move libcrypto into CHANNELLIBS. This will result in sftp, sftp-server and scp no longer being linked against libcrypto. ok djm@ commit 1bdf86725b77733bb5f17c54888b88a10b2f6538 Author: Darren Tucker Date: Fri Jul 22 10:45:47 2022 +1000 Remove seed_rng calls from scp, sftp, sftp-server. These binaries don't use OpenSSL's random functions. The next step will be to stop linking them against libcrypto. ok djm@ commit d73f77b8cb9b422f1ac4facee7890aa10ff2bc21 Author: Darren Tucker Date: Fri Jul 22 09:51:51 2022 +1000 Group libcrypto and PRNGD checks together. They're related more than the libcrypt or libiaf checks which are currently between them. ok djm@ commit f117e372b3f42f2fbdb0a578d063b2609ab58e1f Author: Darren Tucker Date: Fri Jul 22 09:24:45 2022 +1000 Do not link scp, sftp and sftp-server w/ zlib. Some of our binaries (eg sftp, sftp-server, scp) do not interact with the channels code and thus do use libraries such as zlib and libcrypto although they are linked with them. This adds a CHANNELLIBS and starts by moving zlib into it, which means the aformentioned binaries are no longer linked against zlib. ok djm@ commit 800c2483e68db38bd1566ff69677124be974aceb Author: Darren Tucker Date: Mon Jul 25 21:49:04 2022 +1000 Remove workarounds for OpenSSL missing AES-CTR. We have some compatibility hacks that were added to support OpenSSL versions that do not support AES CTR mode. Since that time, however, the minimum OpenSSL version that we support has moved to 1.0.1 which *does* have CTR, so this is no longer needed. ok djm@ commit b7c56b65c12f51fe0dbae798d19c8f58224a5d95 Author: Darren Tucker Date: Mon Jul 25 21:43:00 2022 +1000 Remove workarounds for OpenSSL missing AES-GCM. We have some compatibility hacks that were added to support OpenSSL versions that do not support AES GCM mode. Since that time, however, the minimum OpenSSL version that we support has moved to 1.0.1 which *does* have GCM, so this is no longer needed. ok djm@ commit 5a4a9f7a968fbf92cc1eac519c65638e79ae9f1f Author: dtucker@openbsd.org Date: Mon Jul 25 07:12:45 2022 +0000 upstream: Restore missing "!" in TEST_SSH_ELAPSED_TIMES test. OpenBSD-Regress-ID: 38783f9676ec348c5a792caecee9a16e354b37b0 commit 0ff886be132299386cc29d87c2aa16ff68a1aa08 Author: dtucker@openbsd.org Date: Sun Jul 24 23:29:10 2022 +0000 upstream: Test TEST_SSH_ELAPSED_TIMES for empty string not executable. No-op on most platforms but should prevent warnings in -portable on systems that don't have 'date %s'. OpenBSD-Regress-ID: e39d79867b8065e33d0c5926fa1a31f85659d2a4 commit f69319ad8ad1dd50f90bbcf5912e11cc8ed3e037 Author: Darren Tucker Date: Sat Jul 23 14:38:22 2022 +1000 Convert "have_prog" function into "which". "which" and its behaviour is not standardized, so convert the existing have_prog function into "which" so we can rely on it being available and what its semantics are. Add a have_prog wrapper that maintains the existing behaviour. commit ea7ecc2c3ae39fdf5c6ad97b7bc0b47a98847f43 Author: Darren Tucker Date: Sat Jul 23 14:36:38 2022 +1000 Skip scp3 test if there's no scp on remote path. scp -3 ends up using the scp that's in the remote path and will fail if one is not available. Based on a patch from rapier at psc.edu. commit c46f6fed419167c1671e4227459e108036c760f8 Author: Damien Miller Date: Wed Jul 20 13:39:14 2022 +1000 crank SSH_SK_VERSION_MAJOR in sk-dummy.so commit f208e3b9ffb5ee76cf9c95df7ff967adc7f51c7d Author: djm@openbsd.org Date: Wed Jul 20 03:33:22 2022 +0000 upstream: ssh-keygen: fix touch prompt, pin retries; part of GHPR329 from Pedro Martelletto OpenBSD-Commit-ID: 75d1005bd2ef8f29fa834c90d2684e73556fffe8 commit 8638a2ce7e90c8a51d9af3143404282126c524f8 Author: djm@openbsd.org Date: Wed Jul 20 03:31:42 2022 +0000 upstream: sk-usbhid: preserve error code returned by key_lookup() it conveys useful information, such as the supplied pin being wrong. Part of GHPR329 from Pedro Martelletto OpenBSD-Commit-ID: c0647eb9290f793add363d81378439b273756c1b commit 9ab929ca2d820520327b41929372bcb9e261534c Author: djm@openbsd.org Date: Wed Jul 20 03:29:14 2022 +0000 upstream: when enrolling a resident key on a security token, check if a credential with matching application and user ID strings already exists. if so, prompt the user for confirmation before overwriting the credential. patch from Pedro Martelletto via GHPR329 NB. cranks SSH_SK_VERSION_MAJOR, so any third-party FIDO middleware implementations will need to adjust OpenBSD-Commit-ID: e45e9f1bf2b2f32d9850669e7a8dbd64acc5fca4 commit 5bcfc788b38d5b64e4c347bdc04bd9a01bbc36da Author: djm@openbsd.org Date: Wed Jul 20 03:13:04 2022 +0000 upstream: pull passphrase reading and confirmation into a separate function so it can be used for FIDO2 PINs; no functional change OpenBSD-Commit-ID: bf34f76b8283cc1d3f54633e0d4f13613d87bb2f commit eb679e2959bdb15454eb94751930eb4c9110da94 Author: Darren Tucker Date: Fri Jul 15 21:31:48 2022 +1000 Move vmshutdown to first step. If a previous run on a physical runner has failed to clean up, the next run will fail because it'll try to check out the code to a broken directory mount. Make cleanup the first step. commit 46b91b70ff3cb9c147e2875ef5dc609fd64c0c96 Author: Darren Tucker Date: Fri Jul 15 20:25:27 2022 +1000 Rename bbone test target to ARM. commit 751d22cdeffed9fe921db78eedc32a29f9e80510 Author: Darren Tucker Date: Fri Jul 15 13:37:29 2022 +1000 Add AUDIT_ARCH_PPC to supported seccomp arches. Patch from dries.deschout at dodeco.eu. commit a061792a6e8d235fc40a9b5d4c22a1762bb75a7b Author: Darren Tucker Date: Thu Jul 14 19:20:24 2022 +1000 Remove unintended changes. I inadvertently included a couple of local changes with the OpenSSL 3.0.4 change. Revert, anything that should be there will be committed separately. commit 527cb43fa1b4e55df661feabbac51b8e608b6519 Author: Darren Tucker Date: Thu Jul 14 11:22:08 2022 +1000 Return ERANGE from getcwd() if buffer size is 1. If getcwd() is supplied a buffer size of exactly 1 and a path of "/", it could result in a nul byte being written out of array bounds. POSIX says it should return ERANGE if the path will not fit in the available buffer (with terminating nul). 1 byte cannot fit any possible path with its nul, so immediately return ERANGE in that case. OpenSSH never uses getcwd() with this buffer size, and all current (and even quite old) platforms that we are currently known to work on have a native getcwd() so this code is not used on those anyway. Reported by Qualys, ok djm@ commit 36857fefd8849c4b0e877cfd9d1eb22f79b76650 Author: Darren Tucker Date: Thu Jul 14 10:02:35 2022 +1000 Split README.platform into its own line. README.platform has general platform-specific information, having it following text about FIDO2 on the same line could imply that it only has information about FIDO2. commit 00a496c6c14f2d41f2a9365714d494dd5f3aac9f Author: Darren Tucker Date: Thu Jul 14 09:56:01 2022 +1000 Clarify README.md text. Clarify the text about the implications of building without OpenSSL, and prefix the "configure --help" example command with a "./" so it's likely to work as-is in more shells. From bz#3461. commit f40b52f21fbc52eb513279168a49d3285c65256c Author: Darren Tucker Date: Tue Jul 12 19:48:44 2022 +1000 Remove special casing of crypt(). Configure goes to some lengths to pick crypt() from either libcrypt or OpenSSL's libcrypto because they can more or less featureful (eg supporting md5-style passwords). OpenSSL removed its crypt() interface in 2002: https://github.com/openssl/openssl/commit/69deec58 so these hijinks should no longer be necessary. This also only links sshd with libcrypt which is the only thing that needs it. ok djm@ commit 76f4e48631d7b09fb243b47d7b393d100d3741b7 Author: Darren Tucker Date: Wed Jul 13 13:17:47 2022 +1000 Only refuse to use OpenSSL 3.0.4 on x86_64. The potential RCE only impacts x86_64, so only refuse to use it if we're targetting a potentially impacted architecture. ok djm@ commit e75bbc1d88491fa85e61b2cc8783d4bbd00cd131 Author: Darren Tucker Date: Tue Jul 12 14:37:15 2022 +1000 Capture stderr output from configure. commit d9eaea4bea6271bcee6a2b9428f1271faf2d033b Author: Darren Tucker Date: Tue Jul 12 12:54:49 2022 +1000 Refuse to use OpenSSL 3.0.4 due to potential RCE. OpenSSL has a potential RCE in its RSA implementation (CVE-2022-2274) so refuse to use that specific version. commit fb2f3a61bf3d28fff285524535f7ffcd177c9235 Author: Darren Tucker Date: Tue Jul 12 12:54:24 2022 +1000 Move unset to before we set anything. commit c483a5c0fb8e8b8915fad85c5f6113386a4341ca Author: Darren Tucker Date: Wed Jul 6 11:52:54 2022 +1000 Test against openssl-3.0.5. commit 669a56bcfe73f8b985f2bba476ba834d55253acf Author: Darren Tucker Date: Tue Jul 5 18:35:53 2022 +1000 Update sanitizer test targets: - remove clang-sanitize-memory for now. It takes so long that the test times out. - add gcc sanitize-address and sanitize-undefined test targets. commit 48cc68b69118b3ce8d07fd4f82e00d58667d5379 Author: Darren Tucker Date: Tue Jul 5 16:23:28 2022 +1000 Add GCC address sanitizer build/test. commit 55c60bdd39b82457e92efa77da8d16cfa6a49391 Author: Darren Tucker Date: Tue Jul 5 12:02:33 2022 +1000 Move sanitizer logs into regress for collection. commit 35ef2b3b6ef198f8574904a45780487ec2f17858 Author: dtucker@openbsd.org Date: Mon Jul 4 09:10:31 2022 +0000 upstream: Add TEST_REGRESS_CACHE_DIR. If set, it is used to cache regress test names that have succeeded and skip those on a re-run. OpenBSD-Regress-ID: a7570dd29a58df59f2cca647c3c2ec989b49f247 commit 7394ed80c4de8b228a43c8956cf2fa1b9c6b2622 Author: Darren Tucker Date: Sun Jul 3 21:46:44 2022 +1000 Add clang sanitizer tests. commit bfce0e66b6017a9bfab450b9dc7d4b16f90de817 Author: Darren Tucker Date: Sun Jul 3 18:14:09 2022 +1000 Skip all rlimit tests when sandboxing disabled. The rlimit tests can hang when being run with some compiler sanitizers so skip all of them if sandbox=no. commit 6208d611520f9ea94d5369f9da404b709930029d Author: Darren Tucker Date: Sun Jul 3 17:54:49 2022 +1000 Move checks for pollfd.fd and nfds_t. Move the checks for struct pollfd.fd and nfds_t to before the sandboxing checks. This groups all the sandbox checks together so we can skip them all when sandboxing is disabled. commit 322964f8f2e9c321e77ebae1e4d2cd0ccc5c5a0b Author: dtucker@openbsd.org Date: Fri Jul 1 05:08:23 2022 +0000 upstream: Remove leftover line. Remove extra line leftover from merge conflict. ok djm@ OpenBSD-Commit-ID: 460e2290875d7ae64971a7e669c244b1d1c0ae2e commit 7ec81daad0e03a64e8d91c5590960c48c1a899a3 Author: djm@openbsd.org Date: Fri Jul 1 04:45:50 2022 +0000 upstream: use consistent field names (s/char/byte) in format description OpenBSD-Commit-ID: 3de33572733ee7fcfd7db33d37db23d2280254f0 commit 32e82a392d9f263485effdd606ff5862d289a4a0 Author: Darren Tucker Date: Fri Jul 1 13:55:19 2022 +1000 Skip select+rlimit check if sandboxing is disabled It's not needed in that case, and the test can fail when being built with some compiler memory sanitizer flags. bz#3441 commit 4be7184ebe2a2ccef175983517a35ee06766e1b4 Author: djm@openbsd.org Date: Fri Jul 1 03:52:57 2022 +0000 upstream: bump up loglevel from debug to info when unable to open authorized keys/principals file for errno != ENOENT; bz2042 ok dtucker OpenBSD-Commit-ID: e79aa550d91ade6a80f081bda689da24c086d66b commit 6c31ba10e97b6953c4f325f526f3e846dfea647a Author: dtucker@openbsd.org Date: Fri Jul 1 03:39:44 2022 +0000 upstream: Don't leak the strings allocated by order_hostkeyalgs() and list_hostkey_types() that are passed to compat_pkalg_proposal(). Part of github PR#324 from ZoltanFridrich, ok djm@ This is a roll-forward of the previous rollback now that the required changes in compat.c have been done. OpenBSD-Commit-ID: c7cd93730b3b9f53cdad3ae32462922834ef73eb commit 486c4dc3b83b4b67d663fb0fa62bc24138ec3946 Author: dtucker@openbsd.org Date: Fri Jul 1 03:35:45 2022 +0000 upstream: Always return allocated strings from the kex filtering so that we can free them later. Fix one leak in compat_kex_proposal. Based on github PR#324 from ZoltanFridrich with some simplications by me. ok djm@ OpenBSD-Commit-ID: 9171616da3307612d0ede086fd511142f91246e4 commit 96faa0de6c673a2ce84736eba37fc9fb723d9e5c Author: djm@openbsd.org Date: Fri Jul 1 00:36:30 2022 +0000 upstream: ignore SIGPIPE earlier in main(), specifically before muxclient() which performs operations that could cause one; Reported by Noam Lewis via bz3454, ok dtucker@ OpenBSD-Commit-ID: 63d8e13276869eebac6d7a05d5a96307f9026e47 commit 33efac790f6b09d54894ba6c3e17dfb08b6fc7e1 Author: jmc@openbsd.org Date: Tue Jun 28 06:09:14 2022 +0000 upstream: reflect the update to -D arg name in usage(); OpenBSD-Commit-ID: abdcde4f92b1ef094ae44210ee99d3b0155aad9c commit c71a1442d02f0a3586109dfe2cb366de36dee08e Author: Darren Tucker Date: Wed Jun 29 18:28:47 2022 +1000 Update OpenSSL tests to the most recent releases. commit 2a822f29300b2de7335fbff65f0b187a0c582304 Author: djm@openbsd.org Date: Mon Jun 27 21:41:55 2022 +0000 upstream: allow arguments to sftp -D option, e.g. sftp -D "/usr/libexec/sftp-server -el debug3" ok markus@ OpenBSD-Commit-ID: 5a002b9f3a7aef2731fc0ffa9c921cf15f38ecce commit 2369a2810187e08f2af5d58b343956062fb96ee8 Author: dtucker@openbsd.org Date: Fri Jun 24 10:45:06 2022 +0000 upstream: Roll back previous KEX changes as they aren't safe until compat_pkalg_proposal and friends always allocate their returned strings. Reported by Qualys. OpenBSD-Commit-ID: 1c7a88a0d5033f42f88ab9bec58ef1cf72c81ad0 commit 646686136c34c2dbf6a01296dfaa9ebee029386d Author: dtucker@openbsd.org Date: Fri Jun 24 04:37:00 2022 +0000 upstream: Don't leak the strings allocated by order_hostkeyalgs() and list_hostkey_types() that are passed to compat_pkalg_proposal(). Part of github PR#324 from ZoltanFridrich, ok djm@ OpenBSD-Commit-ID: b2f6e5f60f2bba293b831654328a8a0035ef4a1b commit 193c6d8d905dde836b628fc07a7b9cf2d347e2a3 Author: Darren Tucker Date: Sat Jun 25 12:16:15 2022 +1000 Zero out LIBFIDO2 when SK support not usable. Prevents us from trying to link them into ssh-sk-helper and failing to build. commit 40f5d849d25c60b4ae21261e78484d435f5cfd51 Author: Darren Tucker Date: Sat Jun 25 11:47:28 2022 +1000 Disable SK support if FIDO libs not found. commit 5fd922ade1b25880fe8a8249f5c0385e413108f9 Author: Damien Miller Date: Fri Jun 24 14:43:54 2022 +1000 fix broken case statement in previous commit f51423bdaf0008d46b6af082bcfd7a22a87375f0 Author: Damien Miller Date: Fri Jun 24 14:40:42 2022 +1000 request 1.1x API compatibility for OpenSSL >=3.x idea/patch from Pedro Martelletto via GHPR#322; ok dtucker@ commit 455cee8d6c2e4c48c5af9faead3599c49948411e Author: djm@openbsd.org Date: Fri Jun 24 04:27:14 2022 +0000 upstream: make it clear that RekeyLimit applies to both transmitted and received data. GHPR#328 from Jan Pazdziora OpenBSD-Commit-ID: d180a905fec9ff418a75c07bb96ea41c9308c3f9 commit 17904f05802988d0bb9ed3c8d1d37411e8f459c3 Author: tobhe@openbsd.org Date: Tue Jun 21 14:52:13 2022 +0000 upstream: Make sure not to fclose() the same fd twice in case of an error. ok dtucker@ OpenBSD-Commit-ID: e384c4e05d5521e7866b3d53ca59acd2a86eef99 commit f29d6cf98c25bf044079032d22c1a57c63ab9d8e Author: dtucker@openbsd.org Date: Sat Jun 18 02:17:16 2022 +0000 upstream: Don't attempt to fprintf a null identity comment. From Martin Vahlensieck via tech@. OpenBSD-Commit-ID: 4c54d20a8e8e4e9912c38a7b4ef5bfc5ca2e05c2 commit ad1762173bb38716a106e8979806149fd0f2753e Author: dtucker@openbsd.org Date: Fri Jun 17 01:00:03 2022 +0000 upstream: Log an error if pipe() fails while accepting a connection. bz#3447, from vincent-openssh at vinc17 net, ok djm@ OpenBSD-Commit-ID: 9d59f19872b94900a5c79da2d57850241ac5df94 commit 9c59e7486cc8691401228b43b96a3edbb06e0412 Author: Damien Miller Date: Fri Jun 24 14:20:43 2022 +1000 automatically enable built-in FIDO support If libfido2 is found and usable, then enable the built-in security key support unless --without-security-key-builtin was requested. ok dtucker@ commit 7d25b37fb2a5ff4dadabcbdac6087a97479434f5 Author: Damien Miller Date: Fri Jun 24 13:46:39 2022 +1000 fix possible NULL deref when built without FIDO Analysis/fix from kircher in bz3443; ok dtucker@ commit f5ba85daddfc2da6a8dab6038269e02c0695be44 Author: djm@openbsd.org Date: Wed Jun 15 16:08:25 2022 +0000 upstream: make sure that UseDNS hostname lookup happens in the monitor and not in the pledge(2)'d unprivileged process; fixes regression caused by recent refactoring spotted by henning@ OpenBSD-Commit-ID: a089870b95101cd8881a2dff65b2f1627d13e88d commit acb2059febaddd71ee06c2ebf63dcf211d9ab9f2 Author: djm@openbsd.org Date: Fri Jun 3 04:47:21 2022 +0000 upstream: move auth_openprincipals() and auth_openkeyfile() over to auth2-pubkeyfile.c too; they make more sense there. OpenBSD-Commit-ID: 9970d99f900e1117fdaab13e9e910a621b7c60ee commit 3d9b0845f34510111cc693bb99a667662ca50cd8 Author: djm@openbsd.org Date: Fri Jun 3 04:31:54 2022 +0000 upstream: test setenv in both client and server, test first-match-wins too OpenBSD-Regress-ID: 4c8804f9db38a02db480b9923317457b377fe34b commit 22e1a3a71ad6d108ff0c5f07f93c3fcbd30f8b40 Author: djm@openbsd.org Date: Fri Jun 3 04:30:46 2022 +0000 upstream: Make SetEnv directives first-match-wins in both sshd_config and sshd_config; previously if the same name was reused then the last would win (which is the opposite to how the config is supposed to work). While there, make the ssh_config parsing more like sshd_config. bz3438, ok dtucker OpenBSD-Commit-ID: 797909c1e0262c0d00e09280459d7ab00f18273b commit 38ed6c57e9e592c08e020fa6e82b45b4e1040970 Author: dtucker@openbsd.org Date: Fri Jun 3 04:00:15 2022 +0000 upstream: Add missing *-sk types to ssh-keyscan manpage. From skazi0 via github PR#294. OpenBSD-Commit-ID: fda2c869cdb871f3c90a89fb3f985370bb5d25c0 commit ea97ec98c41ec2b755dfab459347db674ff9a5de Author: dtucker@openbsd.org Date: Fri Jun 3 03:21:09 2022 +0000 upstream: Add period at end of "not known by any other names" message. github PR#320 from jschauma, ok djm@ OpenBSD-Commit-ID: bd60809803c4bfd3ebb7c5c4d918b10e275266f2 commit 88e376fcd67478ad1660d94bc73ab348ac9f4527 Author: dtucker@openbsd.org Date: Fri Jun 3 03:17:42 2022 +0000 upstream: ssh-keygen -A: do not generate DSA keys by default. Based on github PR#303 from jsegitz with man page text from jmc@, ok markus@ djm@ OpenBSD-Commit-ID: 5c4c57bdd7063ff03381cfb6696659dd3f9f5b9f commit 6b3fb624675082a1e5aa615d1b8479873d8b5731 Author: naddy@openbsd.org Date: Tue May 31 14:05:12 2022 +0000 upstream: ssh-keygen: implement "verify-required" certificate option. This was already documented when support for user-verified FIDO keys was added, but the ssh-keygen(1) code was missing. ok djm@ OpenBSD-Commit-ID: f660f973391b593fea4b7b25913c9a15c3eb8a06 commit b7f86ffc301be105bba9a3e0618b6fab3ae379bd Author: jmc@openbsd.org Date: Sat May 28 05:57:56 2022 +0000 upstream: keywords ref ssh_config.5; from caspar schutijser OpenBSD-Commit-ID: f146a19d7d5c9374c3b9c520da43b2732d7d1a4e commit dc7bc52372f2744fa39191577be5306ee57aacd4 Author: Damien Miller Date: Mon May 30 09:29:09 2022 +1000 fix some bugs in the fuzzer commit 1781f507c113667613351c19898efaf1e311a865 Author: Darren Tucker Date: Fri May 27 18:19:48 2022 +1000 Test against OpenSSL 1.1.1o and 3.0.3. commit c53906e0c59e569691b4095d3e8db79cf78fa058 Author: Darren Tucker Date: Fri May 27 18:18:31 2022 +1000 Test against LibreSSL 3.5.3. commit 9b3ad432ad2f19319bcc089370e356c6315d682f Author: Damien Miller Date: Fri May 27 17:00:43 2022 +1000 fuzzer for authorized_keys parsing mostly redundant to authopt_fuzz, but it's sensitive code so IMO it makes sense to test this layer too commit c83d8c4d6f3ccceef84d46de107f6b71cda06359 Author: djm@openbsd.org Date: Fri May 27 05:02:46 2022 +0000 upstream: split the low-level file handling functions out from auth2-pubkey.c Put them in a new auth2-pubkeyfile.c to make it easier to refer to them (e.g. in unit/fuzz tests) without having to refer to everything else pubkey auth brings in. ok dtucker@ OpenBSD-Commit-ID: 3fdca2c61ad97dc1b8d4a7346816f83dc4ce2217 commit 3b0b142d2a0767d8cd838e2f3aefde8a0aaa41e1 Author: djm@openbsd.org Date: Fri May 27 05:01:25 2022 +0000 upstream: refactor authorized_keys/principals handling remove "struct ssh *" from arguments - this was only used to pass the remote host/address. These can be passed in instead and the resulting code is less tightly coupled to ssh_api.[ch] ok dtucker@ OpenBSD-Commit-ID: 9d4373d013edc4cc4b5c21a599e1837ac31dda0d commit 2c334fd36f80cb91cc42e4b978b10aa35e0df236 Author: dtucker@openbsd.org Date: Fri May 27 04:29:40 2022 +0000 upstream: f sshpkt functions fail, then password is not cleared with freezero. Unconditionally call freezero to guarantee that password is removed from RAM. From tobias@ and c3h2_ctf via github PR#286, ok djm@ OpenBSD-Commit-ID: 6b093619c9515328e25b0f8093779c52402c89cd commit 5d3a77f4c5ae774c6796387266503f52c7cdc7c2 Author: dtucker@openbsd.org Date: Fri May 27 04:27:49 2022 +0000 upstream: Avoid kill with -1 argument. The out_ctx label can be reached before fork has been called. If this happens, then kill -1 would be called, sending SIGTERM to all processes reachable by the current process. From tobias@ and c3h2_ctf via github PR#286, ok djm@ OpenBSD-Commit-ID: 6277af1207d81202f5daffdccfeeaed4c763b1a8 commit 533b31cd08e4b97f455466f91c36915e2924c15a Author: dtucker@openbsd.org Date: Fri May 27 04:13:24 2022 +0000 upstream: Note that ProxyJump also accepts the same tokens as ProxyCommand. From pallxk via github PR#305. OpenBSD-Commit-ID: 7115ac351b129205f1f1ffa6bbfd62abd76be7c5 commit 9d8c80f8a304babe61ca28f2e3fb5eb6dc9c39bf Author: djm@openbsd.org Date: Wed May 25 06:03:44 2022 +0000 upstream: revert previous; it was broken (spotted by Theo) OpenBSD-Commit-ID: 457c79afaca2f89ec2606405c1059b98b30d8b0d commit 9e0d02ef7ce88b67643bfb1c2272c9f5f04cc680 Author: djm@openbsd.org Date: Wed May 25 00:31:13 2022 +0000 upstream: make SSHBUF_DBG/SSHBUF_TELL (off by default and only enabled via #define) dump to stderr rather than stdout OpenBSD-Commit-ID: 10298513ee32db8390aecb0397d782d68cb14318 commit 2487163630f28be28b7e2396b4bd6511b98f1d3e Author: Tim Rice Date: Tue May 24 10:21:25 2022 -0700 configure.ac: Add missing AC_DEFINE for caph_cache_tzdata test causing HAVE_CAPH_CACHE_TZDATA to be missing from config.h.in. Spotted by Bryan Drewery commit bedb93415b60db3dfd704a3d525e82adb14a2481 Author: djm@openbsd.org Date: Sun May 15 23:48:07 2022 +0000 upstream: regress test for in-place transfers and clobbering larger files with smaller ones; would have caught last regression in scp(1) OpenBSD-Regress-ID: 19de4e88dd3a4f7e5c1618c9be3c32415bd93bc2 commit b4f0d719c2548cb74da509fb65f384dada4ebd37 Author: anton@openbsd.org Date: Fri Apr 22 05:08:43 2022 +0000 upstream: Only run agent-ptrace.sh if gdb is available as all architectures do not ship with gdb. OpenBSD-Regress-ID: ec53e928803e6b87f9ac142d38888ca79a45348d commit 9b73345f80255a7f3048026462f2c0c6a241eeac Author: djm@openbsd.org Date: Sun May 15 23:47:21 2022 +0000 upstream: fix in-place copies; r1.163 incorrectly skipped truncation in all cases, not just at the start of a transfer. This could cause overwrites of larger files to leave junk at the end. Spotted by tb@ OpenBSD-Commit-ID: b189f19cd68119548c8e24e39c79f61e115bf92c commit 56a0697fe079ff3e1ba30a2d5c26b5e45f7b71f8 Author: djm@openbsd.org Date: Fri May 13 06:31:50 2022 +0000 upstream: arrange for scp, when in sftp mode, to not ftruncate(3) files early previous behavious of unconditionally truncating the destination file would cause "scp ~/foo localhost:" and "scp localhost:foo ~/" to delete all the contents of their destination. spotted by solene@ sthen@, also bz3431; ok dtucker@ OpenBSD-Commit-ID: ca39fdd39e0ec1466b9666f15cbcfddea6aaa179 commit fbcef70c2832712f027bccea1aa9bc4b4103da93 Author: dtucker@openbsd.org Date: Mon May 9 08:25:27 2022 +0000 upstream: Remove errant apostrophe. From haruyama at queen-ml org. OpenBSD-Commit-ID: dc6b294567cb84b384ad6ced9ca469f2bbf0bd10 commit 0086a286ea6bbd11ca9b664ac3bb12b27443d6eb Author: djm@openbsd.org Date: Mon May 9 03:09:53 2022 +0000 upstream: Allow existing -U (use agent) flag to work with "-Y sign" operations, where it will be interpreted to require that the private keys is hosted in an agent; bz3429, suggested by Adam Szkoda; ok dtucker@ OpenBSD-Commit-ID: a7bc69873b99c32c42c7628ed9ea91565ba08c2f commit cb010744cc98f651b1029bb09efa986eb54e4ccf Author: djm@openbsd.org Date: Sun May 8 22:58:35 2022 +0000 upstream: improve error message when 'ssh-keygen -Y sign' is unable to load a private key; bz3429, reported by Adam Szkoda ok dtucker@ OpenBSD-Commit-ID: bb57b285e67bea536ef81b1055467be2fc380e74 commit aa61fc82c63d309a90c22ca74fb1da6c6f4372fd Author: Tobias Heider Date: Mon May 9 02:00:01 2022 +0200 Remove duplicate bcrypt_pbkdf.o from Makefile bcrypt_pbkdf.o is duplicated in the openbsd-compat Makefile's object file list. commit deb506d00da8d11fb04c1e7b9b1e1cc379c1705c Author: djm@openbsd.org Date: Sun May 8 22:32:36 2022 +0000 upstream: When performing operations that glob(3) a remote path, ensure that the implicit working directory used to construct that path escapes glob(3) characters. This prevents glob characters from being processed in places they shouldn't, e.g. "cd /tmp/a*/", "get *.txt" should have the get operation treat the path "/tmp/a*" literally and not attempt to expand it. Reported by Lusia Kundel; ok markus@ OpenBSD-Commit-ID: 4f647f58482cbad3d58b1eab7f6a1691433deeef commit f38cf74f20b5da113cfa823afd5bfb5c6ba65f3d Author: Darren Tucker Date: Fri May 6 14:50:18 2022 +1000 Also retest OpenBSD upstream on .yml changes. commit f87a132800ba3710ab130d703448a31ef1128d77 Author: Darren Tucker Date: Fri May 6 14:46:09 2022 +1000 Note that, for now, we need variadic macros. commit 217b518e0f7c52c4b909e935141a55344c61e644 Author: Darren Tucker Date: Fri May 6 14:39:34 2022 +1000 Add ubsan minimal testcase on OpenBSD. As suggested by djm@. commit 457dce2cfef6a48f5442591cd8b21c7e8cba13f8 Author: djm@openbsd.org Date: Thu May 5 01:04:14 2022 +0000 upstream: sshkey_unshield_private() contains a exact duplicate of the code in private2_check_padding(). Pull private2_check_padding() up so the code can be reused. From Martin Vahlensieck, ok deraadt@ OpenBSD-Commit-ID: 876884c3f0e62e8fd8d1594bab06900f971c9c85 commit 0e44db4d9cb313e68a59a44d27884af66c02356e Author: djm@openbsd.org Date: Thu May 5 00:56:58 2022 +0000 upstream: channel_new no longer frees remote_name. So update the comment accordingly. As remote_name is not modified, it can be const as well. From Martin Vahlensieck OpenBSD-Commit-ID: e4e10dc8dc9f40c166ea5a8e991942bedc75a76a commit 37b62fd5caf19c85a48241535277cefff65adace Author: djm@openbsd.org Date: Thu May 5 00:55:11 2022 +0000 upstream: mux.c: mark argument as const; from Martin Vahlensieck OpenBSD-Commit-ID: 69a1a93a55986c7c2ad9f733c093b46a47184341 commit f4e67c0ad259b4cf10177277a5827fa5545bac53 Author: markus@openbsd.org Date: Wed May 4 07:31:22 2022 +0000 upstream: make sure stdout is non-blocking; ok djm@ OpenBSD-Commit-ID: 64940fffbd1b882eda2d7c8c7a43c79368309c0d commit e5c036d2092c00bef395e9161dc5ce42d4be9565 Author: florian@openbsd.org Date: Tue May 3 07:42:27 2022 +0000 upstream: Add FIDO AUTHENTICATOR section and explain a bit how FIDO works. The wording came mostly from the 8.2 OpenSSH release notes, addapted to fit the man page. Then move the -O bits into the new section as is already done for CERTIFICATES and MODULI GENERATION. Finally we can explain the trade-offs of resident keys. While here, consistently refer to the FIDO thingies as "FIDO authenticators", not "FIDO tokens". input & OK jmc, naddy OpenBSD-Commit-ID: dd98748d7644df048f78dcf793b3b63db9ab1d25 commit 575771bf79bef7127be6aaccddc46031ea15529e Author: jmc@openbsd.org Date: Mon May 2 05:40:37 2022 +0000 upstream: remove an obsolete rsa1 format example from an example; from megan batty ok djm OpenBSD-Commit-ID: db2c89879c29bf083df996bd830abfb1e70d62bf commit 0bc6b4c8f04e292577bdb44d5dc6b630d3448087 Author: djm@openbsd.org Date: Sun May 1 23:20:30 2022 +0000 upstream: fix some integer overflows in sieve_large() that show up when trying to generate modp groups > 16k bits. Reported via GHPR#306 by Bertram Felgenhauer, but fixed in a different way. feedback/ok tb@ OpenBSD-Commit-ID: 81cbc6dd3a21c57bd6fadea10e44afe37bca558e commit a45615cb172bc827e21ec76750de39dfb30ecc05 Author: djm@openbsd.org Date: Fri Apr 29 04:55:07 2022 +0000 upstream: be stricter in which characters will be accepted in specifying a mask length; allow only 0-9. From khaleesicodes via GHPR#278; ok dtucker@ OpenBSD-Commit-ID: e267746c047ea86665cdeccef795a8a56082eeb2 commit 4835544d2dd31de6ffc7dba59f92093aea98155b Author: Darren Tucker Date: Sat Apr 30 10:56:41 2022 +1000 Add Mac OS X 12 test target. commit 97a6a8b8c1f2da09712d0e72d0ef800e4edd34cd Author: Darren Tucker Date: Fri Apr 29 18:27:34 2022 +1000 Only run tests when source files change. Also run tests on changes to V_9_0 branch. commit 6d0392b9ff4b50a56ac5685d1b9392e2cd432ca3 Author: Darren Tucker Date: Fri Apr 29 18:22:34 2022 +1000 Remove now-empty int32_minmax.inc. commit af59463553b5ad52d3b42c4455ee3c5600158bb7 Author: djm@openbsd.org Date: Fri Apr 29 03:24:30 2022 +0000 upstream: mention that the helpers are used by ssh(1), ssh-agent(1) and ssh-keygen(1). Previously only ssh(1) was mentioned. From Pedro Martelletto OpenBSD-Commit-ID: 30f880f989d4b329589c1c404315685960a5f153 commit 3e26b3a6eebcee27be177207cc0846fb844b7a56 Author: dtucker@openbsd.org Date: Fri Apr 29 03:16:48 2022 +0000 upstream: Don't leak SK device. Patch from Pedro Martelletto via github PR#316. ok djm@ OpenBSD-Commit-ID: 17d11327545022e727d95fd08b213171c5a4585d commit 247082b5013f0d4fcae8f97453f2a2f01bcda811 Author: djm@openbsd.org Date: Fri Apr 29 03:13:32 2022 +0000 upstream: fix memleak on session-bind path; from Pedro Martelletto, ok dtucker@ OpenBSD-Commit-ID: e85899a26ba402b4c0717b531317e8fc258f0a7e commit e05522008092ceb86a87bdd4ad7878424315db89 Author: djm@openbsd.org Date: Thu Apr 28 02:53:31 2022 +0000 upstream: avoid printing hash algorithm twice; from lucas AT sexy.is OpenBSD-Commit-ID: 9d24671e10a84141b7c504396cabad600e47a941 commit 0979e29356915261d69a9517a1e0aaade7c9fc75 Author: dtucker@openbsd.org Date: Wed Apr 27 11:08:55 2022 +0000 upstream: Add authfd path to debug output. ok markus@ OpenBSD-Commit-ID: f735a17d1a6f2bee63bfc609d76ef8db8c090890 commit 67b7c784769c74fd4d6b147d91e17e1ac1a8a96d Author: dtucker@openbsd.org Date: Tue Apr 26 07:41:44 2022 +0000 upstream: Check sshauthopt_new() for NULL. bz#3425, from tessgauthier at microsoft.com. ok djm@ OpenBSD-Commit-ID: af0315bc3e44aa406daa7e0ae7c2d719a974483f commit d571314d14b919fbd7c84a61f9bf2065fc0a6841 Author: millert@openbsd.org Date: Wed Apr 20 16:00:25 2022 +0000 upstream: Remove unnecessary includes: openssl/hmac.h and openssl/evp.h. From Martin Vahlensieck. OpenBSD-Commit-ID: a6debb5fb0c8a44e43e8d5ca7cc70ad2f3ea31c3 commit da8dddf8cc1f2516ff894b8183e83a7c5ba3ef80 Author: millert@openbsd.org Date: Wed Apr 20 15:59:18 2022 +0000 upstream: Add missing includes of stdlib.h and stdint.h. We need stdlib.h for malloc(3) and stdint.h for SIZE_MAX. Unlike the other xmss files, ssh-xmss.c does not include xmss_commons.h so ssh-xmss.c must include those headers itself. From Martin Vahlensieck OpenBSD-Commit-ID: 70e28a9818cee3da1be2ef6503d4b396dd421e6b commit fe9d87a6800a7a33be08f4d5ab662a758055ced2 Author: millert@openbsd.org Date: Wed Apr 20 15:56:49 2022 +0000 upstream: Avoid an unnecessary xstrdup in rm_env() when matching patterns. Since match_pattern() doesn't modify its arguments (they are const), there is no need to make an extra copy of the strings in options->send_env. From Martin Vahlensieck OpenBSD-Commit-ID: 2c9db31e3f4d3403b49642c64ee048b2a0a39351 commit 7bf2eb958fbb551e7d61e75c176bb3200383285d Author: Darren Tucker Date: Tue Apr 26 23:30:59 2022 +1000 Add debian-riscv64 test target. commit 3913c935523902482974c4c503bcff20bd850a6a Author: Darren Tucker Date: Mon Apr 25 17:20:06 2022 +1000 Update OpenSSL and LibreSSL versions in tests. commit dcd8dca29bcdb193ff6be35b96fc55e6e30d37d9 Author: Darren Tucker Date: Sat Apr 23 20:40:28 2022 +1000 Include stdlib.h for free() prototype. ... which is used inside the CUSTOM_SYS_AUTH_GET_LASTLOGIN_MSG block. commit 4cc05de568e1c3edd7834ff3bd9d8214eb34861b Author: Darren Tucker Date: Sat Apr 23 20:17:26 2022 +1000 Cache timezone data in capsicum sandbox. From emaste at freebsd.org, originally part of FreeBSD commit r339216 / fc3c19a9 with autoconf bits added by me. commit c31404426d212e2964ff9e5e58e1d0fce3d83f27 Author: dtucker@openbsd.org Date: Thu Apr 21 01:36:46 2022 +0000 upstream: It looks like we can't completely avoid waiting for processes to exit so retrieve the pid via controlmaster and use that. OpenBSD-Regress-ID: 8246f00f22b14e49d2ff1744c94897ead33d457b commit d19b21afab5c8e2f3df6bd8aee9766bdad3d8c58 Author: dtucker@openbsd.org Date: Wed Apr 20 13:25:55 2022 +0000 upstream: Use ssh -f and ControlPersist .. to start up test forwards and ssh -O stop to shut them down intead of sleep loops. This speeds up the test by an order of magnitude. OpenBSD-Regress-ID: eb3db5f805100919b092a3b2579c611fba3e83e7 commit 5f76286a126721fa005de6edf3d1c7a265555f19 Author: dtucker@openbsd.org Date: Wed Apr 20 05:24:13 2022 +0000 upstream: Simplify forward-control test. Since we no longer need to support SSH1 we don't need to run shell commands on the other end of the connection and can use ssh -N instead. This also makes the test less racy. OpenBSD-Regress-ID: 32e94ce272820cc398f30b848b2b0f080d10302c commit 687bbf23572d8bdf25cbbcdf8ac583514e1ba710 Author: djm@openbsd.org Date: Thu Mar 31 03:07:33 2022 +0000 upstream: regression test for sftp cp command OpenBSD-Regress-ID: c96bea9edde3a384b254785e7f9b2b24a81cdf82 commit f1233f19a6a9fe58f52946f50df4772f5b136761 Author: dtucker@openbsd.org Date: Wed Apr 20 01:13:47 2022 +0000 upstream: Import regenerated moduli OpenBSD-Commit-ID: f9a0726d957cf10692a231996a1f34e7f9cdfeb0 commit fec014785de198b9a325d1b94e324bb958c5fe7b Author: djm@openbsd.org Date: Wed Apr 20 04:19:11 2022 +0000 upstream: Try to continue running local I/O for channels in state OPEN during SSH transport rekeying. The most visible benefit is that it should make ~-escapes work in the client (e.g. to exit) if the connection happened to have stalled during a rekey event. Based work by and ok dtucker@ OpenBSD-Commit-ID: a66e8f254e92edd4ce09c9f750883ec8f1ea5f45 commit e68154b0d4f0f5085a050ea896955da1b1be6e30 Author: dtucker@openbsd.org Date: Wed Apr 20 01:13:47 2022 +0000 upstream: Import regenerated moduli OpenBSD-Commit-ID: f9a0726d957cf10692a231996a1f34e7f9cdfeb0 commit 69928b106d8f0fa15b88cf3850d992ed81c44ae0 Author: tj@openbsd.org Date: Sat Apr 16 00:22:31 2022 +0000 upstream: list the correct version number for when usage of the sftp protocol became default and fix a typo from ed maste OpenBSD-Commit-ID: 24e1795ed2283fdeacf16413c2f07503bcdebb31 commit 21042a05c0b304c16f655efeec97438249d2e2cc Author: dtucker@openbsd.org Date: Tue Apr 12 05:09:49 2022 +0000 upstream: Correct path for system known hosts file in description of IgnoreUserKnownHosts. Patch from Martin Vahlensieck via tech@ OpenBSD-Commit-ID: 9b7784f054fa5aa4d63cb36bd563889477127215 commit 53f4aff60a7c1a08a23917bd47496f8901c471f5 Author: Darren Tucker Date: Sat Apr 16 14:33:20 2022 +1000 Resync moduli.5 with upstream. 1.18: remove duplicate publication year; carsten dot kunze at arcor dot de 1.19: ssh-keygen's -G/-T have been replaced with -M generate/screen. commit d2b888762b9844eb0d8eb59909cdf5af5159f810 Author: Darren Tucker Date: Sat Apr 16 14:31:13 2022 +1000 Retire fbsd6 test VM. It's long since out of support, relatively slow (it's i686) and the compiler has trouble with PIE. commit cd1f70009860a154b51230d367c55ea5f9a4504e Author: djm@openbsd.org Date: Mon Apr 11 22:52:08 2022 +0000 upstream: clear io_want/io_ready flags at start of poll() cycle; avoids plausible spin during rekeying if channel io_want flags are reused across cycles. ok markus@ deraadt@ OpenBSD-Commit-ID: 91034f855b7c73cd2591657c49ac30f10322b967 commit aa1920302778273f7f94c2091319aba199068ca0 Author: dtucker@openbsd.org Date: Fri Apr 8 05:43:39 2022 +0000 upstream: Note that curve25519-sha256 was later published in RFC8731. ok djm@ OpenBSD-Commit-ID: 2ac2b5d642d4cf5918eaec8653cad9a4460b2743 commit 4673fa8f2be983f2f88d5afd754adb1a2a39ec9e Author: djm@openbsd.org Date: Fri Apr 8 04:40:40 2022 +0000 upstream: two defensive changes from Tobias Stoeckmann via GHPR287 enforce stricter invarient for sshbuf_set_parent() - never allow a buffer to have a previously-set parent changed. In sshbuf_reset(), if the reallocation fails, then zero the entire buffer and not the (potentially smaller) default initial alloc size. OpenBSD-Commit-ID: 14583203aa5d50ad38d2e209ae10abaf8955e6a9 commit 26eef015e2d2254375e13afaaf753b78932b1bf5 Author: Damien Miller Date: Mon Apr 11 16:07:09 2022 +1000 Revert "update build-aux files to match autoconf-2.71" This reverts commit 0a8ca39fac6ad19096b6c263436f8b2dd51606f2. It turns out that the checked-in copies of these files are actually newer than autoconf-2.71's copies, so this was effectively a downgrade. Spotted by Bo Anderson via github commit 0a8ca39fac6ad19096b6c263436f8b2dd51606f2 Author: Damien Miller Date: Fri Apr 8 14:48:58 2022 +1000 update build-aux files to match autoconf-2.71 i.e. config.guess, config.sub and install-sh commit 94eb6858efecc1b4f02d8a6bd35e149f55c814c8 Author: Damien Miller Date: Wed Apr 6 10:47:48 2022 +1000 update version numbers for release commit 8e4a8eadf4fe74e65e6492f34250f8cf7d67e8da Author: djm@openbsd.org Date: Mon Apr 4 22:45:25 2022 +0000 upstream: openssh-9.0 OpenBSD-Commit-ID: 0dfb461188f4513ec024c1534da8c1ce14c20b64 commit a9f23ea2e3227f406880c2634d066f6f50fa5eaa Author: naddy@openbsd.org Date: Thu Mar 31 17:58:44 2022 +0000 upstream: ssh: document sntrup761x25519-sha512@openssh.com as default KEX OpenBSD-Commit-ID: 12545bfa10bcbf552d04d9d9520d0f4e98b0e171 commit 9ec2713d122af79d66ebb9c1d6d9ae8621a8945f Author: naddy@openbsd.org Date: Thu Mar 31 17:27:27 2022 +0000 upstream: man pages: add missing commas between subordinate and main clauses jmc@ dislikes a comma before "then" in a conditional, so leave those untouched. ok jmc@ OpenBSD-Commit-ID: 9520801729bebcb3c9fe43ad7f9776ab4dd05ea3 commit 3741df98ffaaff92b474ee70d8ef276b5882f85a Author: Darren Tucker Date: Mon Apr 4 23:52:11 2022 +1000 Disable security key on fbsd6 test host. commit 32c12236f27ae83bfe6d2983b67c9bc67a83a417 Author: Darren Tucker Date: Mon Apr 4 15:16:51 2022 +1000 Specify TEST_SHELL=bash on AIX. The system shells cause the agent-restrict test to fail due to some quoting so explicitly specify bash until we can get configure to autmatically work around that. commit 90452c8b69d065b7c7c285ff78b81418a75bcd76 Author: Darren Tucker Date: Fri Apr 1 23:38:44 2022 +1100 Only return events from ppoll that were requested. If the underlying system's select() returns bits that were not in the request set, our ppoll() implementation can return revents for events not requested, which can apparently cause a hang. Only return revents for activity in the requested event set. bz#3416, analysis and fix by yaroslav.kuzmin at vmssoftware com, ok djm@ commit 6c49eb5fabc56f4865164ed818aa5112d09c31a8 Author: Darren Tucker Date: Fri Apr 1 23:21:40 2022 +1100 Only run regression tests on slow VMs. commit f67e47903977b42cb6abcd5565a61bd7293e4dc3 Author: Darren Tucker Date: Fri Apr 1 23:21:06 2022 +1100 Increase test timeout to allow slow VMs to finish commit 02488c1b54065ddc4f25835dbd2618b2a2fe21f5 Author: Darren Tucker Date: Fri Apr 1 16:27:38 2022 +1100 Use bash or ksh if available for SH in Makefile. commit 34c7018c316af4773e432066de28d0ef9d0888cd Author: Darren Tucker Date: Fri Apr 1 14:56:54 2022 +1100 Set Makefile SHELL as determined by configure. This should improve compatibility for users with non-POSIX shells. If using Makefile.in directly (eg make -f Makefile.in distprep) then SHELL will need to be specified on the command line (along with MANFMT in that particular case). ok djm@ commit 5b054d76402faab38c48377efd112426469553a0 Author: Darren Tucker Date: Fri Apr 1 13:16:47 2022 +1100 Skip slow tests on (very) slow test targets. commit b275818065b31a865142c48c2acf6a7c1655c542 Author: Damien Miller Date: Thu Mar 31 14:11:36 2022 +1100 depend commit 3fa539c3ffaabd6211995512d33e29150f88c5c5 Author: djm@openbsd.org Date: Thu Mar 31 03:07:03 2022 +0000 upstream: add a sftp client "cp" command that supports server-side copying of files. Useful for this task and for testing the copy-data extension. Patch from Mike Frysinger; ok dtucker@ OpenBSD-Commit-ID: 1bb1b950af0d49f0d5425b1f267e197aa1b57444 commit 7988bfc4b701c4b3fe9b36c8561a3d1c5d4c9a74 Author: djm@openbsd.org Date: Thu Mar 31 03:05:49 2022 +0000 upstream: add support for the "corp-data" protocol extension to allow server-side copies to be performed without having to go via the client. Patch by Mike Frysinger, ok dtucker@ OpenBSD-Commit-ID: 00aa510940fedd66dab1843b58682de4eb7156d5 commit 32dc1c29a4ac9c592ddfef0a4895eb36c1f567ba Author: djm@openbsd.org Date: Wed Mar 30 21:13:23 2022 +0000 upstream: select post-quantum KEX sntrup761x25519-sha512@openssh.com as the default; ok markus@ OpenBSD-Commit-ID: f02d99cbfce22dffec2e2ab1b60905fbddf48fb9 commit d6556de1db0822c76ba2745cf5c097d9472adf7c Author: djm@openbsd.org Date: Wed Mar 30 21:10:25 2022 +0000 upstream: fix poll() spin when a channel's output fd closes without data in the channel buffer. Introduce more exact packing of channel fds into the pollfd array. fixes bz3405 and bz3411; ok deraadt@ markus@ OpenBSD-Commit-ID: 06740737849c9047785622ad5d472cb6a3907d10 commit 8a74a96d25ca4d32fbf298f6c0ac5a148501777d Author: djm@openbsd.org Date: Wed Mar 30 04:33:09 2022 +0000 upstream: ssh is almost out of getopt() characters; note the remaining remaining available ones in a comment OpenBSD-Commit-ID: 48d38cef59d6bc8e84c6c066f6d601875d3253fd commit 6d4fc51adb9d8a42f67b5474f02f877422379de6 Author: djm@openbsd.org Date: Wed Mar 30 04:27:51 2022 +0000 upstream: avoid NULL deref via ssh-keygen -Y find-principals. bz3409, reported by Mateusz Adamowski OpenBSD-Commit-ID: a3b2c02438052ee858e0ee18e5a288586b5df2c5 commit e937514920335b92b543fd9be79cd6481d1eb0b6 Author: Darren Tucker Date: Mon Mar 28 17:51:03 2022 +1100 Add AIX 5.1 test target. commit 4bbe815ba974b4fd89cc3fc3e3ef1be847a0befe Author: Darren Tucker Date: Sat Mar 26 22:01:31 2022 +1100 Drop leading "v" from release version identifier. It's present in the git tags but not in the release tarball names. Also drop extra "/" from URL path. commit f5cdd3b3c275dffaebfca91df782dca29975e9ac Author: Darren Tucker Date: Sat Mar 26 16:28:04 2022 +1100 Use tarballs when testing LibreSSL releases. This means they'll still work when the combination of -portable and openbsd github repos no longer match. commit 24dc37d198f35a7cf71bf4d5384363c7ef4209d4 Author: Darren Tucker Date: Sat Mar 26 15:02:45 2022 +1100 Remove now-unused passwd variable. commit 5b467ceef2c356f0a77f5e8ab4eb0fac367e4d24 Author: Darren Tucker Date: Sat Mar 26 13:15:44 2022 +1100 Missing semicolon. commit 2923d026e55998133c0f6e5186dca2a3c0fa5ff5 Author: Darren Tucker Date: Sat Mar 26 12:49:50 2022 +1100 Factor out platform-specific locked account check. Also fixes an incorrect free on platforms with both libiaf and shadow passwords (probably only Unixware). Prompted by github PR#284, originally from @c3h2_ctf and stoeckmann@. commit d23efe4b12886ffe416be10bc0a7da6ca8aa72d1 Author: Darren Tucker Date: Sat Mar 26 08:13:46 2022 +1100 Add OpenWRT mips and mipsel test targets. commit 16ea8b85838dd7a4dbeba4e51ac4f43fd68b1e5b Author: djm@openbsd.org Date: Sun Mar 20 08:52:17 2022 +0000 upstream: don't leak argument list; bz3404, reported by Balu Gajjala ok dtucker@ OpenBSD-Commit-ID: fddc32d74e5dd5cff1a49ddd6297b0867eae56a6 commit a72bde294fe0518c9a44ba63864093a1ef2425e3 Author: djm@openbsd.org Date: Sun Mar 20 08:51:21 2022 +0000 upstream: make addargs() and replacearg() a little more robust and improve error reporting make freeargs(NULL) a noop like the other free functions ok dtucker as part of bz3403 OpenBSD-Commit-ID: 15f86da83176978b4d1d288caa24c766dfa2983d commit 731087d2619fa7f01e675b23f57af10d745e8af2 Author: djm@openbsd.org Date: Fri Mar 18 04:04:11 2022 +0000 upstream: don't try to resolve ListenAddress directives in the sshd re-exec path - we're never going to use the result and if the operation fails then it can prevent connections from being accepted. Reported by Aaron Poffenberger; with / ok dtucker@ OpenBSD-Commit-ID: 44c53a43909a328e2f5ab26070fdef3594eded60 commit 1c83c082128694ddd11ac05fdf31d70312ff1763 Author: djm@openbsd.org Date: Fri Mar 18 02:50:21 2022 +0000 upstream: remove blank line OpenBSD-Commit-ID: d5e0182965b2fbfb03ad5f256d1a1ce5706bcddf commit 807be68684da7a1fe969c399ddce2fafb7997dcb Author: djm@openbsd.org Date: Fri Mar 18 02:32:22 2022 +0000 upstream: helpful comment OpenBSD-Commit-ID: e3315a45cb04e7feeb614d76ec80a9fe4ca0e8c7 commit a0b5816f8f1f645acdf74f7bc11b34455ec30bac Author: djm@openbsd.org Date: Fri Mar 18 02:31:25 2022 +0000 upstream: ssh-keygen -Y check-novalidate requires namespace or SEGV will ensue. Patch from Mateusz Adamowski via GHPR#307 OpenBSD-Commit-ID: 99e8ec38f9feb38bce6de240335be34aedeba5fd commit 5a252d54a63be30d5ba4be76210942d754a531c0 Author: djm@openbsd.org Date: Tue Mar 15 05:27:37 2022 +0000 upstream: improve DEBUG_CHANNEL_POLL debugging message OpenBSD-Commit-ID: 2275eb7bc4707d019b1a0194b9c92c0b78da848f commit ce324cf58ba2840e31afeb996935800780c8fa4b Author: cheloha@openbsd.org Date: Sun Mar 13 23:27:54 2022 +0000 upstream: ssh: xstrdup(): use memcpy(3) Copying the given string into the buffer with strlcpy(3) confers no benefit in this context because we have already determined the string's length with strlen(3) in order to allocate that buffer. Thread: https://marc.info/?l=openbsd-tech&m=164687525802691&w=2 ok dtucker@ millert@ OpenBSD-Commit-ID: f8bfc082e36e2d2dc4e1feece02fe274155ca11a commit 2893c5e764557f48f9d6a929e224ed49c59545db Author: Darren Tucker Date: Fri Mar 11 18:43:58 2022 +1100 Resync fmt_scaled. with OpenBSD. Fixes underflow reported in bz#3401. commit 5ae31a0fdd27855af29f48ff027491629fff5979 Author: Darren Tucker Date: Wed Mar 9 09:41:56 2022 +1100 Provide killpg implementation. Based on github PR#301 for Tandem NonStop. commit c41c84b439f4cd74d4fe44298a4b4037ddd7d2ae Author: Darren Tucker Date: Wed Mar 9 09:29:30 2022 +1100 Check for missing ftruncate prototype. From github PR#301 in conjunction with rsbeckerca. commit 8cf5275452a950869cb90eeac7d220b01f77b12e Author: Darren Tucker Date: Tue Mar 8 20:04:06 2022 +1100 Default to not using sandbox when cross compiling. On most systems poll(2) does not work when the number of FDs is reduced with setrlimit, so assume it doesn't when cross compiling and we can't run the test. bz#3398. commit 379b30120da53d7c84aa8299c26b18c51c2a0dac Author: djm@openbsd.org Date: Tue Mar 1 01:59:19 2022 +0000 upstream: pack pollfd array before server_accept_loop() ppoll() call, and terminate sshd if ppoll() returns errno==EINVAL avoids spin in ppoll when MaxStartups > RLIMIT_NOFILE, reported by Daniel Micay feedback/ok deraadt OpenBSD-Commit-ID: dbab1c24993ac977ec24d83283b8b7528f7c2c15 commit eceafbe0bdbbd9bd2f3cf024ccb350666a9934dd Author: naddy@openbsd.org Date: Sun Feb 27 01:33:59 2022 +0000 upstream: include rejected signature algorithm in error message and not the (useless) key type; ok djm@ OpenBSD-Commit-ID: d0c0f552a4d9161203e07e95d58a76eb602a76ff commit f2f3269423618a83157e18902385e720f9776007 Author: dtucker@openbsd.org Date: Fri Feb 25 09:46:24 2022 +0000 upstream: Remove the char * casts from arguments to do_lstat, do_readdir and do_stat paths since the underlying functions now take a const char *. Patch from vapier at gentoo.org. OpenBSD-Commit-ID: 9e4d964dbfb0ed683a2a2900711b88e7f1c0297b commit 4a66dac052c5ff5047161853f36904607649e4f9 Author: djm@openbsd.org Date: Fri Feb 25 02:09:27 2022 +0000 upstream: save an unneccessary alloc/free, based on patch from Martin Vahlensieck; ok dtucker@ OpenBSD-Commit-ID: 90ffbf1f837e509742f2c31a1fbf2c0fd376fd5f commit 6f117cb151efe138ac57bdd8e26165f350328f5f Author: Darren Tucker Date: Tue Mar 1 09:02:06 2022 +1100 Remove unused ivbits argument from chacha_keysetup commit 15974235dd528aeab0ec67fb92a0a1d733f62be2 Author: Darren Tucker Date: Tue Mar 1 09:00:20 2022 +1100 Add OPENBSD ORIGINAL marker. commit f2ff669347d320532e7c1b63cdf5c62f46e73150 Author: Darren Tucker Date: Mon Feb 28 22:21:36 2022 +1100 No unused param warnings for clang-12 and gcc-11. These have too many false positives in -Werror tests on the github CI since we often provide empty stub functions for functionality not needed for particular configurations. commit 96558ecd87adac62efa9a2b5479f686ab86b0be1 Author: Darren Tucker Date: Sat Feb 26 14:10:41 2022 +1100 Add debian-i386 test target. commit 284b6e5394652d519e31782e3b3cdfd7b21d1a81 Author: Darren Tucker Date: Sat Feb 26 14:06:14 2022 +1100 Allow ppoll_time64 in seccomp sandbox. Should fix sandbox violations on (some? at least i386 and armhf) 32bit Linux platforms. Patch from chutzpahu at gentoo.org and cjwatson at debian.org via bz#3396. commit 0132056efabc5edb85c3c7105d2fb6dee41843c6 Author: Darren Tucker Date: Fri Feb 25 19:47:48 2022 +1100 Improve handling of _getshort and _getlong. If the system native ones are exactly as required then use them, otherwise use the local versions mapped to another name to prevent name collisions. commit 8e206e0dd6b9f757b07979e48f53ad5bf9b7b52b Author: Darren Tucker Date: Fri Feb 25 15:14:22 2022 +1100 Constify utimes in compat library to match specs. Patch from vapier at chromium.org. commit 1b2920e3b63db2eddebeec7330ffe8b723055573 Author: Darren Tucker Date: Fri Feb 25 13:50:56 2022 +1100 ANSIfy getshort and getlong. These functions appear to have come from OpenBSD's lib/libc/net/res_comp.c which made this change in 2005. commit 54a86f4f6e1c43a2ca2be23ef799ab8910d4af70 Author: Darren Tucker Date: Fri Feb 25 13:23:04 2022 +1100 Use PICFLAG instead of hard coding -fPIC. commit 3016ba47035ac3561aabd48e2be70167fe157d6a Author: Darren Tucker Date: Fri Feb 25 11:37:11 2022 +1100 Add tests for latest releases of {Libre,Open}SSL. commit f107467179428a0e3ea9e4aa9738ac12ff02822d Author: Colin Watson Date: Thu Feb 24 16:04:18 2022 +0000 Improve detection of -fzero-call-used-regs=all support GCC doesn't tell us whether this option is supported unless it runs into the situation where it would need to emit corresponding code. commit 3383b2cac0e9275bc93c4b4760e6e048f537e1d6 Author: djm@openbsd.org Date: Wed Feb 23 21:21:49 2022 +0000 upstream: free(3) wants stdlib.h OpenBSD-Commit-ID: 227a8c70a95b4428c49e46863c9ef4bd318a3b8a commit a4537e79ab4ac6db4493c5158744b9ebde5efcb0 Author: djm@openbsd.org Date: Wed Feb 23 21:21:16 2022 +0000 upstream: put back the scp manpage changes for SFTP mode too OpenBSD-Commit-ID: 05dc53921f927e1b5e5694e1f3aa314549f2e768 commit 449bcb8403adfb9724805d02a51aea76046de185 Author: deraadt@openbsd.org Date: Wed Feb 23 19:01:00 2022 +0000 upstream: and we go back to testing sftp-scp after the 8.9 release... OpenBSD-Commit-ID: a80440168258adca543a4607b871327a279c569c commit 166456cedad3962b83b848b1e9caf80794831f0f Author: Damien Miller Date: Wed Feb 23 22:31:11 2022 +1100 makedepend commit 32ebaa0dbca5d0bb86e384e72bebc153f48413e4 Author: djm@openbsd.org Date: Wed Feb 23 11:18:13 2022 +0000 upstream: avoid integer overflow of auth attempts (harmless, caught by monitor) OpenBSD-Commit-ID: 488ad570b003b21e0cd9e7a00349cfc1003b4d86 commit 6e0258c64c901753df695e06498b26f9f4812ea6 Author: djm@openbsd.org Date: Wed Feb 23 11:17:10 2022 +0000 upstream: randomise the password used in fakepw OpenBSD-Commit-ID: 34e159f73b1fbf0a924a9c042d8d61edde293947 commit bf114d6f0a9df0b8369823d9a0daa6c72b0c4cc9 Author: djm@openbsd.org Date: Wed Feb 23 11:15:57 2022 +0000 upstream: use asprintf to construct .rhosts paths OpenBSD-Commit-ID: 8286e8d3d2c6ff916ff13d041d1713073f738a8b commit c07e154fbdc7285e9ec54e78d8a31f7325d43537 Author: djm@openbsd.org Date: Wed Feb 23 11:07:09 2022 +0000 upstream: openssh-8.9 OpenBSD-Commit-ID: 5c5f791c87c483cdab6d9266b43acdd9ca7bde0e commit bc16667b4a1c3cad7029304853c143a32ae04bd4 Author: Darren Tucker Date: Tue Feb 22 15:29:22 2022 +1100 Extend select+rlimit sanbox test to include poll. POSIX specifies that poll() shall fail if "nfds argument is greater than {OPEN_MAX}". The setrlimit sandbox sets this to effectively zero so this causes poll() to fail in the preauth privsep process. This is likely the underlying cause for the previously observed similar behaviour of select() on plaforms where it is implement in userspace on top of poll(). commit 6520c488de95366be031d49287ed243620399e23 Author: Darren Tucker Date: Tue Feb 22 13:08:59 2022 +1100 Add Alpine Linux test VM. commit a4b325a3fc82d11e0f5d61f62e7fde29415f7afb Author: Darren Tucker Date: Tue Feb 22 12:27:07 2022 +1100 Include sys/param.h if present. Needed for howmany() on MUSL systems such as Alpine. commit 5a102e9cb287a43bd7dfe594b775a89a8e94697c Author: Darren Tucker Date: Tue Feb 22 12:25:52 2022 +1100 Only include sys/poll.h if we don't have poll.h. Prevents warnings on MUSL based systems such as Alpine. commit 7c0d4ce911d5c58b6166b2db754a4e91f352adf5 Author: Damien Miller Date: Tue Feb 22 11:14:51 2022 +1100 disable agent-restrict test on minix3 Minix seems to have a platform-wide limit on the number of select(2) syscalls that can be concurrently issued. This test seems to exceed this limit. Refer to: https://github.com/Stichting-MINIX-Research-Foundation/minix/blob/R3.3.0/minix/servers/vfs/select.c#L114 https://github.com/Stichting-MINIX-Research-Foundation/minix/blob/R3.3.0/minix/servers/vfs/select.c#L30-L31 commit 81d33d8e3cf7ea5ce3a5653c6102b623e019428a Author: Darren Tucker Date: Mon Feb 21 21:27:20 2022 +1100 Skip agent-getpeereid when running as root. commit fbd772570a25436a33924d91c164d2b24021f010 Author: dtucker@openbsd.org Date: Sun Feb 20 03:47:26 2022 +0000 upstream: Aproximate realpath on the expected output by deduping leading slashes. Fixes test failure when user's home dir is / which is possible in some portable configurations. OpenBSD-Regress-ID: 53b8c53734f8893806961475c7106397f98d9f63 commit 336685d223a59f893faeedf0a562e053fd84058e Author: Darren Tucker Date: Sun Feb 20 13:30:52 2022 +1100 Really move DSA to end of list. In commit ad16a84e syncing from OpenBSD, RSA was accidentally moved to the end of the list instead of DSA. Spotted by andrew at fyfe.gb.net. commit 63bf4f49ed2fdf2da6f97136c9df0c8168546eb3 Author: Darren Tucker Date: Fri Feb 18 12:12:21 2022 +1100 Add test configs for MUSL C library. commit f7fc6a43f1173e8b2c38770bf6cee485a562d03b Author: Damien Miller Date: Thu Feb 17 22:54:19 2022 +1100 minix needs BROKEN_POLL too; chokes on /dev/null commit 667fec5d4fe4406745750a32f69b5d2e1a75e94b Author: djm@openbsd.org Date: Thu Feb 17 10:58:27 2022 +0000 upstream: check for EINTR/EAGAIN failures in the rfd fast-path; caught by dtucker's minix3 vm :) ok dtucker@ OpenBSD-Commit-ID: 2e2c895a3e82ef347aa6694394a76a438be91361 commit 41417dbda9fb55a0af49a8236e3ef9d50d862644 Author: Darren Tucker Date: Thu Feb 17 22:05:29 2022 +1100 Comment hurd test, the VM is currently broken. commit b2aee35a1f0dc798339b3fcf96136da71b7e3f6d Author: Damien Miller Date: Thu Feb 17 21:15:16 2022 +1100 find sk-dummy.so when build_dir != src_dir spotted by Corinna Vinschen; feedback & ok dtucker@ commit 62a2d4e50b2e89f2ef04576931895d5139a5d037 Author: Damien Miller Date: Wed Feb 16 16:26:17 2022 +1100 update versions in preparation for 8.9 release commit dd6d3dded721ac653ea73c017325e5bfeeec837f Author: djm@openbsd.org Date: Tue Feb 15 05:13:36 2022 +0000 upstream: document the unbound/host-bound options to PubkeyAuthentication; spotted by HARUYAMA Seigo OpenBSD-Commit-ID: 298f681b66a9ecd498f0700082c7a6c46e948981 commit df93529dd727fdf2fb290700cd4f1adb0c3c084b Author: Darren Tucker Date: Mon Feb 14 14:19:40 2022 +1100 Test if sshd accidentally acquires controlling tty When SSHD_ACQUIRES_CTTY is defined, test for the problematic behaviour in the STREAMS code before activating the workaround. ok djm@ commit 766176cfdbfd7ec38bb6118dde6e4daa0df34888 Author: Darren Tucker Date: Sat Feb 12 10:24:56 2022 +1100 Add cygwin-release test config. This tests the flags used to build the cygwin release binaries. commit b30698662b862f5397116d23688aac0764e0886e Author: Darren Tucker Date: Fri Feb 11 21:00:35 2022 +1100 Move SSHD_ACQUIRES_CTTY workaround into compat. On some (most? all?) SysV based systems with STREAMS based ptys, sshd could acquire a controlling terminal during pty setup when it pushed the "ptem" module, due to what is probably a bug in the STREAMS driver that's old enough to vote. Because it was the privileged sshd's controlling terminal, it was not available for the user's session, which ended up without one. This is known to affect at least Solaris <=10, derivatives such as OpenIndiana and several other SysV systems. See bz#245 for the backstory. In the we past worked around that by not calling setsid in the privileged sshd child, which meant it was not a session or process group leader. This solved controlling terminal problem because sshd was not eligble to acquire one, but had other side effects such as not cleaning up helper subprocesses in the SIGALRM handler since it was not PG leader. Recent cleanups in the signal handler uncovered this, resulting in the LoginGraceTime timer not cleaning up privsep unprivileged processes. This change moves the workaround into the STREAMS pty allocation code, by allocating a sacrificial pty to act as sshd's controlling terminal before allocating user ptys, so those are still available for users' sessions. On the down side: - this will waste a pty per ssh connection on affected platforms. On the up side: - it makes the process group behaviour consistent between platforms. - it puts the workaround nearest the code that actually causes the problem and competely out of the mainline code. - the workaround is only activated if you use the STREAMS code. If, say, Solaris 11 has the bug but also a working openpty() it doesn't matter that we defined SSHD_ACQUIRES_CTTY. - the workaround is only activated when the fist pty is allocated, ie in the post-auth privsep monitor. This means there's no risk of fd leaks to the unprivileged processes, and there's no effect on sessions that do not allocate a pty. Based on analysis and work by djm@, ok djm@ commit cd00b48cf10f3565936a418c1e6d7e48b5c36140 Author: Darren Tucker Date: Fri Feb 11 20:09:32 2022 +1100 Simplify handling of --with-ssl-dir. ok djm@ commit ea13fc830fc0e0dce2459f1fab2ec5099f73bdf0 Author: Darren Tucker Date: Fri Feb 11 13:39:29 2022 +1100 Stop testing OpenBSD HEAD on 6.9 and 7.0. HEAD is not guaranteed to work on previous stable branches, and at the moment is broken due to libfido API changes. commit 50b9e4a4514697ffb9592200e722de6b427cb9ff Author: dtucker@openbsd.org Date: Fri Feb 11 00:43:56 2022 +0000 upstream: Always initialize delim before passing to hpdelim2 which might not set it. Found by the Valgrind tests on github, ok deraadt@ OpenBSD-Commit-ID: c830c0db185ca43beff3f41c19943c724b4f636d commit 6ee53064f476cf163acd5521da45b11b7c57321b Author: Darren Tucker Date: Fri Feb 11 10:03:06 2022 +1100 Fix helper include path and remove excess code. Looks like test_hpdelim.c was imported twice into the same file. Spotted by kevin.brott at gmail com and chris at cataclysmal org. commit 9fa63a19f68bc87452d3cf5c577cafad2921b7a4 Author: Darren Tucker Date: Thu Feb 10 23:27:02 2022 +1100 Put poll.h inside ifdef. commit 3ac00dfeb54b252c15dcbf1971582e9e3b946de6 Author: Darren Tucker Date: Thu Feb 10 22:17:31 2022 +1100 We now support POLLPRI so actually define it. commit 25bd659cc72268f2858c5415740c442ee950049f Author: dtucker@openbsd.org Date: Sun Feb 6 22:58:33 2022 +0000 upstream: Add test for empty hostname with port. OpenBSD-Regress-ID: e19e89d3c432b68997667efea44cf015bbe2a7e3 commit a29af853cff41c0635f0378c00fe91bf9c91dea4 Author: dtucker@openbsd.org Date: Fri Feb 4 07:53:44 2022 +0000 upstream: Add unit tests for hpdelim. OpenBSD-Regress-ID: be97b85c19895e6a1ce13c639765a3b48fd95018 commit 9699151b039ecc5fad9ac6c6c02e9afdbd26f15f Author: djm@openbsd.org Date: Thu Feb 10 04:12:38 2022 +0000 upstream: revert for imminent OpenSSH release, which wil ship with scp in RCP mode. > revision 1.106 > date: 2021/10/15 14:46:46; author: deraadt; state: Exp; lines: +13 -9; commitid: w5n9B2RE38tFfggl; > openbsd 7.0 release shipped with the (hopefully last) scp that uses RCP > protocol for copying. Let's get back to testing the SFTP protocol. This will be put back once the OpenSSH release is done. OpenBSD-Commit-ID: 0c725481a78210aceecff1537322c0b2df03e768 commit 45279abceb37c3cbfac8ba36dde8b2c8cdd63d32 Author: dtucker@openbsd.org Date: Tue Feb 8 08:59:12 2022 +0000 upstream: Switch hpdelim interface to accept only ":" as delimiter. Historicallly, hpdelim accepted ":" or "/" as a port delimiter between hosts (or addresses) and ports. These days most of the uses for "/" are no longer accepted, so there are several places where it checks the delimiter to disallow it. Make hpdelim accept only ":" and use hpdelim2 in the other cases. ok djm@ OpenBSD-Commit-ID: 7e6420bd1be87590b6840973f5ad5305804e3102 commit a1bcbf04a7c2d81944141db7ecd0ba292d175a66 Author: pedro martelletto Date: Mon Feb 7 09:09:59 2022 +0100 fix typos in previous commit 56192518e329b39f063487bc2dc4d796f791eca0 Author: Damien Miller Date: Mon Feb 7 12:53:47 2022 +1100 compat code for fido_assert_set_clientdata() commit d6b5aa08fdcf9b527f8b8f932432941d5b76b7ab Author: djm@openbsd.org Date: Mon Feb 7 01:25:12 2022 +0000 upstream: use libfido2 1.8.0+ fido_assert_set_clientdata() instead of manually hashing data outselves. Saves a fair bit of code and makes life easier for some -portable platforms. OpenBSD-Commit-ID: 351dfaaa5ab1ee928c0e623041fca28078cff0e0 commit 86cc93fd3c26b2e0c7663c6394995fb04ebfbf3b Author: jsg@openbsd.org Date: Sun Feb 6 00:29:03 2022 +0000 upstream: remove please from manual pages ok jmc@ sthen@ millert@ OpenBSD-Commit-ID: 6543acb00f4f38a23472538e1685c013ca1a99aa commit ad16a84e64a8cf1c69c63de3fb9008320a37009c Author: dtucker@openbsd.org Date: Fri Feb 4 02:49:17 2022 +0000 upstream: Since they are deprecated, move DSA to the end of the default list of public keys so that they will be tried last. From github PR#295 from "ProBackup-nl", ok djm@ OpenBSD-Commit-ID: 7e5d575cf4971d4e2de92e0b6d6efaba53598bf0 commit 253de42753de85dde266e061b6fec12ca6589f7d Author: Damien Miller Date: Wed Feb 2 16:52:07 2022 +1100 portable-specific string array constification from Mike Frysinger commit dfdcc2220cf359c492d5d34eb723370e8bd8a19e Author: djm@openbsd.org Date: Tue Feb 1 23:37:15 2022 +0000 upstream: test 'ssh-keygen -Y find-principals' with wildcard principals; from Fabian Stelzer OpenBSD-Regress-ID: fbe4da5f0032e7ab496527a5bf0010fd700f8f40 commit 968e508967ef42480cebad8cf3172465883baa77 Author: dtucker@openbsd.org Date: Fri Jan 21 02:54:41 2022 +0000 upstream: Enable all supported ciphers and macs in the server before trying to benchmark them. Increase the data file size to get more signal. OpenBSD-Regress-ID: dc3697d9f7defdfc51c608782c8e750128e46eb6 commit 15b7199a1fd37eff4c695e09d573f3db9f4274b7 Author: djm@openbsd.org Date: Tue Feb 1 23:34:47 2022 +0000 upstream: allow 'ssh-keygen -Y find-principals' to match wildcard principals in allowed_signers files; from Fabian Stelzer OpenBSD-Commit-ID: 1e970b9c025b80717dddff5018fe5e6f470c5098 commit 541667fe6dc26d7881e55f0bb3a4baa6f3171645 Author: djm@openbsd.org Date: Tue Feb 1 23:32:51 2022 +0000 upstream: mark const string array contents const too, i.e. static const char *array => static const char * const array from Mike Frysinger OpenBSD-Commit-ID: a664e31ea6a795d7c81153274a5f47b22bdc9bc1 commit 8cfa73f8a2bde4c98773f33f974c650bdb40dd3c Author: djm@openbsd.org Date: Tue Feb 1 23:11:11 2022 +0000 upstream: better match legacy scp behaviour: show un-expanded paths in error messages. Spotted by and ok tb@ OpenBSD-Commit-ID: 866c8ffac5bd7d38ecbfc3357c8adfa58af637b7 commit 4e62c13ab419b4b224c8bc6a761e91fcf048012d Author: dtucker@openbsd.org Date: Tue Feb 1 07:57:32 2022 +0000 upstream: Remove explicit kill of privsep preauth child's PID in SIGALRM handler. It's no longer needed since the child will get terminated by the SIGTERM to the process group that cleans up any auth helpers, it simplifies the signal handler and removes the risk of a race when updating the PID. Based on analysis by HerrSpace in github PR#289, ok djm@ OpenBSD-Commit-ID: 2be1ffa28b4051ad9e33bb4371e2ec8a31d6d663 commit 2a7ccd2ec4022917b745af7186f514f365b7ebe9 Author: guenther@openbsd.org Date: Fri Jan 28 06:18:42 2022 +0000 upstream: When it's the possessive of 'it', it's spelled "its", without the apostrophe. OpenBSD-Commit-ID: fb6ab9c65bd31de831da1eb4631ddac018c5fae7 commit 8a0848cdd3b25c049332cd56034186b7853ae754 Author: Alex James Date: Sun Jan 30 16:13:36 2022 -0600 sandbox-seccomp-filter: allow gettid Some allocators (such as Scudo) use gettid while tracing allocations [1]. Allow gettid in preauth to prevent sshd from crashing with Scudo. [1]: https://github.com/llvm/llvm-project/blob/llvmorg-13.0.0/compiler-rt/lib/gwp_asan/common.cpp#L46 commit b30d32159dc3c7052f4bfdf36357996c905af739 Author: djm@openbsd.org Date: Sat Jan 22 00:49:34 2022 +0000 upstream: add a ssh_packet_process_read() function that reads from a fd directly into the transport input buffer. Use this in the client and server mainloops to avoid unnecessary copying. It also lets us use a more greedy read size without penalty. Yields a 2-3% performance gain on cipher-speed.sh (in a fairly unscientific test tbf) feedback dtucker@ ok markus@ OpenBSD-Commit-ID: df4112125bf79d8e38e79a77113e1b373078e632 commit a1a8efeaaa9cccb15cdc0a2bd7c347a149a3a7e3 Author: djm@openbsd.org Date: Sat Jan 22 00:45:31 2022 +0000 upstream: Use sshbuf_read() to read directly into the channel input buffer rather than into a stack buffer that needs to be copied again; Improves performance by about 1% on cipher-speed.sh feedback dtucker@ ok markus@ OpenBSD-Commit-ID: bf5e6e3c821ac3546dc8241d8a94e70d47716572 commit 29a76994e21623a1f84d68ebb9dc5a3c909fa3a7 Author: Damien Miller Date: Tue Jan 25 11:52:34 2022 +1100 depend commit 754e0d5c7712296a7a3a83ace863812604c7bc4f Author: djm@openbsd.org Date: Sat Jan 22 00:43:43 2022 +0000 upstream: Add a sshbuf_read() that attempts to read(2) directly in to a sshbuf; ok markus@ OpenBSD-Commit-ID: 2d8f249040a4279f3bc23c018947384de8d4a45b commit c7964fb9829d9ae2ece8b51a76e4a02e8449338d Author: djm@openbsd.org Date: Fri Jan 21 07:04:19 2022 +0000 upstream: add a helper for writing an error message to the stderr_buf and setting quit_pending; no functional change but saves a bunch of boilerplate OpenBSD-Commit-ID: 0747657cad6b9eabd514a6732adad537568e232d commit d23b4f7fdb1bd87e2cd7a9ae7c198ae99d347916 Author: djm@openbsd.org Date: Fri Jan 21 06:58:06 2022 +0000 upstream: correct comment and use local variable instead of long indirection; spotted by dtucker@ OpenBSD-Commit-ID: 5f65f5f69db2b7d80a0a81b08f390a63f8845965 commit d069b020a02b6e3935080204ee44d233e8158ebb Author: deraadt@openbsd.org Date: Fri Jan 21 00:53:40 2022 +0000 upstream: When poll(2) returns -1, for some error conditions pfd[].revents is not cleared. There are subtle errors in various programs. In this particular case, the program should error out. ok djm millert OpenBSD-Commit-ID: 00f839b16861f7fb2adcf122e95e8a82fa6a375c commit e204b34337a965feb439826157c191919fd9ecf8 Author: Damien Miller Date: Sat Jan 22 11:38:21 2022 +1100 restore tty force-read hack This portable-specific hack fixes a hang on exit for ttyful sessions on Linux and some SysVish Unix variants. It was accidentally disabled in commit 5c79952dfe1a (a precursor to the mainloop poll(2) conversion). Spotted by John in bz3383 commit 68085066b6bad43643b43f5957fcc5fd34782ccd Author: Corinna Vinschen Date: Fri Jan 21 03:22:56 2022 +1100 Fix signedness bug in Cygwin code The Cygwin-specific pattern match code has a bug. It checks the size_t value returned by mbstowcs for being < 0. The right thing to do is to check against (size_t) -1. Fix that. Signed-off-by: Corinna Vinschen commit 2e5cfed513e84444483baf1d8b31c40072b05103 Author: Darren Tucker Date: Thu Jan 20 13:26:27 2022 +1100 Improve compatibility of early exit trap handling. Dash (as used by the github runners) has some differences in its trap builtin: - it doesn't have -p (which is fine, that's not in posix). - it doesn't work in a subshell (which turns out to be in compliance with posix, which means bash isn't). - it doesn't work in a pipeline, ie "trap|cat" produces no output. commit 3fe6800b6027add478e648934cbb29d684e51943 Author: Darren Tucker Date: Thu Jan 20 00:49:57 2022 +1100 Move more tests out of valgrind-1 runner. commit 20da6ed136dd76e6a0b229ca3036ef9c7c7ef798 Author: Darren Tucker Date: Wed Jan 19 15:37:39 2022 +1100 Invoke EXIT handler early when using Valgrind. When using Valgrind, we need to wait for all invoked programs to complete before checking their valgrind logs. Some tests, notably agent-restrict, set an EXIT trap handler to clean up things like ssh-agent, but those do not get invoked until test-exec.sh exits. This causes the Valgrind wait to deadlock, so if present invoke the EXIT handler before checking the Valgrind logs. commit ad2e0580c87b0714cf166bca9d926a95ddeee1c8 Author: Darren Tucker Date: Tue Jan 18 12:55:21 2022 +1100 Remove line leftover from upstream sync. commit d1051c0f11a6b749027e26bbeb61b07df4b67e15 Author: djm@openbsd.org Date: Mon Jan 17 22:56:04 2022 +0000 upstream: when decompressing zlib compressed packets, use Z_SYNC_FLUSH instead of Z_PARTIAL_FLUSH as the latter is not actually specified as a valid mode for inflate(). There should be no practical change in behaviour as the compression side ensures a flush that should make all data available to the receiver in all cases. repoted by lamm AT ibm.com via bz3372; ok markus OpenBSD-Commit-ID: 67cfc1fa8261feae6d2cc0c554711c97867cc81b commit d5981b1883746b1ae178a46229c26b53af99e37a Author: djm@openbsd.org Date: Mon Jan 17 21:41:04 2022 +0000 upstream: make most of the sftp errors more idiomatic, following the general form of "[local/remote] operation path: error message"; ok markus OpenBSD-Commit-ID: 61364cd5f3a9fecaf8d63b4c38a42c0c91f8b571 commit ac7c9ec894ed0825d04ef69c55babb49bab1d32e Author: djm@openbsd.org Date: Mon Jan 17 21:39:51 2022 +0000 upstream: when transferring multiple files in SFTP mode, create the destination directory if it doesn't already exist to match olde-scp(1) behaviour. noticed by deraadt@ ok markus@ OpenBSD-Commit-ID: cf44dfa231d4112f697c24ff39d7ecf2e6311407 commit 39d17e189f8e72c34c722579d8d4e701fa5132da Author: djm@openbsd.org Date: Fri Jan 14 03:43:48 2022 +0000 upstream: allow pin-required FIDO keys to be added to ssh-agent(1). ssh-askpass will be used to request the PIN at authentication time. From Pedro Martelletto, ok djm OpenBSD-Commit-ID: de8189fcd35b45f632484864523c1655550e2950 commit 52423f64e13db2bdc31a51b32e999cb1bfcf1263 Author: djm@openbsd.org Date: Fri Jan 14 03:35:10 2022 +0000 upstream: ssh-sk: free a resident key's user id From Pedro Martelletto; ok dtucker & me OpenBSD-Commit-ID: 47be40d602b7a6458c4c71114df9b53d149fc2e9 commit 014e2f147a2788bfb3cc58d1b170dcf2bf2ee493 Author: djm@openbsd.org Date: Fri Jan 14 03:34:00 2022 +0000 upstream: sshsk_load_resident: don't preallocate resp resp is allocated by client_converse(), at which point we lose the original pointer. From Pedro Martelletto; ok dtucker & me OpenBSD-Commit-ID: 1f1b5ea3282017d6584dfed4f8370dc1db1f44b1 commit c88265f207dfe0e8bdbaf9f0eda63ed6b33781cf Author: djm@openbsd.org Date: Fri Jan 14 03:32:52 2022 +0000 upstream: sshsk_sign: trim call to sshkey_fingerprint() the resulting fingerprint doesn't appear to be used for anything, and we end up leaking it. from Pedro Martelletto; ok dtucker & me OpenBSD-Commit-ID: 5625cf6c68f082bc2cbbd348e69a3ed731d2f9b7 commit 1cd1b2eac39661b849d5a4b4b56363e22bb5f61e Author: djm@openbsd.org Date: Fri Jan 14 03:31:52 2022 +0000 upstream: use status error message to communicate ~user expansion failures; provides better experience for scp in sftp mode, where ~user paths are more likely to be used; spotted jsg, feedback jsg & deraadt ok jsg & markus (forgot to include this file in previous commit) OpenBSD-Commit-ID: d37cc4c8c861ce48cd6ea9899e96aaac3476847b commit a1d42a6ce0398da3833bedf374ef2571af7fea50 Author: Damien Miller Date: Fri Jan 14 13:49:32 2022 +1100 fix edge case in poll(2) wrapper Correct handling of select(2) exceptfds. These should only be consulted for POLLPRI flagged pfds and not unconditionally converted to POLLERR. with and ok dtucker@ commit 976b9588b4b5babcaceec4767a241c11a67a5ccb Author: Darren Tucker Date: Fri Jan 14 13:46:35 2022 +1100 Wrap OpenSSL includes in unit tests in ifdef. Fixes unit test on systems that do not have OpenSSL headers installed. commit c171879374b2e8b07157503f5639ed0bce59ce89 Author: Darren Tucker Date: Thu Jan 13 15:53:33 2022 +1100 Remove sort wrapper. agent-restrict now takes care of this itself. commit 9cc2654403f1a686bb26c07a6ac790edf334cef5 Author: dtucker@openbsd.org Date: Thu Jan 13 04:53:16 2022 +0000 upstream: Set LC_ALL in both local and remote shells so that sorted output matches regardless of what the user's shell sets it to. ok djm@ OpenBSD-Regress-ID: 4e97dd69a68b05872033175a4c2315345d01837f commit 7a75f748cb2dd2f771bf70ea72698aa027996ab1 Author: dtucker@openbsd.org Date: Thu Jan 13 04:22:10 2022 +0000 upstream: Avoid %'s in commands (not used in OpenBSD, but used in -portable's Valgrind test) being interpretted as printf format strings. OpenBSD-Regress-ID: dc8655db27ac4acd2c386c4681bf42a10d80b043 commit 6c435bd4994d71442192001483a1cdb846e5ffcd Author: Darren Tucker Date: Wed Jan 12 16:58:13 2022 +1100 Stop on first test failure to minimize logs. commit 4bc2ba6095620a4484b708ece12842afd8c7685b Author: dtucker@openbsd.org Date: Wed Jan 12 07:18:37 2022 +0000 upstream: Use egrep when searching for an anchored string. OpenBSD-Regress-ID: dd114a2ac27ac4b06f9e4a586d3f6320c54aeeb4 commit 6bf2efa2679da1e8e60731f41677b2081dedae2c Author: Darren Tucker Date: Wed Jan 12 18:25:06 2022 +1100 Add "rev" command replacement if needed. commit 72bcd7993dadaf967bb3d8564ee31cbf38132b5d Author: dtucker@openbsd.org Date: Wed Jan 12 03:30:32 2022 +0000 upstream: Don't log NULL hostname in restricted agent code, printf("%s", NULL) is not safe on all platforms. with & ok djm OpenBSD-Commit-ID: faf10cdae4adde00cdd668cd1f6e05d0a0e32a02 commit acabefe3f8fb58c867c99fed9bbf84dfa1771727 Author: djm@openbsd.org Date: Tue Jan 11 22:33:16 2022 +0000 upstream: remove hardcoded domain and use window.location.host, so this can be run anywhere OpenBSD-Regress-ID: 2ac2ade3b6227d9c547351d3ccdfe671e62b7f92 commit 96da0946e44f34adc0397eb7caa6ec35a3e79891 Author: dtucker@openbsd.org Date: Tue Jan 11 02:56:19 2022 +0000 upstream: "void" functions should not return anything. From Tim Rice via -portable. OpenBSD-Commit-ID: ce6616304f4c9881b46413e616b226c306830e2a commit a882a09722c9f086c9edb65d0c4022fd965ec1ed Author: djm@openbsd.org Date: Tue Jan 11 01:26:47 2022 +0000 upstream: suppress "Connection to xxx closed" messages at LogLevel >= error bz3378; ok dtucker@ OpenBSD-Commit-ID: d5bf457d5d2eb927b81d0663f45248a31028265c commit 61a1a6af22e17fc94999a5d1294f27346e6c4668 Author: Damien Miller Date: Wed Jan 12 08:57:49 2022 +1100 OS X poll(2) is broken; use compat replacement Darwin's poll(2) implementation is broken. For character-special devices like /dev/null, it returns POLLNVAL when polled with POLLIN. Apparently this is Apple bug 3710161, which is AFAIK not public, but a websearch will find other OSS projects rediscovering it periodically since it was first identified in 2005 (!!) commit 613a6545fc5a9542753b503cbe5906538a640b60 Author: Darren Tucker Date: Tue Jan 11 20:56:01 2022 +1100 libhardended_malloc.so moved into out dir. commit 61761340be5e11046556623f8f5412b236cefa95 Author: Tim Rice Date: Mon Jan 10 11:07:04 2022 -0800 Make USL compilers happy UX:acomp: ERROR: "sftp-server.c", line 567: void function cannot return value commit 3ef403f351e80a59b6f7e9d43cb82c181855483c Author: Darren Tucker Date: Mon Jan 10 21:07:38 2022 +1100 Add wrapper for "sort" to set LC_ALL=C. Found by djm, this should make sorts stable and reduce test flakiness. commit bd69e29f5716090181dbe0b8272eb7eab1a383bb Author: dtucker@openbsd.org Date: Sat Jan 8 07:55:26 2022 +0000 upstream: Remove errant "set -x" left over from debugging. OpenBSD-Regress-ID: cd989268e034264cec5df97be7581549032c87dc commit 1a7c88e26fd673813dc5f61c4ac278564845e004 Author: dtucker@openbsd.org Date: Sat Jan 8 07:01:13 2022 +0000 upstream: Enable all supported hostkey algorithms (but no others). Allows hostbased test to pass when built without OpenSSL. OpenBSD-Regress-ID: 5ddd677a68b672517e1e78460dc6ca2ccc0a9562 commit 12b457c2a42ff271e7967d9bedd068cebb048db9 Author: djm@openbsd.org Date: Sat Jan 8 07:37:32 2022 +0000 upstream: use status error message to communicate ~user expansion failures; provides better experience for scp in sftp mode, where ~user paths are more likely to be used; spotted jsg, feedback jsg & deraadt ok jsg & markus OpenBSD-Commit-ID: fc610ce00ca0cdc2ecdabbd49ce7cb82033f905f commit 63670d4e9030bcee490d5a9cce561373ac5b3b23 Author: djm@openbsd.org Date: Sat Jan 8 07:36:11 2022 +0000 upstream: fix some corner-case bugs in scp sftp-mode handling of ~-prefixed paths; spotted by jsg; feedback jsg & deraadt, ok jsg & markus OpenBSD-Commit-ID: d1697dbaaa9f0f5649d69be897eab25c7d37c222 commit e14940bbec57fc7d3ce0644dbefa35f5a8ec97d0 Author: djm@openbsd.org Date: Sat Jan 8 07:34:57 2022 +0000 upstream: more idiomatic error messages; spotted by jsg & deraadt ok jsg & markus OpenBSD-Commit-ID: 43618c692f3951747b4151c477c7df22afe2bcc8 commit 9acddcd5918c623f7ebf454520ffe946a8f15e90 Author: djm@openbsd.org Date: Sat Jan 8 07:33:54 2022 +0000 upstream: add a variant of send_status() that allows overriding the default, generic error message. feedback/ok markus & jsg OpenBSD-Commit-ID: 81f251e975d759994131b717ee7c0b439659c40f commit 961411337719d4cd78f1ab33e4ac549f3fa22f50 Author: djm@openbsd.org Date: Sat Jan 8 07:32:45 2022 +0000 upstream: refactor tilde_expand_filename() and make it handle ~user paths with no trailing slash; feedback/ok markus and jsg OpenBSD-Commit-ID: a2ab365598a902f0f14ba6a4f8fb2d07a9b5d51d commit dc38236ab6827dec575064cac65c8e7035768773 Author: dtucker@openbsd.org Date: Thu Jan 6 22:14:25 2022 +0000 upstream: Don't explicitly set HostbasedAuthentication in sshd_config. It defaults to "no", and not explicitly setting it allows us to enable it for the (optional) hostbased test. OpenBSD-Regress-ID: aa8e3548eb5793721641d26e56c29f363b767c0c commit e12d912ddf1c873cb72e5de9a197afbe0b6622d2 Author: dtucker@openbsd.org Date: Thu Jan 6 21:46:56 2022 +0000 upstream: Add test for hostbased auth. It requires some external setup (see comments at the top) and thus is disabled unless TEST_SSH_HOSTBASED_AUTH and SUDO are set. OpenBSD-Regress-ID: 3ec8ba3750c5b595fc63e7845d13483065a4827a commit a48533a8da6a0f4f05ecd055dc8048047e53569e Author: Damien Miller Date: Fri Jan 7 09:24:26 2022 +1100 depend commit d9dbb5d9a0326e252d3c7bc13beb9c2434f59409 Author: djm@openbsd.org Date: Thu Jan 6 22:06:51 2022 +0000 upstream: allow hostbased auth to select RSA keys when only RSA/SHA2 are configured (this is the default case); ok markus@ OpenBSD-Commit-ID: 411c18c7bde40c60cc6dfb7017968577b4d4a827 commit fdb1d58d0d3888b042e5a500f6ce524486aaf782 Author: djm@openbsd.org Date: Thu Jan 6 22:05:42 2022 +0000 upstream: add a helper function to match a key type to a list of signature algorithms. RSA keys can make signatures with multiple algorithms, so some special handling is required. ok markus@ OpenBSD-Commit-ID: 03b41b2bda06fa4cd9c84cef6095033b9e49b6ff commit 11e8c4309a5086a45fbbbc87d0af5323c6152914 Author: djm@openbsd.org Date: Thu Jan 6 22:04:20 2022 +0000 upstream: log some details on hostkeys that ssh loads for hostbased authn ok markus@ OpenBSD-Commit-ID: da17061fa1f0e58cb31b88478a40643e18233e38 commit c6706f661739514a34125aa3136532a958929510 Author: djm@openbsd.org Date: Thu Jan 6 22:03:59 2022 +0000 upstream: log signature algorithm during verification by monitor; ok markus OpenBSD-Commit-ID: 02b92bb42c4d4bf05a051702a56eb915151d9ecc commit 8832402bd500d1661ccc80a476fd563335ef6cdc Author: djm@openbsd.org Date: Thu Jan 6 22:02:52 2022 +0000 upstream: piece of UpdateHostkeys client strictification: when updating known_hosts with new keys, ignore NULL keys (forgot to include in prior commit) OpenBSD-Commit-ID: 49d2eda6379490e1ceec40c3b670b973f63dea08 commit c2d9ced1da0276961d86690b3bd7ebdaca7fdbf7 Author: djm@openbsd.org Date: Thu Jan 6 22:01:14 2022 +0000 upstream: include rejected signature algorithm in error message and not the (useless) key type; ok markus OpenBSD-Commit-ID: 4180b5ec7ab347b43f84e00b1972515296dab023 commit 7aa7b096cf2bafe2777085abdeed5ce00581f641 Author: djm@openbsd.org Date: Thu Jan 6 22:00:18 2022 +0000 upstream: make ssh-keysign use the requested signature algorithm and not the default for the keytype. Part of unbreaking hostbased auth for RSA/SHA2 keys. ok markus@ OpenBSD-Commit-ID: b5639a14462948970da3a8020dc06f9a80ecccdc commit 291721bc7c840d113a49518f3fca70e86248b8e8 Author: djm@openbsd.org Date: Thu Jan 6 21:57:28 2022 +0000 upstream: stricter UpdateHostkey signature verification logic on the client- side. Require RSA/SHA2 signatures for RSA hostkeys except when RSA/SHA1 was explicitly negotiated during initial KEX; bz3375 ok markus@ OpenBSD-Commit-ID: 46e75e8dfa2c813781805b842580dcfbd888cf29 commit 0fa33683223c76289470a954404047bc762be84c Author: djm@openbsd.org Date: Thu Jan 6 21:55:23 2022 +0000 upstream: Fix signature algorithm selection logic for UpdateHostkeys on the server side. The previous code tried to prefer RSA/SHA2 for hostkey proofs of RSA keys, but missed some cases. This will use RSA/SHA2 signatures for RSA keys if the client proposed these algorithms in initial KEX. bz3375 Mostly by Dmitry Belyavskiy with some tweaks by me. ok markus@ OpenBSD-Commit-ID: c17ba0c3236340d2c6a248158ebed042ac6a8029 commit 17877bc81db3846e6e7d4cfb124d966bb9c9296b Author: djm@openbsd.org Date: Thu Jan 6 21:48:38 2022 +0000 upstream: convert ssh, sshd mainloops from select() to poll(); feedback & ok deraadt@ and markus@ has been in snaps for a few months OpenBSD-Commit-ID: a77e16a667d5b194dcdb3b76308b8bba7fa7239c commit 5c79952dfe1aa36105c93b3f383ce9be04dee384 Author: djm@openbsd.org Date: Thu Jan 6 21:46:23 2022 +0000 upstream: prepare for conversion of ssh, sshd mainloop from select() to poll() by moving FD_SET construction out of channel handlers into separate functions. ok markus OpenBSD-Commit-ID: 937fbf2a4de12b19fb9d5168424e206124807027 commit 24c5187edfef4651a625b7d5d692c8c7e794f71f Author: djm@openbsd.org Date: Wed Jan 5 21:54:37 2022 +0000 upstream: add a comment so I don't make this mistake again OpenBSD-Commit-ID: 69c7f2362f9de913bb29b6318580c5a1b52c921e commit 7369900441929058263a17f56aa67e05ff7ec628 Author: djm@openbsd.org Date: Wed Jan 5 21:50:00 2022 +0000 upstream: fix cut-and-pasto in error message OpenBSD-Commit-ID: 4cc5c619e4b456cd2e9bb760d17e3a9c84659198 commit 294c11b1c7d56d3fb61e329603a782315ed70c62 Author: djm@openbsd.org Date: Wed Jan 5 08:25:05 2022 +0000 upstream: select all RSA hostkey algorithms for UpdateHostkeys tests, not just RSA-SHA1 OpenBSD-Regress-ID: b40e62b65863f2702a0c10aca583b2fe76772bd8 commit 2ea1108c30e3edb6f872dfc1e6da10b041ddf2c0 Author: djm@openbsd.org Date: Wed Jan 5 04:56:15 2022 +0000 upstream: regress test both sshsig message hash algorithms, possible now because the algorithm is controllable via the CLI OpenBSD-Regress-ID: 0196fa87acc3544b2b4fd98de844a571cb09a39f commit 2327c306b5d4a2b7e71178e5a4d139af9902c2b0 Author: djm@openbsd.org Date: Wed Jan 5 04:50:11 2022 +0000 upstream: allow selection of hash at sshsig signing time; code already supported either sha512 (default) or sha256, but plumbing wasn't there mostly by Linus Nordberg OpenBSD-Commit-ID: 1b536404b9da74a84b3a1c8d0b05fd564cdc96cd commit 56e941d0a00d6d8bae88317717d5e1b7395c9529 Author: djm@openbsd.org Date: Wed Jan 5 04:27:54 2022 +0000 upstream: add missing -O option to usage() for ssh-keygen -Y sign; from Linus Nordberg OpenBSD-Commit-ID: 4e78feb4aa830727ab76bb2e3d940440ae1d7af0 commit 141a14ec9b0924709c98df2dd8013bde5d8d12c7 Author: djm@openbsd.org Date: Wed Jan 5 04:27:01 2022 +0000 upstream: move sig_process_opts() to before sig_sign(); no functional code change OpenBSD-Commit-ID: da02d61f5464f72b4e8b299f83e93c3b657932f9 commit 37a14249ec993599a9051731e4fb0ac5e976aec1 Author: djm@openbsd.org Date: Wed Jan 5 04:10:39 2022 +0000 upstream: regression test for find-principals NULL deref; from Fabian Stelzer OpenBSD-Regress-ID: f845a8632a5a7d5ae26978004c93e796270fd3e5 commit eb1f042142fdaba93f6c9560cf6c91ae25f6884a Author: djm@openbsd.org Date: Wed Jan 5 04:02:42 2022 +0000 upstream: NULL deref when using find-principals when matching an allowed_signers line that contains a namespace restriction, but no restriction specified on the command-line; report and fix from Fabian Stelzer OpenBSD-Commit-ID: 4a201b86afb668c908d1a559c6af456a61f4b145 commit 8f3b18030579f395eca2181da31a5f945af12a59 Author: dtucker@openbsd.org Date: Tue Jan 4 08:38:53 2022 +0000 upstream: Log command invocation while debugging. This will aid in manually reproducing failing commands. OpenBSD-Regress-ID: b4aba8d5ac5675ceebeeeefa3261ce344e67333a commit bbf285164df535f0d38c36237f007551bbdae27f Author: Darren Tucker Date: Sun Dec 26 10:31:15 2021 +1100 Always save config.h as build artifact. Should allow better comparison between failing and succeeding test platforms. commit 03bd4ed0db699687c5cd83405d26f81d2dc28d22 Author: Darren Tucker Date: Sat Dec 25 16:42:51 2021 +1100 Add OpenBSD 7.0 target. Retire 6.8. commit c45a752f0de611afd87755c2887c8a24816d08ee Author: jsg@openbsd.org Date: Sat Jan 1 05:55:06 2022 +0000 upstream: spelling OpenBSD-Commit-ID: c63e43087a64d0727af13409c708938e05147b62 commit c672f83a89a756564db0d3af9934ba0e1cf8fa3e Author: djm@openbsd.org Date: Tue Jan 4 07:20:33 2022 +0000 upstream: unbreak test: was picking up system ssh-add instead of the one supposedly being tested. Spotted by dtucker and using his VM zoo (which includes some systems old enough to lack ed25519 key support) OpenBSD-Regress-ID: 7976eb3df11cc2ca3af91030a6a8c0cef1590bb5 commit a23698c3082ffe661abed14b020eac9b0c25eb9f Author: djm@openbsd.org Date: Sat Jan 1 04:18:06 2022 +0000 upstream: fix memleak in process_extension(); oss-fuzz issue #42719 OpenBSD-Commit-ID: d8d49f840162fb7b8949e3a5adb8107444b6de1e commit cb885178f36b83d0f14cfe9f345d2068103feed0 Author: jsg@openbsd.org Date: Sat Jan 1 01:55:30 2022 +0000 upstream: spelling ok dtucker@ OpenBSD-Commit-ID: bfc7ba74c22c928de2e257328b3f1274a3dfdf19 commit 6b977f8080a32c5b3cbb9edb634b9d5789fb79be Author: djm@openbsd.org Date: Sun Dec 26 23:34:41 2021 +0000 upstream: split method list search functionality from authmethod_lookup() into a separate authmethod_byname(), for cases where we don't need to check whether a method is enabled, etc. use this to fix the "none" authentication method regression reported by Nam Nguyen via bugs@ ok deraadt@ OpenBSD-Commit-ID: 8cd188dc3a83aa8abe5b7693e762975cd8ea8a17 commit 0074aa2c8d605ee7587279a22cdad4270b4ddd07 Author: jmc@openbsd.org Date: Wed Dec 22 06:56:41 2021 +0000 upstream: sort -H and -h in SYNOPSIS/usage(); tweak the -H text; ok djm OpenBSD-Commit-ID: 90721643e41e9e09deb5b776aaa0443456ab0965 commit 1c9853a68b2319f2e5f929179735e8fbb9988a67 Author: Darren Tucker Date: Wed Dec 22 19:33:10 2021 +1100 Use SHA.*_HMAC_BLOCK_SIZE if needed. If the platform has a native SHA2, does not define SHA.*_BLOCK_LENGTH but does define SHA.*_HMAC_BLOCK_SIZE (eg Solaris) then use the latter. Should fix --without-openssl build on Solaris. commit 715c892f0a5295b391ae92c26ef4d6a86ea96e8e Author: Damien Miller Date: Wed Dec 22 09:02:50 2021 +1100 remove sys/param.h in -portable, after upstream commit 7a7c69d8b4022b1e5c0afb169c416af8ce70f3e8 Author: Damien Miller Date: Mon Dec 20 13:05:20 2021 +1100 add agent-restrict.sh file, missed in last commit commit f539136ca51a4976644db5d0be8158cc1914c72a Author: djm@openbsd.org Date: Sun Dec 19 22:20:12 2021 +0000 upstream: regression test for destination restrictions in ssh-agent OpenBSD-Regress-ID: 3c799d91e736b1753b4a42d80c42fc40de5ad33d commit 6e4980eb8ef94c04874a79dd380c3f568e8416d6 Author: anton@openbsd.org Date: Sat Dec 18 06:53:59 2021 +0000 upstream: Make use of ntests variable, pointed out by clang 13. OpenBSD-Regress-ID: 4241a3d21bdfa1630ed429b6d4fee51038d1be72 commit 3eead8158393b697f663ec4301e3c7b6f24580b1 Author: deraadt@openbsd.org Date: Tue Dec 14 21:25:27 2021 +0000 upstream: sys/param.h cleanup, mostly using MINIMUM() and ok dtucker OpenBSD-Regress-ID: 172a4c45d3bcf92fa6cdf6c4b9db3f1b3abe4db0 commit 266678e19eb0e86fdf865b431b6e172e7a95bf48 Author: djm@openbsd.org Date: Sun Dec 19 22:15:42 2021 +0000 upstream: document host-bound publickey authentication OpenBSD-Commit-ID: ea6ed91779a81f06d961e30ecc49316b3d71961b commit 3d00024b3b156aa9bbd05d105f1deb9cb088f6f7 Author: djm@openbsd.org Date: Sun Dec 19 22:15:21 2021 +0000 upstream: document agent protocol extensions OpenBSD-Commit-ID: 09e8bb391bbaf24c409b75a4af44e0cac65405a7 commit c385abf76511451bcba78568167b1cd9e90587d5 Author: djm@openbsd.org Date: Sun Dec 19 22:14:47 2021 +0000 upstream: PubkeyAuthentication=yes|no|unbound|host-bound Allow control over which pubkey methods are used. Added out of concern that some hardware devices may have difficulty signing the longer pubkey authentication challenges. This provides a way for them to disable the extension. It's also handy for testing. feedback / ok markus@ OpenBSD-Commit-ID: ee52580db95c355cf6d563ba89974c210e603b1a commit 34b1e9cc7654f41cd4c5b1cc290b999dcf6579bb Author: djm@openbsd.org Date: Sun Dec 19 22:14:12 2021 +0000 upstream: document destination-constrained keys feedback / ok markus@ OpenBSD-Commit-ID: cd8c526c77268f6d91c06adbee66b014d22d672e commit a6d7677c4abcfba268053e5867f2acabe3aa371b Author: djm@openbsd.org Date: Sun Dec 19 22:13:55 2021 +0000 upstream: Use hostkey parsed from hostbound userauth request Require host-bound userauth requests for forwarded SSH connections. The hostkey parsed from the host-bound userauth request is now checked against the most recently bound session ID / hostkey on the agent socket and the signature refused if they do not match. ok markus@ OpenBSD-Commit-ID: d69877c9a3bd8d1189a5dbdeceefa432044dae02 commit baaff0ff4357cc5a079621ba6e2d7e247b765061 Author: djm@openbsd.org Date: Sun Dec 19 22:13:33 2021 +0000 upstream: agent support for parsing hostkey-bound signatures Allow parse_userauth_request() to work with blobs from publickey-hostbound-v00@openssh.com userauth attempts. Extract hostkey from these blobs. ok markus@ OpenBSD-Commit-ID: 81c064255634c1109477dc65c3e983581d336df8 commit 3e16365a79cdeb2d758cf1da6051b1c5266ceed7 Author: djm@openbsd.org Date: Sun Dec 19 22:13:12 2021 +0000 upstream: EXT_INFO negotiation of hostbound pubkey auth the EXT_INFO packet gets a new publickey-hostbound@openssh.com to advertise the hostbound public key method. Client side support to parse this feature flag and set the kex->flags indicator if the expected version is offered (currently "0"). ok markus@ OpenBSD-Commit-ID: 4cdb2ca5017ec1ed7a9d33bda95c1d6a97b583b0 commit 94ae0c6f0e35903b695e033bf4beacea1d376bb1 Author: djm@openbsd.org Date: Sun Dec 19 22:12:54 2021 +0000 upstream: client side of host-bound pubkey authentication Add kex->flags member to enable the publickey-hostbound-v00@openssh.com authentication method. Use the new hostbound method in client if the kex->flags flag was set, and include the inital KEX hostkey in the userauth request. Note: nothing in kex.c actually sets the new flag yet ok markus@ OpenBSD-Commit-ID: 5a6fce8c6c8a77a80ee1526dc467d91036a5910d commit 288fd0218dbfdcb05d9fbd1885904bed9b6d42e6 Author: djm@openbsd.org Date: Sun Dec 19 22:12:30 2021 +0000 upstream: sshd side of hostbound public key auth This is identical to the standard "publickey" method, but it also includes the initial server hostkey in the message signed by the client. feedback / ok markus@ OpenBSD-Commit-ID: 7ea01bb7238a560c1bfb426fda0c10a8aac07862 commit dbb339f015c33d63484261d140c84ad875a9e548 Author: djm@openbsd.org Date: Sun Dec 19 22:12:07 2021 +0000 upstream: prepare for multiple names for authmethods allow authentication methods to have one additional name beyond their primary name. allow lookup by this synonym Use primary name for authentication decisions, e.g. for PermitRootLogin=publickey Pass actual invoked name to the authmethods, so they can tell whether they were requested via the their primary name or synonym. ok markus@ OpenBSD-Commit-ID: 9e613fcb44b8168823195602ed3d09ffd7994559 commit 39f00dcf44915f20684160f0a88d3ef8a3278351 Author: djm@openbsd.org Date: Sun Dec 19 22:11:39 2021 +0000 upstream: ssh-agent side of destination constraints Gives ssh-agent the ability to parse restrict-destination-v00@openssh.com constraints and to apply them to keys. Check constraints against the hostkeys recorded for a SocketEntry when attempting a signature, adding, listing or deleting keys. Note that the "delete all keys" request will remove constrained keys regardless of location. feedback Jann Horn & markus@ ok markus@ OpenBSD-Commit-ID: 84a7fb81106c2d609a6ac17469436df16d196319 commit ce943912df812c573a33d00bf9e5435b7fcca3f7 Author: djm@openbsd.org Date: Sun Dec 19 22:11:06 2021 +0000 upstream: ssh-add side of destination constraints Have ssh-add accept a list of "destination constraints" that allow restricting where keys may be used in conjunction with a ssh-agent/ssh that supports session ID/hostkey binding. Constraints are specified as either "[user@]host-pattern" or "host-pattern>[user@]host-pattern". The first form permits a key to be used to authenticate as the specified user to the specified host. The second form permits a key that has previously been permitted for use at a host to be available via a forwarded agent to an additional host. For example, constraining a key with "user1@host_a" and "host_a>host_b". Would permit authentication as "user1" at "host_a", and allow the key to be available on an agent forwarded to "host_a" only for authentication to "host_b". The key would not be visible on agent forwarded to other hosts or usable for authentication there. Internally, destination constraints use host keys to identify hosts. The host patterns are used to obtain lists of host keys for that destination that are communicated to the agent. The user/hostkeys are encoded using a new restrict-destination-v00@openssh.com key constraint. host keys are looked up in the default client user/system known_hosts files. It is possible to override this set on the command-line. feedback Jann Horn & markus@ ok markus@ OpenBSD-Commit-ID: 6b52cd2b637f3d29ef543f0ce532a2bce6d86af5 commit 5e950d765727ee0b20fc3d2cbb0c790b21ac2425 Author: djm@openbsd.org Date: Sun Dec 19 22:10:24 2021 +0000 upstream: ssh-add side of destination constraints Have ssh-add accept a list of "destination constraints" that allow restricting where keys may be used in conjunction with a ssh-agent/ssh that supports session ID/hostkey binding. Constraints are specified as either "[user@]host-pattern" or "host-pattern>[user@]host-pattern". The first form permits a key to be used to authenticate as the specified user to the specified host. The second form permits a key that has previously been permitted for use at a host to be available via a forwarded agent to an additional host. For example, constraining a key with "user1@host_a" and "host_a>host_b". Would permit authentication as "user1" at "host_a", and allow the key to be available on an agent forwarded to "host_a" only for authentication to "host_b". The key would not be visible on agent forwarded to other hosts or usable for authentication there. Internally, destination constraints use host keys to identify hosts. The host patterns are used to obtain lists of host keys for that destination that are communicated to the agent. The user/hostkeys are encoded using a new restrict-destination-v00@openssh.com key constraint. host keys are looked up in the default client user/system known_hosts files. It is possible to override this set on the command-line. feedback Jann Horn & markus@ ok markus@ OpenBSD-Commit-ID: ef47fa9ec0e3c2a82e30d37ef616e245df73163e commit 4c1e3ce85e183a9d0c955c88589fed18e4d6a058 Author: djm@openbsd.org Date: Sun Dec 19 22:09:23 2021 +0000 upstream: ssh-agent side of binding record session ID/hostkey/forwarding status for each active socket. Attempt to parse data-to-be-signed at signature request time and extract session ID from the blob if it is a pubkey userauth request. ok markus@ OpenBSD-Commit-ID: a80fd41e292b18b67508362129e9fed549abd318 commit e9497ecf73f3c16667288bce48d4e3d7e746fea1 Author: djm@openbsd.org Date: Sun Dec 19 22:08:48 2021 +0000 upstream: ssh client side of binding send session ID, hostkey, signature and a flag indicating whether the agent connection is being forwarded to ssh agent each time a connection is opened via a new "session-bind@openssh.com" agent extension. ok markus@ OpenBSD-Commit-ID: 2f154844fe13167d3ab063f830d7455fcaa99135 commit b42c61d6840d16ef392ed0f365e8c000734669aa Author: djm@openbsd.org Date: Sun Dec 19 22:08:06 2021 +0000 upstream: Record session ID, host key and sig at intital KEX These will be used later for agent session ID / hostkey binding ok markus@ OpenBSD-Commit-ID: a9af29e33772b18e3e867c6fa8ab35e1694a81fe commit 26ca33d186473d58a32d812e19273ce078b6ffff Author: djm@openbsd.org Date: Tue Dec 7 22:06:45 2021 +0000 upstream: better error message for FIDO keys when we can't match them to a token OpenBSD-Commit-ID: 58255c2a1980088f4ed144db67d879ada2607650 commit adb0ea006d7668190f0c42aafe3a2864d352e34a Author: Darren Tucker Date: Wed Dec 15 10:50:33 2021 +1100 Correct value for IPTOS_DSCP_LE. It needs to allow for the preceeding two ECN bits. From daisuke.higashi at gmail.com via OpenSSH bz#3373, ok claudio@, job@, djm@. commit 3dafd3fe220bd9046f11fcf5191a79ec8800819f Author: Darren Tucker Date: Fri Dec 10 11:57:30 2021 +1100 Increase timeout for test step. commit 5aefb05cd5b843e975b191d6ebb7ddf8de35c112 Author: Darren Tucker Date: Fri Dec 10 10:27:27 2021 +1100 Update the list of tests that don't work on Minix. While there, remove CC (configure will now find clang) and make the test list easier to update via cut and paste. commit 1c09bb1b2e207d091cec299c49416c23d24a1b31 Author: Darren Tucker Date: Fri Dec 10 10:12:57 2021 +1100 Add minix host tuple. Define SETEUID_BREAKS_SETUID for it which should make privsep work. commit a2188579032cf080213a78255373263466cb90cc Author: jsg@openbsd.org Date: Sun Dec 5 12:28:27 2021 +0000 upstream: fix unintended sizeof pointer in debug path ok markus@ OpenBSD-Commit-ID: b9c0481ffc0cd801e0840e342e6a282a85aac93c commit da40355234068c82f1a36196f2d18dd2d81aaafd Author: naddy@openbsd.org Date: Sat Dec 4 00:05:39 2021 +0000 upstream: RSA/SHA-1 is not used by default anymore on the server OpenBSD-Commit-ID: 64abef6cfc3e53088225f6b8a1dcd86d52dc8353 commit e9c71498a083a8b502aa831ea931ce294228eda0 Author: djm@openbsd.org Date: Thu Dec 2 23:45:36 2021 +0000 upstream: hash full host:port when asked to hash output, fixes hashes for non- default ports. bz3367 ok dtucker@ OpenBSD-Commit-ID: 096021cc847da7318ac408742f2d0813ebe9aa73 commit b5601202145a03106012c22cb8980bcac2949f0b Author: djm@openbsd.org Date: Thu Dec 2 23:23:13 2021 +0000 upstream: improve the testing of credentials against inserted FIDO keys a little more: ask the token whether a particular key belongs to it in cases where the token support on-token user- verification (e.g. biometrics) rather than just assuming that it will accept it. Will reduce spurious "Confirm user presence" notifications for key handles that relate to FIDO keys that are not currently inserted in at least some cases. Motivated by bz3366; by Pedro Martelletto OpenBSD-Commit-ID: ffac7f3215842397800e1ae2e20229671a55a63d commit ca709e27c41c90f4565b17282c48dca7756e083c Author: djm@openbsd.org Date: Thu Dec 2 22:40:05 2021 +0000 upstream: move check_sk_options() up so we can use it earlier OpenBSD-Commit-ID: 67fe98ba1c846d22035279782c4664c1865763b4 commit b711bc01a7ec76bb6a285730990cbce9b8ca5773 Author: dtucker@openbsd.org Date: Thu Dec 2 22:35:05 2021 +0000 upstream: ssh-rsa is no longer in the default for PubkeyAcceptedAlgorithms. OpenBSD-Commit-ID: 34a9e1bc30966fdcc922934ae00f09f2596cd73c commit dc91ceea33cd4a9f05be953e8d8062f732db5c8a Author: djm@openbsd.org Date: Thu Dec 2 02:44:44 2021 +0000 upstream: don't put the tty into raw mode when SessionType=none, avoids ^c being unable to kill such a session. bz3360; ok dtucker@ OpenBSD-Commit-ID: 83960c433052303b643b4c380ae2f799ac896f65 commit e6e7d2654a13ba10141da7b42ea683ea4eeb1f38 Author: Damien Miller Date: Mon Nov 29 14:11:03 2021 +1100 previous commit broke bcrypt_pbkdf() Accidentally reverted part of the conversion to use SHA512 from SUPERCOP instead of OpenBSD-style libc SHA512. commit c0459588b8d00b73e506c6095958ecfe62a4a7ba Author: Darren Tucker Date: Mon Nov 29 14:03:19 2021 +1100 Fix typo in Neils' name. commit 158bf854e2a22cf09064305f4a4e442670562685 Author: Damien Miller Date: Mon Nov 29 12:30:22 2021 +1100 sync bcrypt-related files with OpenBSD The main change is that Niels Provos kindly agreed to rescind the BSD license advertising clause, shifting them to the 3-term BSD license. This was the last thing in OpenSSH that used the advertising clause. commit e8976d92a42883ff6b8991438f07df60c2c0d82d Author: Damien Miller Date: Mon Nov 29 12:29:29 2021 +1100 depend commit 8249afeec013e557fe7491a72ca3285de03e25b1 Author: djm@openbsd.org Date: Sun Nov 28 07:21:26 2021 +0000 upstream: sshsig: return "key not found" when searching empty files rather than "internal error" OpenBSD-Commit-ID: e2ccae554c78d7a7cd33fc5d217f35be7e2507ed commit 9e3227d4dbb5ad9c9091b4c14982cab4bba87b4d Author: djm@openbsd.org Date: Sun Nov 28 07:15:10 2021 +0000 upstream: ssh-keygen -Y match-principals doesn't accept any -O options at present, so don't say otherwise in SYNOPSIS; spotted jmc@ OpenBSD-Commit-ID: 9cc43a18f4091010741930b48b3db2f2e4f1d35c commit 56db1f4a4cf5039fc3b42e84c4b16291fdff32b1 Author: djm@openbsd.org Date: Sun Nov 28 07:14:29 2021 +0000 upstream: fix indenting in last commit OpenBSD-Commit-ID: 8b9ba989815d0dec1fdf5427a4a4b58eb9cac4d2 commit 50bea24a9a9bdebad327c76e700def3261f5694e Author: djm@openbsd.org Date: Sun Nov 28 07:10:18 2021 +0000 upstream: missing initialisation for oerrno OpenBSD-Commit-ID: 05d646bba238080259bec821c831a6f0b48d2a95 commit 5a0f4619041d09cd29f3a08da41db5040372bdd1 Author: Darren Tucker Date: Sun Nov 28 15:31:37 2021 +1100 Correct ifdef to activate poll() only if needed. commit d4035c81a71237f690edd7eda32bef7d63fd9528 Author: djm@openbsd.org Date: Sat Nov 27 07:23:35 2021 +0000 upstream: whitespac e OpenBSD-Regress-ID: b9511d41568056bda489e13524390167889908f8 commit a443491e6782ef0f5a8bb87a5536c8ee4ff233a1 Author: djm@openbsd.org Date: Sat Nov 27 07:20:58 2021 +0000 upstream: regression test for match-principals. Mostly by Fabian Stelzer OpenBSD-Regress-ID: ced0bec89af90935103438986bbbc4ad1df9cfa7 commit 78230b3ec8cbabc1e7de68732dc5cbd4837c6675 Author: djm@openbsd.org Date: Sat Nov 27 07:14:46 2021 +0000 upstream: Add ssh-keygen -Y match-principals operation to perform matching of principals names against an allowed signers file. Requested by and mostly written by Fabian Stelzer, towards a TOFU model for SSH signatures in git. Some tweaks by me. "doesn't bother me" deraadt@ OpenBSD-Commit-ID: 8d1b71f5a4127bc5e10a880c8ea6053394465247 commit 15db86611baaafb24c40632784dabf82e3ddb1a7 Author: djm@openbsd.org Date: Thu Nov 25 23:02:24 2021 +0000 upstream: debug("func: ...") -> debug_f("...") OpenBSD-Commit-ID: d58494dc05c985326a895adfbe16fbd5bcc54347 commit b7ffbb17e37f59249c31f1ff59d6c5d80888f689 Author: Darren Tucker Date: Fri Nov 19 18:53:46 2021 +1100 Allow for fd = -1 in compat ppoll overflow check. Fixes tests on at least FreeBSD 6, possibly others. commit 04b172da5b96a51b0d55c905b423ababff9f4e0b Author: Darren Tucker Date: Fri Nov 19 16:01:51 2021 +1100 Don't auto-enable Capsicum sandbox on FreeBSD 9/10. Since we changed from select() to ppoll() tests have been failing. This seems to be because FreeBSD 10 (and presumably 9) do not allow ppoll() in the privsep process and sshd will fail with "Not permitted in capability mode". Setting CAP_EVENT on the FDs doesn't help, but weirdly, poll() works without that. Those versions are EOL so this situation is unlikely to change. commit a823f39986e7b879f26412e64c15630e1cfa0dc5 Author: djm@openbsd.org Date: Thu Nov 18 03:53:48 2021 +0000 upstream: regression test for ssh-keygen -Y find-principals fix; from Fabian Stelzer ok djm markus OpenBSD-Regress-ID: 34fe4088854c1a2eb4c0c51cc4676ba24096bac4 commit 199c4df66c0e39dd5c3333b162af274678c0501d Author: djm@openbsd.org Date: Thu Nov 18 21:32:11 2021 +0000 upstream: less confusing debug message; bz#3365 OpenBSD-Commit-ID: 836268d3642c2cdc84d39b98d65837f5241e4a50 commit 97f9b6e61316c97a32dad94b7a37daa9b5f6b836 Author: djm@openbsd.org Date: Thu Nov 18 21:11:01 2021 +0000 upstream: avoid xmalloc(0) for PKCS#11 keyid for ECDSA keys (we already did this for RSA keys). Avoids fatal errors for PKCS#11 libraries that return empty keyid, e.g. Microchip ATECC608B "cryptoauthlib"; bz#3364 OpenBSD-Commit-ID: 054d4dc1d6a99a2e6f8eebc48207b534057c154d commit c74aa0eb73bd1edf79947d92d9c618fc3424c4a6 Author: djm@openbsd.org Date: Thu Nov 18 03:50:41 2021 +0000 upstream: ssh-keygen -Y find-principals was verifying key validity when using ca certs but not with simple key lifetimes within the allowed signers file. Since it returns the first keys principal it finds this could result in a principal with an expired key even though a valid one is just below. patch from Fabian Stelzer; feedback/ok djm markus OpenBSD-Commit-ID: b108ed0a76b813226baf683ab468dc1cc79e0905 commit d902d728dfd81622454260e23bc09d5e5a9a795e Author: Darren Tucker Date: Thu Nov 18 23:44:07 2021 +1100 Correct calculation of tv_nsec in poll(). commit 21dd5a9a3fb35e8299a1fbcf8d506f1f6b752b85 Author: Darren Tucker Date: Thu Nov 18 23:11:37 2021 +1100 Add compat implementation of ppoll using pselect. commit b544ce1ad4afb7ee2b09f714aa63efffc73fa93a Author: Darren Tucker Date: Thu Nov 18 23:05:34 2021 +1100 Put poll.h inside ifdef HAVE_POLL_H. commit 875408270c5a7dd69ed5449e5d85bd7120c88f70 Author: djm@openbsd.org Date: Thu Nov 18 03:31:44 2021 +0000 upstream: check for POLLHUP wherever we check for POLLIN OpenBSD-Commit-ID: 6aa6f3ec6b17c3bd9bfec672a917f003a76d93e5 commit 36b5e37030d35bbaa18ba56825b1af55971d18a0 Author: djm@openbsd.org Date: Thu Nov 18 03:07:59 2021 +0000 upstream: fd leak in sshd listen loop error path; from Gleb Smirnoff OpenBSD-Commit-ID: a7a2be27a690a74bf2381bc16cea38e265657412 commit b99498d0c93f1edd04857b318308a66b28316bd8 Author: djm@openbsd.org Date: Thu Nov 18 03:07:20 2021 +0000 upstream: check for POLLHUP as well as POLLIN in sshd listen loop; ok deraadt millert OpenBSD-Commit-ID: a4f1244c5a9c2b08dac4f3b1dc22e9d1dc60c587 commit 1f3055d788e8cf80851eb1728b535d57eb0dba6a Author: djm@openbsd.org Date: Thu Nov 18 03:06:03 2021 +0000 upstream: check for POLLHUP as well as POLLIN, handle transient IO errors as well as half-close on the output side; ok deraadt millert OpenBSD-Commit-ID: de5c5b9939a37476d256328cbb96305bdecf511e commit 9778a15fa6dbdac6a95bf15865c2688b4bd6944e Author: Damien Miller Date: Thu Nov 18 10:16:55 2021 +1100 adjust seccomp filter for select->poll conversion Needed to add ppoll syscall but also to relax the fallback rlimit sandbox. Linux poll() fails with EINVAL if npfds > RLIMIT_NOFILE, so we have to allow a single fd in the rlimit. commit fcd8d895bbb849c64f0aed934e3303d37f696f5d Author: Damien Miller Date: Thu Nov 18 10:16:44 2021 +1100 update depends commit 76292787a1e93e668f10e36b4bf59ce0ae28e156 Author: Damien Miller Date: Thu Nov 18 09:26:20 2021 +1100 compat for timespecsub() and friends commit fd7e7de4ddb4399c7e929b44f2bbfc118eddfcf8 Author: djm@openbsd.org Date: Wed Nov 17 21:06:39 2021 +0000 upstream: set num_listen_socks to 0 on close-all instead of -1, which interferes with the new poll()-based listen loop; spotted and debugged by anton@+deraadt@ OpenBSD-Commit-ID: f7ab8ab124f615a2e0c45fee14c38d2f2abbabbd commit fd9343579afac30a971f06643a669733d9acb407 Author: deraadt@openbsd.org Date: Sun Nov 14 18:47:43 2021 +0000 upstream: use ppoll() instead of pselect() with djm OpenBSD-Commit-ID: 980f87c9564d5d2ad55722b7a6f44f21284cd215 commit 092d29b232ef1a19609a5316ed7e4d896bb2e696 Author: deraadt@openbsd.org Date: Sun Nov 14 06:15:36 2021 +0000 upstream: match .events with .fd better OpenBSD-Commit-ID: 77eef212ca0add905949532af390164489c5984b commit 8d642c9a90fa4ed5a3effd785fb3591e14de00cd Author: deraadt@openbsd.org Date: Sun Nov 14 03:25:10 2021 +0000 upstream: convert select() to poll() ok djm OpenBSD-Commit-ID: b53e4940ff10dd24f8d16e8db8ef1970015d7ead commit 6582a31c388968f4073af2bd8621880735c3d42b Author: deraadt@openbsd.org Date: Sat Nov 13 21:14:13 2021 +0000 upstream: replace select() with ppoll(), including converting timeval's to timespec's to make things easier. back and forth and ok; djm OpenBSD-Commit-ID: 89d3b23c60875da919e7820f9de6213286ffbec9 commit 7c025c005550c86a40200a2bcdd355d09413d61a Author: deraadt@openbsd.org Date: Sat Nov 13 17:26:13 2021 +0000 upstream: It really looks like pledge "stdio dns" is possible earlier. Discussed with mestre OpenBSD-Commit-ID: 610873de63a593e0ac7bbbcb7a0f2894d36f4c01 commit 06acb04c20ee483fe4757bd12aec870cc4bb1076 Author: deraadt@openbsd.org Date: Fri Nov 12 05:23:49 2021 +0000 upstream: aggressively pre-fill the pollfd array with fd=-1 OpenBSD-Commit-ID: c2a525de8f83c1a04405bd79122c424140552a5b commit 7eec76793dec06e8f06b6cf71f9473141c69d109 Author: deraadt@openbsd.org Date: Thu Nov 11 15:32:32 2021 +0000 upstream: Convert from select() to ppoll(). Along the way, I observed that the select() code was using exceptfds incorrectly.. ok millert OpenBSD-Commit-ID: 548e05bfc31b2af02319eb3d051286d4128dec96 commit e665ed2d0c24fe11d5470ce72fa1e187377d3fc4 Author: Darren Tucker Date: Fri Nov 12 22:55:27 2021 +1100 Switch from LibreSSL 3.4.0 to 3.4.1. The LibreSSL 3.4.0 release has an OPENBSD_BRANCH that points to "master" and that branch no longer has the files LibreSSL expects and thus it will no longer build, breaking the test. commit 21b6b5a06c8c53c548d25e6074c5240e88e2ef34 Author: djm@openbsd.org Date: Wed Nov 10 06:29:25 2021 +0000 upstream: add the sntrup761x25519-sha512@openssh.com hybrid ECDH/x25519 + Streamlined NTRU Prime post-quantum KEX to the default KEXAlgorithms list (after the ECDH methods but before the prime-group DH ones). ok markus@ OpenBSD-Commit-ID: 22b77e27a04e497a10e22f138107579652854210 commit 239da797cbf07a640d7b1ea02d3f99ace3ef792d Author: djm@openbsd.org Date: Wed Nov 10 06:25:08 2021 +0000 upstream: fix ssh-keysign for KEX algorithms that use SHA384/512 exchange hashes; feedback/ok markus@ OpenBSD-Commit-ID: 09a8fda1c081f5de1e3128df64f28b7bdadee239 commit 6997a592ecb1013df0c6d7f8df3e6517827aef11 Author: djm@openbsd.org Date: Mon Nov 8 21:32:49 2021 +0000 upstream: improve error message when trying to expand a ~user path for a user that doesn't exist; better matches what the shell does ok deraadt@ OpenBSD-Commit-ID: 1ddefa3c3a78b69ce13d1b8f67bc9f2cefd23ad6 commit 10b899a15c88eb40eb5f73cd0fa84ef0966f79c9 Author: Darren Tucker Date: Wed Nov 10 12:34:25 2021 +1100 Don't trust closefrom() on Linux. glibc's closefrom implementation does not work in a chroot when the kernel does not have close_range. It tries to read from /proc/self/fd and when that fails dies with an assertion of sorts. Instead, call close_range ourselves from our compat code and fall back if that fails. bz#3349, with william.wilson at canonical.com and fweimer at redhat.com. commit eb1f63195a9a38b519536a5b398d9939261ec081 Author: dtucker@openbsd.org Date: Sat Nov 6 10:13:39 2021 +0000 upstream: Plug a couple of minor mem leaks. From beldmit at gmail.com via github PR#283, ok markus@ OpenBSD-Commit-ID: ec1fa7d305d46226861c3ca6fb9c9beb2ada2892 commit e4f501bf1d3b53f1cc23d9521fd7c5163307b760 Author: djm@openbsd.org Date: Fri Nov 5 03:10:58 2021 +0000 upstream: move cert_filter_principals() to earlier in the file for reuse; no code change OpenBSD-Commit-ID: 598fa9528b656b2f38bcc3cf5b6f3869a8c115cf commit 59c60f96fee321c7f38f00372826d37f289534af Author: deraadt@openbsd.org Date: Wed Nov 3 22:00:56 2021 +0000 upstream: Many downstreams expect ssh to compile as non-C99... OpenBSD-Commit-ID: e6aa3e08bda68e5fb838fc8a49b1d2dfc38ee783 commit 7a78fe63b0b28ef7231913dfefe9d08f9bc41c61 Author: Darren Tucker Date: Sat Nov 6 21:07:03 2021 +1100 Skip getline() on HP-UX 10.x. HP-UX 10.x has a getline() implementation in libc that does not behave as we expect so don't use it. With correction from Thorsten Glaser and typo fix from Larkin Nickle. commit 343ae252ebb35c6ecae26b447bf1551a7666720e Author: Damien Miller Date: Wed Nov 3 12:08:21 2021 +1100 basic SECURITY.md (refers people to the website) commit ed45a0168638319e0a710633f6085b96b9cec656 Author: djm@openbsd.org Date: Tue Nov 2 22:57:27 2021 +0000 upstream: crank SSH_SK_VERSION_MAJOR to match recent change in usr/bin/ssh OpenBSD-Regress-ID: 113d181c7e3305e138db9b688cdb8b0a0019e552 commit f3c34df860c4c1ebddacb973954e58167d9dbade Author: djm@openbsd.org Date: Tue Nov 2 22:56:40 2021 +0000 upstream: Better handle FIDO keys on tokens that provide user verification (UV) on the device itself, including biometric keys. Query the token during key creation to determine whether it supports on-token UV and, if so, clear the SSH_SK_USER_VERIFICATION_REQD flag in the key so that ssh(1) doesn't automatically prompty for PIN later. When making signatures with the key, query the token's capabilities again and check whether the token is able (right now) to perform user- verification without a PIN. If it is then the PIN prompt is bypassed and user verification delegated to the token. If not (e.g. the token is biometric capable, but no biometric are enrolled), then fall back to user verification via the usual PIN prompt. Work by Pedro Martelletto; ok myself and markus@ NB. cranks SSH_SK_VERSION_MAJOR OpenBSD-Commit-ID: e318a8c258d9833a0b7eb0236cdb68b5143b2f27 commit 0328a081f38c09d2d4d650e94461a47fb5eef536 Author: djm@openbsd.org Date: Fri Oct 29 03:03:06 2021 +0000 upstream: sshsig: add tests for signing key validity and find-principals - adds generic find-principals tests (this command had none before) - tests certs with a timeboxed validity both with and without a restriced lifetime for the CA - test for a revoked CA cert by Fabian Stelzer OpenBSD-Regress-ID: 9704b2c6df5b8ccfbdf2c06c5431f5f8cad280c9 commit ccd358e1e25e25c13f0825996283cbf7a1647a3b Author: djm@openbsd.org Date: Fri Oct 29 02:48:19 2021 +0000 upstream: avoid signedness warning; spotted in -portable OpenBSD-Regress-ID: 4cacc126086487c0ea7f3d86b42dec458cf0d0c6 commit 2741f52beb11490d7033a25e56ed0496f0c78006 Author: djm@openbsd.org Date: Fri Oct 29 03:20:46 2021 +0000 upstream: ssh-keygen: make verify-time argument parsing optional From Fabian Stelzer OpenBSD-Commit-ID: 1ff35e4c366a45a073663df90381be6a8ef4d370 commit a1217d363b88b32cfe54c4f02c6c1cf4bdefdd23 Author: Damien Miller Date: Fri Oct 29 13:48:34 2021 +1100 unbreak fuzz harness for recent changes commit 68e522ed8183587c9367fa3842c5b75f64f3d12b Author: Darren Tucker Date: Fri Oct 29 13:32:24 2021 +1100 Use -Wbitwise-instead-of-logical if supported. commit be28b23012aa3fa323be7ec84863cf238927c078 Author: Damien Miller Date: Thu Oct 28 16:24:53 2021 +1100 use -Wmisleading-indentation cflag if available ok dtucker@ commit 2e6f5f24dd2f9217f4ab8b737ed428d5d5278f91 Author: Damien Miller Date: Thu Oct 28 16:24:44 2021 +1100 depend commit a5ab4882348d26addc9830a44e053238dfa2cb58 Author: Damien Miller Date: Thu May 6 10:08:30 2021 +1000 remove built-in support for md5crypt() Users of MD5-hashed password should arrange for ./configure to link against libxcrypt or similar. Though it would be better to avoid use of MD5 password hashing entirely, it's arguably worse than DEScrypt. feedback and ok dtucker@ commit c5de1fffa6328b8246b87da28fa9df05813f76a3 Author: djm@openbsd.org Date: Thu Oct 28 02:55:30 2021 +0000 upstream: increment SSH_SK_VERSION_MAJOR to match last change OpenBSD-Regress-ID: 17873814d1cbda97f49c8528d7b5ac9cadf6ddc0 commit 0001d04e55802d5bd9d6dece1081a99aa4ba2828 Author: djm@openbsd.org Date: Thu Oct 28 02:54:18 2021 +0000 upstream: When downloading resident keys from a FIDO token, pass back the user ID that was used when the key was created and append it to the filename the key is written to (if it is not the default). Avoids keys being clobbered if the user created multiple resident keys with the same application string but different user IDs. feedback Pedro Martelletto; ok markus NB. increments SSH_SK_VERSION_MAJOR OpenBSD-Commit-ID: dbd658b5950f583106d945641a634bc6562dd3a3 commit d4bed5445646e605c383a4374fa962e23bf9e3a3 Author: deraadt@openbsd.org Date: Sun Oct 24 21:24:17 2021 +0000 upstream: For open/openat, if the flags parameter does not contain O_CREAT, the 3rd (variadic) mode_t parameter is irrelevant. Many developers in the past have passed mode_t (0, 044, 0644, or such), which might lead future people to copy this broken idiom, and perhaps even believe this parameter has some meaning or implication or application. Delete them all. This comes out of a conversation where tb@ noticed that a strange (but intentional) pledge behaviour is to always knock-out high-bits from mode_t on a number of system calls as a safety factor, and his bewilderment that this appeared to be happening against valid modes (at least visually), but no sorry, they are all irrelevant junk. They could all be 0xdeafbeef. ok millert OpenBSD-Commit-ID: 503d11633497115688c0c6952686524f01f53121 commit d575cf44895104e0fcb0629920fb645207218129 Author: Darren Tucker Date: Fri Oct 22 23:27:41 2021 +1100 kitchensink test target now needs krb5. commit 4ae39cada214e955bcfd3448ff28f0ed18886706 Author: Darren Tucker Date: Fri Oct 22 22:54:33 2021 +1100 Test both MIT KRB5 and Heimdal. commit 22b2681d88619e5247dc53c9f112058a7e248d48 Author: dtucker@openbsd.org Date: Fri Oct 22 10:51:57 2021 +0000 upstream: Plug mem addrinfo mem leaks. Prevent mem leaks in the (unlikely) event that getaddrinfo returns no addresses. ALso, remove an unneeded NULL check in addr_ntop. From khaleesicodes via github PR#281, ok deraadt@ OpenBSD-Commit-ID: e8a5afc686376637c355c5f7e122dc4b080b9c1a commit 27c8c343b610263f83ac2328735feeb881c6c92f Author: dtucker@openbsd.org Date: Fri Oct 22 09:22:04 2021 +0000 upstream: Remove unnecessary semicolons ... in case statements. From khaleesicodes via github PR#280. OpenBSD-Commit-ID: e1e89360b65775cff83e77ce040b342015caf4ed commit e7eb73b8d1fe1008d92433ea949491ce654bfaba Author: dtucker@openbsd.org Date: Fri Oct 22 09:19:34 2021 +0000 upstream: Fix typos in comments. From khaleesicodes via github PR#280. OpenBSD-Commit-ID: 26fdd83652c40f098bf7c685e8ebb9eb72cc45fc commit 052a9d8494175e24312daa6c132665e58c17fe6e Author: deraadt@openbsd.org Date: Fri Oct 15 14:46:46 2021 +0000 upstream: switch scp(1) back to sftp protocol. openbsd 7.0 release shipped with the (hopefully last) scp that uses RCP protocol for copying. Let's get back to testing the SFTP protocol. OpenBSD-Commit-ID: 9eaa35d95fd547b78b0a043b3f518e135f151f30 commit a07664646bf6d293f5bbd45a5de54f3c36bb85da Author: Darren Tucker Date: Fri Oct 22 14:00:05 2021 +1100 Source configs script so setup_ci can use settings commit 34df52c201c6b47e5a46b50c215e4d98a8bf6587 Author: Darren Tucker Date: Fri Oct 22 09:42:14 2021 +1100 Install libedit and pam based on config flags. commit 8c626cc563e8d21d844d06f9971a9ee01de6aa2a Author: Darren Tucker Date: Thu Oct 21 16:53:39 2021 +1100 Don't use 'here string", it's not POSIX. commit 086a4b5977472aefa3de918b88efad0faf83b2b1 Author: Darren Tucker Date: Thu Oct 21 15:33:27 2021 +1100 Remove -Werror from compiler package to install. commit 5a7a4687507d057f9b5e7497f3d3f82e64753c02 Author: Darren Tucker Date: Thu Oct 21 15:00:53 2021 +1100 Build with -Werror on most recent gcc and clang. commit 4d2cbdb525d673acf941d48a7044fcf03125611a Author: Darren Tucker Date: Fri Oct 15 12:59:06 2021 +1100 Include string.h and stdio.h for strerror. commit fff13aaa262b7b3ec83ed21e29674cbf331780a7 Author: Darren Tucker Date: Fri Oct 15 12:43:36 2021 +1100 Include error reason if trace disabling fails. commit d4b38144c02f3faa5271e5fb35df93507e06f1b4 Author: Darren Tucker Date: Tue Oct 12 22:55:51 2021 +1100 Add tcmalloc test target. commit 002d65b0a30063c6e49bf8a53e709d8d5a0d45c1 Author: dtucker@openbsd.org Date: Sat Oct 9 10:52:42 2021 +0000 upstream: Document that CASignatureAlgorithms, ExposeAuthInfo and PubkeyAuthOptions can be used in a Match block. Patch from eehakkin via github PR#277. OpenBSD-Commit-ID: c0a63f5f52e918645967ac022b28392da4b866aa commit 40bd3709dddaae3a1b6113748bec3faa6a607531 Author: Darren Tucker Date: Thu Oct 7 15:55:49 2021 +1100 Skip SK unit tests when built without security-key commit 482f73be10f10b93f818df19fcc8a912c0c371fc Author: Darren Tucker Date: Thu Oct 7 15:55:04 2021 +1100 Include relevant env vars on command line. Makes it easier to reproduce a build by cut/pasting the configure line. commit ef5916b8acd9b1d2f39fad4951dae03b00dbe390 Author: Darren Tucker Date: Thu Oct 7 14:28:02 2021 +1100 Only enable sk-* key types if ENABLE_SK is defined commit 52d4232b493a9858fe616e28a8bbcc89afa2ad4d Author: Darren Tucker Date: Wed Oct 6 18:14:37 2021 +1100 Disable security key on minix3. The test doesn't work so disable. commit 7cd062c3a29669b8d7dc2a97e6575f4dcb7d35a2 Author: Darren Tucker Date: Wed Oct 6 17:45:28 2021 +1100 Add USE_LIBC_SHA2 for (at least) NetBSD 9. commit 639c440f6c3c2a8216a5eb9455ef13bf4204089c Author: Darren Tucker Date: Wed Oct 6 17:09:31 2021 +1100 Define OPENSSL_NO_SHA including OpenSSL from test. We don't use SHA256 from OpenSSL in the sk-dummy module and the definitions can conflict with system sha2.h (eg on NetBSD) so define OPENSSL_NO_SHA so we don't attempt to redefine them. commit 8f4be526a338d06624f146fa26007bb9dd3a4f7b Author: Darren Tucker Date: Wed Oct 6 15:40:58 2021 +1100 Disable security key on NetBSD4 test. sk-dummy used for the security key test includes both sha2.h and OpenSSL causing the definitions conflict so disable security key support on this platform. commit 3b353ae58aa07a1cbbeb1da3ace21fc0dcccd66a Author: Damien Miller Date: Wed Oct 6 15:07:01 2021 +1100 clean regress/misc/sk-dummy in cleandir target commit 57680a2ab43518c5ccbd8242c40482106cde6ac1 Author: dtucker@openbsd.org Date: Sat Oct 2 03:17:01 2021 +0000 upstream: Dynamically allocate encoded HashKnownHosts and free as appropriate. Saves 1k of static storage and prevents snprintf "possible truncation" warnings from newer compilers (although in this case it's false positive since the actual sizes are limited by the output size of the SHA1). ok djm@ OpenBSD-Commit-ID: e254ae723f7e3dce352c7d5abc4b6d87faf61bf4 commit e3e62deb549fde215b777d95276c304f84bf00c6 Author: djm@openbsd.org Date: Wed Oct 6 03:35:13 2021 +0000 upstream: use libc SHA256 functions; make this work when compiled !WITH_OPENSSL OpenBSD-Regress-ID: fda0764c1097cd42f979ace29b07eb3481259890 commit 12937d867019469ebce83c2ff614cdc6688fc2d8 Author: dtucker@openbsd.org Date: Fri Oct 1 05:20:20 2021 +0000 upstream: Add test for ssh hashed known_hosts handling. OpenBSD-Regress-ID: bcef3b3cd5a1ad9899327b4b2183de2541aaf9cf commit 5a37cc118f464416d08cd0291a9b1611d8de9943 Author: Damien Miller Date: Wed Oct 6 13:16:21 2021 +1100 fix broken OPENSSL_HAS_ECC test spotted by dtucker - -commit 16a25414f303cd6790eb967aeb962040e32c9c7a -Author: Damien Miller -Date: Fri Oct 1 22:40:06 2021 +1000 - - make sk-dummy.so work without libcrypto installed - -commit dee22129bbc61e25b1003adfa2bc584c5406ef2d -Author: Damien Miller -Date: Fri Oct 1 16:35:49 2021 +1000 - - make OPENSSL_HAS_ECC checks more thorough - - ok dtucker - -commit 872595572b6c9a584ed754165e8b7c4c9e7e1d61 -Author: Damien Miller -Date: Fri Oct 1 16:35:05 2021 +1000 - - fix FIDO key support for !OPENSSL_HAS_ECC case - - ok dtucker - -commit 489741dc68366940d369ac670b210b4834a6c272 -Author: Damien Miller -Date: Fri Oct 1 14:51:37 2021 +1000 - - enable security key support for --without-openssl - -commit c978565c8589acfe4ea37ab5099d39c84158c713 -Author: Damien Miller -Date: Fri Oct 1 13:27:50 2021 +1000 - - need stdlib.h for free(3) - -commit 76a398edfb51951b2d65d522d7b02c72304db300 -Author: dtucker@openbsd.org -Date: Thu Sep 30 05:26:26 2021 +0000 - - upstream: Fix up whitespace left by previous - - change removing privsep. No other changes. - - OpenBSD-Regress-ID: 87adec225d8afaee4d6a91b2b71203f52bf14b15 - -commit ddcb53b7a7b29be65d57562302b2d5f41733e8dd -Author: dtucker@openbsd.org -Date: Thu Sep 30 05:20:08 2021 +0000 - - upstream: Remove references to privsep. - - This removes several do..while loops but does not change the - indentation of the now-shallower loops, which will be done in a separate - whitespace-only commit to keep changes of style and substance separate. - - OpenBSD-Regress-ID: 4bed1a0249df7b4a87c965066ce689e79472a8f7 - -commit ece2fbe486164860de8df3f8b943cccca3085eff -Author: dtucker@openbsd.org -Date: Thu Sep 30 04:22:50 2021 +0000 - - upstream: Use "skip" instead of "fatal" - - if SUDO isn't set for the *-command tests. This means running "make tests" - without SUDO set will perform all of the tests that it can instead of - failing on the ones it cannot run. - - OpenBSD-Regress-ID: bd4dbbb02f34b2e8c890558ad4a696248def763a - -commit bb754b470c360e787a99fb4e88e2668198e97b41 -Author: djm@openbsd.org -Date: Fri Oct 1 04:50:36 2021 +0000 - - upstream: unbreak FIDO sk-ed25519 key enrollment for OPENSSL=no builds; - - ok dtucker@ - - OpenBSD-Commit-ID: 6323a5241728626cbb2bf0452cf6a5bcbd7ff709 - -commit 207648d7a6415dc915260ca75850404dbf9f0a0b -Author: Darren Tucker -Date: Wed Sep 29 20:03:58 2021 +1000 - - Include stdlib.h for arc4random_uniform prototype. - -commit 696aadc854582c164d5fc04933d2f3e212dc0e06 -Author: Darren Tucker -Date: Wed Sep 29 20:00:30 2021 +1000 - - Look for clang after cc and gcc. - -commit a3c6375555026d85dbf811fab566b9f76f196144 -Author: Darren Tucker -Date: Wed Sep 29 19:30:59 2021 +1000 - - Use backticks instead of $(..) for portability. - - Older shells (eg /bin/sh on Solaris 10) don't support $() syntax. - -commit 958aaa0387133d51f84fe9c8f30bca03025f2867 -Author: Darren Tucker -Date: Wed Sep 29 18:53:32 2021 +1000 - - Skip file-based tests by default on Mac OS. - - The file-based tests need OpenSSL so skip them. - -commit 55c8bdf6e9afb0f9fa8e4f10c25c7f0081b48fd0 -Author: Darren Tucker -Date: Wed Sep 29 18:42:47 2021 +1000 - - Build without OpenSSL on Mac OS. - - Modern versions don't ship enough libcrypto to build against. - -commit c9172193ea975415facf0afb356d87df21535f88 -Author: Darren Tucker -Date: Wed Sep 29 18:33:38 2021 +1000 - - Remove TEST_SSH_ECC. - - Convert the only remaining user of it to runtime detection using ssh -Q. - -commit 5e6d28b7874b0deae95d2c68947c45212d32e599 -Author: Darren Tucker -Date: Wed Sep 29 17:48:09 2021 +1000 - - Split c89 test openssl setting out. - -commit c4ac7f98e230e83c015678dc958b1ffe828564ad -Author: Darren Tucker -Date: Wed Sep 29 17:40:50 2021 +1000 - - Expand TEST_SHELL consistently with other vars. - -commit cfe5f7b0eb7621bfb0a756222de0431315c2ab8b -Author: Darren Tucker -Date: Wed Sep 29 17:26:50 2021 +1000 - - Replace `pwd` with make variable in regress cmd. - -commit 899be59da5fbc3372444bd0fbe74af48313bed33 -Author: Darren Tucker -Date: Wed Sep 29 17:14:33 2021 +1000 - - Get BUILDDIR from autoconf. - - Use this to replace `pwd`s in regress test command line. - -commit c8d92d3d4f7d560146f2f936156ec4dac3fc5811 -Author: Darren Tucker -Date: Wed Sep 29 13:28:56 2021 +1000 - - Add make clean step to tests. - -commit 360fb41ef8359619ab90b0d131c914494e55d3dd -Author: Darren Tucker -Date: Wed Sep 29 11:36:13 2021 +1000 - - Test all available clang and gcc versions. - -commit 4fb49899d7da22952d35a4bc4c9bdb2311087893 -Author: djm@openbsd.org -Date: Wed Sep 29 01:32:21 2021 +0000 - - upstream: Test certificate hostkeys held in ssh-agent too. Would have - - caught regression fixed in sshd r1.575 - - ok markus@ - - OpenBSD-Regress-ID: 1f164d7bd89f83762db823eec4ddf2d2556145ed - -commit ce4854e12e749a05646e5775e9deb8cfaf49a755 -Author: djm@openbsd.org -Date: Wed Sep 29 01:33:32 2021 +0000 - - upstream: add some debug output showing how many key file/command lines - - were processed. Useful to see whether a file or command actually has keys - present - - OpenBSD-Commit-ID: 0bd9ff94e84e03a22df8e6c12f6074a95d27f23c - -commit 15abdd523501c349b703d9a27e2bb4252ad921ef -Author: dtucker@openbsd.org -Date: Tue Sep 28 11:14:50 2021 +0000 - - upstream: Make prototype for rijndaelEncrypt match function - - including the bounds. Fixes error in portable where GCC>=11 takes notice of - the bounds. ok deraadt@ - - OpenBSD-Commit-ID: cdd2f05fd1549e1786a70871e513cf9e9cf099a6 - -commit d1d29ea1d1ef1a1a54b209f062ec1dcc8399cf03 -Author: dtucker@openbsd.org -Date: Tue Sep 28 11:10:05 2021 +0000 - - upstream: Import regenerated moduli. - - OpenBSD-Commit-ID: 4bec5db13b736b64b06a0fca704cbecc2874c8e1 - -commit 39f2111b1d5f00206446257377dcce58cc72369f -Author: Darren Tucker -Date: Wed Sep 29 10:53:55 2021 +1000 - - Add new compiler hardening flags. - - Add -fzero-call-used-regs and -ftrivial-auto-var-init to the list of - compiler hardening flags that configure checks for. These are supported - by clang and gcc, and make ROP gadgets less useful and mitigate - stack-based infoleaks respectively. ok djm@ - -commit bf944e3794eff5413f2df1ef37cddf96918c6bde -Author: Damien Miller -Date: Mon Sep 27 00:03:19 2021 +1000 - - initgroups needs grp.h - -commit 8c5b5655149bd76ea21026d7fe73ab387dbc3bc7 -Author: djm@openbsd.org -Date: Sun Sep 26 14:01:11 2021 +0000 - - upstream: openssh-8.8 - - OpenBSD-Commit-ID: 12357794602ac979eb7312a1fb190c453f492ec4 - -commit f3cbe43e28fe71427d41cfe3a17125b972710455 -Author: djm@openbsd.org -Date: Sun Sep 26 14:01:03 2021 +0000 - - upstream: need initgroups() before setresgid(); reported by anton@, - - ok deraadt@ - - OpenBSD-Commit-ID: 6aa003ee658b316960d94078f2a16edbc25087ce - -commit 8acaff41f7518be40774c626334157b1b1c5583c -Author: Damien Miller -Date: Sun Sep 26 22:16:36 2021 +1000 - - update version numbers for release - -commit d39039ddc0010baa91c70a0fa0753a2699bbf435 -Author: kn@openbsd.org -Date: Sat Sep 25 09:40:33 2021 +0000 - - upstream: RSA/SHA-1 is not used by default anymore - - OK dtucker deraadt djm - - OpenBSD-Commit-ID: 055c51a221c3f099dd75c95362f902da1b8678c6 - -commit 9b2ee74e3aa8c461eb5552a6ebf260449bb06f7e -Author: Darren Tucker -Date: Fri Sep 24 11:08:03 2021 +1000 - - Move the fgrep replacement to hostkey-rotate.sh. - - The fgrep replacement for buggy greps doesn't work in the sftp-glob test - so move it to just where we know it's needed. - -commit f7039541570d4b66d76e6f574544db176d8d5c02 -Author: Darren Tucker -Date: Fri Sep 24 08:04:14 2021 +1000 - - Replacement function for buggy fgrep. - - GNU (f)grep <=2.18, as shipped by FreeBSD<=12 and NetBSD<=9 will - occasionally fail to find ssh host keys in the hostkey-rotate test. - If we have those versions, use awk instead. - -commit f6a660e5bf28a01962af87568e118a2d2e79eaa0 -Author: David Manouchehri -Date: Thu Sep 23 17:03:18 2021 -0400 - - Don't prompt for yes/no questions. - -commit 7ed1a3117c09f8c3f1add35aad77d3ebe1b85b4d -Author: djm@openbsd.org -Date: Mon Sep 20 06:53:56 2021 +0000 - - upstream: fix missing -s in SYNOPSYS and usage() as well as a - - capitalisation mistake; spotted by jmc@ - - OpenBSD-Commit-ID: 0ed8ee085c7503c60578941d8b45f3a61d4c9710 - -commit 8c07170135dde82a26886b600a8bf6fb290b633d -Author: dtucker@openbsd.org -Date: Mon Sep 20 04:02:13 2021 +0000 - - upstream: Fix "Allocated port" debug message - - for unix domain sockets. From peder.stray at gmail.com via github PR#272, - ok deraadt@ - - OpenBSD-Commit-ID: 8d5ef3fbdcdd29ebb0792b5022a4942db03f017e - -commit 277d3c6adfb128b4129db08e3d65195d94b55fe7 -Author: djm@openbsd.org -Date: Mon Sep 20 01:55:42 2021 +0000 - - upstream: Switch scp back to use the old protocol by default, ahead of - - release. We'll wait a little longer for people to pick up sftp-server(8) that - supports the extension that scp needs for ~user paths to continue working in - SFTP protocol mode. Discussed with deraadt@ - - OpenBSD-Commit-ID: f281f603a705fba317ff076e7b11bcf2df941871 - -commit ace19b34cc15bea3482be90450c1ed0cd0dd0669 -Author: djm@openbsd.org -Date: Sat Sep 18 02:03:25 2021 +0000 - - upstream: better error message for ~user failures when the - - sftp-server lacks the expand-path extension; ok deraadt@ - - OpenBSD-Commit-ID: 9c1d965d389411f7e86f0a445158bf09b8f9e4bc - -commit 6b1238ba971ee722a310d95037b498ede5539c03 -Author: djm@openbsd.org -Date: Thu Sep 16 15:22:22 2021 +0000 - - upstream: make some more scp-in-SFTP mode better match Unix idioms - - suggested by deraadt@ - - OpenBSD-Commit-ID: 0f2439404ed4cf0b0be8bf49a1ee734836e1ac87 - -commit e694f8ac4409931e67d08ac44ed251b20b10a957 -Author: djm@openbsd.org -Date: Thu Sep 16 15:11:19 2021 +0000 - - upstream: allow log_stderr==2 to prefix log messages with argv[0] - - use this to make scp's SFTP mode error messages more scp-like - - prompted by and ok deraadt@ - - OpenBSD-Commit-ID: 0e821dbde423fc2280e47414bdc22aaa5b4e0733 - -commit 8a7a06ee505cb833e613f74a07392e9296286c30 -Author: Darren Tucker -Date: Fri Sep 17 13:03:31 2021 +1000 - - Test against LibreSSL 3.2.6, 3.3.4, 3.4.0. - -commit c25c84074a47f700dd6534995b4af4b456927150 -Author: djm@openbsd.org -Date: Thu Sep 16 05:36:03 2021 +0000 - - upstream: missing space character in ssh -G output broke the - - t-sshcfgparse regression test; spotted by anton@ - - OpenBSD-Commit-ID: bcc36fae2f233caac4baa8e58482da4aa350eed0 - -commit a4bee1934bf5e5575fea486628f4123d6a29dff8 -Author: djm@openbsd.org -Date: Wed Sep 15 06:56:01 2021 +0000 - - upstream: allow CanonicalizePermittedCNAMEs=none in ssh_config; ok - - markus@ - - OpenBSD-Commit-ID: 668a82ba8e56d731b26ffc5703213bfe071df623 - -commit d0fffc88c8fe90c1815c6f4097bc8cbcabc0f3dd -Author: mbuhl@openbsd.org -Date: Tue Sep 14 11:04:21 2021 +0000 - - upstream: put back the mux_ctx memleak fix for SSH_CHANNEL_MUX_CLIENT - - OK mfriedl@ - - OpenBSD-Commit-ID: 1aba1da828956cacaadb81a637338734697d9798 - -commit 19b3d846f06697c85957ab79a63454f57f8e22d6 -Author: schwarze@openbsd.org -Date: Sat Sep 11 09:05:50 2021 +0000 - - upstream: Do not ignore SIGINT while waiting for input if editline(3) - - is not used. Instead, in non-interactive mode, exit sftp(1), like for other - serious errors. As pointed out by dtucker@, when compiled without editline(3) - support in portable OpenSSH, the el == NULL branch is also used for - interactive mode. In that case, discard the input line and provide a fresh - prompt to the user just like in the case where editline(3) is used. OK djm@ - - OpenBSD-Commit-ID: 7d06f4d3ebba62115527fafacf38370d09dfb393 - -commit ba61123eef9c6356d438c90c1199a57a0d7bcb0a -Author: djm@openbsd.org -Date: Sat Sep 11 00:40:24 2021 +0000 - - upstream: when using SFTP protocol, continue transferring files after a - - transfer error occurs. This matches original scp/rcp behaviour. ok dtucker@ - - OpenBSD-Commit-ID: dfe4558d71dd09707e9b5d6e7d2e53b793da69fa - -commit b0ec59a708b493c6f3940336b1a537bcb64dd2a7 -Author: dtucker@openbsd.org -Date: Fri Sep 10 11:38:38 2021 +0000 - - upstream: Document that non-interactive commands are run via the user's - - shell using the -c flag. ok jmc@ - - OpenBSD-Commit-ID: 4f0d912077732eead10423afd1acf4fc0ceec477 - -commit 66a658b5d9e009ea11f8a0ca6e69c7feb2d851ea -Author: dtucker@openbsd.org -Date: Fri Sep 10 10:26:02 2021 +0000 - - upstream: Document behaviour of arguments following non-interactive - - commands. Prompted by github PR#139 from EvanTheB, feedback & ok djm@ jmc@ - - OpenBSD-Commit-ID: fc758d1fe0471dfab4304fcad6cd4ecc3d79162a - -commit 1d47e28e407d1f95fdf8f799be23f48dcfa5206b -Author: dtucker@openbsd.org -Date: Fri Sep 10 07:11:11 2021 +0000 - - upstream: Clarify which file's attributes -p preserves, and that - - it's specifically the file mode bits. bz#3340 from calestyo at scientia.net, - ok djm@ jmc@ - - OpenBSD-Commit-ID: f09e6098ed1c4be00c730873049825f8ee7cb884 - -commit b344db7a413478e4c21e4cadba4a970ad3e6128a -Author: djm@openbsd.org -Date: Fri Sep 10 05:46:09 2021 +0000 - - upstream: openssh-7.4 was incorrectly listed twice; spotted by - - Dmitry Belyavskiy, ok dtucker@ - - OpenBSD-Commit-ID: 4b823ae448f6e899927ce7b04225ac9e489f58ef - -commit 9136d6239ad7a4a293e0418a49b69e70c76d58b8 -Author: jmc@openbsd.org -Date: Thu Sep 9 06:17:39 2021 +0000 - - upstream: - move CAVEATS to its correct order - use the term - - "legacy" protocol rather than "original", as the latter made the text - misleading - uppercase SCP - - ok djm - - OpenBSD-Commit-ID: 8479255746d5fa76a358ee59e7340fecf4245ff0 - -commit 2d678c5e3bdc2f5c99f7af5122e9d054925d560d -Author: David Carlier -Date: Wed Sep 8 19:49:54 2021 +0100 - - Disable tracing on FreeBSD using procctl. - - Placed at the start of platform_disable_tracing() to prevent declaration - after code errors from strict C89 compilers (in the unlikely event that - more than one method is enabled). - -commit 73050fa38fb36ae3326d768b574806352b97002d -Author: djm@openbsd.org -Date: Wed Sep 8 23:31:39 2021 +0000 - - upstream: Use the SFTP protocol by default. The original scp/rcp - - protocol remains available via the -O flag. - - Note that ~user/ prefixed paths in SFTP mode require a protocol extension - that was first shipped in OpenSSH 8.7. - - ok deraadt, after baking in snaps for a while without incident - - OpenBSD-Commit-ID: 23588976e28c281ff5988da0848cb821fec9213c - -commit c4565e69ffa2485cff715aa842ea7a350296bfb6 -Author: Darren Tucker -Date: Wed Sep 8 21:09:49 2021 +1000 - - Really fix test on OpenSSL 1.1.1 stable. - -commit 79f1bb5f56cef3ae9276207316345b8309248478 -Author: Darren Tucker -Date: Wed Sep 8 18:51:39 2021 +1000 - - Correct OpenSSL 1.1.1 stable identifier. - -commit b6255593ed5ccbe5e7d3d4b26b2ad31ad4afc232 -Author: Darren Tucker -Date: Wed Sep 8 18:39:44 2021 +1000 - - Increment nfds when coming from startup_pipe. - - If we have to increase nfds because startup_pipe[0] is above any of the - descriptors passed in the fd_sets, we also need to add 1 to nfds since - select takes highest FD number plus one. bz#3345 from yaroslav.kuzmin - at vmssoftware.com. - -commit a3e92a6794817df6012ac8546aea19652cc91b61 -Author: Darren Tucker -Date: Wed Sep 8 13:45:10 2021 +1000 - - Tests for OpenSSL 3.0.0 release & 1.1.1 branch. - -commit 4afe431da98ec1cf6a2933fe5658f4fd68dee9e2 -Author: djm@openbsd.org -Date: Wed Sep 8 03:23:44 2021 +0000 - - upstream: correct my mistake in previous fix; spotted by halex - - OpenBSD-Commit-ID: 3cc62d92e3f70006bf02468fc146bfc36fffa183 - -commit ca0e455b9331213ff9505a21b94c38e34faa2bba -Author: djm@openbsd.org -Date: Tue Sep 7 06:03:51 2021 +0000 - - upstream: avoid NULL deref in -Y find-principals. Report and fix - - from Carlo Marcelo Arenas Belón - MIME-Version: 1.0 - Content-Type: text/plain; charset=UTF-8 - Content-Transfer-Encoding: 8bit - - OpenBSD-Commit-ID: 6238486f8ecc888d6ccafcd9ad99e621bb41f1e0 - -commit 37616807f150fb46610bbd5031c31af4857ad1e9 -Author: millert@openbsd.org -Date: Mon Sep 6 00:36:01 2021 +0000 - - upstream: revision 1.381 neglected to remove - - sChallengeResponseAuthentication from the enum. Noticed by - christos@zoulas.com. OK dtucker@ - - OpenBSD-Commit-ID: b533283a4dd6d04a867da411a4c7a8fbc90e34ff - -commit 7acb3578cdfec0b3d34501408071f7a96c1684ea -Author: Darren Tucker -Date: Sun Sep 5 20:45:42 2021 +1000 - - Correct version_num for OpenSSL dev branch. - -commit 65bb01111320dfd0d25e21e1fd4d3f2b77532669 -Author: Darren Tucker -Date: Sun Sep 5 19:37:39 2021 +1000 - - Test against OpenSSL 3 branch as well as dev. - - Now that OpenSSL development has moved to 3.1, test against the most - recent version of the openssl-3.0 branch too. - -commit 864ed0d5e04a503b97202c776b7cf3f163f3eeaa -Author: Darren Tucker -Date: Sun Sep 5 19:33:22 2021 +1000 - - OpenSSL development is now 3.1.* - -commit a60209a586a928f92ab323bf23bd07f57093342e -Author: dtucker@openbsd.org -Date: Fri Sep 3 07:43:23 2021 +0000 - - upstream: Use .Cm instead of .Dq in StrictHostKeyChecking list for - - consistency. Patch from scop via github PR#257, ok jmc@ - - OpenBSD-Commit-ID: 3652a91564570779431802c31224fb4a9cf39872 - -commit 8d1d9eb6de37331e872700e9e399a3190cca1242 -Author: dtucker@openbsd.org -Date: Fri Sep 3 07:27:03 2021 +0000 - - upstream: Mention using ssh -i for specifying the public key file - - in the case where the private key is loaded into ssh-agent but is not present - locally. Based on patch from rafork via github PR#215, ok jmc@ - - OpenBSD-Commit-ID: 2282e83b0ff78d2efbe705883b67240745fa5bb2 - -commit eb4362e5e3aa7ac26138b11e44d8c191910aff64 -Author: dtucker@openbsd.org -Date: Fri Sep 3 05:25:50 2021 +0000 - - upstream: Refer to KEX "algorithms" instead of "methods" to match - - other references and improve consistency. Patch from scop via github PR#241, - ok djm@ - - OpenBSD-Commit-ID: 840bc94ff6861b28d8603c8e8c16499bfb65e32c - -commit b3318946ce5725da43c4bf7eeea1b73129c47d2a -Author: dtucker@openbsd.org -Date: Fri Sep 3 05:12:25 2021 +0000 - - upstream: Remove redundant attrib_clear in upload_dir_internal. - - The subsequent call to stat_to_attrib clears the struct as its first step - anyway. From pmeinhardt via github PR#220, ok djm@ - - OpenBSD-Commit-ID: f5234fc6d7425b607e179acb3383f21716f3029e - -commit 7cc3fe28896e653956a6a2eed0a25d551b83a029 -Author: dtucker@openbsd.org -Date: Fri Sep 3 04:11:13 2021 +0000 - - upstream: Add test for client termination status on signal. - - Based on patch from Alexxz via github PR#235 with some tweaks, to - match patch in bz#3281. - - OpenBSD-Regress-ID: d87c7446fb8b5f8b45894fbbd6875df326e729e2 - -commit 5428b0d239f6b516c81d1dd15aa9fe9e60af75d4 -Author: deraadt@openbsd.org -Date: Thu Sep 2 21:03:54 2021 +0000 - - upstream: sys/param.h is not needed for any visible reason - - OpenBSD-Commit-ID: 8bdea2d0c75692e4c5777670ac039d4b01c1f368 - -commit 1ff38f34b4c4545eb28106629cafa1e0496bc726 -Author: Shchelkunov Artem -Date: Wed Aug 11 18:07:58 2021 +0500 - - Fix memory leak in error path. - - *info is allocated via xstrdup but was leaked in the PAM_AUTH_ERR path. - From github PR#266. - -commit cb37e2f0c0ca4fef844ed7edc5d0e3b7d0e83f6a -Author: dtucker@openbsd.org -Date: Wed Sep 1 03:16:06 2021 +0000 - - upstream: Fix ssh-rsa fallback for old PuTTY interop tests. - - OpenBSD-Regress-ID: a19ac929da604843a5b5f0f48d2c0eb6e0773d37 - -commit 8b02ef0f28dc24cda8cbcd8b7eb02bda8f8bbe59 -Author: dtucker@openbsd.org -Date: Wed Sep 1 00:50:27 2021 +0000 - - upstream: Add a function to skip remaining tests. - - Many tests skip tests for various reasons but not in a consistent way and - don't always clean up, so add that and switch the tests that do that over. - - OpenBSD-Regress-ID: 72d2ec90a3ee8849486956a808811734281af735 - -commit d486845c07324c04240f1674ac513985bd356f66 -Author: dtucker@openbsd.org -Date: Tue Aug 31 07:13:59 2021 +0000 - - upstream: Specify path to PuTTY keys. - - Portable needs this and it makes no difference on OpenBSD, so resync - them. (Id sync only, Portable already had this.) - - OpenBSD-Regress-ID: 33f6f66744455886d148527af8368811e4264162 - -commit d22b299115e27606e846b23490746f69fdd4fb38 -Author: dtucker@openbsd.org -Date: Tue Aug 31 06:13:23 2021 +0000 - - upstream: Better compat tests with old PuTTY. - - When running PuTTY interop tests and using a PuTTY version older than - 0.76, re-enable the ssh-rsa host key algorithm (the 256 and 512 variants - of RSA were added some time between 0.73 and 0.76). - - OpenBSD-Regress-ID: e6138d6987aa705fa1e4f216db0bb386e1ff38e1 - -commit 87ad70d605c3e39c9b8aa275db27120d7cc09b77 -Author: Darren Tucker -Date: Tue Aug 31 17:04:50 2021 +1000 - - Resync PuTTY interop tests. - - Resync behaviour when REGRESS_INTEROP_PUTTY is not set with OpenBSD. - -commit e47b82a7bf51021afac218bf59a3be121827653d -Author: dtucker@openbsd.org -Date: Tue Aug 31 01:25:27 2021 +0000 - - upstream: Specify hostkeyalgorithms in SSHFP test. - - Specify host key algorithms in sshd's default set for the SSHFP test, - from djm@. Make the reason for when the test is skipped a bit clearer. - - OpenBSD-Regress-ID: 4f923dfc761480d5411de17ea6f0b30de3e32cea - -commit 7db3e0a9e8477c018757b59ee955f7372c0b55fb -Author: djm@openbsd.org -Date: Mon Aug 30 01:15:45 2021 +0000 - - upstream: adapt to RSA/SHA1 deprectation - - OpenBSD-Regress-ID: 952397c39a22722880e4de9d1c50bb1a14f907bb - -commit 2344750250247111a6c3c6a4fe84ed583a61cc11 -Author: djm@openbsd.org -Date: Sun Aug 29 23:53:10 2021 +0000 - - upstream: After years of forewarning, disable the RSA/SHA-1 - - signature algorithm by default. It is feasible to create colliding SHA1 - hashes, so we need to deprecate its use. - - RSA/SHA-256/512 remains available and will be transparently selected - instead of RSA/SHA1 for most SSH servers released in the last five+ - years. There is no need to regenerate RSA keys. - - The use of RSA/SHA1 can be re-enabled by adding "ssh-rsa" to the - PubkeyAcceptedAlgorithms directives on the client and server. - - ok dtucker deraadt - - OpenBSD-Commit-ID: 189bcc4789c7254e09e23734bdd5def8354ff1d5 - -commit 56c4455d3b54b7d481c77c82115c830b9c8ce328 -Author: djm@openbsd.org -Date: Sun Aug 29 23:44:07 2021 +0000 - - upstream: wrap at 80 columns - - OpenBSD-Commit-ID: 47ca2286d6b52a9747f34da16d742879e1a37bf0 - -commit 95401eea8503943449f712e5f3de52fc0bc612c5 -Author: Darren Tucker -Date: Fri Aug 20 18:14:13 2021 +1000 - - Replace shell function with ssh-keygen -A. - - Prevents the init script in the SysV package from trying (and failing) - to generate unsupported key types. Remove now-unused COMMENT_OUT_ECC. - ok tim@ - -commit d83ec9ed995a76ed1d5c65cf10b447222ec86131 -Author: Darren Tucker -Date: Fri Aug 20 15:39:05 2021 +1000 - - Remove obsolete Redhat PAM config and init script. - -commit e1a596186c81e65a34ce13076449712d3bf97eb4 -Author: Damien Miller -Date: Fri Aug 20 14:03:49 2021 +1000 - - depend - -commit 5450606c8f7f7a0d70211cea78bc2dab74ab35d1 -Author: Damien Miller -Date: Fri Aug 20 13:59:43 2021 +1000 - - update version numbers - -commit feee2384ab8d694c770b7750cfa76a512bdf8246 -Author: djm@openbsd.org -Date: Fri Aug 20 03:22:55 2021 +0000 - - upstream: openssh-8.7 - - OpenBSD-Commit-ID: 8769dff0fd76ae3193d77bf83b439adee0f300cd - -commit 9a2ed62173cc551b2b5f479460bb015b19499de8 -Author: Darren Tucker -Date: Fri Aug 20 10:48:13 2021 +1000 - - Also check pid in pselect_notify_setup. - - Spotted by djm@. - -commit deaadcb93ca15d4f38aa38fb340156077792ce87 -Author: Darren Tucker -Date: Fri Aug 20 08:39:33 2021 +1000 - - Prefix pselect functions to clarify debug messages - -commit 10e45654cff221ca60fd35ee069df67208fcf415 -Author: Darren Tucker -Date: Fri Aug 20 08:30:42 2021 +1000 - - Fix race in pselect replacement code. - - On the second and subsequent calls to pselect the notify_pipe was not - added to the select readset, opening up a race that om G. Christensen - discovered on multiprocessor Solaris <=9 systems. - - Also reinitialize notify_pipe if the pid changes. This will prevent a - parent and child from using the same FD, although this is not an issue - in the current structure it might be in future. - -commit 464ba22f1e38d25402e5ec79a9b8d34a32df5a3f -Author: Darren Tucker -Date: Wed Aug 18 12:51:30 2021 +1000 - - Check compiler for c99 declarations after code. - - The sntrup761 reference code contains c99-style declarations after code - so don't try to build that if the compiler doesn't support it. - -commit 7d878679a4b155a359d32104ff473f789501748d -Author: Darren Tucker -Date: Tue Aug 17 15:12:04 2021 +1000 - - Remove trailing backslash on regress-unit-binaries - -commit b71b2508f17c68c5d9dbbe537686d81cedb9a781 -Author: Darren Tucker -Date: Tue Aug 17 07:59:27 2021 +1000 - - Put stdint.h inside HAVE_STDINT_H. - - From Tom G. Christensen. - -commit 6a24567a29bd7b4ab64e1afad859ea845cbc6b8c -Author: Darren Tucker -Date: Mon Aug 16 14:13:02 2021 +1000 - - Improve github test driver script. - - - use a trap to always output any failed regress logs (since the script - sets -e, the existing log output is never invoked). - - pass LTESTS and SKIP_LTESTS when re-running with sshd options (eg. - UsePAM). - -commit b467cf13705f59ed348b620722ac098fe31879b7 -Author: Darren Tucker -Date: Mon Aug 16 11:32:23 2021 +1000 - - Remove deprecated ubuntu-16.04 test targets. - - Github has deprecated ubuntu-16.04 and it will be removed on 20 - September. - -commit 20e6eefcdf78394f05e453d456c1212ffaa6b6a4 -Author: Darren Tucker -Date: Sun Aug 15 23:25:26 2021 +1000 - - Skip agent ptrace test on hurd. - -commit 7c9115bbbf958fbf85259a061c1122e2d046aabf -Author: Darren Tucker -Date: Sun Aug 15 19:37:22 2021 +1000 - - Add hurd test target. - -commit 7909a566f6c6a78fcd30708dc49f4e4f9bb80ce3 -Author: Darren Tucker -Date: Sun Aug 15 12:45:10 2021 +1000 - - Skip scp3 tests on all dfly58 and 60 configs. - -commit e65198e52cb03534e8c846d1bca74c310b1526de -Author: Tim Rice -Date: Sat Aug 14 13:08:07 2021 -0700 - - openbsd-compat/openbsd-compat.h: put bsd-signal.h before bsd-misc.h - to get sigset_t from signal.h needed for the pselect replacement. - -commit e50635640f79920d9375e0155cb3f4adb870eee5 -Author: Darren Tucker -Date: Fri Aug 13 13:21:00 2021 +1000 - - Test OpenSSH from OpenBSD head on 6.8 and 6.9. - -commit e0ba38861c490c680117b7fe0a1d61a181cd00e7 -Author: Darren Tucker -Date: Fri Aug 13 13:00:14 2021 +1000 - - Skip scp3 test on dragonfly 58 and 60. - - The tests hang, so skip until we figure them out. - -commit dcce2a2bcf007bf817a2fb0dce3db83fa9201e92 -Author: djm@openbsd.org -Date: Thu Aug 12 23:59:25 2021 +0000 - - upstream: mention that CASignatureAlgorithms accepts +/- similarly to - - the other algorithm list directives; ok jmc bz#3335 - - OpenBSD-Commit-ID: 0d46b53995817052c78e2dce9dbd133963b073d9 - -commit 090a82486e5d7a8f7f16613d67e66a673a40367f -Author: schwarze@openbsd.org -Date: Thu Aug 12 09:59:00 2021 +0000 - - upstream: In the editline(3) branch of the sftp(1) event loop, - - handle SIGINT rather than ignoring it, such that the user can use Ctrl-C to - discard the currently edited command line and get a fresh prompt, just like - in ftp(1), bc(1), and in shells. - - It is critical to not use ssl_signal() for this particular case - because that function unconditionally sets SA_RESTART, but here we - need the signal to interrupt the read(2) in the el_gets(3) event loop. - - OK dtucker@ deraadt@ - - OpenBSD-Commit-ID: 8025115a773f52e9bb562eaab37ea2e021cc7299 - -commit e1371e4f58404d6411d9f95eb774b444cea06a26 -Author: naddy@openbsd.org -Date: Wed Aug 11 14:07:54 2021 +0000 - - upstream: scp: tweak man page and error message for -3 by default - - Now that the -3 option is enabled by default, flip the documentation - and error message logic from "requires -3" to "blocked by -R". - - ok djm@ - - OpenBSD-Commit-ID: a872592118444fb3acda5267b2a8c3d4c4252020 - -commit 49f46f6d77328a3d10a758522b670a3e8c2235e7 -Author: naddy@openbsd.org -Date: Wed Aug 11 14:05:19 2021 +0000 - - upstream: scp: do not spawn ssh with two -s flags for - - remote-to-remote copies - - Do not add another "-s" to the argument vector every time an SFTP - connection is initiated. Instead, introduce a subsystem flag to - do_cmd() and add "-s" when the flag is set. - - ok djm@ - - OpenBSD-Commit-ID: 25df69759f323661d31b2e1e790faa22e27966c1 - -commit 2a2cd00783e1da45ee730b7f453408af1358ef5b -Author: djm@openbsd.org -Date: Wed Aug 11 08:55:04 2021 +0000 - - upstream: test -Oprint-pubkey - - OpenBSD-Regress-ID: 3d51afb6d1f287975fb6fddd7a2c00a3bc5094e0 - -commit b9f4635ea5bc33ed5ebbacf332d79bae463b0f54 -Author: djm@openbsd.org -Date: Wed Aug 11 08:54:17 2021 +0000 - - upstream: when verifying sshsig signatures, support an option - - (-Oprint-pubkey) to dump the full public key to stdout; based on patch from - Fabian Stelzer; ok markus@ - - OpenBSD-Commit-ID: 0598000e5b9adfb45d42afa76ff80daaa12fc3e2 - -commit 750c1a45ba4e8ad63793d49418a0780e77947b9b -Author: djm@openbsd.org -Date: Wed Aug 11 05:21:32 2021 +0000 - - upstream: oops, missed one more %p - - OpenBSD-Commit-ID: e7e62818d1564cc5cd9086eaf7a51cbd1a9701eb - -commit b5aa27b69ab2e1c13ac2b5ad3f8f7d389bad7489 -Author: djm@openbsd.org -Date: Wed Aug 11 05:20:17 2021 +0000 - - upstream: remove a bunch of %p in format strings; leftovers of - - debuggings past. prompted by Michael Forney, ok dtucker@ - - OpenBSD-Commit-ID: 4853a0d6c9cecaba9ecfcc19066e52d3a8dcb2ac - -commit 419aa01123db5ff5dbc68b2376ef23b222862338 -Author: Darren Tucker -Date: Wed Aug 11 09:21:09 2021 +1000 - - Add includes.h to compat tests. - - On platforms where closefrom returns void (eg glibc>=2.34) the prototype - for closefrom in its compat tests would cause compile errors. Remove - this and have the tests pull in the compat headers in the same way as - the main code. bz#3336. - -commit 931f592f26239154eea3eb35a086585897b1a185 -Author: djm@openbsd.org -Date: Tue Aug 10 03:35:45 2021 +0000 - - upstream: adapt to scp -M flag change; make scp3.sh test SFTP mode too - - OpenBSD-Regress-ID: 43fea26704a0f0b962b53c1fabcb68179638f9c0 - -commit 391ca67fb978252c48d20c910553f803f988bd37 -Author: djm@openbsd.org -Date: Tue Aug 10 03:33:34 2021 +0000 - - upstream: Prepare for a future where scp(1) uses the SFTP protocol by - - default. Replace recently added -M option to select the protocol with -O - (olde) and -s (SFTP) flags, and label the -s flag with a clear warning that - it will be removed in the near future (so no, don't use it in scripts!). - - prompted by/feedback from deraadt@ - - OpenBSD-Commit-ID: 92ad72cc6f0023c9be9e316d8b30eb6d8d749cfc - -commit bfdd4b722f124a4fa9173d20dd64dd0fc69856be -Author: djm@openbsd.org -Date: Mon Aug 9 23:56:36 2021 +0000 - - upstream: make scp -3 the default for remote-to-remote copies. It - - provides a much better and more intuitive user experience and doesn't require - exposing credentials to the source host. - - thanks naddy@ for catching the missing argument in usage() - - "Yes please!" - markus@ - "makes a lot of sense" - deraadt@ - "the right thing to do" - dtucker@ - - OpenBSD-Commit-ID: d0d2af5f0965c5192ba5b2fa461c9f9b130e5dd9 - -commit 2f7a3b51cef689ad9e93d0c6c17db5a194eb5555 -Author: djm@openbsd.org -Date: Mon Aug 9 23:49:31 2021 +0000 - - upstream: make scp in SFTP mode try to use relative paths as much - - as possible. Previosuly, it would try to make relative and ~/-rooted paths - absolute before requesting transfers. - - prompted by and much discussion deraadt@ - ok markus@ - - OpenBSD-Commit-ID: 46639d382ea99546a4914b545fa7b00fa1be5566 - -commit 2ab864010e0a93c5dd95116fb5ceaf430e2fc23c -Author: djm@openbsd.org -Date: Mon Aug 9 23:47:44 2021 +0000 - - upstream: SFTP protocol extension to allow the server to expand - - ~-prefixed paths, in particular ~user ones. Allows scp in sftp mode to accept - these paths, like scp in rcp mode does. - - prompted by and much discussion deraadt@ - ok markus@ - - OpenBSD-Commit-ID: 7d794def9e4de348e1e777f6030fc9bafdfff392 - -commit 41b019ac067f1d1f7d99914d0ffee4d2a547c3d8 -Author: djm@openbsd.org -Date: Mon Aug 9 23:44:32 2021 +0000 - - upstream: when scp is in SFTP mode, try to deal better with ~ - - prefixed paths. ~user paths aren't supported, but ~/ paths will be accepted - and prefixed with the SFTP server starting directory (more to come) - - prompted by and discussed with deraadt@ - ok markus@ - - OpenBSD-Commit-ID: 263a071f14555c045fd03132a8fb6cbd983df00d - -commit b4b3f3da6cdceb3fd168b5fab69d11fba73bd0ae -Author: djm@openbsd.org -Date: Mon Aug 9 07:21:01 2021 +0000 - - upstream: on fatal errors, make scp wait for ssh connection before - - exiting avoids LogLevel=verbose (or greater) messages from ssh appearing - after scp has returned exited and control has returned to the shell; ok - markus@ - - (this was originally committed as r1.223 along with unrelated stuff that - I rolled back in r1.224) - - OpenBSD-Commit-ID: 1261fd667ad918484889ed3d7aec074f3956a74b - -commit 2ae7771749e0b4cecb107f9d4860bec16c3f4245 -Author: djm@openbsd.org -Date: Mon Aug 9 07:19:12 2021 +0000 - - upstream: rever r1.223 - I accidentally committed unrelated changes - - OpenBSD-Commit-ID: fb73f3865b2647a27dd94db73d6589506a9625f9 - -commit 986abe94d481a1e82a01747360bd767b96b41eda -Author: djm@openbsd.org -Date: Mon Aug 9 07:16:09 2021 +0000 - - upstream: show only the final path component in the progress meter; - - more useful with long paths (that may truncate) and better matches - traditional scp behaviour; spotted by naddy@ ok deraadt@ - - OpenBSD-Commit-ID: 26b544d0074f03ebb8a3ebce42317d8d7ee291a3 - -commit 2b67932bb3176dee4fd447af4368789e04a82b93 -Author: djm@openbsd.org -Date: Mon Aug 9 07:13:54 2021 +0000 - - upstream: on fatal errors, make scp wait for ssh connection before - - exiting avoids LogLevel=verbose (or greater) messages from ssh appearing - after scp has returned exited and control has returned to the shell; ok - markus@ - - OpenBSD-Commit-ID: ef9dab5ef5ae54a6a4c3b15d380568e94263456c diff --git a/crypto/openssh/PROTOCOL b/crypto/openssh/PROTOCOL index 27804d0cadbd..d453c779be92 100644 --- a/crypto/openssh/PROTOCOL +++ b/crypto/openssh/PROTOCOL @@ -1,715 +1,748 @@ This documents OpenSSH's deviations and extensions to the published SSH protocol. Note that OpenSSH's sftp and sftp-server implement revision 3 of the SSH filexfer protocol described in: https://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt Newer versions of the draft will not be supported, though some features are individually implemented as extensions described below. The protocol used by OpenSSH's ssh-agent is described in the file PROTOCOL.agent 1. Transport protocol changes 1.1. transport: Protocol 2 MAC algorithm "umac-64@openssh.com" This is a new transport-layer MAC method using the UMAC algorithm (rfc4418). This method is identical to the "umac-64" method documented in: https://www.openssh.com/txt/draft-miller-secsh-umac-01.txt 1.2. transport: Protocol 2 compression algorithm "zlib@openssh.com" This transport-layer compression method uses the zlib compression algorithm (identical to the "zlib" method in rfc4253), but delays the start of compression until after authentication has completed. This avoids exposing compression code to attacks from unauthenticated users. The method is documented in: https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt 1.3. transport: New public key algorithms "ssh-rsa-cert-v01@openssh.com", "ssh-dsa-cert-v01@openssh.com", "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ecdsa-sha2-nistp384-cert-v01@openssh.com" and "ecdsa-sha2-nistp521-cert-v01@openssh.com" OpenSSH introduces new public key algorithms to support certificate authentication for users and host keys. These methods are documented in the file PROTOCOL.certkeys 1.4. transport: Elliptic Curve cryptography OpenSSH supports ECC key exchange and public key authentication as specified in RFC5656. Only the ecdsa-sha2-nistp256, ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 curves over GF(p) are supported. Elliptic curve points encoded using point compression are NOT accepted or generated. 1.5 transport: Protocol 2 Encrypt-then-MAC MAC algorithms OpenSSH supports MAC algorithms, whose names contain "-etm", that perform the calculations in a different order to that defined in RFC 4253. These variants use the so-called "encrypt then MAC" ordering, calculating the MAC over the packet ciphertext rather than the plaintext. This ordering closes a security flaw in the SSH transport protocol, where decryption of unauthenticated ciphertext provided a "decryption oracle" that could, in conjunction with cipher flaws, reveal session plaintext. Specifically, the "-etm" MAC algorithms modify the transport protocol to calculate the MAC over the packet ciphertext and to send the packet length unencrypted. This is necessary for the transport to obtain the length of the packet and location of the MAC tag so that it may be verified without decrypting unauthenticated data. As such, the MAC covers: mac = MAC(key, sequence_number || packet_length || encrypted_packet) where "packet_length" is encoded as a uint32 and "encrypted_packet" contains: byte padding_length byte[n1] payload; n1 = packet_length - padding_length - 1 byte[n2] random padding; n2 = padding_length 1.6 transport: AES-GCM OpenSSH supports the AES-GCM algorithm as specified in RFC 5647. Because of problems with the specification of the key exchange the behaviour of OpenSSH differs from the RFC as follows: AES-GCM is only negotiated as the cipher algorithms "aes128-gcm@openssh.com" or "aes256-gcm@openssh.com" and never as an MAC algorithm. Additionally, if AES-GCM is selected as the cipher the exchanged MAC algorithms are ignored and there doesn't have to be a matching MAC. 1.7 transport: chacha20-poly1305@openssh.com authenticated encryption OpenSSH supports authenticated encryption using ChaCha20 and Poly1305 as described in PROTOCOL.chacha20poly1305. 1.8 transport: curve25519-sha256@libssh.org key exchange algorithm OpenSSH supports the use of ECDH in Curve25519 for key exchange as described at: http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.org.txt?h=curve25519 This is identical to curve25519-sha256 as later published in RFC8731. +1.9 transport: ping facility + +OpenSSH implements a transport level ping message SSH2_MSG_PING +and a corresponding SSH2_MSG_PONG reply. + +#define SSH2_MSG_PING 192 +#define SSH2_MSG_PONG 193 + +The ping message is simply: + + byte SSH_MSG_PING + string data + +The reply copies the data (which may be the empty string) from the +ping: + + byte SSH_MSG_PONG + string data + +Replies are sent in order. They are sent immediately except when rekeying +is in progress, in which case they are queued until rekeying completes. + +The server advertises support for these messages using the +SSH2_MSG_EXT_INFO mechanism (RFC8308), with the following message: + + string "ping@openssh.com" + string "0" (version) + +The ping/reply message is implemented at the transport layer rather +than as a named global or channel request to allow pings with very +short packet lengths, which would not be possible with other +approaches. + 2. Connection protocol changes 2.1. connection: Channel write close extension "eow@openssh.com" The SSH connection protocol (rfc4254) provides the SSH_MSG_CHANNEL_EOF message to allow an endpoint to signal its peer that it will send no more data over a channel. Unfortunately, there is no symmetric way for an endpoint to request that its peer should cease sending data to it while still keeping the channel open for the endpoint to send data to the peer. This is desirable, since it saves the transmission of data that would otherwise need to be discarded and it allows an endpoint to signal local processes of the condition, e.g. by closing the corresponding file descriptor. OpenSSH implements a channel extension message to perform this signalling: "eow@openssh.com" (End Of Write). This message is sent by an endpoint when the local output of a session channel is closed or experiences a write error. The message is formatted as follows: byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "eow@openssh.com" boolean FALSE On receiving this message, the peer SHOULD cease sending data of the channel and MAY signal the process from which the channel data originates (e.g. by closing its read file descriptor). As with the symmetric SSH_MSG_CHANNEL_EOF message, the channel does remain open after a "eow@openssh.com" has been sent and more data may still be sent in the other direction. This message does not consume window space and may be sent even if no window space is available. NB. due to certain broken SSH implementations aborting upon receipt of this message (in contravention of RFC4254 section 5.4), this message is only sent to OpenSSH peers (identified by banner). Other SSH implementations may be listed to receive this message upon request. 2.2. connection: disallow additional sessions extension "no-more-sessions@openssh.com" Most SSH connections will only ever request a single session, but a attacker may abuse a running ssh client to surreptitiously open additional sessions under their control. OpenSSH provides a global request "no-more-sessions@openssh.com" to mitigate this attack. When an OpenSSH client expects that it will never open another session (i.e. it has been started with connection multiplexing disabled), it will send the following global request: byte SSH_MSG_GLOBAL_REQUEST string "no-more-sessions@openssh.com" char want-reply On receipt of such a message, an OpenSSH server will refuse to open future channels of type "session" and instead immediately abort the connection. Note that this is not a general defence against compromised clients (that is impossible), but it thwarts a simple attack. NB. due to certain broken SSH implementations aborting upon receipt of this message, the no-more-sessions request is only sent to OpenSSH servers (identified by banner). Other SSH implementations may be listed to receive this message upon request. 2.3. connection: Tunnel forward extension "tun@openssh.com" OpenSSH supports layer 2 and layer 3 tunnelling via the "tun@openssh.com" channel type. This channel type supports forwarding of network packets with datagram boundaries intact between endpoints equipped with interfaces like the BSD tun(4) device. Tunnel forwarding channels are requested by the client with the following packet: byte SSH_MSG_CHANNEL_OPEN string "tun@openssh.com" uint32 sender channel uint32 initial window size uint32 maximum packet size uint32 tunnel mode uint32 remote unit number The "tunnel mode" parameter specifies whether the tunnel should forward layer 2 frames or layer 3 packets. It may take one of the following values: SSH_TUNMODE_POINTOPOINT 1 /* layer 3 packets */ SSH_TUNMODE_ETHERNET 2 /* layer 2 frames */ The "tunnel unit number" specifies the remote interface number, or may be 0x7fffffff to allow the server to automatically choose an interface. A server that is not willing to open a client-specified unit should refuse the request with a SSH_MSG_CHANNEL_OPEN_FAILURE error. On successful open, the server should reply with SSH_MSG_CHANNEL_OPEN_SUCCESS. Once established the client and server may exchange packet or frames over the tunnel channel by encapsulating them in SSH protocol strings and sending them as channel data. This ensures that packet boundaries are kept intact. Specifically, packets are transmitted using normal SSH_MSG_CHANNEL_DATA packets: byte SSH_MSG_CHANNEL_DATA uint32 recipient channel string data The contents of the "data" field for layer 3 packets is: uint32 packet length uint32 address family byte[packet length - 4] packet data The "address family" field identifies the type of packet in the message. It may be one of: SSH_TUN_AF_INET 2 /* IPv4 */ SSH_TUN_AF_INET6 24 /* IPv6 */ The "packet data" field consists of the IPv4/IPv6 datagram itself without any link layer header. The contents of the "data" field for layer 2 packets is: uint32 packet length byte[packet length] frame The "frame" field contains an IEEE 802.3 Ethernet frame, including header. 2.4. connection: Unix domain socket forwarding OpenSSH supports local and remote Unix domain socket forwarding using the "streamlocal" extension. Forwarding is initiated as per TCP sockets but with a single path instead of a host and port. Similar to direct-tcpip, direct-streamlocal is sent by the client to request that the server make a connection to a Unix domain socket. byte SSH_MSG_CHANNEL_OPEN string "direct-streamlocal@openssh.com" uint32 sender channel uint32 initial window size uint32 maximum packet size string socket path string reserved uint32 reserved Similar to forwarded-tcpip, forwarded-streamlocal is sent by the server when the client has previously send the server a streamlocal-forward GLOBAL_REQUEST. byte SSH_MSG_CHANNEL_OPEN string "forwarded-streamlocal@openssh.com" uint32 sender channel uint32 initial window size uint32 maximum packet size string socket path string reserved for future use The reserved field is not currently defined and is ignored on the remote end. It is intended to be used in the future to pass information about the socket file, such as ownership and mode. The client currently sends the empty string for this field. Similar to tcpip-forward, streamlocal-forward is sent by the client to request remote forwarding of a Unix domain socket. byte SSH2_MSG_GLOBAL_REQUEST string "streamlocal-forward@openssh.com" boolean TRUE string socket path Similar to cancel-tcpip-forward, cancel-streamlocal-forward is sent by the client cancel the forwarding of a Unix domain socket. byte SSH2_MSG_GLOBAL_REQUEST string "cancel-streamlocal-forward@openssh.com" boolean FALSE string socket path 2.5. connection: hostkey update and rotation "hostkeys-00@openssh.com" and "hostkeys-prove-00@openssh.com" OpenSSH supports a protocol extension allowing a server to inform a client of all its protocol v.2 host keys after user-authentication has completed. byte SSH_MSG_GLOBAL_REQUEST string "hostkeys-00@openssh.com" char 0 /* want-reply */ string[] hostkeys Upon receiving this message, a client should check which of the supplied host keys are present in known_hosts. Note that the server may send key types that the client does not support. The client should disregard such keys if they are received. If the client identifies any keys that are not present for the host, it should send a "hostkeys-prove@openssh.com" message to request the server prove ownership of the private half of the key. byte SSH_MSG_GLOBAL_REQUEST string "hostkeys-prove-00@openssh.com" char 1 /* want-reply */ string[] hostkeys When a server receives this message, it should generate a signature using each requested key over the following: string "hostkeys-prove-00@openssh.com" string session identifier string hostkey These signatures should be included in the reply, in the order matching the hostkeys in the request: byte SSH_MSG_REQUEST_SUCCESS string[] signatures When the client receives this reply (and not a failure), it should validate the signatures and may update its known_hosts file, adding keys that it has not seen before and deleting keys for the server host that are no longer offered. These extensions let a client learn key types that it had not previously encountered, thereby allowing it to potentially upgrade from weaker key algorithms to better ones. It also supports graceful key rotation: a server may offer multiple keys of the same type for a period (to give clients an opportunity to learn them using this extension) before removing the deprecated key from those offered. 2.6. connection: SIGINFO support for "signal" channel request The SSH channels protocol (RFC4254 section 6.9) supports sending a signal to a session attached to a channel. OpenSSH supports one extension signal "INFO@openssh.com" that allows sending SIGINFO on BSD-derived systems. 3. Authentication protocol changes 3.1. Host-bound public key authentication This is trivial change to the traditional "publickey" authentication method. The authentication request is identical to the original method but for the name and one additional field: byte SSH2_MSG_USERAUTH_REQUEST string username string "ssh-connection" string "publickey-hostbound-v00@openssh.com" bool has_signature string pkalg string public key string server host key Because the entire SSH2_MSG_USERAUTH_REQUEST message is included in the signed data, this ensures that a binding between the destination user, the server identity and the session identifier is visible to the signer. OpenSSH uses this binding via signed data to implement per-key restrictions in ssh-agent. A server may advertise this method using the SSH2_MSG_EXT_INFO mechanism (RFC8308), with the following message: string "publickey-hostbound@openssh.com" string "0" (version) Clients should prefer host-bound authentication when advertised by server. 4. SFTP protocol changes 4.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK When OpenSSH's sftp-server was implemented, the order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately, the reversal was not noticed until the server was widely deployed. Since fixing this to follow the specification would cause incompatibility, the current order was retained. For correct operation, clients should send SSH_FXP_SYMLINK as follows: uint32 id string targetpath string linkpath 4.2. sftp: Server extension announcement in SSH_FXP_VERSION OpenSSH's sftp-server lists the extensions it supports using the standard extension announcement mechanism in the SSH_FXP_VERSION server hello packet: uint32 3 /* protocol version */ string ext1-name string ext1-version string ext2-name string ext2-version ... string extN-name string extN-version Each extension reports its integer version number as an ASCII encoded string, e.g. "1". The version will be incremented if the extension is ever changed in an incompatible way. The server MAY advertise the same extension with multiple versions (though this is unlikely). Clients MUST check the version number before attempting to use the extension. 4.3. sftp: Extension request "posix-rename@openssh.com" This operation provides a rename operation with POSIX semantics, which are different to those provided by the standard SSH_FXP_RENAME in draft-ietf-secsh-filexfer-02.txt. This request is implemented as a SSH_FXP_EXTENDED request with the following format: uint32 id string "posix-rename@openssh.com" string oldpath string newpath On receiving this request the server will perform the POSIX operation rename(oldpath, newpath) and will respond with a SSH_FXP_STATUS message. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 4.4. sftp: Extension requests "statvfs@openssh.com" and "fstatvfs@openssh.com" These requests correspond to the statvfs and fstatvfs POSIX system interfaces. The "statvfs@openssh.com" request operates on an explicit pathname, and is formatted as follows: uint32 id string "statvfs@openssh.com" string path The "fstatvfs@openssh.com" operates on an open file handle: uint32 id string "fstatvfs@openssh.com" string handle These requests return a SSH_FXP_STATUS reply on failure. On success they return the following SSH_FXP_EXTENDED_REPLY reply: uint32 id uint64 f_bsize /* file system block size */ uint64 f_frsize /* fundamental fs block size */ uint64 f_blocks /* number of blocks (unit f_frsize) */ uint64 f_bfree /* free blocks in file system */ uint64 f_bavail /* free blocks for non-root */ uint64 f_files /* total file inodes */ uint64 f_ffree /* free file inodes */ uint64 f_favail /* free file inodes for to non-root */ uint64 f_fsid /* file system id */ uint64 f_flag /* bit mask of f_flag values */ uint64 f_namemax /* maximum filename length */ The values of the f_flag bitmask are as follows: #define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ #define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ Both the "statvfs@openssh.com" and "fstatvfs@openssh.com" extensions are advertised in the SSH_FXP_VERSION hello with version "2". 4.5. sftp: Extension request "hardlink@openssh.com" This request is for creating a hard link to a regular file. This request is implemented as a SSH_FXP_EXTENDED request with the following format: uint32 id string "hardlink@openssh.com" string oldpath string newpath On receiving this request the server will perform the operation link(oldpath, newpath) and will respond with a SSH_FXP_STATUS message. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 4.6. sftp: Extension request "fsync@openssh.com" This request asks the server to call fsync(2) on an open file handle. uint32 id string "fsync@openssh.com" string handle On receiving this request, a server will call fsync(handle_fd) and will respond with a SSH_FXP_STATUS message. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 4.7. sftp: Extension request "lsetstat@openssh.com" This request is like the "setstat" command, but sets file attributes on symlinks. It is implemented as a SSH_FXP_EXTENDED request with the following format: uint32 id string "lsetstat@openssh.com" string path ATTRS attrs See the "setstat" command for more details. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 4.8. sftp: Extension request "limits@openssh.com" This request is used to determine various limits the server might impose. Clients should not attempt to exceed these limits as the server might sever the connection immediately. uint32 id string "limits@openssh.com" The server will respond with a SSH_FXP_EXTENDED_REPLY reply: uint32 id uint64 max-packet-length uint64 max-read-length uint64 max-write-length uint64 max-open-handles The 'max-packet-length' applies to the total number of bytes in a single SFTP packet. Servers SHOULD set this at least to 34000. The 'max-read-length' is the largest length in a SSH_FXP_READ packet. Even if the client requests a larger size, servers will usually respond with a shorter SSH_FXP_DATA packet. Servers SHOULD set this at least to 32768. The 'max-write-length' is the largest length in a SSH_FXP_WRITE packet the server will accept. Servers SHOULD set this at least to 32768. The 'max-open-handles' is the maximum number of active handles that the server allows (e.g. handles created by SSH_FXP_OPEN and SSH_FXP_OPENDIR packets). Servers MAY count internal file handles against this limit (e.g. system logging or stdout/stderr), so clients SHOULD NOT expect to open this many handles in practice. If the server doesn't enforce a specific limit, then the field may be set to 0. This implies the server relies on the OS to enforce limits (e.g. available memory or file handles), and such limits might be dynamic. The client SHOULD take care to not try to exceed reasonable limits. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 4.9. sftp: Extension request "expand-path@openssh.com" This request supports canonicalisation of relative paths and those that need tilde-expansion, i.e. "~", "~/..." and "~user/..." These paths are expanded using shell-like rules and the resultant path is canonicalised similarly to SSH2_FXP_REALPATH. It is implemented as a SSH_FXP_EXTENDED request with the following format: uint32 id string "expand-path@openssh.com" string path Its reply is the same format as that of SSH2_FXP_REALPATH. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 4.10. sftp: Extension request "copy-data" This request asks the server to copy data from one open file handle and write it to a different open file handle. This avoids needing to transfer the data across the network twice (a download followed by an upload). byte SSH_FXP_EXTENDED uint32 id string "copy-data" string read-from-handle uint64 read-from-offset uint64 read-data-length string write-to-handle uint64 write-to-offset The server will copy read-data-length bytes starting from read-from-offset from the read-from-handle and write them to write-to-handle starting from write-to-offset, and then respond with a SSH_FXP_STATUS message. It's equivalent to issuing a series of SSH_FXP_READ requests on read-from-handle and a series of requests of SSH_FXP_WRITE on write-to-handle. If read-from-handle and write-to-handle are the same, the server will fail the request and respond with a SSH_FX_INVALID_PARAMETER message. If read-data-length is 0, then the server will read data from the read-from-handle until EOF is reached. This extension is advertised in the SSH_FXP_VERSION hello with version "1". This request is identical to the "copy-data" request documented in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7 4.11. sftp: Extension request "home-directory" This request asks the server to expand the specified user's home directory. An empty username implies the current user. This can be used by the client to expand ~/ type paths locally. byte SSH_FXP_EXTENDED uint32 id string "home-directory" string username This extension is advertised in the SSH_FXP_VERSION hello with version "1". This provides similar information as the "expand-path@openssh.com" extension. This request is identical to the "home-directory" request documented in: https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-extensions-00#section-5 4.12. sftp: Extension request "users-groups-by-id@openssh.com" This request asks the server to return user and/or group names that correspond to one or more IDs (e.g. as returned from a SSH_FXP_STAT request). This may be used by the client to provide usernames in directory listings. byte SSH_FXP_EXTENDED uint32 id string "users-groups-by-id@openssh.com" string uids string gids Where "uids" and "gids" consists of one or more integer user or group identifiers: uint32 id-0 ... The server will reply with a SSH_FXP_EXTENDED_REPLY: byte SSH_FXP_EXTENDED_REPLY string usernames string groupnames Where "username" and "groupnames" consists of names in identical request order to "uids" and "gids" respectively: string name-0 ... If a name cannot be identified for a given user or group ID, an empty string will be returned in its place. It is acceptable for either "uids" or "gids" to be an empty set, in which case the respective "usernames" or "groupnames" list will also be empty. This extension is advertised in the SSH_FXP_VERSION hello with version "1". 5. Miscellaneous changes 5.1 Public key format OpenSSH public keys, as generated by ssh-keygen(1) and appearing in authorized_keys files, are formatted as a single line of text consisting of the public key algorithm name followed by a base64-encoded key blob. The public key blob (before base64 encoding) is the same format used for the encoding of public keys sent on the wire: as described in RFC4253 section 6.6 for RSA and DSA keys, RFC5656 section 3.1 for ECDSA keys and the "New public key formats" section of PROTOCOL.certkeys for the OpenSSH certificate formats. 5.2 Private key format OpenSSH private keys, as generated by ssh-keygen(1) use the format described in PROTOCOL.key by default. As a legacy option, PEM format (RFC7468) private keys are also supported for RSA, DSA and ECDSA keys and were the default format before OpenSSH 7.8. 5.3 KRL format OpenSSH supports a compact format for Key Revocation Lists (KRLs). This format is described in the PROTOCOL.krl file. 5.4 Connection multiplexing OpenSSH's connection multiplexing uses messages as described in PROTOCOL.mux over a Unix domain socket for communications between a master instance and later clients. 5.5. Agent protocol extensions OpenSSH extends the usual agent protocol. These changes are documented in the PROTOCOL.agent file. -$OpenBSD: PROTOCOL,v 1.48 2022/11/07 01:53:01 dtucker Exp $ +$OpenBSD: PROTOCOL,v 1.49 2023/08/28 03:28:43 djm Exp $ diff --git a/crypto/openssh/PROTOCOL.agent b/crypto/openssh/PROTOCOL.agent index 44e463674f19..1c4841147a2d 100644 --- a/crypto/openssh/PROTOCOL.agent +++ b/crypto/openssh/PROTOCOL.agent @@ -1,84 +1,84 @@ The SSH agent protocol is described in -https://tools.ietf.org/html/draft-miller-ssh-agent-04 +https://tools.ietf.org/html/draft-miller-ssh-agent This file documents OpenSSH's extensions to the agent protocol. 1. session-bind@openssh.com extension This extension allows a ssh client to bind an agent connection to a particular SSH session identifier as derived from the initial key exchange (as per RFC4253 section 7.2) and the host key used for that exchange. This binding is verifiable at the agent by including the initial KEX signature made by the host key. The message format is: byte SSH_AGENTC_EXTENSION (0x1b) string session-bind@openssh.com string hostkey string session identifier string signature bool is_forwarding Where 'hostkey' is the encoded server host public key, 'session identifier' is the exchange hash derived from the initial key exchange, 'signature' is the server's signature of the session identifier using the private hostkey, as sent in the final SSH2_MSG_KEXDH_REPLY/SSH2_MSG_KEXECDH_REPLY message of the initial key exchange. 'is_forwarding' is a flag indicating whether this connection should be bound for user authentication or forwarding. When an agent received this message, it will verify the signature and check the consistency of its contents, including refusing to accept a duplicate session identifier, or any attempt to bind a connection previously bound for authentication. It will then record the binding for the life of the connection for use later in testing per-key destination constraints. 2. restrict-destination-v00@openssh.com key constraint extension The key constraint extension supports destination- and forwarding path- restricted keys. It may be attached as a constraint when keys or smartcard keys are added to an agent. byte SSH_AGENT_CONSTRAIN_EXTENSION (0xff) string restrict-destination-v00@openssh.com constraint[] constraints Where a constraint consists of: string from_username (must be empty) string from_hostname keyspec[] from_hostkeys string to_username string to_hostname keyspec[] to_hostkeys And a keyspec consists of: string keyblob bool is_ca When receiving this message, the agent will ensure that the 'from_username' field is empty, and that 'to_hostname' and 'to_hostkeys' have been supplied (empty 'from_hostname' and 'from_hostkeys' are valid and signify the initial hop from the host running ssh-agent). The agent will then record the constraint against the key. Subsequent operations on this key including add/remove/request identities and, in particular, signature requests will check the key constraints against the session-bind@openssh.com bindings recorded for the agent connection over which they were received. 3. SSH_AGENT_CONSTRAIN_MAXSIGN key constraint This key constraint allows communication to an agent of the maximum number of signatures that may be made with an XMSS key. The format of the constraint is: byte SSH_AGENT_CONSTRAIN_MAXSIGN (0x03) uint32 max_signatures This option is only valid for XMSS keys. -$OpenBSD: PROTOCOL.agent,v 1.19 2023/04/12 08:53:54 jsg Exp $ +$OpenBSD: PROTOCOL.agent,v 1.20 2023/10/03 23:56:10 djm Exp $ diff --git a/crypto/openssh/README b/crypto/openssh/README index e44e44ced60b..6e41c8657b82 100644 --- a/crypto/openssh/README +++ b/crypto/openssh/README @@ -1,53 +1,53 @@ -See https://www.openssh.com/releasenotes.html#9.4p1 for the release +See https://www.openssh.com/releasenotes.html#9.5p1 for the release notes. Please read https://www.openssh.com/report.html for bug reporting instructions and note that we do not use Github for bug reporting or patch/pull-request management. This is the port of OpenBSD's excellent OpenSSH[0] to Linux and other Unices. OpenSSH is based on the last free version of Tatu Ylonen's sample implementation with all patent-encumbered algorithms removed (to external libraries), all known security bugs fixed, new features reintroduced and many other clean-ups. OpenSSH has been created by Aaron Campbell, Bob Beck, Markus Friedl, Niels Provos, Theo de Raadt, and Dug Song. It has a homepage at https://www.openssh.com/ This port consists of the re-introduction of autoconf support, PAM support, EGD/PRNGD support and replacements for OpenBSD library functions that are (regrettably) absent from other unices. This port has been best tested on AIX, Cygwin, HP-UX, Linux, MacOS/X, FreeBSD, NetBSD, OpenBSD, OpenServer, Solaris and UnixWare. This version actively tracks changes in the OpenBSD CVS repository. The PAM support is now more functional than the popular packages of commercial ssh-1.2.x. It checks "account" and "session" modules for all logins, not just when using password authentication. There is now several mailing lists for this port of OpenSSH. Please refer to https://www.openssh.com/list.html for details on how to join. Please send bug reports and patches to https://bugzilla.mindrot.org or the mailing list openssh-unix-dev@mindrot.org. To mitigate spam, the list only allows posting from subscribed addresses. Code contribution are welcomed, but please follow the OpenBSD style guidelines[1]. Please refer to the INSTALL document for information on dependencies and how to install OpenSSH on your system. Damien Miller Miscellania - This version of OpenSSH is based upon code retrieved from the OpenBSD CVS repository which in turn was based on the last free sample implementation released by Tatu Ylonen. References - [0] https://www.openssh.com/ [1] https://man.openbsd.org/style.9 diff --git a/crypto/openssh/auth2.c b/crypto/openssh/auth2.c index 84377a906df4..d2a420d0deec 100644 --- a/crypto/openssh/auth2.c +++ b/crypto/openssh/auth2.c @@ -1,847 +1,854 @@ -/* $OpenBSD: auth2.c,v 1.166 2023/03/08 04:43:12 guenther Exp $ */ +/* $OpenBSD: auth2.c,v 1.167 2023/08/28 09:48:11 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include #include #include #include "stdlib.h" #include "atomicio.h" #include "xmalloc.h" #include "ssh2.h" #include "packet.h" #include "log.h" #include "sshbuf.h" #include "misc.h" #include "servconf.h" #include "sshkey.h" #include "hostfile.h" #include "auth.h" #include "dispatch.h" #include "pathnames.h" #include "ssherr.h" #include "blacklist_client.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "digest.h" /* import */ extern ServerOptions options; extern struct sshbuf *loginmsg; /* methods */ extern Authmethod method_none; extern Authmethod method_pubkey; extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; #ifdef GSSAPI extern Authmethod method_gssapi; #endif Authmethod *authmethods[] = { &method_none, &method_pubkey, #ifdef GSSAPI &method_gssapi, #endif &method_passwd, &method_kbdint, &method_hostbased, NULL }; /* protocol */ static int input_service_request(int, u_int32_t, struct ssh *); static int input_userauth_request(int, u_int32_t, struct ssh *); /* helper */ static Authmethod *authmethod_byname(const char *); static Authmethod *authmethod_lookup(Authctxt *, const char *); static char *authmethods_get(Authctxt *authctxt); #define MATCH_NONE 0 /* method or submethod mismatch */ #define MATCH_METHOD 1 /* method matches (no submethod specified) */ #define MATCH_BOTH 2 /* method and submethod match */ #define MATCH_PARTIAL 3 /* method matches, submethod can't be checked */ static int list_starts_with(const char *, const char *, const char *); char * auth2_read_banner(void) { struct stat st; char *banner = NULL; size_t len, n; int fd; if ((fd = open(options.banner, O_RDONLY)) == -1) return (NULL); if (fstat(fd, &st) == -1) { close(fd); return (NULL); } if (st.st_size <= 0 || st.st_size > 1*1024*1024) { close(fd); return (NULL); } len = (size_t)st.st_size; /* truncate */ banner = xmalloc(len + 1); n = atomicio(read, fd, banner, len); close(fd); if (n != len) { free(banner); return (NULL); } banner[n] = '\0'; return (banner); } static void userauth_send_banner(struct ssh *ssh, const char *msg) { int r; if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_BANNER)) != 0 || (r = sshpkt_put_cstring(ssh, msg)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || /* language, unused */ (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); debug("%s: sent", __func__); } static void userauth_banner(struct ssh *ssh) { char *banner = NULL; if (options.banner == NULL) return; if ((banner = PRIVSEP(auth2_read_banner())) == NULL) goto done; userauth_send_banner(ssh, banner); done: free(banner); } /* * loop until authctxt->success == TRUE */ void do_authentication2(struct ssh *ssh) { Authctxt *authctxt = ssh->authctxt; ssh_dispatch_init(ssh, &dispatch_protocol_error); ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_REQUEST, &input_service_request); ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt->success); ssh->authctxt = NULL; } static int input_service_request(int type, u_int32_t seq, struct ssh *ssh) { Authctxt *authctxt = ssh->authctxt; char *service = NULL; int r, acceptit = 0; if ((r = sshpkt_get_cstring(ssh, &service, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) goto out; if (authctxt == NULL) fatal("input_service_request: no authctxt"); if (strcmp(service, "ssh-userauth") == 0) { if (!authctxt->success) { acceptit = 1; /* now we can handle user-auth requests */ ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_REQUEST, &input_userauth_request); } } /* XXX all other service requests are denied */ if (acceptit) { if ((r = sshpkt_start(ssh, SSH2_MSG_SERVICE_ACCEPT)) != 0 || (r = sshpkt_put_cstring(ssh, service)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) goto out; } else { debug("bad service request %s", service); ssh_packet_disconnect(ssh, "bad service request %s", service); } r = 0; out: free(service); return r; } #define MIN_FAIL_DELAY_SECONDS 0.005 +#define MAX_FAIL_DELAY_SECONDS 5.0 static double user_specific_delay(const char *user) { char b[512]; size_t len = ssh_digest_bytes(SSH_DIGEST_SHA512); u_char *hash = xmalloc(len); double delay; (void)snprintf(b, sizeof b, "%llu%s", (unsigned long long)options.timing_secret, user); if (ssh_digest_memory(SSH_DIGEST_SHA512, b, strlen(b), hash, len) != 0) fatal_f("ssh_digest_memory"); /* 0-4.2 ms of delay */ delay = (double)PEEK_U32(hash) / 1000 / 1000 / 1000 / 1000; freezero(hash, len); debug3_f("user specific delay %0.3lfms", delay/1000); return MIN_FAIL_DELAY_SECONDS + delay; } static void ensure_minimum_time_since(double start, double seconds) { struct timespec ts; double elapsed = monotime_double() - start, req = seconds, remain; + if (elapsed > MAX_FAIL_DELAY_SECONDS) { + debug3_f("elapsed %0.3lfms exceeded the max delay " + "requested %0.3lfms)", elapsed*1000, req*1000); + return; + } + /* if we've already passed the requested time, scale up */ while ((remain = seconds - elapsed) < 0.0) seconds *= 2; ts.tv_sec = remain; ts.tv_nsec = (remain - ts.tv_sec) * 1000000000; debug3_f("elapsed %0.3lfms, delaying %0.3lfms (requested %0.3lfms)", elapsed*1000, remain*1000, req*1000); nanosleep(&ts, NULL); } static int input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) { Authctxt *authctxt = ssh->authctxt; Authmethod *m = NULL; char *user = NULL, *service = NULL, *method = NULL, *style = NULL; int r, authenticated = 0; double tstart = monotime_double(); if (authctxt == NULL) fatal("input_userauth_request: no authctxt"); if ((r = sshpkt_get_cstring(ssh, &user, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &service, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &method, NULL)) != 0) goto out; debug("userauth-request for user %s service %s method %s", user, service, method); debug("attempt %d failures %d", authctxt->attempt, authctxt->failures); if ((style = strchr(user, ':')) != NULL) *style++ = 0; if (authctxt->attempt >= 1024) auth_maxtries_exceeded(ssh); if (authctxt->attempt++ == 0) { /* setup auth context */ authctxt->pw = PRIVSEP(getpwnamallow(ssh, user)); authctxt->user = xstrdup(user); if (authctxt->pw && strcmp(service, "ssh-connection")==0) { authctxt->valid = 1; debug2_f("setting up authctxt for %s", user); } else { authctxt->valid = 0; /* Invalid user, fake password information */ authctxt->pw = fakepw(); #ifdef SSH_AUDIT_EVENTS PRIVSEP(audit_event(ssh, SSH_INVALID_USER)); #endif } #ifdef USE_PAM if (options.use_pam) PRIVSEP(start_pam(ssh)); #endif ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating " : "invalid ", user); setproctitle("%s%s", authctxt->valid ? user : "unknown", use_privsep ? " [net]" : ""); authctxt->service = xstrdup(service); authctxt->style = style ? xstrdup(style) : NULL; if (use_privsep) mm_inform_authserv(service, style); userauth_banner(ssh); if (auth2_setup_methods_lists(authctxt) != 0) ssh_packet_disconnect(ssh, "no authentication methods enabled"); } else if (strcmp(user, authctxt->user) != 0 || strcmp(service, authctxt->service) != 0) { ssh_packet_disconnect(ssh, "Change of username or service " "not allowed: (%s,%s) -> (%s,%s)", authctxt->user, authctxt->service, user, service); } /* reset state */ auth2_challenge_stop(ssh); #ifdef GSSAPI /* XXX move to auth2_gssapi_stop() */ ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); #endif auth2_authctxt_reset_info(authctxt); authctxt->postponed = 0; authctxt->server_caused_failure = 0; /* try to authenticate user */ m = authmethod_lookup(authctxt, method); if (m != NULL && authctxt->failures < options.max_authtries) { debug2("input_userauth_request: try method %s", method); authenticated = m->userauth(ssh, method); } - if (!authctxt->authenticated) + if (!authctxt->authenticated && strcmp(method, "none") != 0) ensure_minimum_time_since(tstart, user_specific_delay(authctxt->user)); userauth_finish(ssh, authenticated, method, NULL); r = 0; out: free(service); free(user); free(method); return r; } void userauth_finish(struct ssh *ssh, int authenticated, const char *packet_method, const char *submethod) { Authctxt *authctxt = ssh->authctxt; Authmethod *m = NULL; const char *method = packet_method; char *methods; int r, partial = 0; if (authenticated) { if (!authctxt->valid) { fatal("INTERNAL ERROR: authenticated invalid user %s", authctxt->user); } if (authctxt->postponed) fatal("INTERNAL ERROR: authenticated and postponed"); /* prefer primary authmethod name to possible synonym */ if ((m = authmethod_byname(method)) == NULL) fatal("INTERNAL ERROR: bad method %s", method); method = m->name; } /* Special handling for root */ if (authenticated && authctxt->pw->pw_uid == 0 && !auth_root_allowed(ssh, method)) { authenticated = 0; #ifdef SSH_AUDIT_EVENTS PRIVSEP(audit_event(ssh, SSH_LOGIN_ROOT_DENIED)); #endif } if (authenticated && options.num_auth_methods != 0) { if (!auth2_update_methods_lists(authctxt, method, submethod)) { authenticated = 0; partial = 1; } } /* Log before sending the reply */ auth_log(ssh, authenticated, partial, method, submethod); /* Update information exposed to session */ if (authenticated || partial) auth2_update_session_info(authctxt, method, submethod); if (authctxt->postponed) return; #ifdef USE_PAM if (options.use_pam && authenticated) { int r, success = PRIVSEP(do_pam_account()); /* If PAM returned a message, send it to the user. */ if (sshbuf_len(loginmsg) > 0) { if ((r = sshbuf_put(loginmsg, "\0", 1)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); userauth_send_banner(ssh, sshbuf_ptr(loginmsg)); if ((r = ssh_packet_write_wait(ssh)) != 0) { sshpkt_fatal(ssh, r, "%s: send PAM banner", __func__); } } if (!success) { fatal("Access denied for user %s by PAM account " "configuration", authctxt->user); } } #endif if (authenticated == 1) { /* turn off userauth */ ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_SUCCESS)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send success packet"); /* now we can break out */ authctxt->success = 1; ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user); } else { /* Allow initial try of "none" auth without failure penalty */ if (!partial && !authctxt->server_caused_failure && (authctxt->attempt > 1 || strcmp(method, "none") != 0)) { authctxt->failures++; BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "ssh"); } if (authctxt->failures >= options.max_authtries) { #ifdef SSH_AUDIT_EVENTS PRIVSEP(audit_event(ssh, SSH_LOGIN_EXCEED_MAXTRIES)); #endif auth_maxtries_exceeded(ssh); } methods = authmethods_get(authctxt); debug3_f("failure partial=%d next methods=\"%s\"", partial, methods); if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_FAILURE)) != 0 || (r = sshpkt_put_cstring(ssh, methods)) != 0 || (r = sshpkt_put_u8(ssh, partial)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send failure packet"); free(methods); } } /* * Checks whether method is allowed by at least one AuthenticationMethods * methods list. Returns 1 if allowed, or no methods lists configured. * 0 otherwise. */ int auth2_method_allowed(Authctxt *authctxt, const char *method, const char *submethod) { u_int i; /* * NB. authctxt->num_auth_methods might be zero as a result of * auth2_setup_methods_lists(), so check the configuration. */ if (options.num_auth_methods == 0) return 1; for (i = 0; i < authctxt->num_auth_methods; i++) { if (list_starts_with(authctxt->auth_methods[i], method, submethod) != MATCH_NONE) return 1; } return 0; } static char * authmethods_get(Authctxt *authctxt) { struct sshbuf *b; char *list; int i, r; if ((b = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); for (i = 0; authmethods[i] != NULL; i++) { if (strcmp(authmethods[i]->name, "none") == 0) continue; if (authmethods[i]->enabled == NULL || *(authmethods[i]->enabled) == 0) continue; if (!auth2_method_allowed(authctxt, authmethods[i]->name, NULL)) continue; if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) ? "," : "", authmethods[i]->name)) != 0) fatal_fr(r, "buffer error"); } if ((list = sshbuf_dup_string(b)) == NULL) fatal_f("sshbuf_dup_string failed"); sshbuf_free(b); return list; } static Authmethod * authmethod_byname(const char *name) { int i; if (name == NULL) fatal_f("NULL authentication method name"); for (i = 0; authmethods[i] != NULL; i++) { if (strcmp(name, authmethods[i]->name) == 0 || (authmethods[i]->synonym != NULL && strcmp(name, authmethods[i]->synonym) == 0)) return authmethods[i]; } debug_f("unrecognized authentication method name: %s", name); return NULL; } static Authmethod * authmethod_lookup(Authctxt *authctxt, const char *name) { Authmethod *method; if ((method = authmethod_byname(name)) == NULL) return NULL; if (method->enabled == NULL || *(method->enabled) == 0) { debug3_f("method %s not enabled", name); return NULL; } if (!auth2_method_allowed(authctxt, method->name, NULL)) { debug3_f("method %s not allowed " "by AuthenticationMethods", name); return NULL; } return method; } /* * Check a comma-separated list of methods for validity. Is need_enable is * non-zero, then also require that the methods are enabled. * Returns 0 on success or -1 if the methods list is invalid. */ int auth2_methods_valid(const char *_methods, int need_enable) { char *methods, *omethods, *method, *p; u_int i, found; int ret = -1; if (*_methods == '\0') { error("empty authentication method list"); return -1; } omethods = methods = xstrdup(_methods); while ((method = strsep(&methods, ",")) != NULL) { for (found = i = 0; !found && authmethods[i] != NULL; i++) { if ((p = strchr(method, ':')) != NULL) *p = '\0'; if (strcmp(method, authmethods[i]->name) != 0) continue; if (need_enable) { if (authmethods[i]->enabled == NULL || *(authmethods[i]->enabled) == 0) { error("Disabled method \"%s\" in " "AuthenticationMethods list \"%s\"", method, _methods); goto out; } } found = 1; break; } if (!found) { error("Unknown authentication method \"%s\" in list", method); goto out; } } ret = 0; out: free(omethods); return ret; } /* * Prune the AuthenticationMethods supplied in the configuration, removing * any methods lists that include disabled methods. Note that this might * leave authctxt->num_auth_methods == 0, even when multiple required auth * has been requested. For this reason, all tests for whether multiple is * enabled should consult options.num_auth_methods directly. */ int auth2_setup_methods_lists(Authctxt *authctxt) { u_int i; /* First, normalise away the "any" pseudo-method */ if (options.num_auth_methods == 1 && strcmp(options.auth_methods[0], "any") == 0) { free(options.auth_methods[0]); options.auth_methods[0] = NULL; options.num_auth_methods = 0; } if (options.num_auth_methods == 0) return 0; debug3_f("checking methods"); authctxt->auth_methods = xcalloc(options.num_auth_methods, sizeof(*authctxt->auth_methods)); authctxt->num_auth_methods = 0; for (i = 0; i < options.num_auth_methods; i++) { if (auth2_methods_valid(options.auth_methods[i], 1) != 0) { logit("Authentication methods list \"%s\" contains " "disabled method, skipping", options.auth_methods[i]); continue; } debug("authentication methods list %d: %s", authctxt->num_auth_methods, options.auth_methods[i]); authctxt->auth_methods[authctxt->num_auth_methods++] = xstrdup(options.auth_methods[i]); } if (authctxt->num_auth_methods == 0) { error("No AuthenticationMethods left after eliminating " "disabled methods"); return -1; } return 0; } static int list_starts_with(const char *methods, const char *method, const char *submethod) { size_t l = strlen(method); int match; const char *p; if (strncmp(methods, method, l) != 0) return MATCH_NONE; p = methods + l; match = MATCH_METHOD; if (*p == ':') { if (!submethod) return MATCH_PARTIAL; l = strlen(submethod); p += 1; if (strncmp(submethod, p, l)) return MATCH_NONE; p += l; match = MATCH_BOTH; } if (*p != ',' && *p != '\0') return MATCH_NONE; return match; } /* * Remove method from the start of a comma-separated list of methods. * Returns 0 if the list of methods did not start with that method or 1 * if it did. */ static int remove_method(char **methods, const char *method, const char *submethod) { char *omethods = *methods, *p; size_t l = strlen(method); int match; match = list_starts_with(omethods, method, submethod); if (match != MATCH_METHOD && match != MATCH_BOTH) return 0; p = omethods + l; if (submethod && match == MATCH_BOTH) p += 1 + strlen(submethod); /* include colon */ if (*p == ',') p++; *methods = xstrdup(p); free(omethods); return 1; } /* * Called after successful authentication. Will remove the successful method * from the start of each list in which it occurs. If it was the last method * in any list, then authentication is deemed successful. * Returns 1 if the method completed any authentication list or 0 otherwise. */ int auth2_update_methods_lists(Authctxt *authctxt, const char *method, const char *submethod) { u_int i, found = 0; debug3_f("updating methods list after \"%s\"", method); for (i = 0; i < authctxt->num_auth_methods; i++) { if (!remove_method(&(authctxt->auth_methods[i]), method, submethod)) continue; found = 1; if (*authctxt->auth_methods[i] == '\0') { debug2("authentication methods list %d complete", i); return 1; } debug3("authentication methods list %d remaining: \"%s\"", i, authctxt->auth_methods[i]); } /* This should not happen, but would be bad if it did */ if (!found) fatal_f("method not in AuthenticationMethods"); return 0; } /* Reset method-specific information */ void auth2_authctxt_reset_info(Authctxt *authctxt) { sshkey_free(authctxt->auth_method_key); free(authctxt->auth_method_info); authctxt->auth_method_key = NULL; authctxt->auth_method_info = NULL; } /* Record auth method-specific information for logs */ void auth2_record_info(Authctxt *authctxt, const char *fmt, ...) { va_list ap; int i; free(authctxt->auth_method_info); authctxt->auth_method_info = NULL; va_start(ap, fmt); i = vasprintf(&authctxt->auth_method_info, fmt, ap); va_end(ap); if (i == -1) fatal_f("vasprintf failed"); } /* * Records a public key used in authentication. This is used for logging * and to ensure that the same key is not subsequently accepted again for * multiple authentication. */ void auth2_record_key(Authctxt *authctxt, int authenticated, const struct sshkey *key) { struct sshkey **tmp, *dup; int r; if ((r = sshkey_from_private(key, &dup)) != 0) fatal_fr(r, "copy key"); sshkey_free(authctxt->auth_method_key); authctxt->auth_method_key = dup; if (!authenticated) return; /* If authenticated, make sure we don't accept this key again */ if ((r = sshkey_from_private(key, &dup)) != 0) fatal_fr(r, "copy key"); if (authctxt->nprev_keys >= INT_MAX || (tmp = recallocarray(authctxt->prev_keys, authctxt->nprev_keys, authctxt->nprev_keys + 1, sizeof(*authctxt->prev_keys))) == NULL) fatal_f("reallocarray failed"); authctxt->prev_keys = tmp; authctxt->prev_keys[authctxt->nprev_keys] = dup; authctxt->nprev_keys++; } /* Checks whether a key has already been previously used for authentication */ int auth2_key_already_used(Authctxt *authctxt, const struct sshkey *key) { u_int i; char *fp; for (i = 0; i < authctxt->nprev_keys; i++) { if (sshkey_equal_public(key, authctxt->prev_keys[i])) { fp = sshkey_fingerprint(authctxt->prev_keys[i], options.fingerprint_hash, SSH_FP_DEFAULT); debug3_f("key already used: %s %s", sshkey_type(authctxt->prev_keys[i]), fp == NULL ? "UNKNOWN" : fp); free(fp); return 1; } } return 0; } /* * Updates authctxt->session_info with details of authentication. Should be * whenever an authentication method succeeds. */ void auth2_update_session_info(Authctxt *authctxt, const char *method, const char *submethod) { int r; if (authctxt->session_info == NULL) { if ((authctxt->session_info = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); } /* Append method[/submethod] */ if ((r = sshbuf_putf(authctxt->session_info, "%s%s%s", method, submethod == NULL ? "" : "/", submethod == NULL ? "" : submethod)) != 0) fatal_fr(r, "append method"); /* Append key if present */ if (authctxt->auth_method_key != NULL) { if ((r = sshbuf_put_u8(authctxt->session_info, ' ')) != 0 || (r = sshkey_format_text(authctxt->auth_method_key, authctxt->session_info)) != 0) fatal_fr(r, "append key"); } if (authctxt->auth_method_info != NULL) { /* Ensure no ambiguity here */ if (strchr(authctxt->auth_method_info, '\n') != NULL) fatal_f("auth_method_info contains \\n"); if ((r = sshbuf_put_u8(authctxt->session_info, ' ')) != 0 || (r = sshbuf_putf(authctxt->session_info, "%s", authctxt->auth_method_info)) != 0) { fatal_fr(r, "append method info"); } } if ((r = sshbuf_put_u8(authctxt->session_info, '\n')) != 0) fatal_fr(r, "append"); } diff --git a/crypto/openssh/channels.c b/crypto/openssh/channels.c index da66b7b3494c..598ff322a175 100644 --- a/crypto/openssh/channels.c +++ b/crypto/openssh/channels.c @@ -1,5282 +1,5291 @@ -/* $OpenBSD: channels.c,v 1.432 2023/07/04 03:59:21 dlg Exp $ */ +/* $OpenBSD: channels.c,v 1.433 2023/09/04 00:01:46 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This file contains functions for generic socket connection forwarding. * There is also code for initiating connection forwarding for X11 connections, * arbitrary tcp/ip connections, and the authentication agent connection. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support added by Markus Friedl. * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 1999 Dug Song. All rights reserved. * Copyright (c) 1999 Theo de Raadt. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #ifdef HAVE_STDINT_H # include #endif #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "ssherr.h" #include "sshbuf.h" #include "packet.h" #include "log.h" #include "misc.h" #include "channels.h" #include "compat.h" #include "canohost.h" #include "sshkey.h" #include "authfd.h" #include "pathnames.h" #include "match.h" /* XXX remove once we're satisfied there's no lurking bugs */ /* #define DEBUG_CHANNEL_POLL 1 */ /* -- agent forwarding */ #define NUM_SOCKS 10 /* -- tcp forwarding */ /* special-case port number meaning allow any port */ #define FWD_PERMIT_ANY_PORT 0 /* special-case wildcard meaning allow any host */ #define FWD_PERMIT_ANY_HOST "*" /* -- X11 forwarding */ /* Maximum number of fake X11 displays to try. */ #define MAX_DISPLAYS 1000 /* Per-channel callback for pre/post IO actions */ typedef void chan_fn(struct ssh *, Channel *c); /* * Data structure for storing which hosts are permitted for forward requests. * The local sides of any remote forwards are stored in this array to prevent * a corrupt remote server from accessing arbitrary TCP/IP ports on our local * network (which might be behind a firewall). */ /* XXX: streamlocal wants a path instead of host:port */ /* Overload host_to_connect; we could just make this match Forward */ /* XXX - can we use listen_host instead of listen_path? */ struct permission { char *host_to_connect; /* Connect to 'host'. */ int port_to_connect; /* Connect to 'port'. */ char *listen_host; /* Remote side should listen address. */ char *listen_path; /* Remote side should listen path. */ int listen_port; /* Remote side should listen port. */ Channel *downstream; /* Downstream mux*/ }; /* * Stores the forwarding permission state for a single direction (local or * remote). */ struct permission_set { /* * List of all local permitted host/port pairs to allow for the * user. */ u_int num_permitted_user; struct permission *permitted_user; /* * List of all permitted host/port pairs to allow for the admin. */ u_int num_permitted_admin; struct permission *permitted_admin; /* * If this is true, all opens/listens are permitted. This is the * case on the server on which we have to trust the client anyway, * and the user could do anything after logging in. */ int all_permitted; }; /* Used to record timeouts per channel type */ struct ssh_channel_timeout { char *type_pattern; int timeout_secs; }; /* Master structure for channels state */ struct ssh_channels { /* * Pointer to an array containing all allocated channels. The array * is dynamically extended as needed. */ Channel **channels; /* * Size of the channel array. All slots of the array must always be * initialized (at least the type field); unused slots set to NULL */ u_int channels_alloc; /* * 'channel_pre*' are called just before IO to add any bits * relevant to channels in the c->io_want bitmasks. * * 'channel_post*': perform any appropriate operations for * channels which have c->io_ready events pending. */ chan_fn **channel_pre; chan_fn **channel_post; /* -- tcp forwarding */ struct permission_set local_perms; struct permission_set remote_perms; /* -- X11 forwarding */ /* Saved X11 local (client) display. */ char *x11_saved_display; /* Saved X11 authentication protocol name. */ char *x11_saved_proto; /* Saved X11 authentication data. This is the real data. */ char *x11_saved_data; u_int x11_saved_data_len; /* Deadline after which all X11 connections are refused */ time_t x11_refuse_time; /* * Fake X11 authentication data. This is what the server will be * sending us; we should replace any occurrences of this by the * real data. */ u_char *x11_fake_data; u_int x11_fake_data_len; /* AF_UNSPEC or AF_INET or AF_INET6 */ int IPv4or6; /* Channel timeouts by type */ struct ssh_channel_timeout *timeouts; size_t ntimeouts; }; /* helper */ static void port_open_helper(struct ssh *ssh, Channel *c, char *rtype); static const char *channel_rfwd_bind_host(const char *listen_host); /* non-blocking connect helpers */ static int connect_next(struct channel_connect *); static void channel_connect_ctx_free(struct channel_connect *); static Channel *rdynamic_connect_prepare(struct ssh *, char *, char *); static int rdynamic_connect_finish(struct ssh *, Channel *); /* Setup helper */ static void channel_handler_init(struct ssh_channels *sc); /* -- channel core */ void channel_init_channels(struct ssh *ssh) { struct ssh_channels *sc; if ((sc = calloc(1, sizeof(*sc))) == NULL) fatal_f("allocation failed"); sc->channels_alloc = 10; sc->channels = xcalloc(sc->channels_alloc, sizeof(*sc->channels)); sc->IPv4or6 = AF_UNSPEC; channel_handler_init(sc); ssh->chanctxt = sc; } Channel * channel_by_id(struct ssh *ssh, int id) { Channel *c; if (id < 0 || (u_int)id >= ssh->chanctxt->channels_alloc) { logit_f("%d: bad id", id); return NULL; } c = ssh->chanctxt->channels[id]; if (c == NULL) { logit_f("%d: bad id: channel free", id); return NULL; } return c; } Channel * channel_by_remote_id(struct ssh *ssh, u_int remote_id) { Channel *c; u_int i; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { c = ssh->chanctxt->channels[i]; if (c != NULL && c->have_remote_id && c->remote_id == remote_id) return c; } return NULL; } /* * Returns the channel if it is allowed to receive protocol messages. * Private channels, like listening sockets, may not receive messages. */ Channel * channel_lookup(struct ssh *ssh, int id) { Channel *c; if ((c = channel_by_id(ssh, id)) == NULL) return NULL; switch (c->type) { case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_RDYNAMIC_OPEN: case SSH_CHANNEL_RDYNAMIC_FINISH: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_ABANDONED: case SSH_CHANNEL_MUX_PROXY: return c; } logit("Non-public channel %d, type %d.", id, c->type); return NULL; } /* * Add a timeout for open channels whose c->ctype (or c->xctype if it is set) * match type_pattern. */ void channel_add_timeout(struct ssh *ssh, const char *type_pattern, int timeout_secs) { struct ssh_channels *sc = ssh->chanctxt; debug2_f("channel type \"%s\" timeout %d seconds", type_pattern, timeout_secs); sc->timeouts = xrecallocarray(sc->timeouts, sc->ntimeouts, sc->ntimeouts + 1, sizeof(*sc->timeouts)); sc->timeouts[sc->ntimeouts].type_pattern = xstrdup(type_pattern); sc->timeouts[sc->ntimeouts].timeout_secs = timeout_secs; sc->ntimeouts++; } /* Clears all previously-added channel timeouts */ void channel_clear_timeouts(struct ssh *ssh) { struct ssh_channels *sc = ssh->chanctxt; size_t i; debug3_f("clearing"); for (i = 0; i < sc->ntimeouts; i++) free(sc->timeouts[i].type_pattern); free(sc->timeouts); sc->timeouts = NULL; sc->ntimeouts = 0; } static int lookup_timeout(struct ssh *ssh, const char *type) { struct ssh_channels *sc = ssh->chanctxt; size_t i; for (i = 0; i < sc->ntimeouts; i++) { if (match_pattern(type, sc->timeouts[i].type_pattern)) return sc->timeouts[i].timeout_secs; } return 0; } /* * Sets "extended type" of a channel; used by session layer to add additional * information about channel types (e.g. shell, login, subsystem) that can then * be used to select timeouts. * Will reset c->inactive_deadline as a side-effect. */ void channel_set_xtype(struct ssh *ssh, int id, const char *xctype) { Channel *c; if ((c = channel_by_id(ssh, id)) == NULL) fatal_f("missing channel %d", id); if (c->xctype != NULL) free(c->xctype); c->xctype = xstrdup(xctype); /* Type has changed, so look up inactivity deadline again */ c->inactive_deadline = lookup_timeout(ssh, c->xctype); debug2_f("labeled channel %d as %s (inactive timeout %u)", id, xctype, c->inactive_deadline); } /* * Register filedescriptors for a channel, used when allocating a channel or * when the channel consumer/producer is ready, e.g. shell exec'd */ static void channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd, int extusage, int nonblock, int is_tty) { int val; if (rfd != -1) (void)fcntl(rfd, F_SETFD, FD_CLOEXEC); if (wfd != -1 && wfd != rfd) (void)fcntl(wfd, F_SETFD, FD_CLOEXEC); if (efd != -1 && efd != rfd && efd != wfd) (void)fcntl(efd, F_SETFD, FD_CLOEXEC); c->rfd = rfd; c->wfd = wfd; c->sock = (rfd == wfd) ? rfd : -1; c->efd = efd; c->extended_usage = extusage; if ((c->isatty = is_tty) != 0) debug2("channel %d: rfd %d isatty", c->self, c->rfd); #ifdef _AIX /* XXX: Later AIX versions can't push as much data to tty */ c->wfd_isatty = is_tty || isatty(c->wfd); #endif /* enable nonblocking mode */ c->restore_block = 0; if (nonblock == CHANNEL_NONBLOCK_STDIO) { /* * Special handling for stdio file descriptors: do not set * non-blocking mode if they are TTYs. Otherwise prepare to * restore their blocking state on exit to avoid interfering * with other programs that follow. */ if (rfd != -1 && !isatty(rfd) && (val = fcntl(rfd, F_GETFL)) != -1 && !(val & O_NONBLOCK)) { c->restore_flags[0] = val; c->restore_block |= CHANNEL_RESTORE_RFD; set_nonblock(rfd); } if (wfd != -1 && !isatty(wfd) && (val = fcntl(wfd, F_GETFL)) != -1 && !(val & O_NONBLOCK)) { c->restore_flags[1] = val; c->restore_block |= CHANNEL_RESTORE_WFD; set_nonblock(wfd); } if (efd != -1 && !isatty(efd) && (val = fcntl(efd, F_GETFL)) != -1 && !(val & O_NONBLOCK)) { c->restore_flags[2] = val; c->restore_block |= CHANNEL_RESTORE_EFD; set_nonblock(efd); } } else if (nonblock) { if (rfd != -1) set_nonblock(rfd); if (wfd != -1) set_nonblock(wfd); if (efd != -1) set_nonblock(efd); } } /* * Allocate a new channel object and set its type and socket. */ Channel * channel_new(struct ssh *ssh, char *ctype, int type, int rfd, int wfd, int efd, u_int window, u_int maxpack, int extusage, const char *remote_name, int nonblock) { struct ssh_channels *sc = ssh->chanctxt; u_int i, found = 0; Channel *c; int r; /* Try to find a free slot where to put the new channel. */ for (i = 0; i < sc->channels_alloc; i++) { if (sc->channels[i] == NULL) { /* Found a free slot. */ found = i; break; } } if (i >= sc->channels_alloc) { /* * There are no free slots. Take last+1 slot and expand * the array. */ found = sc->channels_alloc; if (sc->channels_alloc > CHANNELS_MAX_CHANNELS) fatal_f("internal error: channels_alloc %d too big", sc->channels_alloc); sc->channels = xrecallocarray(sc->channels, sc->channels_alloc, sc->channels_alloc + 10, sizeof(*sc->channels)); sc->channels_alloc += 10; debug2("channel: expanding %d", sc->channels_alloc); } /* Initialize and return new channel. */ c = sc->channels[found] = xcalloc(1, sizeof(Channel)); if ((c->input = sshbuf_new()) == NULL || (c->output = sshbuf_new()) == NULL || (c->extended = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_set_max_size(c->input, CHAN_INPUT_MAX)) != 0) fatal_fr(r, "sshbuf_set_max_size"); c->ostate = CHAN_OUTPUT_OPEN; c->istate = CHAN_INPUT_OPEN; channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, 0); c->self = found; c->type = type; c->ctype = ctype; c->local_window = window; c->local_window_max = window; c->local_maxpacket = maxpack; c->remote_name = xstrdup(remote_name); c->ctl_chan = -1; c->delayed = 1; /* prevent call to channel_post handler */ c->inactive_deadline = lookup_timeout(ssh, c->ctype); TAILQ_INIT(&c->status_confirms); debug("channel %d: new %s [%s] (inactive timeout: %u)", found, c->ctype, remote_name, c->inactive_deadline); return c; } int channel_close_fd(struct ssh *ssh, Channel *c, int *fdp) { int ret, fd = *fdp; if (fd == -1) return 0; /* restore blocking */ if (*fdp == c->rfd && (c->restore_block & CHANNEL_RESTORE_RFD) != 0) (void)fcntl(*fdp, F_SETFL, c->restore_flags[0]); else if (*fdp == c->wfd && (c->restore_block & CHANNEL_RESTORE_WFD) != 0) (void)fcntl(*fdp, F_SETFL, c->restore_flags[1]); else if (*fdp == c->efd && (c->restore_block & CHANNEL_RESTORE_EFD) != 0) (void)fcntl(*fdp, F_SETFL, c->restore_flags[2]); if (*fdp == c->rfd) { c->io_want &= ~SSH_CHAN_IO_RFD; c->io_ready &= ~SSH_CHAN_IO_RFD; c->rfd = -1; c->pfds[0] = -1; } if (*fdp == c->wfd) { c->io_want &= ~SSH_CHAN_IO_WFD; c->io_ready &= ~SSH_CHAN_IO_WFD; c->wfd = -1; c->pfds[1] = -1; } if (*fdp == c->efd) { c->io_want &= ~SSH_CHAN_IO_EFD; c->io_ready &= ~SSH_CHAN_IO_EFD; c->efd = -1; c->pfds[2] = -1; } if (*fdp == c->sock) { c->io_want &= ~SSH_CHAN_IO_SOCK; c->io_ready &= ~SSH_CHAN_IO_SOCK; c->sock = -1; c->pfds[3] = -1; } ret = close(fd); *fdp = -1; /* probably redundant */ return ret; } /* Close all channel fd/socket. */ static void channel_close_fds(struct ssh *ssh, Channel *c) { int sock = c->sock, rfd = c->rfd, wfd = c->wfd, efd = c->efd; channel_close_fd(ssh, c, &c->sock); if (rfd != sock) channel_close_fd(ssh, c, &c->rfd); if (wfd != sock && wfd != rfd) channel_close_fd(ssh, c, &c->wfd); if (efd != sock && efd != rfd && efd != wfd) channel_close_fd(ssh, c, &c->efd); } static void fwd_perm_clear(struct permission *perm) { free(perm->host_to_connect); free(perm->listen_host); free(perm->listen_path); memset(perm, 0, sizeof(*perm)); } /* Returns an printable name for the specified forwarding permission list */ static const char * fwd_ident(int who, int where) { if (who == FORWARD_ADM) { if (where == FORWARD_LOCAL) return "admin local"; else if (where == FORWARD_REMOTE) return "admin remote"; } else if (who == FORWARD_USER) { if (where == FORWARD_LOCAL) return "user local"; else if (where == FORWARD_REMOTE) return "user remote"; } fatal("Unknown forward permission list %d/%d", who, where); } /* Returns the forwarding permission list for the specified direction */ static struct permission_set * permission_set_get(struct ssh *ssh, int where) { struct ssh_channels *sc = ssh->chanctxt; switch (where) { case FORWARD_LOCAL: return &sc->local_perms; break; case FORWARD_REMOTE: return &sc->remote_perms; break; default: fatal_f("invalid forwarding direction %d", where); } } /* Returns pointers to the specified forwarding list and its element count */ static void permission_set_get_array(struct ssh *ssh, int who, int where, struct permission ***permpp, u_int **npermpp) { struct permission_set *pset = permission_set_get(ssh, where); switch (who) { case FORWARD_USER: *permpp = &pset->permitted_user; *npermpp = &pset->num_permitted_user; break; case FORWARD_ADM: *permpp = &pset->permitted_admin; *npermpp = &pset->num_permitted_admin; break; default: fatal_f("invalid forwarding client %d", who); } } /* Adds an entry to the specified forwarding list */ static int permission_set_add(struct ssh *ssh, int who, int where, const char *host_to_connect, int port_to_connect, const char *listen_host, const char *listen_path, int listen_port, Channel *downstream) { struct permission **permp; u_int n, *npermp; permission_set_get_array(ssh, who, where, &permp, &npermp); if (*npermp >= INT_MAX) fatal_f("%s overflow", fwd_ident(who, where)); *permp = xrecallocarray(*permp, *npermp, *npermp + 1, sizeof(**permp)); n = (*npermp)++; #define MAYBE_DUP(s) ((s == NULL) ? NULL : xstrdup(s)) (*permp)[n].host_to_connect = MAYBE_DUP(host_to_connect); (*permp)[n].port_to_connect = port_to_connect; (*permp)[n].listen_host = MAYBE_DUP(listen_host); (*permp)[n].listen_path = MAYBE_DUP(listen_path); (*permp)[n].listen_port = listen_port; (*permp)[n].downstream = downstream; #undef MAYBE_DUP return (int)n; } static void mux_remove_remote_forwardings(struct ssh *ssh, Channel *c) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; struct permission *perm; int r; u_int i; for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (perm->downstream != c) continue; /* cancel on the server, since mux client is gone */ debug("channel %d: cleanup remote forward for %s:%u", c->self, perm->listen_host, perm->listen_port); if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "cancel-tcpip-forward")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || (r = sshpkt_put_cstring(ssh, channel_rfwd_bind_host(perm->listen_host))) != 0 || (r = sshpkt_put_u32(ssh, perm->listen_port)) != 0 || (r = sshpkt_send(ssh)) != 0) { fatal_fr(r, "channel %i", c->self); } fwd_perm_clear(perm); /* unregister */ } } /* Free the channel and close its fd/socket. */ void channel_free(struct ssh *ssh, Channel *c) { struct ssh_channels *sc = ssh->chanctxt; char *s; u_int i, n; Channel *other; struct channel_confirm *cc; for (n = 0, i = 0; i < sc->channels_alloc; i++) { if ((other = sc->channels[i]) == NULL) continue; n++; /* detach from mux client and prepare for closing */ if (c->type == SSH_CHANNEL_MUX_CLIENT && other->type == SSH_CHANNEL_MUX_PROXY && other->mux_ctx == c) { other->mux_ctx = NULL; other->type = SSH_CHANNEL_OPEN; other->istate = CHAN_INPUT_CLOSED; other->ostate = CHAN_OUTPUT_CLOSED; } } debug("channel %d: free: %s, nchannels %u", c->self, c->remote_name ? c->remote_name : "???", n); if (c->type == SSH_CHANNEL_MUX_CLIENT) { mux_remove_remote_forwardings(ssh, c); free(c->mux_ctx); c->mux_ctx = NULL; } else if (c->type == SSH_CHANNEL_MUX_LISTENER) { free(c->mux_ctx); c->mux_ctx = NULL; } if (log_level_get() >= SYSLOG_LEVEL_DEBUG3) { s = channel_open_message(ssh); debug3("channel %d: status: %s", c->self, s); free(s); } channel_close_fds(ssh, c); sshbuf_free(c->input); sshbuf_free(c->output); sshbuf_free(c->extended); c->input = c->output = c->extended = NULL; free(c->remote_name); c->remote_name = NULL; free(c->path); c->path = NULL; free(c->listening_addr); c->listening_addr = NULL; free(c->xctype); c->xctype = NULL; while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) { if (cc->abandon_cb != NULL) cc->abandon_cb(ssh, c, cc->ctx); TAILQ_REMOVE(&c->status_confirms, cc, entry); freezero(cc, sizeof(*cc)); } if (c->filter_cleanup != NULL && c->filter_ctx != NULL) c->filter_cleanup(ssh, c->self, c->filter_ctx); sc->channels[c->self] = NULL; freezero(c, sizeof(*c)); } void channel_free_all(struct ssh *ssh) { u_int i; struct ssh_channels *sc = ssh->chanctxt; for (i = 0; i < sc->channels_alloc; i++) if (sc->channels[i] != NULL) channel_free(ssh, sc->channels[i]); free(sc->channels); sc->channels = NULL; sc->channels_alloc = 0; free(sc->x11_saved_display); sc->x11_saved_display = NULL; free(sc->x11_saved_proto); sc->x11_saved_proto = NULL; free(sc->x11_saved_data); sc->x11_saved_data = NULL; sc->x11_saved_data_len = 0; free(sc->x11_fake_data); sc->x11_fake_data = NULL; sc->x11_fake_data_len = 0; } /* * Closes the sockets/fds of all channels. This is used to close extra file * descriptors after a fork. */ void channel_close_all(struct ssh *ssh) { u_int i; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) if (ssh->chanctxt->channels[i] != NULL) channel_close_fds(ssh, ssh->chanctxt->channels[i]); } /* * Stop listening to channels. */ void channel_stop_listening(struct ssh *ssh) { u_int i; Channel *c; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { c = ssh->chanctxt->channels[i]; if (c != NULL) { switch (c->type) { case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_UNIX_LISTENER: case SSH_CHANNEL_RUNIX_LISTENER: channel_close_fd(ssh, c, &c->sock); channel_free(ssh, c); break; } } } } /* * Returns true if no channel has too much buffered data, and false if one or * more channel is overfull. */ int channel_not_very_much_buffered_data(struct ssh *ssh) { u_int i; u_int maxsize = ssh_packet_get_maxsize(ssh); Channel *c; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { c = ssh->chanctxt->channels[i]; if (c == NULL || c->type != SSH_CHANNEL_OPEN) continue; if (sshbuf_len(c->output) > maxsize) { debug2("channel %d: big output buffer %zu > %u", c->self, sshbuf_len(c->output), maxsize); return 0; } } return 1; } /* Returns true if any channel is still open. */ int channel_still_open(struct ssh *ssh) { u_int i; Channel *c; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { c = ssh->chanctxt->channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_RDYNAMIC_OPEN: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: case SSH_CHANNEL_ABANDONED: case SSH_CHANNEL_UNIX_LISTENER: case SSH_CHANNEL_RUNIX_LISTENER: continue; case SSH_CHANNEL_LARVAL: continue; case SSH_CHANNEL_OPENING: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_RDYNAMIC_FINISH: case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_MUX_CLIENT: case SSH_CHANNEL_MUX_PROXY: return 1; default: fatal_f("bad channel type %d", c->type); /* NOTREACHED */ } } return 0; } /* Returns the id of an open channel suitable for keepaliving */ int channel_find_open(struct ssh *ssh) { u_int i; Channel *c; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { c = ssh->chanctxt->channels[i]; if (c == NULL || !c->have_remote_id) continue; switch (c->type) { case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_RDYNAMIC_OPEN: case SSH_CHANNEL_RDYNAMIC_FINISH: case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_MUX_CLIENT: case SSH_CHANNEL_MUX_PROXY: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: case SSH_CHANNEL_ABANDONED: case SSH_CHANNEL_UNIX_LISTENER: case SSH_CHANNEL_RUNIX_LISTENER: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: return i; default: fatal_f("bad channel type %d", c->type); /* NOTREACHED */ } } return -1; } /* Returns the state of the channel's extended usage flag */ const char * channel_format_extended_usage(const Channel *c) { if (c->efd == -1) return "closed"; switch (c->extended_usage) { case CHAN_EXTENDED_WRITE: return "write"; case CHAN_EXTENDED_READ: return "read"; case CHAN_EXTENDED_IGNORE: return "ignore"; default: return "UNKNOWN"; } } static char * channel_format_status(const Channel *c) { char *ret = NULL; xasprintf(&ret, "t%d [%s] %s%u i%u/%zu o%u/%zu e[%s]/%zu " "fd %d/%d/%d sock %d cc %d io 0x%02x/0x%02x", c->type, c->xctype != NULL ? c->xctype : c->ctype, c->have_remote_id ? "r" : "nr", c->remote_id, c->istate, sshbuf_len(c->input), c->ostate, sshbuf_len(c->output), channel_format_extended_usage(c), sshbuf_len(c->extended), c->rfd, c->wfd, c->efd, c->sock, c->ctl_chan, c->io_want, c->io_ready); return ret; } /* * Returns a message describing the currently open forwarded connections, * suitable for sending to the client. The message contains crlf pairs for * newlines. */ char * channel_open_message(struct ssh *ssh) { struct sshbuf *buf; Channel *c; u_int i; int r; char *cp, *ret; if ((buf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); if ((r = sshbuf_putf(buf, "The following connections are open:\r\n")) != 0) fatal_fr(r, "sshbuf_putf"); for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { c = ssh->chanctxt->channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_ZOMBIE: case SSH_CHANNEL_ABANDONED: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_UNIX_LISTENER: case SSH_CHANNEL_RUNIX_LISTENER: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_RDYNAMIC_OPEN: case SSH_CHANNEL_RDYNAMIC_FINISH: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_MUX_PROXY: case SSH_CHANNEL_MUX_CLIENT: cp = channel_format_status(c); if ((r = sshbuf_putf(buf, " #%d %.300s (%s)\r\n", c->self, c->remote_name, cp)) != 0) { free(cp); fatal_fr(r, "sshbuf_putf"); } free(cp); continue; default: fatal_f("bad channel type %d", c->type); /* NOTREACHED */ } } if ((ret = sshbuf_dup_string(buf)) == NULL) fatal_f("sshbuf_dup_string"); sshbuf_free(buf); return ret; } static void open_preamble(struct ssh *ssh, const char *where, Channel *c, const char *type) { int r; if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN)) != 0 || (r = sshpkt_put_cstring(ssh, type)) != 0 || (r = sshpkt_put_u32(ssh, c->self)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window)) != 0 || (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0) { fatal_r(r, "%s: channel %i: open", where, c->self); } } void channel_send_open(struct ssh *ssh, int id) { Channel *c = channel_lookup(ssh, id); int r; if (c == NULL) { logit("channel_send_open: %d: bad id", id); return; } debug2("channel %d: send open", id); open_preamble(ssh, __func__, c, c->ctype); if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i", c->self); } void channel_request_start(struct ssh *ssh, int id, char *service, int wantconfirm) { Channel *c = channel_lookup(ssh, id); int r; if (c == NULL) { logit_f("%d: unknown channel id", id); return; } if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); debug2("channel %d: request %s confirm %d", id, service, wantconfirm); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_REQUEST)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_cstring(ssh, service)) != 0 || (r = sshpkt_put_u8(ssh, wantconfirm)) != 0) { fatal_fr(r, "channel %i", c->self); } } void channel_register_status_confirm(struct ssh *ssh, int id, channel_confirm_cb *cb, channel_confirm_abandon_cb *abandon_cb, void *ctx) { struct channel_confirm *cc; Channel *c; if ((c = channel_lookup(ssh, id)) == NULL) fatal_f("%d: bad id", id); cc = xcalloc(1, sizeof(*cc)); cc->cb = cb; cc->abandon_cb = abandon_cb; cc->ctx = ctx; TAILQ_INSERT_TAIL(&c->status_confirms, cc, entry); } void channel_register_open_confirm(struct ssh *ssh, int id, channel_open_fn *fn, void *ctx) { Channel *c = channel_lookup(ssh, id); if (c == NULL) { logit_f("%d: bad id", id); return; } c->open_confirm = fn; c->open_confirm_ctx = ctx; } void channel_register_cleanup(struct ssh *ssh, int id, channel_callback_fn *fn, int do_close) { Channel *c = channel_by_id(ssh, id); if (c == NULL) { logit_f("%d: bad id", id); return; } c->detach_user = fn; c->detach_close = do_close; } void channel_cancel_cleanup(struct ssh *ssh, int id) { Channel *c = channel_by_id(ssh, id); if (c == NULL) { logit_f("%d: bad id", id); return; } c->detach_user = NULL; c->detach_close = 0; } void channel_register_filter(struct ssh *ssh, int id, channel_infilter_fn *ifn, channel_outfilter_fn *ofn, channel_filter_cleanup_fn *cfn, void *ctx) { Channel *c = channel_lookup(ssh, id); if (c == NULL) { logit_f("%d: bad id", id); return; } c->input_filter = ifn; c->output_filter = ofn; c->filter_ctx = ctx; c->filter_cleanup = cfn; } void channel_set_fds(struct ssh *ssh, int id, int rfd, int wfd, int efd, int extusage, int nonblock, int is_tty, u_int window_max) { Channel *c = channel_lookup(ssh, id); int r; if (c == NULL || c->type != SSH_CHANNEL_LARVAL) fatal("channel_activate for non-larval channel %d.", id); if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, is_tty); c->type = SSH_CHANNEL_OPEN; c->lastused = monotime(); c->local_window = c->local_window_max = window_max; if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i", c->self); } static void channel_pre_listener(struct ssh *ssh, Channel *c) { c->io_want = SSH_CHAN_IO_SOCK_R; } static void channel_pre_connecting(struct ssh *ssh, Channel *c) { debug3("channel %d: waiting for connection", c->self); c->io_want = SSH_CHAN_IO_SOCK_W; } static void channel_pre_open(struct ssh *ssh, Channel *c) { c->io_want = 0; if (c->istate == CHAN_INPUT_OPEN && c->remote_window > 0 && sshbuf_len(c->input) < c->remote_window && sshbuf_check_reserve(c->input, CHAN_RBUF) == 0) c->io_want |= SSH_CHAN_IO_RFD; if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (sshbuf_len(c->output) > 0) { c->io_want |= SSH_CHAN_IO_WFD; } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) debug2("channel %d: " "obuf_empty delayed efd %d/(%zu)", c->self, c->efd, sshbuf_len(c->extended)); else chan_obuf_empty(ssh, c); } } /** XXX check close conditions, too */ if (c->efd != -1 && !(c->istate == CHAN_INPUT_CLOSED && c->ostate == CHAN_OUTPUT_CLOSED)) { if (c->extended_usage == CHAN_EXTENDED_WRITE && sshbuf_len(c->extended) > 0) c->io_want |= SSH_CHAN_IO_EFD_W; else if (c->efd != -1 && !(c->flags & CHAN_EOF_SENT) && (c->extended_usage == CHAN_EXTENDED_READ || c->extended_usage == CHAN_EXTENDED_IGNORE) && sshbuf_len(c->extended) < c->remote_window) c->io_want |= SSH_CHAN_IO_EFD_R; } /* XXX: What about efd? races? */ } /* * This is a special state for X11 authentication spoofing. An opened X11 * connection (when authentication spoofing is being done) remains in this * state until the first packet has been completely read. The authentication * data in that packet is then substituted by the real data if it matches the * fake data, and the channel is put into normal mode. * XXX All this happens at the client side. * Returns: 0 = need more data, -1 = wrong cookie, 1 = ok */ static int x11_open_helper(struct ssh *ssh, struct sshbuf *b) { struct ssh_channels *sc = ssh->chanctxt; u_char *ucp; u_int proto_len, data_len; /* Is this being called after the refusal deadline? */ if (sc->x11_refuse_time != 0 && monotime() >= sc->x11_refuse_time) { verbose("Rejected X11 connection after ForwardX11Timeout " "expired"); return -1; } /* Check if the fixed size part of the packet is in buffer. */ if (sshbuf_len(b) < 12) return 0; /* Parse the lengths of variable-length fields. */ ucp = sshbuf_mutable_ptr(b); if (ucp[0] == 0x42) { /* Byte order MSB first. */ proto_len = 256 * ucp[6] + ucp[7]; data_len = 256 * ucp[8] + ucp[9]; } else if (ucp[0] == 0x6c) { /* Byte order LSB first. */ proto_len = ucp[6] + 256 * ucp[7]; data_len = ucp[8] + 256 * ucp[9]; } else { debug2("Initial X11 packet contains bad byte order byte: 0x%x", ucp[0]); return -1; } /* Check if the whole packet is in buffer. */ if (sshbuf_len(b) < 12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) return 0; /* Check if authentication protocol matches. */ if (proto_len != strlen(sc->x11_saved_proto) || memcmp(ucp + 12, sc->x11_saved_proto, proto_len) != 0) { debug2("X11 connection uses different authentication protocol."); return -1; } /* Check if authentication data matches our fake data. */ if (data_len != sc->x11_fake_data_len || timingsafe_bcmp(ucp + 12 + ((proto_len + 3) & ~3), sc->x11_fake_data, sc->x11_fake_data_len) != 0) { debug2("X11 auth data does not match fake data."); return -1; } /* Check fake data length */ if (sc->x11_fake_data_len != sc->x11_saved_data_len) { error("X11 fake_data_len %d != saved_data_len %d", sc->x11_fake_data_len, sc->x11_saved_data_len); return -1; } /* * Received authentication protocol and data match * our fake data. Substitute the fake data with real * data. */ memcpy(ucp + 12 + ((proto_len + 3) & ~3), sc->x11_saved_data, sc->x11_saved_data_len); return 1; } void channel_force_close(struct ssh *ssh, Channel *c, int abandon) { debug3_f("channel %d: forcibly closing", c->self); if (c->istate == CHAN_INPUT_OPEN) chan_read_failed(ssh, c); if (c->istate == CHAN_INPUT_WAIT_DRAIN) { sshbuf_reset(c->input); chan_ibuf_empty(ssh, c); } if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { sshbuf_reset(c->output); chan_write_failed(ssh, c); } if (c->detach_user) c->detach_user(ssh, c->self, 1, NULL); if (c->efd != -1) channel_close_fd(ssh, c, &c->efd); if (abandon) c->type = SSH_CHANNEL_ABANDONED; /* exempt from inactivity timeouts */ c->inactive_deadline = 0; c->lastused = 0; } static void channel_pre_x11_open(struct ssh *ssh, Channel *c) { int ret = x11_open_helper(ssh, c->output); /* c->force_drain = 1; */ if (ret == 1) { c->type = SSH_CHANNEL_OPEN; c->lastused = monotime(); channel_pre_open(ssh, c); } else if (ret == -1) { logit("X11 connection rejected because of wrong " "authentication."); debug2("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate); channel_force_close(ssh, c, 0); } } static void channel_pre_mux_client(struct ssh *ssh, Channel *c) { c->io_want = 0; if (c->istate == CHAN_INPUT_OPEN && !c->mux_pause && sshbuf_check_reserve(c->input, CHAN_RBUF) == 0) c->io_want |= SSH_CHAN_IO_RFD; if (c->istate == CHAN_INPUT_WAIT_DRAIN) { /* clear buffer immediately (discard any partial packet) */ sshbuf_reset(c->input); chan_ibuf_empty(ssh, c); /* Start output drain. XXX just kill chan? */ chan_rcvd_oclose(ssh, c); } if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (sshbuf_len(c->output) > 0) c->io_want |= SSH_CHAN_IO_WFD; else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) chan_obuf_empty(ssh, c); } } /* try to decode a socks4 header */ static int channel_decode_socks4(Channel *c, struct sshbuf *input, struct sshbuf *output) { const u_char *p; char *host; u_int len, have, i, found, need; char username[256]; struct { u_int8_t version; u_int8_t command; u_int16_t dest_port; struct in_addr dest_addr; } s4_req, s4_rsp; int r; debug2("channel %d: decode socks4", c->self); have = sshbuf_len(input); len = sizeof(s4_req); if (have < len) return 0; p = sshbuf_ptr(input); need = 1; /* SOCKS4A uses an invalid IP address 0.0.0.x */ if (p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] != 0) { debug2("channel %d: socks4a request", c->self); /* ... and needs an extra string (the hostname) */ need = 2; } /* Check for terminating NUL on the string(s) */ for (found = 0, i = len; i < have; i++) { if (p[i] == '\0') { found++; if (found == need) break; } if (i > 1024) { /* the peer is probably sending garbage */ debug("channel %d: decode socks4: too long", c->self); return -1; } } if (found < need) return 0; if ((r = sshbuf_get(input, &s4_req.version, 1)) != 0 || (r = sshbuf_get(input, &s4_req.command, 1)) != 0 || (r = sshbuf_get(input, &s4_req.dest_port, 2)) != 0 || (r = sshbuf_get(input, &s4_req.dest_addr, 4)) != 0) { debug_r(r, "channels %d: decode socks4", c->self); return -1; } have = sshbuf_len(input); p = sshbuf_ptr(input); if (memchr(p, '\0', have) == NULL) { error("channel %d: decode socks4: unterminated user", c->self); return -1; } len = strlen(p); debug2("channel %d: decode socks4: user %s/%d", c->self, p, len); len++; /* trailing '\0' */ strlcpy(username, p, sizeof(username)); if ((r = sshbuf_consume(input, len)) != 0) fatal_fr(r, "channel %d: consume", c->self); free(c->path); c->path = NULL; if (need == 1) { /* SOCKS4: one string */ host = inet_ntoa(s4_req.dest_addr); c->path = xstrdup(host); } else { /* SOCKS4A: two strings */ have = sshbuf_len(input); p = sshbuf_ptr(input); if (memchr(p, '\0', have) == NULL) { error("channel %d: decode socks4a: host not nul " "terminated", c->self); return -1; } len = strlen(p); debug2("channel %d: decode socks4a: host %s/%d", c->self, p, len); len++; /* trailing '\0' */ if (len > NI_MAXHOST) { error("channel %d: hostname \"%.100s\" too long", c->self, p); return -1; } c->path = xstrdup(p); if ((r = sshbuf_consume(input, len)) != 0) fatal_fr(r, "channel %d: consume", c->self); } c->host_port = ntohs(s4_req.dest_port); debug2("channel %d: dynamic request: socks4 host %s port %u command %u", c->self, c->path, c->host_port, s4_req.command); if (s4_req.command != 1) { debug("channel %d: cannot handle: %s cn %d", c->self, need == 1 ? "SOCKS4" : "SOCKS4A", s4_req.command); return -1; } s4_rsp.version = 0; /* vn: 0 for reply */ s4_rsp.command = 90; /* cd: req granted */ s4_rsp.dest_port = 0; /* ignored */ s4_rsp.dest_addr.s_addr = INADDR_ANY; /* ignored */ if ((r = sshbuf_put(output, &s4_rsp, sizeof(s4_rsp))) != 0) fatal_fr(r, "channel %d: append reply", c->self); return 1; } /* try to decode a socks5 header */ #define SSH_SOCKS5_AUTHDONE 0x1000 #define SSH_SOCKS5_NOAUTH 0x00 #define SSH_SOCKS5_IPV4 0x01 #define SSH_SOCKS5_DOMAIN 0x03 #define SSH_SOCKS5_IPV6 0x04 #define SSH_SOCKS5_CONNECT 0x01 #define SSH_SOCKS5_SUCCESS 0x00 static int channel_decode_socks5(Channel *c, struct sshbuf *input, struct sshbuf *output) { /* XXX use get/put_u8 instead of trusting struct padding */ struct { u_int8_t version; u_int8_t command; u_int8_t reserved; u_int8_t atyp; } s5_req, s5_rsp; u_int16_t dest_port; char dest_addr[255+1], ntop[INET6_ADDRSTRLEN]; const u_char *p; u_int have, need, i, found, nmethods, addrlen, af; int r; debug2("channel %d: decode socks5", c->self); p = sshbuf_ptr(input); if (p[0] != 0x05) return -1; have = sshbuf_len(input); if (!(c->flags & SSH_SOCKS5_AUTHDONE)) { /* format: ver | nmethods | methods */ if (have < 2) return 0; nmethods = p[1]; if (have < nmethods + 2) return 0; /* look for method: "NO AUTHENTICATION REQUIRED" */ for (found = 0, i = 2; i < nmethods + 2; i++) { if (p[i] == SSH_SOCKS5_NOAUTH) { found = 1; break; } } if (!found) { debug("channel %d: method SSH_SOCKS5_NOAUTH not found", c->self); return -1; } if ((r = sshbuf_consume(input, nmethods + 2)) != 0) fatal_fr(r, "channel %d: consume", c->self); /* version, method */ if ((r = sshbuf_put_u8(output, 0x05)) != 0 || (r = sshbuf_put_u8(output, SSH_SOCKS5_NOAUTH)) != 0) fatal_fr(r, "channel %d: append reply", c->self); c->flags |= SSH_SOCKS5_AUTHDONE; debug2("channel %d: socks5 auth done", c->self); return 0; /* need more */ } debug2("channel %d: socks5 post auth", c->self); if (have < sizeof(s5_req)+1) return 0; /* need more */ memcpy(&s5_req, p, sizeof(s5_req)); if (s5_req.version != 0x05 || s5_req.command != SSH_SOCKS5_CONNECT || s5_req.reserved != 0x00) { debug2("channel %d: only socks5 connect supported", c->self); return -1; } switch (s5_req.atyp){ case SSH_SOCKS5_IPV4: addrlen = 4; af = AF_INET; break; case SSH_SOCKS5_DOMAIN: addrlen = p[sizeof(s5_req)]; af = -1; break; case SSH_SOCKS5_IPV6: addrlen = 16; af = AF_INET6; break; default: debug2("channel %d: bad socks5 atyp %d", c->self, s5_req.atyp); return -1; } need = sizeof(s5_req) + addrlen + 2; if (s5_req.atyp == SSH_SOCKS5_DOMAIN) need++; if (have < need) return 0; if ((r = sshbuf_consume(input, sizeof(s5_req))) != 0) fatal_fr(r, "channel %d: consume", c->self); if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { /* host string length */ if ((r = sshbuf_consume(input, 1)) != 0) fatal_fr(r, "channel %d: consume", c->self); } if ((r = sshbuf_get(input, &dest_addr, addrlen)) != 0 || (r = sshbuf_get(input, &dest_port, 2)) != 0) { debug_r(r, "channel %d: parse addr/port", c->self); return -1; } dest_addr[addrlen] = '\0'; free(c->path); c->path = NULL; if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { if (addrlen >= NI_MAXHOST) { error("channel %d: dynamic request: socks5 hostname " "\"%.100s\" too long", c->self, dest_addr); return -1; } c->path = xstrdup(dest_addr); } else { if (inet_ntop(af, dest_addr, ntop, sizeof(ntop)) == NULL) return -1; c->path = xstrdup(ntop); } c->host_port = ntohs(dest_port); debug2("channel %d: dynamic request: socks5 host %s port %u command %u", c->self, c->path, c->host_port, s5_req.command); s5_rsp.version = 0x05; s5_rsp.command = SSH_SOCKS5_SUCCESS; s5_rsp.reserved = 0; /* ignored */ s5_rsp.atyp = SSH_SOCKS5_IPV4; dest_port = 0; /* ignored */ if ((r = sshbuf_put(output, &s5_rsp, sizeof(s5_rsp))) != 0 || (r = sshbuf_put_u32(output, ntohl(INADDR_ANY))) != 0 || (r = sshbuf_put(output, &dest_port, sizeof(dest_port))) != 0) fatal_fr(r, "channel %d: append reply", c->self); return 1; } Channel * channel_connect_stdio_fwd(struct ssh *ssh, const char *host_to_connect, int port_to_connect, int in, int out, int nonblock) { Channel *c; debug_f("%s:%d", host_to_connect, port_to_connect); c = channel_new(ssh, "stdio-forward", SSH_CHANNEL_OPENING, in, out, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "stdio-forward", nonblock); c->path = xstrdup(host_to_connect); c->host_port = port_to_connect; c->listening_port = 0; c->force_drain = 1; channel_register_fds(ssh, c, in, out, -1, 0, 1, 0); port_open_helper(ssh, c, port_to_connect == PORT_STREAMLOCAL ? "direct-streamlocal@openssh.com" : "direct-tcpip"); return c; } /* dynamic port forwarding */ static void channel_pre_dynamic(struct ssh *ssh, Channel *c) { const u_char *p; u_int have; int ret; c->io_want = 0; have = sshbuf_len(c->input); debug2("channel %d: pre_dynamic: have %d", c->self, have); /* sshbuf_dump(c->input, stderr); */ /* check if the fixed size part of the packet is in buffer. */ if (have < 3) { /* need more */ c->io_want |= SSH_CHAN_IO_RFD; return; } /* try to guess the protocol */ p = sshbuf_ptr(c->input); /* XXX sshbuf_peek_u8? */ switch (p[0]) { case 0x04: ret = channel_decode_socks4(c, c->input, c->output); break; case 0x05: ret = channel_decode_socks5(c, c->input, c->output); break; default: ret = -1; break; } if (ret < 0) { chan_mark_dead(ssh, c); } else if (ret == 0) { debug2("channel %d: pre_dynamic: need more", c->self); /* need more */ c->io_want |= SSH_CHAN_IO_RFD; if (sshbuf_len(c->output)) c->io_want |= SSH_CHAN_IO_WFD; } else { /* switch to the next state */ c->type = SSH_CHANNEL_OPENING; port_open_helper(ssh, c, "direct-tcpip"); } } /* simulate read-error */ static void rdynamic_close(struct ssh *ssh, Channel *c) { c->type = SSH_CHANNEL_OPEN; channel_force_close(ssh, c, 0); } /* reverse dynamic port forwarding */ static void channel_before_prepare_io_rdynamic(struct ssh *ssh, Channel *c) { const u_char *p; u_int have, len; int r, ret; have = sshbuf_len(c->output); debug2("channel %d: pre_rdynamic: have %d", c->self, have); /* sshbuf_dump(c->output, stderr); */ /* EOF received */ if (c->flags & CHAN_EOF_RCVD) { if ((r = sshbuf_consume(c->output, have)) != 0) fatal_fr(r, "channel %d: consume", c->self); rdynamic_close(ssh, c); return; } /* check if the fixed size part of the packet is in buffer. */ if (have < 3) return; /* try to guess the protocol */ p = sshbuf_ptr(c->output); switch (p[0]) { case 0x04: /* switch input/output for reverse forwarding */ ret = channel_decode_socks4(c, c->output, c->input); break; case 0x05: ret = channel_decode_socks5(c, c->output, c->input); break; default: ret = -1; break; } if (ret < 0) { rdynamic_close(ssh, c); } else if (ret == 0) { debug2("channel %d: pre_rdynamic: need more", c->self); /* send socks request to peer */ len = sshbuf_len(c->input); if (len > 0 && len < c->remote_window) { if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_stringb(ssh, c->input)) != 0 || (r = sshpkt_send(ssh)) != 0) { fatal_fr(r, "channel %i: rdynamic", c->self); } if ((r = sshbuf_consume(c->input, len)) != 0) fatal_fr(r, "channel %d: consume", c->self); c->remote_window -= len; } } else if (rdynamic_connect_finish(ssh, c) < 0) { /* the connect failed */ rdynamic_close(ssh, c); } } /* This is our fake X11 server socket. */ static void channel_post_x11_listener(struct ssh *ssh, Channel *c) { Channel *nc; struct sockaddr_storage addr; int r, newsock, oerrno, remote_port; socklen_t addrlen; char buf[16384], *remote_ipaddr; if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; debug("X11 connection requested."); addrlen = sizeof(addr); newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); if (c->single_connection) { oerrno = errno; debug2("single_connection: closing X11 listener."); channel_close_fd(ssh, c, &c->sock); chan_mark_dead(ssh, c); errno = oerrno; } if (newsock == -1) { if (errno != EINTR && errno != EWOULDBLOCK && errno != ECONNABORTED) error("accept: %.100s", strerror(errno)); if (errno == EMFILE || errno == ENFILE) c->notbefore = monotime() + 1; return; } set_nodelay(newsock); remote_ipaddr = get_peer_ipaddr(newsock); remote_port = get_peer_port(newsock); snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", remote_ipaddr, remote_port); nc = channel_new(ssh, "x11-connection", SSH_CHANNEL_OPENING, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, buf, 1); open_preamble(ssh, __func__, nc, "x11"); if ((r = sshpkt_put_cstring(ssh, remote_ipaddr)) != 0 || (r = sshpkt_put_u32(ssh, remote_port)) != 0) { fatal_fr(r, "channel %i: reply", c->self); } if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: send", c->self); free(remote_ipaddr); } static void port_open_helper(struct ssh *ssh, Channel *c, char *rtype) { char *local_ipaddr = get_local_ipaddr(c->sock); int local_port = c->sock == -1 ? 65536 : get_local_port(c->sock); char *remote_ipaddr = get_peer_ipaddr(c->sock); int remote_port = get_peer_port(c->sock); int r; if (remote_port == -1) { /* Fake addr/port to appease peers that validate it (Tectia) */ free(remote_ipaddr); remote_ipaddr = xstrdup("127.0.0.1"); remote_port = 65535; } free(c->remote_name); xasprintf(&c->remote_name, "%s: listening port %d for %.100s port %d, " "connect from %.200s port %d to %.100s port %d", rtype, c->listening_port, c->path, c->host_port, remote_ipaddr, remote_port, local_ipaddr, local_port); open_preamble(ssh, __func__, c, rtype); if (strcmp(rtype, "direct-tcpip") == 0) { /* target host, port */ if ((r = sshpkt_put_cstring(ssh, c->path)) != 0 || (r = sshpkt_put_u32(ssh, c->host_port)) != 0) fatal_fr(r, "channel %i: reply", c->self); } else if (strcmp(rtype, "direct-streamlocal@openssh.com") == 0) { /* target path */ if ((r = sshpkt_put_cstring(ssh, c->path)) != 0) fatal_fr(r, "channel %i: reply", c->self); } else if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) { /* listen path */ if ((r = sshpkt_put_cstring(ssh, c->path)) != 0) fatal_fr(r, "channel %i: reply", c->self); } else { /* listen address, port */ if ((r = sshpkt_put_cstring(ssh, c->path)) != 0 || (r = sshpkt_put_u32(ssh, local_port)) != 0) fatal_fr(r, "channel %i: reply", c->self); } if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) { /* reserved for future owner/mode info */ if ((r = sshpkt_put_cstring(ssh, "")) != 0) fatal_fr(r, "channel %i: reply", c->self); } else { /* originator host and port */ if ((r = sshpkt_put_cstring(ssh, remote_ipaddr)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)remote_port)) != 0) fatal_fr(r, "channel %i: reply", c->self); } if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: send", c->self); free(remote_ipaddr); free(local_ipaddr); } void channel_set_x11_refuse_time(struct ssh *ssh, time_t refuse_time) { ssh->chanctxt->x11_refuse_time = refuse_time; } /* * This socket is listening for connections to a forwarded TCP/IP port. */ static void channel_post_port_listener(struct ssh *ssh, Channel *c) { Channel *nc; struct sockaddr_storage addr; int newsock, nextstate; socklen_t addrlen; char *rtype; if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; debug("Connection to port %d forwarding to %.100s port %d requested.", c->listening_port, c->path, c->host_port); if (c->type == SSH_CHANNEL_RPORT_LISTENER) { nextstate = SSH_CHANNEL_OPENING; rtype = "forwarded-tcpip"; } else if (c->type == SSH_CHANNEL_RUNIX_LISTENER) { nextstate = SSH_CHANNEL_OPENING; rtype = "forwarded-streamlocal@openssh.com"; } else if (c->host_port == PORT_STREAMLOCAL) { nextstate = SSH_CHANNEL_OPENING; rtype = "direct-streamlocal@openssh.com"; } else if (c->host_port == 0) { nextstate = SSH_CHANNEL_DYNAMIC; rtype = "dynamic-tcpip"; } else { nextstate = SSH_CHANNEL_OPENING; rtype = "direct-tcpip"; } addrlen = sizeof(addr); newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); if (newsock == -1) { if (errno != EINTR && errno != EWOULDBLOCK && errno != ECONNABORTED) error("accept: %.100s", strerror(errno)); if (errno == EMFILE || errno == ENFILE) c->notbefore = monotime() + 1; return; } if (c->host_port != PORT_STREAMLOCAL) set_nodelay(newsock); nc = channel_new(ssh, rtype, nextstate, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, rtype, 1); nc->listening_port = c->listening_port; nc->host_port = c->host_port; if (c->path != NULL) nc->path = xstrdup(c->path); if (nextstate != SSH_CHANNEL_DYNAMIC) port_open_helper(ssh, nc, rtype); } /* * This is the authentication agent socket listening for connections from * clients. */ static void channel_post_auth_listener(struct ssh *ssh, Channel *c) { Channel *nc; int r, newsock; struct sockaddr_storage addr; socklen_t addrlen; if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; addrlen = sizeof(addr); newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); if (newsock == -1) { error("accept from auth socket: %.100s", strerror(errno)); if (errno == EMFILE || errno == ENFILE) c->notbefore = monotime() + 1; return; } nc = channel_new(ssh, "agent-connection", SSH_CHANNEL_OPENING, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, "accepted auth socket", 1); open_preamble(ssh, __func__, nc, "auth-agent@openssh.com"); if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i", c->self); } static void channel_post_connecting(struct ssh *ssh, Channel *c) { int err = 0, sock, isopen, r; socklen_t sz = sizeof(err); if ((c->io_ready & SSH_CHAN_IO_SOCK_W) == 0) return; if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); /* for rdynamic the OPEN_CONFIRMATION has been sent already */ isopen = (c->type == SSH_CHANNEL_RDYNAMIC_FINISH); if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) == -1) { err = errno; error("getsockopt SO_ERROR failed"); } if (err == 0) { /* Non-blocking connection completed */ debug("channel %d: connected to %s port %d", c->self, c->connect_ctx.host, c->connect_ctx.port); channel_connect_ctx_free(&c->connect_ctx); c->type = SSH_CHANNEL_OPEN; c->lastused = monotime(); if (isopen) { /* no message necessary */ } else { if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, c->self)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window)) != 0 || (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i open confirm", c->self); } return; } if (err == EINTR || err == EAGAIN || err == EINPROGRESS) return; /* Non-blocking connection failed */ debug("channel %d: connection failed: %s", c->self, strerror(err)); /* Try next address, if any */ if ((sock = connect_next(&c->connect_ctx)) == -1) { /* Exhausted all addresses for this destination */ error("connect_to %.100s port %d: failed.", c->connect_ctx.host, c->connect_ctx.port); channel_connect_ctx_free(&c->connect_ctx); if (isopen) { rdynamic_close(ssh, c); } else { if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, SSH2_OPEN_CONNECT_FAILED)) != 0 || (r = sshpkt_put_cstring(ssh, strerror(err))) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: failure", c->self); chan_mark_dead(ssh, c); } } /* New non-blocking connection in progress */ close(c->sock); c->sock = c->rfd = c->wfd = sock; } static int channel_handle_rfd(struct ssh *ssh, Channel *c) { char buf[CHAN_RBUF]; ssize_t len; int r, force; size_t nr = 0, have, avail, maxlen = CHANNEL_MAX_READ; int pty_zeroread = 0; #ifdef PTY_ZEROREAD /* Bug on AIX: read(1) can return 0 for a non-closed fd */ pty_zeroread = c->isatty; #endif force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; if (!force && (c->io_ready & SSH_CHAN_IO_RFD) == 0) return 1; if ((avail = sshbuf_avail(c->input)) == 0) return 1; /* Shouldn't happen */ /* * For "simple" channels (i.e. not datagram or filtered), we can * read directly to the channel buffer. */ if (!pty_zeroread && c->input_filter == NULL && !c->datagram) { /* Only OPEN channels have valid rwin */ if (c->type == SSH_CHANNEL_OPEN) { if ((have = sshbuf_len(c->input)) >= c->remote_window) return 1; /* shouldn't happen */ if (maxlen > c->remote_window - have) maxlen = c->remote_window - have; } if (maxlen > avail) maxlen = avail; if ((r = sshbuf_read(c->rfd, c->input, maxlen, &nr)) != 0) { if (errno == EINTR || (!force && (errno == EAGAIN || errno == EWOULDBLOCK))) return 1; debug2("channel %d: read failed rfd %d maxlen %zu: %s", c->self, c->rfd, maxlen, ssh_err(r)); goto rfail; } if (nr != 0) c->lastused = monotime(); return 1; } errno = 0; len = read(c->rfd, buf, sizeof(buf)); /* fixup AIX zero-length read with errno set to look more like errors */ if (pty_zeroread && len == 0 && errno != 0) len = -1; if (len == -1 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !force))) return 1; if (len < 0 || (!pty_zeroread && len == 0)) { debug2("channel %d: read<=0 rfd %d len %zd: %s", c->self, c->rfd, len, len == 0 ? "closed" : strerror(errno)); rfail: if (c->type != SSH_CHANNEL_OPEN) { debug2("channel %d: not open", c->self); chan_mark_dead(ssh, c); return -1; } else { chan_read_failed(ssh, c); } return -1; } c->lastused = monotime(); if (c->input_filter != NULL) { if (c->input_filter(ssh, c, buf, len) == -1) { debug2("channel %d: filter stops", c->self); chan_read_failed(ssh, c); } } else if (c->datagram) { if ((r = sshbuf_put_string(c->input, buf, len)) != 0) fatal_fr(r, "channel %i: put datagram", c->self); } else if ((r = sshbuf_put(c->input, buf, len)) != 0) fatal_fr(r, "channel %i: put data", c->self); return 1; } static int channel_handle_wfd(struct ssh *ssh, Channel *c) { struct termios tio; u_char *data = NULL, *buf; /* XXX const; need filter API change */ size_t dlen, olen = 0; int r, len; if ((c->io_ready & SSH_CHAN_IO_WFD) == 0) return 1; if (sshbuf_len(c->output) == 0) return 1; /* Send buffered output data to the socket. */ olen = sshbuf_len(c->output); if (c->output_filter != NULL) { if ((buf = c->output_filter(ssh, c, &data, &dlen)) == NULL) { debug2("channel %d: filter stops", c->self); if (c->type != SSH_CHANNEL_OPEN) chan_mark_dead(ssh, c); else chan_write_failed(ssh, c); return -1; } } else if (c->datagram) { if ((r = sshbuf_get_string(c->output, &data, &dlen)) != 0) fatal_fr(r, "channel %i: get datagram", c->self); buf = data; } else { buf = data = sshbuf_mutable_ptr(c->output); dlen = sshbuf_len(c->output); } if (c->datagram) { /* ignore truncated writes, datagrams might get lost */ len = write(c->wfd, buf, dlen); free(data); if (len == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return 1; if (len <= 0) goto write_fail; goto out; } #ifdef _AIX /* XXX: Later AIX versions can't push as much data to tty */ if (c->wfd_isatty) dlen = MINIMUM(dlen, 8*1024); #endif len = write(c->wfd, buf, dlen); if (len == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return 1; if (len <= 0) { write_fail: if (c->type != SSH_CHANNEL_OPEN) { debug2("channel %d: not open", c->self); chan_mark_dead(ssh, c); return -1; } else { chan_write_failed(ssh, c); } return -1; } c->lastused = monotime(); #ifndef BROKEN_TCGETATTR_ICANON if (c->isatty && dlen >= 1 && buf[0] != '\r') { if (tcgetattr(c->wfd, &tio) == 0 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { /* * Simulate echo to reduce the impact of * traffic analysis. We need to match the * size of a SSH2_MSG_CHANNEL_DATA message * (4 byte channel id + buf) */ if ((r = sshpkt_msg_ignore(ssh, 4+len)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: ignore", c->self); } } #endif /* BROKEN_TCGETATTR_ICANON */ if ((r = sshbuf_consume(c->output, len)) != 0) fatal_fr(r, "channel %i: consume", c->self); out: c->local_consumed += olen - sshbuf_len(c->output); return 1; } static int channel_handle_efd_write(struct ssh *ssh, Channel *c) { int r; ssize_t len; if ((c->io_ready & SSH_CHAN_IO_EFD_W) == 0) return 1; if (sshbuf_len(c->extended) == 0) return 1; len = write(c->efd, sshbuf_ptr(c->extended), sshbuf_len(c->extended)); debug2("channel %d: written %zd to efd %d", c->self, len, c->efd); if (len == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return 1; if (len <= 0) { debug2("channel %d: closing write-efd %d", c->self, c->efd); channel_close_fd(ssh, c, &c->efd); } else { if ((r = sshbuf_consume(c->extended, len)) != 0) fatal_fr(r, "channel %i: consume", c->self); c->local_consumed += len; c->lastused = monotime(); } return 1; } static int channel_handle_efd_read(struct ssh *ssh, Channel *c) { char buf[CHAN_RBUF]; ssize_t len; int r, force; force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; if (!force && (c->io_ready & SSH_CHAN_IO_EFD_R) == 0) return 1; len = read(c->efd, buf, sizeof(buf)); debug2("channel %d: read %zd from efd %d", c->self, len, c->efd); if (len == -1 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !force))) return 1; if (len <= 0) { debug2("channel %d: closing read-efd %d", c->self, c->efd); channel_close_fd(ssh, c, &c->efd); return 1; } c->lastused = monotime(); if (c->extended_usage == CHAN_EXTENDED_IGNORE) debug3("channel %d: discard efd", c->self); else if ((r = sshbuf_put(c->extended, buf, len)) != 0) fatal_fr(r, "channel %i: append", c->self); return 1; } static int channel_handle_efd(struct ssh *ssh, Channel *c) { if (c->efd == -1) return 1; /** XXX handle drain efd, too */ if (c->extended_usage == CHAN_EXTENDED_WRITE) return channel_handle_efd_write(ssh, c); else if (c->extended_usage == CHAN_EXTENDED_READ || c->extended_usage == CHAN_EXTENDED_IGNORE) return channel_handle_efd_read(ssh, c); return 1; } static int channel_check_window(struct ssh *ssh, Channel *c) { int r; if (c->type == SSH_CHANNEL_OPEN && !(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && ((c->local_window_max - c->local_window > c->local_maxpacket*3) || c->local_window < c->local_window_max/2) && c->local_consumed > 0) { if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, c->local_consumed)) != 0 || (r = sshpkt_send(ssh)) != 0) { fatal_fr(r, "channel %i", c->self); } debug2("channel %d: window %d sent adjust %d", c->self, c->local_window, c->local_consumed); c->local_window += c->local_consumed; c->local_consumed = 0; } return 1; } static void channel_post_open(struct ssh *ssh, Channel *c) { channel_handle_rfd(ssh, c); channel_handle_wfd(ssh, c); channel_handle_efd(ssh, c); channel_check_window(ssh, c); } static u_int read_mux(struct ssh *ssh, Channel *c, u_int need) { char buf[CHAN_RBUF]; ssize_t len; u_int rlen; int r; if (sshbuf_len(c->input) < need) { rlen = need - sshbuf_len(c->input); len = read(c->rfd, buf, MINIMUM(rlen, CHAN_RBUF)); if (len == -1 && (errno == EINTR || errno == EAGAIN)) return sshbuf_len(c->input); if (len <= 0) { debug2("channel %d: ctl read<=0 rfd %d len %zd", c->self, c->rfd, len); chan_read_failed(ssh, c); return 0; } else if ((r = sshbuf_put(c->input, buf, len)) != 0) fatal_fr(r, "channel %i: append", c->self); } return sshbuf_len(c->input); } static void channel_post_mux_client_read(struct ssh *ssh, Channel *c) { u_int need; if ((c->io_ready & SSH_CHAN_IO_RFD) == 0) return; if (c->istate != CHAN_INPUT_OPEN && c->istate != CHAN_INPUT_WAIT_DRAIN) return; if (c->mux_pause) return; /* * Don't not read past the precise end of packets to * avoid disrupting fd passing. */ if (read_mux(ssh, c, 4) < 4) /* read header */ return; /* XXX sshbuf_peek_u32 */ need = PEEK_U32(sshbuf_ptr(c->input)); #define CHANNEL_MUX_MAX_PACKET (256 * 1024) if (need > CHANNEL_MUX_MAX_PACKET) { debug2("channel %d: packet too big %u > %u", c->self, CHANNEL_MUX_MAX_PACKET, need); chan_rcvd_oclose(ssh, c); return; } if (read_mux(ssh, c, need + 4) < need + 4) /* read body */ return; if (c->mux_rcb(ssh, c) != 0) { debug("channel %d: mux_rcb failed", c->self); chan_mark_dead(ssh, c); return; } } static void channel_post_mux_client_write(struct ssh *ssh, Channel *c) { ssize_t len; int r; if ((c->io_ready & SSH_CHAN_IO_WFD) == 0) return; if (sshbuf_len(c->output) == 0) return; len = write(c->wfd, sshbuf_ptr(c->output), sshbuf_len(c->output)); if (len == -1 && (errno == EINTR || errno == EAGAIN)) return; if (len <= 0) { chan_mark_dead(ssh, c); return; } if ((r = sshbuf_consume(c->output, len)) != 0) fatal_fr(r, "channel %i: consume", c->self); } static void channel_post_mux_client(struct ssh *ssh, Channel *c) { channel_post_mux_client_read(ssh, c); channel_post_mux_client_write(ssh, c); } static void channel_post_mux_listener(struct ssh *ssh, Channel *c) { Channel *nc; struct sockaddr_storage addr; socklen_t addrlen; int newsock; uid_t euid; gid_t egid; if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; debug("multiplexing control connection"); /* * Accept connection on control socket */ memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); if ((newsock = accept(c->sock, (struct sockaddr*)&addr, &addrlen)) == -1) { error_f("accept: %s", strerror(errno)); if (errno == EMFILE || errno == ENFILE) c->notbefore = monotime() + 1; return; } if (getpeereid(newsock, &euid, &egid) == -1) { error_f("getpeereid failed: %s", strerror(errno)); close(newsock); return; } if ((euid != 0) && (getuid() != euid)) { error("multiplex uid mismatch: peer euid %u != uid %u", (u_int)euid, (u_int)getuid()); close(newsock); return; } nc = channel_new(ssh, "mux-control", SSH_CHANNEL_MUX_CLIENT, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, "mux-control", 1); nc->mux_rcb = c->mux_rcb; debug3_f("new mux channel %d fd %d", nc->self, nc->sock); /* establish state */ nc->mux_rcb(ssh, nc); /* mux state transitions must not elicit protocol messages */ nc->flags |= CHAN_LOCAL; } static void channel_handler_init(struct ssh_channels *sc) { chan_fn **pre, **post; if ((pre = calloc(SSH_CHANNEL_MAX_TYPE, sizeof(*pre))) == NULL || (post = calloc(SSH_CHANNEL_MAX_TYPE, sizeof(*post))) == NULL) fatal_f("allocation failed"); pre[SSH_CHANNEL_OPEN] = &channel_pre_open; pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; pre[SSH_CHANNEL_RPORT_LISTENER] = &channel_pre_listener; pre[SSH_CHANNEL_UNIX_LISTENER] = &channel_pre_listener; pre[SSH_CHANNEL_RUNIX_LISTENER] = &channel_pre_listener; pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; pre[SSH_CHANNEL_RDYNAMIC_FINISH] = &channel_pre_connecting; pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; post[SSH_CHANNEL_OPEN] = &channel_post_open; post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; post[SSH_CHANNEL_RPORT_LISTENER] = &channel_post_port_listener; post[SSH_CHANNEL_UNIX_LISTENER] = &channel_post_port_listener; post[SSH_CHANNEL_RUNIX_LISTENER] = &channel_post_port_listener; post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; post[SSH_CHANNEL_RDYNAMIC_FINISH] = &channel_post_connecting; post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; sc->channel_pre = pre; sc->channel_post = post; } /* gc dead channels */ static void channel_garbage_collect(struct ssh *ssh, Channel *c) { if (c == NULL) return; if (c->detach_user != NULL) { if (!chan_is_dead(ssh, c, c->detach_close)) return; debug2("channel %d: gc: notify user", c->self); c->detach_user(ssh, c->self, 0, NULL); /* if we still have a callback */ if (c->detach_user != NULL) return; debug2("channel %d: gc: user detached", c->self); } if (!chan_is_dead(ssh, c, 1)) return; debug2("channel %d: garbage collecting", c->self); channel_free(ssh, c); } enum channel_table { CHAN_PRE, CHAN_POST }; static void channel_handler(struct ssh *ssh, int table, struct timespec *timeout) { struct ssh_channels *sc = ssh->chanctxt; chan_fn **ftab = table == CHAN_PRE ? sc->channel_pre : sc->channel_post; u_int i, oalloc; Channel *c; time_t now; now = monotime(); for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) { c = sc->channels[i]; if (c == NULL) continue; /* Try to keep IO going while rekeying */ if (ssh_packet_is_rekeying(ssh) && c->type != SSH_CHANNEL_OPEN) continue; if (c->delayed) { if (table == CHAN_PRE) c->delayed = 0; else continue; } if (ftab[c->type] != NULL) { if (table == CHAN_PRE && c->type == SSH_CHANNEL_OPEN && c->inactive_deadline != 0 && c->lastused != 0 && now >= c->lastused + c->inactive_deadline) { /* channel closed for inactivity */ verbose("channel %d: closing after %u seconds " "of inactivity", c->self, c->inactive_deadline); channel_force_close(ssh, c, 1); } else if (c->notbefore <= now) { /* Run handlers that are not paused. */ (*ftab[c->type])(ssh, c); /* inactivity timeouts must interrupt poll() */ if (timeout != NULL && c->type == SSH_CHANNEL_OPEN && c->lastused != 0 && c->inactive_deadline != 0) { ptimeout_deadline_monotime(timeout, c->lastused + c->inactive_deadline); } } else if (timeout != NULL) { /* * Arrange for poll() wakeup when channel pause * timer expires. */ ptimeout_deadline_monotime(timeout, c->notbefore); } } channel_garbage_collect(ssh, c); } } /* * Create sockets before preparing IO. * This is necessary for things that need to happen after reading * the network-input but need to be completed before IO event setup, e.g. * because they may create new channels. */ static void channel_before_prepare_io(struct ssh *ssh) { struct ssh_channels *sc = ssh->chanctxt; Channel *c; u_int i, oalloc; for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) { c = sc->channels[i]; if (c == NULL) continue; if (c->type == SSH_CHANNEL_RDYNAMIC_OPEN) channel_before_prepare_io_rdynamic(ssh, c); } } static void dump_channel_poll(const char *func, const char *what, Channel *c, u_int pollfd_offset, struct pollfd *pfd) { #ifdef DEBUG_CHANNEL_POLL debug3("%s: channel %d: %s r%d w%d e%d s%d c->pfds [ %d %d %d %d ] " "io_want 0x%02x io_ready 0x%02x pfd[%u].fd=%d " "pfd.ev 0x%02x pfd.rev 0x%02x", func, c->self, what, c->rfd, c->wfd, c->efd, c->sock, c->pfds[0], c->pfds[1], c->pfds[2], c->pfds[3], c->io_want, c->io_ready, pollfd_offset, pfd->fd, pfd->events, pfd->revents); #endif } /* Prepare pollfd entries for a single channel */ static void channel_prepare_pollfd(Channel *c, u_int *next_pollfd, struct pollfd *pfd, u_int npfd) { u_int ev, p = *next_pollfd; if (c == NULL) return; if (p + 4 > npfd) { /* Shouldn't happen */ fatal_f("channel %d: bad pfd offset %u (max %u)", c->self, p, npfd); } c->pfds[0] = c->pfds[1] = c->pfds[2] = c->pfds[3] = -1; /* * prepare c->rfd * * This is a special case, since c->rfd might be the same as * c->wfd, c->efd and/or c->sock. Handle those here if they want * IO too. */ if (c->rfd != -1) { ev = 0; if ((c->io_want & SSH_CHAN_IO_RFD) != 0) ev |= POLLIN; /* rfd == wfd */ if (c->wfd == c->rfd) { if ((c->io_want & SSH_CHAN_IO_WFD) != 0) ev |= POLLOUT; } /* rfd == efd */ if (c->efd == c->rfd) { if ((c->io_want & SSH_CHAN_IO_EFD_R) != 0) ev |= POLLIN; if ((c->io_want & SSH_CHAN_IO_EFD_W) != 0) ev |= POLLOUT; } /* rfd == sock */ if (c->sock == c->rfd) { if ((c->io_want & SSH_CHAN_IO_SOCK_R) != 0) ev |= POLLIN; if ((c->io_want & SSH_CHAN_IO_SOCK_W) != 0) ev |= POLLOUT; } /* Pack a pfd entry if any event armed for this fd */ if (ev != 0) { c->pfds[0] = p; pfd[p].fd = c->rfd; pfd[p].events = ev; dump_channel_poll(__func__, "rfd", c, p, &pfd[p]); p++; } } /* prepare c->wfd if wanting IO and not already handled above */ if (c->wfd != -1 && c->rfd != c->wfd) { ev = 0; if ((c->io_want & SSH_CHAN_IO_WFD)) ev |= POLLOUT; /* Pack a pfd entry if any event armed for this fd */ if (ev != 0) { c->pfds[1] = p; pfd[p].fd = c->wfd; pfd[p].events = ev; dump_channel_poll(__func__, "wfd", c, p, &pfd[p]); p++; } } /* prepare c->efd if wanting IO and not already handled above */ if (c->efd != -1 && c->rfd != c->efd) { ev = 0; if ((c->io_want & SSH_CHAN_IO_EFD_R) != 0) ev |= POLLIN; if ((c->io_want & SSH_CHAN_IO_EFD_W) != 0) ev |= POLLOUT; /* Pack a pfd entry if any event armed for this fd */ if (ev != 0) { c->pfds[2] = p; pfd[p].fd = c->efd; pfd[p].events = ev; dump_channel_poll(__func__, "efd", c, p, &pfd[p]); p++; } } /* prepare c->sock if wanting IO and not already handled above */ if (c->sock != -1 && c->rfd != c->sock) { ev = 0; if ((c->io_want & SSH_CHAN_IO_SOCK_R) != 0) ev |= POLLIN; if ((c->io_want & SSH_CHAN_IO_SOCK_W) != 0) ev |= POLLOUT; /* Pack a pfd entry if any event armed for this fd */ if (ev != 0) { c->pfds[3] = p; pfd[p].fd = c->sock; pfd[p].events = 0; dump_channel_poll(__func__, "sock", c, p, &pfd[p]); p++; } } *next_pollfd = p; } /* * Allocate/prepare poll structure */ void channel_prepare_poll(struct ssh *ssh, struct pollfd **pfdp, u_int *npfd_allocp, u_int *npfd_activep, u_int npfd_reserved, struct timespec *timeout) { struct ssh_channels *sc = ssh->chanctxt; u_int i, oalloc, p, npfd = npfd_reserved; channel_before_prepare_io(ssh); /* might create a new channel */ /* clear out I/O flags from last poll */ for (i = 0; i < sc->channels_alloc; i++) { if (sc->channels[i] == NULL) continue; sc->channels[i]->io_want = sc->channels[i]->io_ready = 0; } /* Allocate 4x pollfd for each channel (rfd, wfd, efd, sock) */ if (sc->channels_alloc >= (INT_MAX / 4) - npfd_reserved) fatal_f("too many channels"); /* shouldn't happen */ npfd += sc->channels_alloc * 4; if (npfd > *npfd_allocp) { *pfdp = xrecallocarray(*pfdp, *npfd_allocp, npfd, sizeof(**pfdp)); *npfd_allocp = npfd; } *npfd_activep = npfd_reserved; oalloc = sc->channels_alloc; channel_handler(ssh, CHAN_PRE, timeout); if (oalloc != sc->channels_alloc) { /* shouldn't happen */ fatal_f("channels_alloc changed during CHAN_PRE " "(was %u, now %u)", oalloc, sc->channels_alloc); } /* Prepare pollfd */ p = npfd_reserved; for (i = 0; i < sc->channels_alloc; i++) channel_prepare_pollfd(sc->channels[i], &p, *pfdp, npfd); *npfd_activep = p; } static void fd_ready(Channel *c, int p, struct pollfd *pfds, u_int npfd, int fd, const char *what, u_int revents_mask, u_int ready) { struct pollfd *pfd = &pfds[p]; if (fd == -1) return; if (p == -1 || (u_int)p >= npfd) fatal_f("channel %d: bad pfd %d (max %u)", c->self, p, npfd); dump_channel_poll(__func__, what, c, p, pfd); if (pfd->fd != fd) { fatal("channel %d: inconsistent %s fd=%d pollfd[%u].fd %d " "r%d w%d e%d s%d", c->self, what, fd, p, pfd->fd, c->rfd, c->wfd, c->efd, c->sock); } if ((pfd->revents & POLLNVAL) != 0) { fatal("channel %d: invalid %s pollfd[%u].fd %d r%d w%d e%d s%d", c->self, what, p, pfd->fd, c->rfd, c->wfd, c->efd, c->sock); } if ((pfd->revents & (revents_mask|POLLHUP|POLLERR)) != 0) c->io_ready |= ready & c->io_want; } /* * After poll, perform any appropriate operations for channels which have * events pending. */ void channel_after_poll(struct ssh *ssh, struct pollfd *pfd, u_int npfd) { struct ssh_channels *sc = ssh->chanctxt; u_int i; int p; Channel *c; #ifdef DEBUG_CHANNEL_POLL for (p = 0; p < (int)npfd; p++) { if (pfd[p].revents == 0) continue; debug_f("pfd[%u].fd %d rev 0x%04x", p, pfd[p].fd, pfd[p].revents); } #endif /* Convert pollfd into c->io_ready */ for (i = 0; i < sc->channels_alloc; i++) { c = sc->channels[i]; if (c == NULL) continue; /* if rfd is shared with efd/sock then wfd should be too */ if (c->rfd != -1 && c->wfd != -1 && c->rfd != c->wfd && (c->rfd == c->efd || c->rfd == c->sock)) { /* Shouldn't happen */ fatal_f("channel %d: unexpected fds r%d w%d e%d s%d", c->self, c->rfd, c->wfd, c->efd, c->sock); } c->io_ready = 0; /* rfd, potentially shared with wfd, efd and sock */ if (c->rfd != -1 && (p = c->pfds[0]) != -1) { fd_ready(c, p, pfd, npfd, c->rfd, "rfd", POLLIN, SSH_CHAN_IO_RFD); if (c->rfd == c->wfd) { fd_ready(c, p, pfd, npfd, c->wfd, "wfd/r", POLLOUT, SSH_CHAN_IO_WFD); } if (c->rfd == c->efd) { fd_ready(c, p, pfd, npfd, c->efd, "efdr/r", POLLIN, SSH_CHAN_IO_EFD_R); fd_ready(c, p, pfd, npfd, c->efd, "efdw/r", POLLOUT, SSH_CHAN_IO_EFD_W); } if (c->rfd == c->sock) { fd_ready(c, p, pfd, npfd, c->sock, "sockr/r", POLLIN, SSH_CHAN_IO_SOCK_R); fd_ready(c, p, pfd, npfd, c->sock, "sockw/r", POLLOUT, SSH_CHAN_IO_SOCK_W); } dump_channel_poll(__func__, "rfd", c, p, pfd); } /* wfd */ if (c->wfd != -1 && c->wfd != c->rfd && (p = c->pfds[1]) != -1) { fd_ready(c, p, pfd, npfd, c->wfd, "wfd", POLLOUT, SSH_CHAN_IO_WFD); dump_channel_poll(__func__, "wfd", c, p, pfd); } /* efd */ if (c->efd != -1 && c->efd != c->rfd && (p = c->pfds[2]) != -1) { fd_ready(c, p, pfd, npfd, c->efd, "efdr", POLLIN, SSH_CHAN_IO_EFD_R); fd_ready(c, p, pfd, npfd, c->efd, "efdw", POLLOUT, SSH_CHAN_IO_EFD_W); dump_channel_poll(__func__, "efd", c, p, pfd); } /* sock */ if (c->sock != -1 && c->sock != c->rfd && (p = c->pfds[3]) != -1) { fd_ready(c, p, pfd, npfd, c->sock, "sockr", POLLIN, SSH_CHAN_IO_SOCK_R); fd_ready(c, p, pfd, npfd, c->sock, "sockw", POLLOUT, SSH_CHAN_IO_SOCK_W); dump_channel_poll(__func__, "sock", c, p, pfd); } } channel_handler(ssh, CHAN_POST, NULL); } /* * Enqueue data for channels with open or draining c->input. + * Returns non-zero if a packet was enqueued. */ -static void +static int channel_output_poll_input_open(struct ssh *ssh, Channel *c) { size_t len, plen; const u_char *pkt; int r; if ((len = sshbuf_len(c->input)) == 0) { if (c->istate == CHAN_INPUT_WAIT_DRAIN) { /* * input-buffer is empty and read-socket shutdown: * tell peer, that we will not send more data: * send IEOF. * hack for extended data: delay EOF if EFD still * in use. */ if (CHANNEL_EFD_INPUT_ACTIVE(c)) debug2("channel %d: " "ibuf_empty delayed efd %d/(%zu)", c->self, c->efd, sshbuf_len(c->extended)); else chan_ibuf_empty(ssh, c); } - return; + return 0; } if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); if (c->datagram) { /* Check datagram will fit; drop if not */ if ((r = sshbuf_get_string_direct(c->input, &pkt, &plen)) != 0) fatal_fr(r, "channel %i: get datagram", c->self); /* * XXX this does tail-drop on the datagram queue which is * usually suboptimal compared to head-drop. Better to have * backpressure at read time? (i.e. read + discard) */ if (plen > c->remote_window || plen > c->remote_maxpacket) { debug("channel %d: datagram too big", c->self); - return; + return 0; } /* Enqueue it */ if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_string(ssh, pkt, plen)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: send datagram", c->self); c->remote_window -= plen; - return; + return 1; } /* Enqueue packet for buffered data. */ if (len > c->remote_window) len = c->remote_window; if (len > c->remote_maxpacket) len = c->remote_maxpacket; if (len == 0) - return; + return 0; if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_string(ssh, sshbuf_ptr(c->input), len)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: send data", c->self); if ((r = sshbuf_consume(c->input, len)) != 0) fatal_fr(r, "channel %i: consume", c->self); c->remote_window -= len; + return 1; } /* * Enqueue data for channels with open c->extended in read mode. + * Returns non-zero if a packet was enqueued. */ -static void +static int channel_output_poll_extended_read(struct ssh *ssh, Channel *c) { size_t len; int r; if ((len = sshbuf_len(c->extended)) == 0) - return; + return 0; debug2("channel %d: rwin %u elen %zu euse %d", c->self, c->remote_window, sshbuf_len(c->extended), c->extended_usage); if (len > c->remote_window) len = c->remote_window; if (len > c->remote_maxpacket) len = c->remote_maxpacket; if (len == 0) - return; + return 0; if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_EXTENDED_DATA)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, SSH2_EXTENDED_DATA_STDERR)) != 0 || (r = sshpkt_put_string(ssh, sshbuf_ptr(c->extended), len)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %i: data", c->self); if ((r = sshbuf_consume(c->extended, len)) != 0) fatal_fr(r, "channel %i: consume", c->self); c->remote_window -= len; debug2("channel %d: sent ext data %zu", c->self, len); + return 1; } -/* If there is data to send to the connection, enqueue some of it now. */ -void +/* + * If there is data to send to the connection, enqueue some of it now. + * Returns non-zero if data was enqueued. + */ +int channel_output_poll(struct ssh *ssh) { struct ssh_channels *sc = ssh->chanctxt; Channel *c; u_int i; + int ret = 0; for (i = 0; i < sc->channels_alloc; i++) { c = sc->channels[i]; if (c == NULL) continue; /* * We are only interested in channels that can have buffered * incoming data. */ if (c->type != SSH_CHANNEL_OPEN) continue; if ((c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { /* XXX is this true? */ debug3("channel %d: will not send data after close", c->self); continue; } /* Get the amount of buffered data for this channel. */ if (c->istate == CHAN_INPUT_OPEN || c->istate == CHAN_INPUT_WAIT_DRAIN) - channel_output_poll_input_open(ssh, c); + ret |= channel_output_poll_input_open(ssh, c); /* Send extended data, i.e. stderr */ if (!(c->flags & CHAN_EOF_SENT) && c->extended_usage == CHAN_EXTENDED_READ) - channel_output_poll_extended_read(ssh, c); + ret |= channel_output_poll_extended_read(ssh, c); } + return ret; } /* -- mux proxy support */ /* * When multiplexing channel messages for mux clients we have to deal * with downstream messages from the mux client and upstream messages * from the ssh server: * 1) Handling downstream messages is straightforward and happens * in channel_proxy_downstream(): * - We forward all messages (mostly) unmodified to the server. * - However, in order to route messages from upstream to the correct * downstream client, we have to replace the channel IDs used by the * mux clients with a unique channel ID because the mux clients might * use conflicting channel IDs. * - so we inspect and change both SSH2_MSG_CHANNEL_OPEN and * SSH2_MSG_CHANNEL_OPEN_CONFIRMATION messages, create a local * SSH_CHANNEL_MUX_PROXY channel and replace the mux clients ID * with the newly allocated channel ID. * 2) Upstream messages are received by matching SSH_CHANNEL_MUX_PROXY * channels and processed by channel_proxy_upstream(). The local channel ID * is then translated back to the original mux client ID. * 3) In both cases we need to keep track of matching SSH2_MSG_CHANNEL_CLOSE * messages so we can clean up SSH_CHANNEL_MUX_PROXY channels. * 4) The SSH_CHANNEL_MUX_PROXY channels also need to closed when the * downstream mux client are removed. * 5) Handling SSH2_MSG_CHANNEL_OPEN messages from the upstream server * requires more work, because they are not addressed to a specific * channel. E.g. client_request_forwarded_tcpip() needs to figure * out whether the request is addressed to the local client or a * specific downstream client based on the listen-address/port. * 6) Agent and X11-Forwarding have a similar problem and are currently * not supported as the matching session/channel cannot be identified * easily. */ /* * receive packets from downstream mux clients: * channel callback fired on read from mux client, creates * SSH_CHANNEL_MUX_PROXY channels and translates channel IDs * on channel creation. */ int channel_proxy_downstream(struct ssh *ssh, Channel *downstream) { Channel *c = NULL; struct sshbuf *original = NULL, *modified = NULL; const u_char *cp; char *ctype = NULL, *listen_host = NULL; u_char type; size_t have; int ret = -1, r; u_int id, remote_id, listen_port; /* sshbuf_dump(downstream->input, stderr); */ if ((r = sshbuf_get_string_direct(downstream->input, &cp, &have)) != 0) { error_fr(r, "parse"); return -1; } if (have < 2) { error_f("short message"); return -1; } type = cp[1]; /* skip padlen + type */ cp += 2; have -= 2; if (ssh_packet_log_type(type)) debug3_f("channel %u: down->up: type %u", downstream->self, type); switch (type) { case SSH2_MSG_CHANNEL_OPEN: if ((original = sshbuf_from(cp, have)) == NULL || (modified = sshbuf_new()) == NULL) { error_f("alloc"); goto out; } if ((r = sshbuf_get_cstring(original, &ctype, NULL)) != 0 || (r = sshbuf_get_u32(original, &id)) != 0) { error_fr(r, "parse"); goto out; } c = channel_new(ssh, "mux-proxy", SSH_CHANNEL_MUX_PROXY, -1, -1, -1, 0, 0, 0, ctype, 1); c->mux_ctx = downstream; /* point to mux client */ c->mux_downstream_id = id; /* original downstream id */ if ((r = sshbuf_put_cstring(modified, ctype)) != 0 || (r = sshbuf_put_u32(modified, c->self)) != 0 || (r = sshbuf_putb(modified, original)) != 0) { error_fr(r, "compose"); channel_free(ssh, c); goto out; } break; case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: /* * Almost the same as SSH2_MSG_CHANNEL_OPEN, except then we * need to parse 'remote_id' instead of 'ctype'. */ if ((original = sshbuf_from(cp, have)) == NULL || (modified = sshbuf_new()) == NULL) { error_f("alloc"); goto out; } if ((r = sshbuf_get_u32(original, &remote_id)) != 0 || (r = sshbuf_get_u32(original, &id)) != 0) { error_fr(r, "parse"); goto out; } c = channel_new(ssh, "mux-proxy", SSH_CHANNEL_MUX_PROXY, -1, -1, -1, 0, 0, 0, "mux-down-connect", 1); c->mux_ctx = downstream; /* point to mux client */ c->mux_downstream_id = id; c->remote_id = remote_id; c->have_remote_id = 1; if ((r = sshbuf_put_u32(modified, remote_id)) != 0 || (r = sshbuf_put_u32(modified, c->self)) != 0 || (r = sshbuf_putb(modified, original)) != 0) { error_fr(r, "compose"); channel_free(ssh, c); goto out; } break; case SSH2_MSG_GLOBAL_REQUEST: if ((original = sshbuf_from(cp, have)) == NULL) { error_f("alloc"); goto out; } if ((r = sshbuf_get_cstring(original, &ctype, NULL)) != 0) { error_fr(r, "parse"); goto out; } if (strcmp(ctype, "tcpip-forward") != 0) { error_f("unsupported request %s", ctype); goto out; } if ((r = sshbuf_get_u8(original, NULL)) != 0 || (r = sshbuf_get_cstring(original, &listen_host, NULL)) != 0 || (r = sshbuf_get_u32(original, &listen_port)) != 0) { error_fr(r, "parse"); goto out; } if (listen_port > 65535) { error_f("tcpip-forward for %s: bad port %u", listen_host, listen_port); goto out; } /* Record that connection to this host/port is permitted. */ permission_set_add(ssh, FORWARD_USER, FORWARD_LOCAL, "", -1, listen_host, NULL, (int)listen_port, downstream); listen_host = NULL; break; case SSH2_MSG_CHANNEL_CLOSE: if (have < 4) break; remote_id = PEEK_U32(cp); if ((c = channel_by_remote_id(ssh, remote_id)) != NULL) { if (c->flags & CHAN_CLOSE_RCVD) channel_free(ssh, c); else c->flags |= CHAN_CLOSE_SENT; } break; } if (modified) { if ((r = sshpkt_start(ssh, type)) != 0 || (r = sshpkt_putb(ssh, modified)) != 0 || (r = sshpkt_send(ssh)) != 0) { error_fr(r, "send"); goto out; } } else { if ((r = sshpkt_start(ssh, type)) != 0 || (r = sshpkt_put(ssh, cp, have)) != 0 || (r = sshpkt_send(ssh)) != 0) { error_fr(r, "send"); goto out; } } ret = 0; out: free(ctype); free(listen_host); sshbuf_free(original); sshbuf_free(modified); return ret; } /* * receive packets from upstream server and de-multiplex packets * to correct downstream: * implemented as a helper for channel input handlers, * replaces local (proxy) channel ID with downstream channel ID. */ int channel_proxy_upstream(Channel *c, int type, u_int32_t seq, struct ssh *ssh) { struct sshbuf *b = NULL; Channel *downstream; const u_char *cp = NULL; size_t len; int r; /* * When receiving packets from the peer we need to check whether we * need to forward the packets to the mux client. In this case we * restore the original channel id and keep track of CLOSE messages, * so we can cleanup the channel. */ if (c == NULL || c->type != SSH_CHANNEL_MUX_PROXY) return 0; if ((downstream = c->mux_ctx) == NULL) return 0; switch (type) { case SSH2_MSG_CHANNEL_CLOSE: case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EOF: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: case SSH2_MSG_CHANNEL_OPEN_FAILURE: case SSH2_MSG_CHANNEL_WINDOW_ADJUST: case SSH2_MSG_CHANNEL_SUCCESS: case SSH2_MSG_CHANNEL_FAILURE: case SSH2_MSG_CHANNEL_REQUEST: break; default: debug2_f("channel %u: unsupported type %u", c->self, type); return 0; } if ((b = sshbuf_new()) == NULL) { error_f("alloc reply"); goto out; } /* get remaining payload (after id) */ cp = sshpkt_ptr(ssh, &len); if (cp == NULL) { error_f("no packet"); goto out; } /* translate id and send to muxclient */ if ((r = sshbuf_put_u8(b, 0)) != 0 || /* padlen */ (r = sshbuf_put_u8(b, type)) != 0 || (r = sshbuf_put_u32(b, c->mux_downstream_id)) != 0 || (r = sshbuf_put(b, cp, len)) != 0 || (r = sshbuf_put_stringb(downstream->output, b)) != 0) { error_fr(r, "compose muxclient"); goto out; } /* sshbuf_dump(b, stderr); */ if (ssh_packet_log_type(type)) debug3_f("channel %u: up->down: type %u", c->self, type); out: /* update state */ switch (type) { case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: /* record remote_id for SSH2_MSG_CHANNEL_CLOSE */ if (cp && len > 4) { c->remote_id = PEEK_U32(cp); c->have_remote_id = 1; } break; case SSH2_MSG_CHANNEL_CLOSE: if (c->flags & CHAN_CLOSE_SENT) channel_free(ssh, c); else c->flags |= CHAN_CLOSE_RCVD; break; } sshbuf_free(b); return 1; } /* -- protocol input */ /* Parse a channel ID from the current packet */ static int channel_parse_id(struct ssh *ssh, const char *where, const char *what) { u_int32_t id; int r; if ((r = sshpkt_get_u32(ssh, &id)) != 0) { error_r(r, "%s: parse id", where); ssh_packet_disconnect(ssh, "Invalid %s message", what); } if (id > INT_MAX) { error_r(r, "%s: bad channel id %u", where, id); ssh_packet_disconnect(ssh, "Invalid %s channel id", what); } return (int)id; } /* Lookup a channel from an ID in the current packet */ static Channel * channel_from_packet_id(struct ssh *ssh, const char *where, const char *what) { int id = channel_parse_id(ssh, where, what); Channel *c; if ((c = channel_lookup(ssh, id)) == NULL) { ssh_packet_disconnect(ssh, "%s packet referred to nonexistent channel %d", what, id); } return c; } int channel_input_data(int type, u_int32_t seq, struct ssh *ssh) { const u_char *data; size_t data_len, win_len; Channel *c = channel_from_packet_id(ssh, __func__, "data"); int r; if (channel_proxy_upstream(c, type, seq, ssh)) return 0; /* Ignore any data for non-open channels (might happen on close) */ if (c->type != SSH_CHANNEL_OPEN && c->type != SSH_CHANNEL_RDYNAMIC_OPEN && c->type != SSH_CHANNEL_RDYNAMIC_FINISH && c->type != SSH_CHANNEL_X11_OPEN) return 0; /* Get the data. */ if ((r = sshpkt_get_string_direct(ssh, &data, &data_len)) != 0 || (r = sshpkt_get_end(ssh)) != 0) fatal_fr(r, "channel %i: get data", c->self); win_len = data_len; if (c->datagram) win_len += 4; /* string length header */ /* * The sending side reduces its window as it sends data, so we * must 'fake' consumption of the data in order to ensure that window * updates are sent back. Otherwise the connection might deadlock. */ if (c->ostate != CHAN_OUTPUT_OPEN) { c->local_window -= win_len; c->local_consumed += win_len; return 0; } if (win_len > c->local_maxpacket) { logit("channel %d: rcvd big packet %zu, maxpack %u", c->self, win_len, c->local_maxpacket); return 0; } if (win_len > c->local_window) { logit("channel %d: rcvd too much data %zu, win %u", c->self, win_len, c->local_window); return 0; } c->local_window -= win_len; if (c->datagram) { if ((r = sshbuf_put_string(c->output, data, data_len)) != 0) fatal_fr(r, "channel %i: append datagram", c->self); } else if ((r = sshbuf_put(c->output, data, data_len)) != 0) fatal_fr(r, "channel %i: append data", c->self); return 0; } int channel_input_extended_data(int type, u_int32_t seq, struct ssh *ssh) { const u_char *data; size_t data_len; u_int32_t tcode; Channel *c = channel_from_packet_id(ssh, __func__, "extended data"); int r; if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if (c->type != SSH_CHANNEL_OPEN) { logit("channel %d: ext data for non open", c->self); return 0; } if (c->flags & CHAN_EOF_RCVD) { if (ssh->compat & SSH_BUG_EXTEOF) debug("channel %d: accepting ext data after eof", c->self); else ssh_packet_disconnect(ssh, "Received extended_data " "after EOF on channel %d.", c->self); } if ((r = sshpkt_get_u32(ssh, &tcode)) != 0) { error_fr(r, "parse tcode"); ssh_packet_disconnect(ssh, "Invalid extended_data message"); } if (c->efd == -1 || c->extended_usage != CHAN_EXTENDED_WRITE || tcode != SSH2_EXTENDED_DATA_STDERR) { logit("channel %d: bad ext data", c->self); return 0; } if ((r = sshpkt_get_string_direct(ssh, &data, &data_len)) != 0 || (r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "parse data"); ssh_packet_disconnect(ssh, "Invalid extended_data message"); } if (data_len > c->local_window) { logit("channel %d: rcvd too much extended_data %zu, win %u", c->self, data_len, c->local_window); return 0; } debug2("channel %d: rcvd ext data %zu", c->self, data_len); /* XXX sshpkt_getb? */ if ((r = sshbuf_put(c->extended, data, data_len)) != 0) error_fr(r, "append"); c->local_window -= data_len; return 0; } int channel_input_ieof(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = channel_from_packet_id(ssh, __func__, "ieof"); int r; if ((r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "parse data"); ssh_packet_disconnect(ssh, "Invalid ieof message"); } if (channel_proxy_upstream(c, type, seq, ssh)) return 0; chan_rcvd_ieof(ssh, c); /* XXX force input close */ if (c->force_drain && c->istate == CHAN_INPUT_OPEN) { debug("channel %d: FORCE input drain", c->self); c->istate = CHAN_INPUT_WAIT_DRAIN; if (sshbuf_len(c->input) == 0) chan_ibuf_empty(ssh, c); } return 0; } int channel_input_oclose(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = channel_from_packet_id(ssh, __func__, "oclose"); int r; if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if ((r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "parse data"); ssh_packet_disconnect(ssh, "Invalid oclose message"); } chan_rcvd_oclose(ssh, c); return 0; } int channel_input_open_confirmation(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = channel_from_packet_id(ssh, __func__, "open confirmation"); u_int32_t remote_window, remote_maxpacket; int r; if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if (c->type != SSH_CHANNEL_OPENING) ssh_packet_disconnect(ssh, "Received open confirmation for " "non-opening channel %d.", c->self); /* * Record the remote channel number and mark that the channel * is now open. */ if ((r = sshpkt_get_u32(ssh, &c->remote_id)) != 0 || (r = sshpkt_get_u32(ssh, &remote_window)) != 0 || (r = sshpkt_get_u32(ssh, &remote_maxpacket)) != 0 || (r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "window/maxpacket"); ssh_packet_disconnect(ssh, "Invalid open confirmation message"); } c->have_remote_id = 1; c->remote_window = remote_window; c->remote_maxpacket = remote_maxpacket; c->type = SSH_CHANNEL_OPEN; if (c->open_confirm) { debug2_f("channel %d: callback start", c->self); c->open_confirm(ssh, c->self, 1, c->open_confirm_ctx); debug2_f("channel %d: callback done", c->self); } c->lastused = monotime(); debug2("channel %d: open confirm rwindow %u rmax %u", c->self, c->remote_window, c->remote_maxpacket); return 0; } static char * reason2txt(int reason) { switch (reason) { case SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED: return "administratively prohibited"; case SSH2_OPEN_CONNECT_FAILED: return "connect failed"; case SSH2_OPEN_UNKNOWN_CHANNEL_TYPE: return "unknown channel type"; case SSH2_OPEN_RESOURCE_SHORTAGE: return "resource shortage"; } return "unknown reason"; } int channel_input_open_failure(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = channel_from_packet_id(ssh, __func__, "open failure"); u_int32_t reason; char *msg = NULL; int r; if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if (c->type != SSH_CHANNEL_OPENING) ssh_packet_disconnect(ssh, "Received open failure for " "non-opening channel %d.", c->self); if ((r = sshpkt_get_u32(ssh, &reason)) != 0) { error_fr(r, "parse reason"); ssh_packet_disconnect(ssh, "Invalid open failure message"); } /* skip language */ if ((r = sshpkt_get_cstring(ssh, &msg, NULL)) != 0 || (r = sshpkt_get_string_direct(ssh, NULL, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "parse msg/lang"); ssh_packet_disconnect(ssh, "Invalid open failure message"); } logit("channel %d: open failed: %s%s%s", c->self, reason2txt(reason), msg ? ": ": "", msg ? msg : ""); free(msg); if (c->open_confirm) { debug2_f("channel %d: callback start", c->self); c->open_confirm(ssh, c->self, 0, c->open_confirm_ctx); debug2_f("channel %d: callback done", c->self); } /* Schedule the channel for cleanup/deletion. */ chan_mark_dead(ssh, c); return 0; } int channel_input_window_adjust(int type, u_int32_t seq, struct ssh *ssh) { int id = channel_parse_id(ssh, __func__, "window adjust"); Channel *c; u_int32_t adjust; u_int new_rwin; int r; if ((c = channel_lookup(ssh, id)) == NULL) { logit("Received window adjust for non-open channel %d.", id); return 0; } if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if ((r = sshpkt_get_u32(ssh, &adjust)) != 0 || (r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "parse adjust"); ssh_packet_disconnect(ssh, "Invalid window adjust message"); } debug2("channel %d: rcvd adjust %u", c->self, adjust); if ((new_rwin = c->remote_window + adjust) < c->remote_window) { fatal("channel %d: adjust %u overflows remote window %u", c->self, adjust, c->remote_window); } c->remote_window = new_rwin; return 0; } int channel_input_status_confirm(int type, u_int32_t seq, struct ssh *ssh) { int id = channel_parse_id(ssh, __func__, "status confirm"); Channel *c; struct channel_confirm *cc; /* Reset keepalive timeout */ ssh_packet_set_alive_timeouts(ssh, 0); debug2_f("type %d id %d", type, id); if ((c = channel_lookup(ssh, id)) == NULL) { logit_f("%d: unknown", id); return 0; } if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if (sshpkt_get_end(ssh) != 0) ssh_packet_disconnect(ssh, "Invalid status confirm message"); if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL) return 0; cc->cb(ssh, type, c, cc->ctx); TAILQ_REMOVE(&c->status_confirms, cc, entry); freezero(cc, sizeof(*cc)); return 0; } /* -- tcp forwarding */ void channel_set_af(struct ssh *ssh, int af) { ssh->chanctxt->IPv4or6 = af; } /* * Determine whether or not a port forward listens to loopback, the * specified address or wildcard. On the client, a specified bind * address will always override gateway_ports. On the server, a * gateway_ports of 1 (``yes'') will override the client's specification * and force a wildcard bind, whereas a value of 2 (``clientspecified'') * will bind to whatever address the client asked for. * * Special-case listen_addrs are: * * "0.0.0.0" -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR * "" (empty string), "*" -> wildcard v4/v6 * "localhost" -> loopback v4/v6 * "127.0.0.1" / "::1" -> accepted even if gateway_ports isn't set */ static const char * channel_fwd_bind_addr(struct ssh *ssh, const char *listen_addr, int *wildcardp, int is_client, struct ForwardOptions *fwd_opts) { const char *addr = NULL; int wildcard = 0; if (listen_addr == NULL) { /* No address specified: default to gateway_ports setting */ if (fwd_opts->gateway_ports) wildcard = 1; } else if (fwd_opts->gateway_ports || is_client) { if (((ssh->compat & SSH_OLD_FORWARD_ADDR) && strcmp(listen_addr, "0.0.0.0") == 0 && is_client == 0) || *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 || (!is_client && fwd_opts->gateway_ports == 1)) { wildcard = 1; /* * Notify client if they requested a specific listen * address and it was overridden. */ if (*listen_addr != '\0' && strcmp(listen_addr, "0.0.0.0") != 0 && strcmp(listen_addr, "*") != 0) { ssh_packet_send_debug(ssh, "Forwarding listen address " "\"%s\" overridden by server " "GatewayPorts", listen_addr); } } else if (strcmp(listen_addr, "localhost") != 0 || strcmp(listen_addr, "127.0.0.1") == 0 || strcmp(listen_addr, "::1") == 0) { /* * Accept explicit localhost address when * GatewayPorts=yes. The "localhost" hostname is * deliberately skipped here so it will listen on all * available local address families. */ addr = listen_addr; } } else if (strcmp(listen_addr, "127.0.0.1") == 0 || strcmp(listen_addr, "::1") == 0) { /* * If a specific IPv4/IPv6 localhost address has been * requested then accept it even if gateway_ports is in * effect. This allows the client to prefer IPv4 or IPv6. */ addr = listen_addr; } if (wildcardp != NULL) *wildcardp = wildcard; return addr; } static int channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type, struct Forward *fwd, int *allocated_listen_port, struct ForwardOptions *fwd_opts) { Channel *c; int sock, r, success = 0, wildcard = 0, is_client; struct addrinfo hints, *ai, *aitop; const char *host, *addr; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; in_port_t *lport_p; is_client = (type == SSH_CHANNEL_PORT_LISTENER); if (is_client && fwd->connect_path != NULL) { host = fwd->connect_path; } else { host = (type == SSH_CHANNEL_RPORT_LISTENER) ? fwd->listen_host : fwd->connect_host; if (host == NULL) { error("No forward host name."); return 0; } if (strlen(host) >= NI_MAXHOST) { error("Forward host name too long."); return 0; } } /* Determine the bind address, cf. channel_fwd_bind_addr() comment */ addr = channel_fwd_bind_addr(ssh, fwd->listen_host, &wildcard, is_client, fwd_opts); debug3_f("type %d wildcard %d addr %s", type, wildcard, (addr == NULL) ? "NULL" : addr); /* * getaddrinfo returns a loopback address if the hostname is * set to NULL and hints.ai_flags is not AI_PASSIVE */ memset(&hints, 0, sizeof(hints)); hints.ai_family = ssh->chanctxt->IPv4or6; hints.ai_flags = wildcard ? AI_PASSIVE : 0; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%d", fwd->listen_port); if ((r = getaddrinfo(addr, strport, &hints, &aitop)) != 0) { if (addr == NULL) { /* This really shouldn't happen */ ssh_packet_disconnect(ssh, "getaddrinfo: fatal error: %s", ssh_gai_strerror(r)); } else { error_f("getaddrinfo(%.64s): %s", addr, ssh_gai_strerror(r)); } return 0; } if (allocated_listen_port != NULL) *allocated_listen_port = 0; for (ai = aitop; ai; ai = ai->ai_next) { switch (ai->ai_family) { case AF_INET: lport_p = &((struct sockaddr_in *)ai->ai_addr)-> sin_port; break; case AF_INET6: lport_p = &((struct sockaddr_in6 *)ai->ai_addr)-> sin6_port; break; default: continue; } /* * If allocating a port for -R forwards, then use the * same port for all address families. */ if (type == SSH_CHANNEL_RPORT_LISTENER && fwd->listen_port == 0 && allocated_listen_port != NULL && *allocated_listen_port > 0) *lport_p = htons(*allocated_listen_port); if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error_f("getnameinfo failed"); continue; } /* Create a port to listen for the host. */ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) { /* this is no error since kernel may not support ipv6 */ verbose("socket [%s]:%s: %.100s", ntop, strport, strerror(errno)); continue; } set_reuseaddr(sock); if (ai->ai_family == AF_INET6) sock_set_v6only(sock); debug("Local forwarding listening on %s port %s.", ntop, strport); /* Bind the socket to the address. */ if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) { /* * address can be in if use ipv6 address is * already bound */ if (!ai->ai_next) error("bind [%s]:%s: %.100s", ntop, strport, strerror(errno)); else verbose("bind [%s]:%s: %.100s", ntop, strport, strerror(errno)); close(sock); continue; } /* Start listening for connections on the socket. */ if (listen(sock, SSH_LISTEN_BACKLOG) == -1) { error("listen [%s]:%s: %.100s", ntop, strport, strerror(errno)); close(sock); continue; } /* * fwd->listen_port == 0 requests a dynamically allocated port - * record what we got. */ if (type == SSH_CHANNEL_RPORT_LISTENER && fwd->listen_port == 0 && allocated_listen_port != NULL && *allocated_listen_port == 0) { *allocated_listen_port = get_local_port(sock); debug("Allocated listen port %d", *allocated_listen_port); } /* Allocate a channel number for the socket. */ c = channel_new(ssh, "port-listener", type, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "port listener", 1); c->path = xstrdup(host); c->host_port = fwd->connect_port; c->listening_addr = addr == NULL ? NULL : xstrdup(addr); if (fwd->listen_port == 0 && allocated_listen_port != NULL && !(ssh->compat & SSH_BUG_DYNAMIC_RPORT)) c->listening_port = *allocated_listen_port; else c->listening_port = fwd->listen_port; success = 1; } if (success == 0) error_f("cannot listen to port: %d", fwd->listen_port); freeaddrinfo(aitop); return success; } static int channel_setup_fwd_listener_streamlocal(struct ssh *ssh, int type, struct Forward *fwd, struct ForwardOptions *fwd_opts) { struct sockaddr_un sunaddr; const char *path; Channel *c; int port, sock; mode_t omask; switch (type) { case SSH_CHANNEL_UNIX_LISTENER: if (fwd->connect_path != NULL) { if (strlen(fwd->connect_path) > sizeof(sunaddr.sun_path)) { error("Local connecting path too long: %s", fwd->connect_path); return 0; } path = fwd->connect_path; port = PORT_STREAMLOCAL; } else { if (fwd->connect_host == NULL) { error("No forward host name."); return 0; } if (strlen(fwd->connect_host) >= NI_MAXHOST) { error("Forward host name too long."); return 0; } path = fwd->connect_host; port = fwd->connect_port; } break; case SSH_CHANNEL_RUNIX_LISTENER: path = fwd->listen_path; port = PORT_STREAMLOCAL; break; default: error_f("unexpected channel type %d", type); return 0; } if (fwd->listen_path == NULL) { error("No forward path name."); return 0; } if (strlen(fwd->listen_path) > sizeof(sunaddr.sun_path)) { error("Local listening path too long: %s", fwd->listen_path); return 0; } debug3_f("type %d path %s", type, fwd->listen_path); /* Start a Unix domain listener. */ omask = umask(fwd_opts->streamlocal_bind_mask); sock = unix_listener(fwd->listen_path, SSH_LISTEN_BACKLOG, fwd_opts->streamlocal_bind_unlink); umask(omask); if (sock < 0) return 0; debug("Local forwarding listening on path %s.", fwd->listen_path); /* Allocate a channel number for the socket. */ c = channel_new(ssh, "unix-listener", type, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "unix listener", 1); c->path = xstrdup(path); c->host_port = port; c->listening_port = PORT_STREAMLOCAL; c->listening_addr = xstrdup(fwd->listen_path); return 1; } static int channel_cancel_rport_listener_tcpip(struct ssh *ssh, const char *host, u_short port) { u_int i; int found = 0; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { Channel *c = ssh->chanctxt->channels[i]; if (c == NULL || c->type != SSH_CHANNEL_RPORT_LISTENER) continue; if (strcmp(c->path, host) == 0 && c->listening_port == port) { debug2_f("close channel %d", i); channel_free(ssh, c); found = 1; } } return found; } static int channel_cancel_rport_listener_streamlocal(struct ssh *ssh, const char *path) { u_int i; int found = 0; for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { Channel *c = ssh->chanctxt->channels[i]; if (c == NULL || c->type != SSH_CHANNEL_RUNIX_LISTENER) continue; if (c->path == NULL) continue; if (strcmp(c->path, path) == 0) { debug2_f("close channel %d", i); channel_free(ssh, c); found = 1; } } return found; } int channel_cancel_rport_listener(struct ssh *ssh, struct Forward *fwd) { if (fwd->listen_path != NULL) { return channel_cancel_rport_listener_streamlocal(ssh, fwd->listen_path); } else { return channel_cancel_rport_listener_tcpip(ssh, fwd->listen_host, fwd->listen_port); } } static int channel_cancel_lport_listener_tcpip(struct ssh *ssh, const char *lhost, u_short lport, int cport, struct ForwardOptions *fwd_opts) { u_int i; int found = 0; const char *addr = channel_fwd_bind_addr(ssh, lhost, NULL, 1, fwd_opts); for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { Channel *c = ssh->chanctxt->channels[i]; if (c == NULL || c->type != SSH_CHANNEL_PORT_LISTENER) continue; if (c->listening_port != lport) continue; if (cport == CHANNEL_CANCEL_PORT_STATIC) { /* skip dynamic forwardings */ if (c->host_port == 0) continue; } else { if (c->host_port != cport) continue; } if ((c->listening_addr == NULL && addr != NULL) || (c->listening_addr != NULL && addr == NULL)) continue; if (addr == NULL || strcmp(c->listening_addr, addr) == 0) { debug2_f("close channel %d", i); channel_free(ssh, c); found = 1; } } return found; } static int channel_cancel_lport_listener_streamlocal(struct ssh *ssh, const char *path) { u_int i; int found = 0; if (path == NULL) { error_f("no path specified."); return 0; } for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { Channel *c = ssh->chanctxt->channels[i]; if (c == NULL || c->type != SSH_CHANNEL_UNIX_LISTENER) continue; if (c->listening_addr == NULL) continue; if (strcmp(c->listening_addr, path) == 0) { debug2_f("close channel %d", i); channel_free(ssh, c); found = 1; } } return found; } int channel_cancel_lport_listener(struct ssh *ssh, struct Forward *fwd, int cport, struct ForwardOptions *fwd_opts) { if (fwd->listen_path != NULL) { return channel_cancel_lport_listener_streamlocal(ssh, fwd->listen_path); } else { return channel_cancel_lport_listener_tcpip(ssh, fwd->listen_host, fwd->listen_port, cport, fwd_opts); } } /* protocol local port fwd, used by ssh */ int channel_setup_local_fwd_listener(struct ssh *ssh, struct Forward *fwd, struct ForwardOptions *fwd_opts) { if (fwd->listen_path != NULL) { return channel_setup_fwd_listener_streamlocal(ssh, SSH_CHANNEL_UNIX_LISTENER, fwd, fwd_opts); } else { return channel_setup_fwd_listener_tcpip(ssh, SSH_CHANNEL_PORT_LISTENER, fwd, NULL, fwd_opts); } } /* Matches a remote forwarding permission against a requested forwarding */ static int remote_open_match(struct permission *allowed_open, struct Forward *fwd) { int ret; char *lhost; /* XXX add ACLs for streamlocal */ if (fwd->listen_path != NULL) return 1; if (fwd->listen_host == NULL || allowed_open->listen_host == NULL) return 0; if (allowed_open->listen_port != FWD_PERMIT_ANY_PORT && allowed_open->listen_port != fwd->listen_port) return 0; /* Match hostnames case-insensitively */ lhost = xstrdup(fwd->listen_host); lowercase(lhost); ret = match_pattern(lhost, allowed_open->listen_host); free(lhost); return ret; } /* Checks whether a requested remote forwarding is permitted */ static int check_rfwd_permission(struct ssh *ssh, struct Forward *fwd) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->remote_perms; u_int i, permit, permit_adm = 1; struct permission *perm; /* XXX apply GatewayPorts override before checking? */ permit = pset->all_permitted; if (!permit) { for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (remote_open_match(perm, fwd)) { permit = 1; break; } } } if (pset->num_permitted_admin > 0) { permit_adm = 0; for (i = 0; i < pset->num_permitted_admin; i++) { perm = &pset->permitted_admin[i]; if (remote_open_match(perm, fwd)) { permit_adm = 1; break; } } } return permit && permit_adm; } /* protocol v2 remote port fwd, used by sshd */ int channel_setup_remote_fwd_listener(struct ssh *ssh, struct Forward *fwd, int *allocated_listen_port, struct ForwardOptions *fwd_opts) { if (!check_rfwd_permission(ssh, fwd)) { ssh_packet_send_debug(ssh, "port forwarding refused"); if (fwd->listen_path != NULL) /* XXX always allowed, see remote_open_match() */ logit("Received request from %.100s port %d to " "remote forward to path \"%.100s\", " "but the request was denied.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), fwd->listen_path); else if(fwd->listen_host != NULL) logit("Received request from %.100s port %d to " "remote forward to host %.100s port %d, " "but the request was denied.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), fwd->listen_host, fwd->listen_port ); else logit("Received request from %.100s port %d to remote " "forward, but the request was denied.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); return 0; } if (fwd->listen_path != NULL) { return channel_setup_fwd_listener_streamlocal(ssh, SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts); } else { return channel_setup_fwd_listener_tcpip(ssh, SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port, fwd_opts); } } /* * Translate the requested rfwd listen host to something usable for * this server. */ static const char * channel_rfwd_bind_host(const char *listen_host) { if (listen_host == NULL) { return "localhost"; } else if (*listen_host == '\0' || strcmp(listen_host, "*") == 0) { return ""; } else return listen_host; } /* * Initiate forwarding of connections to port "port" on remote host through * the secure channel to host:port from local side. * Returns handle (index) for updating the dynamic listen port with * channel_update_permission(). */ int channel_request_remote_forwarding(struct ssh *ssh, struct Forward *fwd) { int r, success = 0, idx = -1; const char *host_to_connect, *listen_host, *listen_path; int port_to_connect, listen_port; /* Send the forward request to the remote side. */ if (fwd->listen_path != NULL) { if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "streamlocal-forward@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 1)) != 0 || /* want reply */ (r = sshpkt_put_cstring(ssh, fwd->listen_path)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "request streamlocal"); } else { if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "tcpip-forward")) != 0 || (r = sshpkt_put_u8(ssh, 1)) != 0 || /* want reply */ (r = sshpkt_put_cstring(ssh, channel_rfwd_bind_host(fwd->listen_host))) != 0 || (r = sshpkt_put_u32(ssh, fwd->listen_port)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "request tcpip-forward"); } /* Assume that server accepts the request */ success = 1; if (success) { /* Record that connection to this host/port is permitted. */ host_to_connect = listen_host = listen_path = NULL; port_to_connect = listen_port = 0; if (fwd->connect_path != NULL) { host_to_connect = fwd->connect_path; port_to_connect = PORT_STREAMLOCAL; } else { host_to_connect = fwd->connect_host; port_to_connect = fwd->connect_port; } if (fwd->listen_path != NULL) { listen_path = fwd->listen_path; listen_port = PORT_STREAMLOCAL; } else { listen_host = fwd->listen_host; listen_port = fwd->listen_port; } idx = permission_set_add(ssh, FORWARD_USER, FORWARD_LOCAL, host_to_connect, port_to_connect, listen_host, listen_path, listen_port, NULL); } return idx; } static int open_match(struct permission *allowed_open, const char *requestedhost, int requestedport) { if (allowed_open->host_to_connect == NULL) return 0; if (allowed_open->port_to_connect != FWD_PERMIT_ANY_PORT && allowed_open->port_to_connect != requestedport) return 0; if (strcmp(allowed_open->host_to_connect, FWD_PERMIT_ANY_HOST) != 0 && strcmp(allowed_open->host_to_connect, requestedhost) != 0) return 0; return 1; } /* * Note that in the listen host/port case * we don't support FWD_PERMIT_ANY_PORT and * need to translate between the configured-host (listen_host) * and what we've sent to the remote server (channel_rfwd_bind_host) */ static int open_listen_match_tcpip(struct permission *allowed_open, const char *requestedhost, u_short requestedport, int translate) { const char *allowed_host; if (allowed_open->host_to_connect == NULL) return 0; if (allowed_open->listen_port != requestedport) return 0; if (!translate && allowed_open->listen_host == NULL && requestedhost == NULL) return 1; allowed_host = translate ? channel_rfwd_bind_host(allowed_open->listen_host) : allowed_open->listen_host; if (allowed_host == NULL || requestedhost == NULL || strcmp(allowed_host, requestedhost) != 0) return 0; return 1; } static int open_listen_match_streamlocal(struct permission *allowed_open, const char *requestedpath) { if (allowed_open->host_to_connect == NULL) return 0; if (allowed_open->listen_port != PORT_STREAMLOCAL) return 0; if (allowed_open->listen_path == NULL || strcmp(allowed_open->listen_path, requestedpath) != 0) return 0; return 1; } /* * Request cancellation of remote forwarding of connection host:port from * local side. */ static int channel_request_rforward_cancel_tcpip(struct ssh *ssh, const char *host, u_short port) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; int r; u_int i; struct permission *perm = NULL; for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (open_listen_match_tcpip(perm, host, port, 0)) break; perm = NULL; } if (perm == NULL) { debug_f("requested forward not found"); return -1; } if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "cancel-tcpip-forward")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || /* want reply */ (r = sshpkt_put_cstring(ssh, channel_rfwd_bind_host(host))) != 0 || (r = sshpkt_put_u32(ssh, port)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send cancel"); fwd_perm_clear(perm); /* unregister */ return 0; } /* * Request cancellation of remote forwarding of Unix domain socket * path from local side. */ static int channel_request_rforward_cancel_streamlocal(struct ssh *ssh, const char *path) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; int r; u_int i; struct permission *perm = NULL; for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (open_listen_match_streamlocal(perm, path)) break; perm = NULL; } if (perm == NULL) { debug_f("requested forward not found"); return -1; } if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "cancel-streamlocal-forward@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || /* want reply */ (r = sshpkt_put_cstring(ssh, path)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send cancel"); fwd_perm_clear(perm); /* unregister */ return 0; } /* * Request cancellation of remote forwarding of a connection from local side. */ int channel_request_rforward_cancel(struct ssh *ssh, struct Forward *fwd) { if (fwd->listen_path != NULL) { return channel_request_rforward_cancel_streamlocal(ssh, fwd->listen_path); } else { return channel_request_rforward_cancel_tcpip(ssh, fwd->listen_host, fwd->listen_port ? fwd->listen_port : fwd->allocated_port); } } /* * Permits opening to any host/port if permitted_user[] is empty. This is * usually called by the server, because the user could connect to any port * anyway, and the server has no way to know but to trust the client anyway. */ void channel_permit_all(struct ssh *ssh, int where) { struct permission_set *pset = permission_set_get(ssh, where); if (pset->num_permitted_user == 0) pset->all_permitted = 1; } /* * Permit the specified host/port for forwarding. */ void channel_add_permission(struct ssh *ssh, int who, int where, char *host, int port) { int local = where == FORWARD_LOCAL; struct permission_set *pset = permission_set_get(ssh, where); debug("allow %s forwarding to host %s port %d", fwd_ident(who, where), host, port); /* * Remote forwards set listen_host/port, local forwards set * host/port_to_connect. */ permission_set_add(ssh, who, where, local ? host : 0, local ? port : 0, local ? NULL : host, NULL, local ? 0 : port, NULL); pset->all_permitted = 0; } /* * Administratively disable forwarding. */ void channel_disable_admin(struct ssh *ssh, int where) { channel_clear_permission(ssh, FORWARD_ADM, where); permission_set_add(ssh, FORWARD_ADM, where, NULL, 0, NULL, NULL, 0, NULL); } /* * Clear a list of permitted opens. */ void channel_clear_permission(struct ssh *ssh, int who, int where) { struct permission **permp; u_int *npermp; permission_set_get_array(ssh, who, where, &permp, &npermp); *permp = xrecallocarray(*permp, *npermp, 0, sizeof(**permp)); *npermp = 0; } /* * Update the listen port for a dynamic remote forward, after * the actual 'newport' has been allocated. If 'newport' < 0 is * passed then they entry will be invalidated. */ void channel_update_permission(struct ssh *ssh, int idx, int newport) { struct permission_set *pset = &ssh->chanctxt->local_perms; if (idx < 0 || (u_int)idx >= pset->num_permitted_user) { debug_f("index out of range: %d num_permitted_user %d", idx, pset->num_permitted_user); return; } debug("%s allowed port %d for forwarding to host %s port %d", newport > 0 ? "Updating" : "Removing", newport, pset->permitted_user[idx].host_to_connect, pset->permitted_user[idx].port_to_connect); if (newport <= 0) fwd_perm_clear(&pset->permitted_user[idx]); else { pset->permitted_user[idx].listen_port = (ssh->compat & SSH_BUG_DYNAMIC_RPORT) ? 0 : newport; } } /* returns port number, FWD_PERMIT_ANY_PORT or -1 on error */ int permitopen_port(const char *p) { int port; if (strcmp(p, "*") == 0) return FWD_PERMIT_ANY_PORT; if ((port = a2port(p)) > 0) return port; return -1; } /* Try to start non-blocking connect to next host in cctx list */ static int connect_next(struct channel_connect *cctx) { int sock, saved_errno; struct sockaddr_un *sunaddr; char ntop[NI_MAXHOST]; char strport[MAXIMUM(NI_MAXSERV, sizeof(sunaddr->sun_path))]; for (; cctx->ai; cctx->ai = cctx->ai->ai_next) { switch (cctx->ai->ai_family) { case AF_UNIX: /* unix:pathname instead of host:port */ sunaddr = (struct sockaddr_un *)cctx->ai->ai_addr; strlcpy(ntop, "unix", sizeof(ntop)); strlcpy(strport, sunaddr->sun_path, sizeof(strport)); break; case AF_INET: case AF_INET6: if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error_f("getnameinfo failed"); continue; } break; default: continue; } debug_f("start for host %.100s ([%.100s]:%s)", cctx->host, ntop, strport); if ((sock = socket(cctx->ai->ai_family, cctx->ai->ai_socktype, cctx->ai->ai_protocol)) == -1) { if (cctx->ai->ai_next == NULL) error("socket: %.100s", strerror(errno)); else verbose("socket: %.100s", strerror(errno)); continue; } if (set_nonblock(sock) == -1) fatal_f("set_nonblock(%d)", sock); if (connect(sock, cctx->ai->ai_addr, cctx->ai->ai_addrlen) == -1 && errno != EINPROGRESS) { debug_f("host %.100s ([%.100s]:%s): %.100s", cctx->host, ntop, strport, strerror(errno)); saved_errno = errno; close(sock); errno = saved_errno; continue; /* fail -- try next */ } if (cctx->ai->ai_family != AF_UNIX) set_nodelay(sock); debug_f("connect host %.100s ([%.100s]:%s) in progress, fd=%d", cctx->host, ntop, strport, sock); cctx->ai = cctx->ai->ai_next; return sock; } return -1; } static void channel_connect_ctx_free(struct channel_connect *cctx) { free(cctx->host); if (cctx->aitop) { if (cctx->aitop->ai_family == AF_UNIX) free(cctx->aitop); else freeaddrinfo(cctx->aitop); } memset(cctx, 0, sizeof(*cctx)); } /* * Return connecting socket to remote host:port or local socket path, * passing back the failure reason if appropriate. */ static int connect_to_helper(struct ssh *ssh, const char *name, int port, int socktype, char *ctype, char *rname, struct channel_connect *cctx, int *reason, const char **errmsg) { struct addrinfo hints; int gaierr; int sock = -1; char strport[NI_MAXSERV]; if (port == PORT_STREAMLOCAL) { struct sockaddr_un *sunaddr; struct addrinfo *ai; if (strlen(name) > sizeof(sunaddr->sun_path)) { error("%.100s: %.100s", name, strerror(ENAMETOOLONG)); return -1; } /* * Fake up a struct addrinfo for AF_UNIX connections. * channel_connect_ctx_free() must check ai_family * and use free() not freeaddirinfo() for AF_UNIX. */ ai = xmalloc(sizeof(*ai) + sizeof(*sunaddr)); memset(ai, 0, sizeof(*ai) + sizeof(*sunaddr)); ai->ai_addr = (struct sockaddr *)(ai + 1); ai->ai_addrlen = sizeof(*sunaddr); ai->ai_family = AF_UNIX; ai->ai_socktype = socktype; ai->ai_protocol = PF_UNSPEC; sunaddr = (struct sockaddr_un *)ai->ai_addr; sunaddr->sun_family = AF_UNIX; strlcpy(sunaddr->sun_path, name, sizeof(sunaddr->sun_path)); cctx->aitop = ai; } else { memset(&hints, 0, sizeof(hints)); hints.ai_family = ssh->chanctxt->IPv4or6; hints.ai_socktype = socktype; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(name, strport, &hints, &cctx->aitop)) != 0) { if (errmsg != NULL) *errmsg = ssh_gai_strerror(gaierr); if (reason != NULL) *reason = SSH2_OPEN_CONNECT_FAILED; error("connect_to %.100s: unknown host (%s)", name, ssh_gai_strerror(gaierr)); return -1; } } cctx->host = xstrdup(name); cctx->port = port; cctx->ai = cctx->aitop; if ((sock = connect_next(cctx)) == -1) { error("connect to %.100s port %d failed: %s", name, port, strerror(errno)); return -1; } return sock; } /* Return CONNECTING channel to remote host:port or local socket path */ static Channel * connect_to(struct ssh *ssh, const char *host, int port, char *ctype, char *rname) { struct channel_connect cctx; Channel *c; int sock; memset(&cctx, 0, sizeof(cctx)); sock = connect_to_helper(ssh, host, port, SOCK_STREAM, ctype, rname, &cctx, NULL, NULL); if (sock == -1) { channel_connect_ctx_free(&cctx); return NULL; } c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); c->host_port = port; c->path = xstrdup(host); c->connect_ctx = cctx; return c; } /* * returns either the newly connected channel or the downstream channel * that needs to deal with this connection. */ Channel * channel_connect_by_listen_address(struct ssh *ssh, const char *listen_host, u_short listen_port, char *ctype, char *rname) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; u_int i; struct permission *perm; for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (open_listen_match_tcpip(perm, listen_host, listen_port, 1)) { if (perm->downstream) return perm->downstream; if (perm->port_to_connect == 0) return rdynamic_connect_prepare(ssh, ctype, rname); return connect_to(ssh, perm->host_to_connect, perm->port_to_connect, ctype, rname); } } error("WARNING: Server requests forwarding for unknown listen_port %d", listen_port); return NULL; } Channel * channel_connect_by_listen_path(struct ssh *ssh, const char *path, char *ctype, char *rname) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; u_int i; struct permission *perm; for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (open_listen_match_streamlocal(perm, path)) { return connect_to(ssh, perm->host_to_connect, perm->port_to_connect, ctype, rname); } } error("WARNING: Server requests forwarding for unknown path %.100s", path); return NULL; } /* Check if connecting to that port is permitted and connect. */ Channel * channel_connect_to_port(struct ssh *ssh, const char *host, u_short port, char *ctype, char *rname, int *reason, const char **errmsg) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; struct channel_connect cctx; Channel *c; u_int i, permit, permit_adm = 1; int sock; struct permission *perm; permit = pset->all_permitted; if (!permit) { for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (open_match(perm, host, port)) { permit = 1; break; } } } if (pset->num_permitted_admin > 0) { permit_adm = 0; for (i = 0; i < pset->num_permitted_admin; i++) { perm = &pset->permitted_admin[i]; if (open_match(perm, host, port)) { permit_adm = 1; break; } } } if (!permit || !permit_adm) { logit("Received request from %.100s port %d to connect to " "host %.100s port %d, but the request was denied.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), host, port); if (reason != NULL) *reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; return NULL; } memset(&cctx, 0, sizeof(cctx)); sock = connect_to_helper(ssh, host, port, SOCK_STREAM, ctype, rname, &cctx, reason, errmsg); if (sock == -1) { channel_connect_ctx_free(&cctx); return NULL; } c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); c->host_port = port; c->path = xstrdup(host); c->connect_ctx = cctx; return c; } /* Check if connecting to that path is permitted and connect. */ Channel * channel_connect_to_path(struct ssh *ssh, const char *path, char *ctype, char *rname) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; u_int i, permit, permit_adm = 1; struct permission *perm; permit = pset->all_permitted; if (!permit) { for (i = 0; i < pset->num_permitted_user; i++) { perm = &pset->permitted_user[i]; if (open_match(perm, path, PORT_STREAMLOCAL)) { permit = 1; break; } } } if (pset->num_permitted_admin > 0) { permit_adm = 0; for (i = 0; i < pset->num_permitted_admin; i++) { perm = &pset->permitted_admin[i]; if (open_match(perm, path, PORT_STREAMLOCAL)) { permit_adm = 1; break; } } } if (!permit || !permit_adm) { logit("Received request to connect to path %.100s, " "but the request was denied.", path); return NULL; } return connect_to(ssh, path, PORT_STREAMLOCAL, ctype, rname); } void channel_send_window_changes(struct ssh *ssh) { struct ssh_channels *sc = ssh->chanctxt; struct winsize ws; int r; u_int i; for (i = 0; i < sc->channels_alloc; i++) { if (sc->channels[i] == NULL || !sc->channels[i]->client_tty || sc->channels[i]->type != SSH_CHANNEL_OPEN) continue; if (ioctl(sc->channels[i]->rfd, TIOCGWINSZ, &ws) == -1) continue; channel_request_start(ssh, i, "window-change", 0); if ((r = sshpkt_put_u32(ssh, (u_int)ws.ws_col)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_row)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_xpixel)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_ypixel)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "channel %u; send window-change", i); } } /* Return RDYNAMIC_OPEN channel: channel allows SOCKS, but is not connected */ static Channel * rdynamic_connect_prepare(struct ssh *ssh, char *ctype, char *rname) { Channel *c; int r; c = channel_new(ssh, ctype, SSH_CHANNEL_RDYNAMIC_OPEN, -1, -1, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); c->host_port = 0; c->path = NULL; /* * We need to open the channel before we have a FD, * so that we can get SOCKS header from peer. */ if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, c->self)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window)) != 0 || (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0) fatal_fr(r, "channel %i; confirm", c->self); return c; } /* Return CONNECTING socket to remote host:port or local socket path */ static int rdynamic_connect_finish(struct ssh *ssh, Channel *c) { struct ssh_channels *sc = ssh->chanctxt; struct permission_set *pset = &sc->local_perms; struct permission *perm; struct channel_connect cctx; u_int i, permit_adm = 1; int sock; if (pset->num_permitted_admin > 0) { permit_adm = 0; for (i = 0; i < pset->num_permitted_admin; i++) { perm = &pset->permitted_admin[i]; if (open_match(perm, c->path, c->host_port)) { permit_adm = 1; break; } } } if (!permit_adm) { debug_f("requested forward not permitted"); return -1; } memset(&cctx, 0, sizeof(cctx)); sock = connect_to_helper(ssh, c->path, c->host_port, SOCK_STREAM, NULL, NULL, &cctx, NULL, NULL); if (sock == -1) channel_connect_ctx_free(&cctx); else { /* similar to SSH_CHANNEL_CONNECTING but we've already sent the open */ c->type = SSH_CHANNEL_RDYNAMIC_FINISH; c->connect_ctx = cctx; channel_register_fds(ssh, c, sock, sock, -1, 0, 1, 0); } return sock; } /* -- X11 forwarding */ /* * Creates an internet domain socket for listening for X11 connections. * Returns 0 and a suitable display number for the DISPLAY variable * stored in display_numberp , or -1 if an error occurs. */ int x11_create_display_inet(struct ssh *ssh, int x11_display_offset, int x11_use_localhost, int single_connection, u_int *display_numberp, int **chanids) { Channel *nc = NULL; int display_number, sock; u_short port; struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; if (chanids == NULL) return -1; for (display_number = x11_display_offset; display_number < MAX_DISPLAYS; display_number++) { port = 6000 + display_number; memset(&hints, 0, sizeof(hints)); hints.ai_family = ssh->chanctxt->IPv4or6; hints.ai_flags = x11_use_localhost ? 0: AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) { error("getaddrinfo: %.100s", ssh_gai_strerror(gaierr)); return -1; } for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) { if ((errno != EINVAL) && (errno != EAFNOSUPPORT) #ifdef EPFNOSUPPORT && (errno != EPFNOSUPPORT) #endif ) { error("socket: %.100s", strerror(errno)); freeaddrinfo(aitop); return -1; } else { debug("x11_create_display_inet: Socket family %d not supported", ai->ai_family); continue; } } if (ai->ai_family == AF_INET6) sock_set_v6only(sock); if (x11_use_localhost) set_reuseaddr(sock); if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) { debug2_f("bind port %d: %.100s", port, strerror(errno)); close(sock); for (n = 0; n < num_socks; n++) close(socks[n]); num_socks = 0; break; } socks[num_socks++] = sock; if (num_socks == NUM_SOCKS) break; } freeaddrinfo(aitop); if (num_socks > 0) break; } if (display_number >= MAX_DISPLAYS) { error("Failed to allocate internet-domain X11 display socket."); return -1; } /* Start listening for connections on the socket. */ for (n = 0; n < num_socks; n++) { sock = socks[n]; if (listen(sock, SSH_LISTEN_BACKLOG) == -1) { error("listen: %.100s", strerror(errno)); close(sock); return -1; } } /* Allocate a channel for each socket. */ *chanids = xcalloc(num_socks + 1, sizeof(**chanids)); for (n = 0; n < num_socks; n++) { sock = socks[n]; nc = channel_new(ssh, "x11-listener", SSH_CHANNEL_X11_LISTENER, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "X11 inet listener", 1); nc->single_connection = single_connection; (*chanids)[n] = nc->self; } (*chanids)[n] = -1; /* Return the display number for the DISPLAY environment variable. */ *display_numberp = display_number; return 0; } static int connect_local_xsocket_path(const char *pathname) { int sock; struct sockaddr_un addr; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock == -1) { error("socket: %.100s", strerror(errno)); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strlcpy(addr.sun_path, pathname, sizeof addr.sun_path); if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) return sock; close(sock); error("connect %.100s: %.100s", addr.sun_path, strerror(errno)); return -1; } static int connect_local_xsocket(u_int dnr) { char buf[1024]; snprintf(buf, sizeof buf, _PATH_UNIX_X, dnr); return connect_local_xsocket_path(buf); } #ifdef __APPLE__ static int is_path_to_xsocket(const char *display, char *path, size_t pathlen) { struct stat sbuf; if (strlcpy(path, display, pathlen) >= pathlen) { error("%s: display path too long", __func__); return 0; } if (display[0] != '/') return 0; if (stat(path, &sbuf) == 0) { return 1; } else { char *dot = strrchr(path, '.'); if (dot != NULL) { *dot = '\0'; if (stat(path, &sbuf) == 0) { return 1; } } } return 0; } #endif int x11_connect_display(struct ssh *ssh) { u_int display_number; const char *display; char buf[1024], *cp; struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; int gaierr, sock = 0; /* Try to open a socket for the local X server. */ display = getenv("DISPLAY"); if (!display) { error("DISPLAY not set."); return -1; } /* * Now we decode the value of the DISPLAY variable and make a * connection to the real X server. */ #ifdef __APPLE__ /* Check if display is a path to a socket (as set by launchd). */ { char path[PATH_MAX]; if (is_path_to_xsocket(display, path, sizeof(path))) { debug("x11_connect_display: $DISPLAY is launchd"); /* Create a socket. */ sock = connect_local_xsocket_path(path); if (sock < 0) return -1; /* OK, we now have a connection to the display. */ return sock; } } #endif /* * Check if it is a unix domain socket. Unix domain displays are in * one of the following formats: unix:d[.s], :d[.s], ::d[.s] */ if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') { /* Connect to the unix domain socket. */ if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) { error("Could not parse display number from DISPLAY: " "%.100s", display); return -1; } /* Create a socket. */ sock = connect_local_xsocket(display_number); if (sock < 0) return -1; /* OK, we now have a connection to the display. */ return sock; } /* * Connect to an inet socket. The DISPLAY value is supposedly * hostname:d[.s], where hostname may also be numeric IP address. */ strlcpy(buf, display, sizeof(buf)); cp = strchr(buf, ':'); if (!cp) { error("Could not find ':' in DISPLAY: %.100s", display); return -1; } *cp = 0; /* * buf now contains the host name. But first we parse the * display number. */ if (sscanf(cp + 1, "%u", &display_number) != 1) { error("Could not parse display number from DISPLAY: %.100s", display); return -1; } /* Look up the host address */ memset(&hints, 0, sizeof(hints)); hints.ai_family = ssh->chanctxt->IPv4or6; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%u", 6000 + display_number); if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { error("%.100s: unknown host. (%s)", buf, ssh_gai_strerror(gaierr)); return -1; } for (ai = aitop; ai; ai = ai->ai_next) { /* Create a socket. */ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) { debug2("socket: %.100s", strerror(errno)); continue; } /* Connect it to the display. */ if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { debug2("connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); close(sock); continue; } /* Success */ break; } freeaddrinfo(aitop); if (!ai) { error("connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); return -1; } set_nodelay(sock); return sock; } /* * Requests forwarding of X11 connections, generates fake authentication * data, and enables authentication spoofing. * This should be called in the client only. */ void x11_request_forwarding_with_spoofing(struct ssh *ssh, int client_session_id, const char *disp, const char *proto, const char *data, int want_reply) { struct ssh_channels *sc = ssh->chanctxt; u_int data_len = (u_int) strlen(data) / 2; u_int i, value; const char *cp; char *new_data; int r, screen_number; if (sc->x11_saved_display == NULL) sc->x11_saved_display = xstrdup(disp); else if (strcmp(disp, sc->x11_saved_display) != 0) { error("x11_request_forwarding_with_spoofing: different " "$DISPLAY already forwarded"); return; } cp = strchr(disp, ':'); if (cp) cp = strchr(cp, '.'); if (cp) screen_number = (u_int)strtonum(cp + 1, 0, 400, NULL); else screen_number = 0; if (sc->x11_saved_proto == NULL) { /* Save protocol name. */ sc->x11_saved_proto = xstrdup(proto); /* Extract real authentication data. */ sc->x11_saved_data = xmalloc(data_len); for (i = 0; i < data_len; i++) { if (sscanf(data + 2 * i, "%2x", &value) != 1) { fatal("x11_request_forwarding: bad " "authentication data: %.100s", data); } sc->x11_saved_data[i] = value; } sc->x11_saved_data_len = data_len; /* Generate fake data of the same length. */ sc->x11_fake_data = xmalloc(data_len); arc4random_buf(sc->x11_fake_data, data_len); sc->x11_fake_data_len = data_len; } /* Convert the fake data into hex. */ new_data = tohex(sc->x11_fake_data, data_len); /* Send the request packet. */ channel_request_start(ssh, client_session_id, "x11-req", want_reply); if ((r = sshpkt_put_u8(ssh, 0)) != 0 || /* bool: single connection */ (r = sshpkt_put_cstring(ssh, proto)) != 0 || (r = sshpkt_put_cstring(ssh, new_data)) != 0 || (r = sshpkt_put_u32(ssh, screen_number)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send x11-req"); free(new_data); } diff --git a/crypto/openssh/channels.h b/crypto/openssh/channels.h index 7afba78377e0..58019a84e71c 100644 --- a/crypto/openssh/channels.h +++ b/crypto/openssh/channels.h @@ -1,399 +1,399 @@ -/* $OpenBSD: channels.h,v 1.151 2023/07/04 03:59:21 dlg Exp $ */ +/* $OpenBSD: channels.h,v 1.152 2023/09/04 00:01:46 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ /* * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CHANNEL_H #define CHANNEL_H /* Definitions for channel types. */ #define SSH_CHANNEL_X11_LISTENER 1 /* Listening for inet X11 conn. */ #define SSH_CHANNEL_PORT_LISTENER 2 /* Listening on a port. */ #define SSH_CHANNEL_OPENING 3 /* waiting for confirmation */ #define SSH_CHANNEL_OPEN 4 /* normal open two-way channel */ #define SSH_CHANNEL_CLOSED 5 /* waiting for close confirmation */ #define SSH_CHANNEL_AUTH_SOCKET 6 /* authentication socket */ #define SSH_CHANNEL_X11_OPEN 7 /* reading first X11 packet */ #define SSH_CHANNEL_LARVAL 10 /* larval session */ #define SSH_CHANNEL_RPORT_LISTENER 11 /* Listening to a R-style port */ #define SSH_CHANNEL_CONNECTING 12 #define SSH_CHANNEL_DYNAMIC 13 #define SSH_CHANNEL_ZOMBIE 14 /* Almost dead. */ #define SSH_CHANNEL_MUX_LISTENER 15 /* Listener for mux conn. */ #define SSH_CHANNEL_MUX_CLIENT 16 /* Conn. to mux client */ #define SSH_CHANNEL_ABANDONED 17 /* Abandoned session, eg mux */ #define SSH_CHANNEL_UNIX_LISTENER 18 /* Listening on a domain socket. */ #define SSH_CHANNEL_RUNIX_LISTENER 19 /* Listening to a R-style domain socket. */ #define SSH_CHANNEL_MUX_PROXY 20 /* proxy channel for mux-client */ #define SSH_CHANNEL_RDYNAMIC_OPEN 21 /* reverse SOCKS, parsing request */ #define SSH_CHANNEL_RDYNAMIC_FINISH 22 /* reverse SOCKS, finishing connect */ #define SSH_CHANNEL_MAX_TYPE 23 #define CHANNEL_CANCEL_PORT_STATIC -1 /* nonblocking flags for channel_new */ #define CHANNEL_NONBLOCK_LEAVE 0 /* don't modify non-blocking state */ #define CHANNEL_NONBLOCK_SET 1 /* set non-blocking state */ #define CHANNEL_NONBLOCK_STDIO 2 /* set non-blocking and restore on close */ /* c->restore_block mask flags */ #define CHANNEL_RESTORE_RFD 0x01 #define CHANNEL_RESTORE_WFD 0x02 #define CHANNEL_RESTORE_EFD 0x04 /* TCP forwarding */ #define FORWARD_DENY 0 #define FORWARD_REMOTE (1) #define FORWARD_LOCAL (1<<1) #define FORWARD_ALLOW (FORWARD_REMOTE|FORWARD_LOCAL) #define FORWARD_ADM 0x100 #define FORWARD_USER 0x101 struct ssh; struct Channel; typedef struct Channel Channel; struct fwd_perm_list; typedef void channel_open_fn(struct ssh *, int, int, void *); typedef void channel_callback_fn(struct ssh *, int, int, void *); typedef int channel_infilter_fn(struct ssh *, struct Channel *, char *, int); typedef void channel_filter_cleanup_fn(struct ssh *, int, void *); typedef u_char *channel_outfilter_fn(struct ssh *, struct Channel *, u_char **, size_t *); /* Channel success/failure callbacks */ typedef void channel_confirm_cb(struct ssh *, int, struct Channel *, void *); typedef void channel_confirm_abandon_cb(struct ssh *, struct Channel *, void *); struct channel_confirm { TAILQ_ENTRY(channel_confirm) entry; channel_confirm_cb *cb; channel_confirm_abandon_cb *abandon_cb; void *ctx; }; TAILQ_HEAD(channel_confirms, channel_confirm); /* Context for non-blocking connects */ struct channel_connect { char *host; int port; struct addrinfo *ai, *aitop; }; /* Callbacks for mux channels back into client-specific code */ typedef int mux_callback_fn(struct ssh *, struct Channel *); /* * NB. channel IDs on the wire and in c->remote_id are uint32, but local * channel IDs (e.g. c->self) only ever use the int32 subset of this range, * because we use local channel ID -1 for housekeeping. Remote channels have * a dedicated "have_remote_id" flag to indicate their validity. */ struct Channel { int type; /* channel type/state */ int self; /* my own channel identifier */ uint32_t remote_id; /* channel identifier for remote peer */ int have_remote_id; /* non-zero if remote_id is valid */ u_int istate; /* input from channel (state of receive half) */ u_int ostate; /* output to channel (state of transmit half) */ int flags; /* close sent/rcvd */ int rfd; /* read fd */ int wfd; /* write fd */ int efd; /* extended fd */ int sock; /* sock fd */ u_int io_want; /* bitmask of SSH_CHAN_IO_* */ u_int io_ready; /* bitmask of SSH_CHAN_IO_* */ int pfds[4]; /* pollfd entries for rfd/wfd/efd/sock */ int ctl_chan; /* control channel (multiplexed connections) */ int isatty; /* rfd is a tty */ #ifdef _AIX int wfd_isatty; /* wfd is a tty */ #endif int client_tty; /* (client) TTY has been requested */ int force_drain; /* force close on iEOF */ time_t notbefore; /* Pause IO until deadline (time_t) */ int delayed; /* post-IO handlers for newly created * channels are delayed until the first call * to a matching pre-IO handler. * this way post-IO handlers are not * accidentally called if a FD gets reused */ int restore_block; /* fd mask to restore blocking status */ int restore_flags[3];/* flags to restore */ struct sshbuf *input; /* data read from socket, to be sent over * encrypted connection */ struct sshbuf *output; /* data received over encrypted connection for * send on socket */ struct sshbuf *extended; char *path; /* path for unix domain sockets, or host name for forwards */ int listening_port; /* port being listened for forwards */ char *listening_addr; /* addr being listened for forwards */ int host_port; /* remote port to connect for forwards */ char *remote_name; /* remote hostname */ u_int remote_window; u_int remote_maxpacket; u_int local_window; u_int local_window_max; u_int local_consumed; u_int local_maxpacket; int extended_usage; int single_connection; char *ctype; /* const type - NB. not freed on channel_free */ char *xctype; /* extended type */ /* callback */ channel_open_fn *open_confirm; void *open_confirm_ctx; channel_callback_fn *detach_user; int detach_close; struct channel_confirms status_confirms; /* filter */ channel_infilter_fn *input_filter; channel_outfilter_fn *output_filter; void *filter_ctx; channel_filter_cleanup_fn *filter_cleanup; /* keep boundaries */ int datagram; /* non-blocking connect */ /* XXX make this a pointer so the structure can be opaque */ struct channel_connect connect_ctx; /* multiplexing protocol hook, called for each packet received */ mux_callback_fn *mux_rcb; void *mux_ctx; int mux_pause; int mux_downstream_id; /* Inactivity timeouts */ /* Last traffic seen for OPEN channels */ time_t lastused; /* Inactivity timeout deadline in seconds (0 = no timeout) */ int inactive_deadline; }; #define CHAN_EXTENDED_IGNORE 0 #define CHAN_EXTENDED_READ 1 #define CHAN_EXTENDED_WRITE 2 /* default window/packet sizes for tcp/x11-fwd-channel */ #define CHAN_SES_PACKET_DEFAULT (32*1024) #define CHAN_SES_WINDOW_DEFAULT (64*CHAN_SES_PACKET_DEFAULT) #define CHAN_TCP_PACKET_DEFAULT (32*1024) #define CHAN_TCP_WINDOW_DEFAULT (64*CHAN_TCP_PACKET_DEFAULT) #define CHAN_X11_PACKET_DEFAULT (16*1024) #define CHAN_X11_WINDOW_DEFAULT (4*CHAN_X11_PACKET_DEFAULT) /* possible input states */ #define CHAN_INPUT_OPEN 0 #define CHAN_INPUT_WAIT_DRAIN 1 #define CHAN_INPUT_WAIT_OCLOSE 2 #define CHAN_INPUT_CLOSED 3 /* possible output states */ #define CHAN_OUTPUT_OPEN 0 #define CHAN_OUTPUT_WAIT_DRAIN 1 #define CHAN_OUTPUT_WAIT_IEOF 2 #define CHAN_OUTPUT_CLOSED 3 #define CHAN_CLOSE_SENT 0x01 #define CHAN_CLOSE_RCVD 0x02 #define CHAN_EOF_SENT 0x04 #define CHAN_EOF_RCVD 0x08 #define CHAN_LOCAL 0x10 /* file descriptor events */ #define SSH_CHAN_IO_RFD 0x01 #define SSH_CHAN_IO_WFD 0x02 #define SSH_CHAN_IO_EFD_R 0x04 #define SSH_CHAN_IO_EFD_W 0x08 #define SSH_CHAN_IO_EFD (SSH_CHAN_IO_EFD_R|SSH_CHAN_IO_EFD_W) #define SSH_CHAN_IO_SOCK_R 0x10 #define SSH_CHAN_IO_SOCK_W 0x20 #define SSH_CHAN_IO_SOCK (SSH_CHAN_IO_SOCK_R|SSH_CHAN_IO_SOCK_W) /* Read buffer size */ #define CHAN_RBUF (16*1024) /* Maximum size for direct reads to buffers */ #define CHANNEL_MAX_READ CHAN_SES_PACKET_DEFAULT /* Maximum channel input buffer size */ #define CHAN_INPUT_MAX (16*1024*1024) /* Hard limit on number of channels */ #define CHANNELS_MAX_CHANNELS (16*1024) /* check whether 'efd' is still in use */ #define CHANNEL_EFD_INPUT_ACTIVE(c) \ (c->extended_usage == CHAN_EXTENDED_READ && \ (c->efd != -1 || \ sshbuf_len(c->extended) > 0)) #define CHANNEL_EFD_OUTPUT_ACTIVE(c) \ (c->extended_usage == CHAN_EXTENDED_WRITE && \ c->efd != -1 && (!(c->flags & (CHAN_EOF_RCVD|CHAN_CLOSE_RCVD)) || \ sshbuf_len(c->extended) > 0)) /* Add channel management structures to SSH transport instance */ void channel_init_channels(struct ssh *ssh); /* channel management */ Channel *channel_by_id(struct ssh *, int); Channel *channel_by_remote_id(struct ssh *, u_int); Channel *channel_lookup(struct ssh *, int); Channel *channel_new(struct ssh *, char *, int, int, int, int, u_int, u_int, int, const char *, int); void channel_set_fds(struct ssh *, int, int, int, int, int, int, int, u_int); void channel_free(struct ssh *, Channel *); void channel_free_all(struct ssh *); void channel_stop_listening(struct ssh *); void channel_force_close(struct ssh *, Channel *, int); void channel_set_xtype(struct ssh *, int, const char *); void channel_send_open(struct ssh *, int); void channel_request_start(struct ssh *, int, char *, int); void channel_register_cleanup(struct ssh *, int, channel_callback_fn *, int); void channel_register_open_confirm(struct ssh *, int, channel_open_fn *, void *); void channel_register_filter(struct ssh *, int, channel_infilter_fn *, channel_outfilter_fn *, channel_filter_cleanup_fn *, void *); void channel_register_status_confirm(struct ssh *, int, channel_confirm_cb *, channel_confirm_abandon_cb *, void *); void channel_cancel_cleanup(struct ssh *, int); int channel_close_fd(struct ssh *, Channel *, int *); void channel_send_window_changes(struct ssh *); /* channel inactivity timeouts */ void channel_add_timeout(struct ssh *, const char *, int); void channel_clear_timeouts(struct ssh *); /* mux proxy support */ int channel_proxy_downstream(struct ssh *, Channel *mc); int channel_proxy_upstream(Channel *, int, u_int32_t, struct ssh *); /* protocol handler */ int channel_input_data(int, u_int32_t, struct ssh *); int channel_input_extended_data(int, u_int32_t, struct ssh *); int channel_input_ieof(int, u_int32_t, struct ssh *); int channel_input_oclose(int, u_int32_t, struct ssh *); int channel_input_open_confirmation(int, u_int32_t, struct ssh *); int channel_input_open_failure(int, u_int32_t, struct ssh *); int channel_input_port_open(int, u_int32_t, struct ssh *); int channel_input_window_adjust(int, u_int32_t, struct ssh *); int channel_input_status_confirm(int, u_int32_t, struct ssh *); /* file descriptor handling (read/write) */ struct pollfd; struct timespec; void channel_prepare_poll(struct ssh *, struct pollfd **, u_int *, u_int *, u_int, struct timespec *); void channel_after_poll(struct ssh *, struct pollfd *, u_int); -void channel_output_poll(struct ssh *); +int channel_output_poll(struct ssh *); int channel_not_very_much_buffered_data(struct ssh *); void channel_close_all(struct ssh *); int channel_still_open(struct ssh *); const char *channel_format_extended_usage(const Channel *); char *channel_open_message(struct ssh *); int channel_find_open(struct ssh *); /* tcp forwarding */ struct Forward; struct ForwardOptions; void channel_set_af(struct ssh *, int af); void channel_permit_all(struct ssh *, int); void channel_add_permission(struct ssh *, int, int, char *, int); void channel_clear_permission(struct ssh *, int, int); void channel_disable_admin(struct ssh *, int); void channel_update_permission(struct ssh *, int, int); Channel *channel_connect_to_port(struct ssh *, const char *, u_short, char *, char *, int *, const char **); Channel *channel_connect_to_path(struct ssh *, const char *, char *, char *); Channel *channel_connect_stdio_fwd(struct ssh *, const char*, int, int, int, int); Channel *channel_connect_by_listen_address(struct ssh *, const char *, u_short, char *, char *); Channel *channel_connect_by_listen_path(struct ssh *, const char *, char *, char *); int channel_request_remote_forwarding(struct ssh *, struct Forward *); int channel_setup_local_fwd_listener(struct ssh *, struct Forward *, struct ForwardOptions *); int channel_request_rforward_cancel(struct ssh *, struct Forward *); int channel_setup_remote_fwd_listener(struct ssh *, struct Forward *, int *, struct ForwardOptions *); int channel_cancel_rport_listener(struct ssh *, struct Forward *); int channel_cancel_lport_listener(struct ssh *, struct Forward *, int, struct ForwardOptions *); int permitopen_port(const char *); /* x11 forwarding */ void channel_set_x11_refuse_time(struct ssh *, time_t); int x11_connect_display(struct ssh *); int x11_create_display_inet(struct ssh *, int, int, int, u_int *, int **); void x11_request_forwarding_with_spoofing(struct ssh *, int, const char *, const char *, const char *, int); /* channel close */ int chan_is_dead(struct ssh *, Channel *, int); void chan_mark_dead(struct ssh *, Channel *); /* channel events */ void chan_rcvd_oclose(struct ssh *, Channel *); void chan_rcvd_eow(struct ssh *, Channel *); void chan_read_failed(struct ssh *, Channel *); void chan_ibuf_empty(struct ssh *, Channel *); void chan_rcvd_ieof(struct ssh *, Channel *); void chan_write_failed(struct ssh *, Channel *); void chan_obuf_empty(struct ssh *, Channel *); #endif diff --git a/crypto/openssh/clientloop.c b/crypto/openssh/clientloop.c index 99846a978397..3e9fa3220b7d 100644 --- a/crypto/openssh/clientloop.c +++ b/crypto/openssh/clientloop.c @@ -1,2694 +1,2865 @@ -/* $OpenBSD: clientloop.c,v 1.392 2023/04/03 08:10:54 dtucker Exp $ */ +/* $OpenBSD: clientloop.c,v 1.398 2023/09/10 03:51:55 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * The main loop for the interactive session (client side). * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * Copyright (c) 1999 Theo de Raadt. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * SSH2 support added by Markus Friedl. * Copyright (c) 1999, 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #ifdef HAVE_PATHS_H #include #endif #ifdef HAVE_POLL_H #include #endif #include #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "packet.h" #include "sshbuf.h" #include "compat.h" #include "channels.h" #include "dispatch.h" #include "sshkey.h" #include "cipher.h" #include "kex.h" #include "myproposal.h" #include "log.h" #include "misc.h" #include "readconf.h" #include "clientloop.h" #include "sshconnect.h" #include "authfd.h" #include "atomicio.h" #include "sshpty.h" #include "match.h" #include "msg.h" #include "ssherr.h" #include "hostfile.h" /* Permitted RSA signature algorithms for UpdateHostkeys proofs */ #define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256" +/* Uncertainty (in percent) of keystroke timing intervals */ +#define SSH_KEYSTROKE_TIMING_FUZZ 10 + /* import options */ extern Options options; /* Control socket */ extern int muxserver_sock; /* XXX use mux_client_cleanup() instead */ /* * Name of the host we are connecting to. This is the name given on the * command line, or the Hostname specified for the user-supplied name in a * configuration file. */ extern char *host; /* * If this field is not NULL, the ForwardAgent socket is this path and different * instead of SSH_AUTH_SOCK. */ extern char *forward_agent_sock_path; /* * Flag to indicate that we have received a window change signal which has * not yet been processed. This will cause a message indicating the new * window size to be sent to the server a little later. This is volatile * because this is updated in a signal handler. */ static volatile sig_atomic_t received_window_change_signal = 0; static volatile sig_atomic_t received_signal = 0; /* Time when backgrounded control master using ControlPersist should exit */ static time_t control_persist_exit_time = 0; /* Common data for the client loop code. */ volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ static int last_was_cr; /* Last character was a newline. */ static int exit_status; /* Used to store the command exit status. */ static struct sshbuf *stderr_buffer; /* Used for final exit message. */ static int connection_in; /* Connection to server (input). */ static int connection_out; /* Connection to server (output). */ static int need_rekeying; /* Set to non-zero if rekeying is requested. */ static int session_closed; /* In SSH2: login session closed. */ static time_t x11_refuse_time; /* If >0, refuse x11 opens after this time. */ static time_t server_alive_time; /* Time to do server_alive_check */ static int hostkeys_update_complete; static int session_setup_complete; static void client_init_dispatch(struct ssh *ssh); int session_ident = -1; /* Track escape per proto2 channel */ struct escape_filter_ctx { int escape_pending; int escape_char; }; /* Context for channel confirmation replies */ struct channel_reply_ctx { const char *request_type; int id; enum confirm_action action; }; /* Global request success/failure callbacks */ /* XXX move to struct ssh? */ struct global_confirm { TAILQ_ENTRY(global_confirm) entry; global_confirm_cb *cb; void *ctx; int ref_count; }; TAILQ_HEAD(global_confirms, global_confirm); static struct global_confirms global_confirms = TAILQ_HEAD_INITIALIZER(global_confirms); void ssh_process_session2_setup(int, int, int, struct sshbuf *); static void quit_message(const char *fmt, ...) __attribute__((__format__ (printf, 1, 2))); static void quit_message(const char *fmt, ...) { char *msg; va_list args; int r; va_start(args, fmt); xvasprintf(&msg, fmt, args); va_end(args); if ((r = sshbuf_putf(stderr_buffer, "%s\r\n", msg)) != 0) fatal_fr(r, "sshbuf_putf"); quit_pending = 1; } /* * Signal handler for the window change signal (SIGWINCH). This just sets a * flag indicating that the window has changed. */ static void window_change_handler(int sig) { received_window_change_signal = 1; } /* * Signal handler for signals that cause the program to terminate. These * signals must be trapped to restore terminal modes. */ static void signal_handler(int sig) { received_signal = sig; quit_pending = 1; } /* * Sets control_persist_exit_time to the absolute time when the * backgrounded control master should exit due to expiry of the * ControlPersist timeout. Sets it to 0 if we are not a backgrounded * control master process, or if there is no ControlPersist timeout. */ static void set_control_persist_exit_time(struct ssh *ssh) { if (muxserver_sock == -1 || !options.control_persist || options.control_persist_timeout == 0) { /* not using a ControlPersist timeout */ control_persist_exit_time = 0; } else if (channel_still_open(ssh)) { /* some client connections are still open */ if (control_persist_exit_time > 0) debug2_f("cancel scheduled exit"); control_persist_exit_time = 0; } else if (control_persist_exit_time <= 0) { /* a client connection has recently closed */ control_persist_exit_time = monotime() + (time_t)options.control_persist_timeout; debug2_f("schedule exit in %d seconds", options.control_persist_timeout); } /* else we are already counting down to the timeout */ } #define SSH_X11_VALID_DISPLAY_CHARS ":/.-_" static int client_x11_display_valid(const char *display) { size_t i, dlen; if (display == NULL) return 0; dlen = strlen(display); for (i = 0; i < dlen; i++) { if (!isalnum((u_char)display[i]) && strchr(SSH_X11_VALID_DISPLAY_CHARS, display[i]) == NULL) { debug("Invalid character '%c' in DISPLAY", display[i]); return 0; } } return 1; } #define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1" #define X11_TIMEOUT_SLACK 60 int client_x11_get_proto(struct ssh *ssh, const char *display, const char *xauth_path, u_int trusted, u_int timeout, char **_proto, char **_data) { char *cmd, line[512], xdisplay[512]; char xauthfile[PATH_MAX], xauthdir[PATH_MAX]; static char proto[512], data[512]; FILE *f; int got_data = 0, generated = 0, do_unlink = 0, r; struct stat st; u_int now, x11_timeout_real; *_proto = proto; *_data = data; proto[0] = data[0] = xauthfile[0] = xauthdir[0] = '\0'; if (!client_x11_display_valid(display)) { if (display != NULL) logit("DISPLAY \"%s\" invalid; disabling X11 forwarding", display); return -1; } if (xauth_path != NULL && stat(xauth_path, &st) == -1) { debug("No xauth program."); xauth_path = NULL; } if (xauth_path != NULL) { /* * Handle FamilyLocal case where $DISPLAY does * not match an authorization entry. For this we * just try "xauth list unix:displaynum.screennum". * XXX: "localhost" match to determine FamilyLocal * is not perfect. */ if (strncmp(display, "localhost:", 10) == 0) { if ((r = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10)) < 0 || (size_t)r >= sizeof(xdisplay)) { error_f("display name too long"); return -1; } display = xdisplay; } if (trusted == 0) { /* * Generate an untrusted X11 auth cookie. * * The authentication cookie should briefly outlive * ssh's willingness to forward X11 connections to * avoid nasty fail-open behaviour in the X server. */ mktemp_proto(xauthdir, sizeof(xauthdir)); if (mkdtemp(xauthdir) == NULL) { error_f("mkdtemp: %s", strerror(errno)); return -1; } do_unlink = 1; if ((r = snprintf(xauthfile, sizeof(xauthfile), "%s/xauthfile", xauthdir)) < 0 || (size_t)r >= sizeof(xauthfile)) { error_f("xauthfile path too long"); rmdir(xauthdir); return -1; } if (timeout == 0) { /* auth doesn't time out */ xasprintf(&cmd, "%s -f %s generate %s %s " "untrusted 2>%s", xauth_path, xauthfile, display, SSH_X11_PROTO, _PATH_DEVNULL); } else { /* Add some slack to requested expiry */ if (timeout < UINT_MAX - X11_TIMEOUT_SLACK) x11_timeout_real = timeout + X11_TIMEOUT_SLACK; else { /* Don't overflow on long timeouts */ x11_timeout_real = UINT_MAX; } xasprintf(&cmd, "%s -f %s generate %s %s " "untrusted timeout %u 2>%s", xauth_path, xauthfile, display, SSH_X11_PROTO, x11_timeout_real, _PATH_DEVNULL); } debug2_f("xauth command: %s", cmd); if (timeout != 0 && x11_refuse_time == 0) { now = monotime() + 1; if (SSH_TIME_T_MAX - timeout < now) x11_refuse_time = SSH_TIME_T_MAX; else x11_refuse_time = now + timeout; channel_set_x11_refuse_time(ssh, x11_refuse_time); } if (system(cmd) == 0) generated = 1; free(cmd); } /* * When in untrusted mode, we read the cookie only if it was * successfully generated as an untrusted one in the step * above. */ if (trusted || generated) { xasprintf(&cmd, "%s %s%s list %s 2>" _PATH_DEVNULL, xauth_path, generated ? "-f " : "" , generated ? xauthfile : "", display); debug2("x11_get_proto: %s", cmd); f = popen(cmd, "r"); if (f && fgets(line, sizeof(line), f) && sscanf(line, "%*s %511s %511s", proto, data) == 2) got_data = 1; if (f) pclose(f); free(cmd); } } if (do_unlink) { unlink(xauthfile); rmdir(xauthdir); } /* Don't fall back to fake X11 data for untrusted forwarding */ if (!trusted && !got_data) { error("Warning: untrusted X11 forwarding setup failed: " "xauth key data not generated"); return -1; } /* * If we didn't get authentication data, just make up some * data. The forwarding code will check the validity of the * response anyway, and substitute this data. The X11 * server, however, will ignore this fake data and use * whatever authentication mechanisms it was using otherwise * for the local connection. */ if (!got_data) { u_int8_t rnd[16]; u_int i; logit("Warning: No xauth data; " "using fake authentication data for X11 forwarding."); strlcpy(proto, SSH_X11_PROTO, sizeof proto); arc4random_buf(rnd, sizeof(rnd)); for (i = 0; i < sizeof(rnd); i++) { snprintf(data + 2 * i, sizeof data - 2 * i, "%02x", rnd[i]); } } return 0; } /* * Checks if the client window has changed, and sends a packet about it to * the server if so. The actual change is detected elsewhere (by a software * interrupt on Unix); this just checks the flag and sends a message if * appropriate. */ static void client_check_window_change(struct ssh *ssh) { if (!received_window_change_signal) return; received_window_change_signal = 0; debug2_f("changed"); channel_send_window_changes(ssh); } static int client_global_request_reply(int type, u_int32_t seq, struct ssh *ssh) { struct global_confirm *gc; if ((gc = TAILQ_FIRST(&global_confirms)) == NULL) return 0; if (gc->cb != NULL) gc->cb(ssh, type, seq, gc->ctx); if (--gc->ref_count <= 0) { TAILQ_REMOVE(&global_confirms, gc, entry); freezero(gc, sizeof(*gc)); } ssh_packet_set_alive_timeouts(ssh, 0); return 0; } static void schedule_server_alive_check(void) { if (options.server_alive_interval > 0) server_alive_time = monotime() + options.server_alive_interval; } static void server_alive_check(struct ssh *ssh) { int r; if (ssh_packet_inc_alive_timeouts(ssh) > options.server_alive_count_max) { logit("Timeout, server %s not responding.", host); cleanup_exit(255); } if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "keepalive@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 1)) != 0 || /* boolean: want reply */ (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); /* Insert an empty placeholder to maintain ordering */ client_register_global_confirm(NULL, NULL); schedule_server_alive_check(); } +/* Try to send a dummy keystroke */ +static int +send_chaff(struct ssh *ssh) +{ + int r; + + if ((ssh->kex->flags & KEX_HAS_PING) == 0) + return 0; + /* XXX probabilistically send chaff? */ + /* + * a SSH2_MSG_CHANNEL_DATA payload is 9 bytes: + * 4 bytes channel ID + 4 bytes string length + 1 byte string data + * simulate that here. + */ + if ((r = sshpkt_start(ssh, SSH2_MSG_PING)) != 0 || + (r = sshpkt_put_cstring(ssh, "PING!")) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal_fr(r, "send packet"); + return 1; +} + +/* Sets the next interval to send a keystroke or chaff packet */ +static void +set_next_interval(const struct timespec *now, struct timespec *next_interval, + u_int interval_ms, int starting) +{ + struct timespec tmp; + long long interval_ns, fuzz_ns; + static long long rate_fuzz; + + interval_ns = interval_ms * (1000LL * 1000); + fuzz_ns = (interval_ns * SSH_KEYSTROKE_TIMING_FUZZ) / 100; + /* Center fuzz around requested interval */ + if (fuzz_ns > INT_MAX) + fuzz_ns = INT_MAX; + if (fuzz_ns > interval_ns) { + /* Shouldn't happen */ + fatal_f("internal error: fuzz %u%% %lldns > interval %lldns", + SSH_KEYSTROKE_TIMING_FUZZ, fuzz_ns, interval_ns); + } + /* + * Randomise the keystroke/chaff intervals in two ways: + * 1. Each interval has some random jitter applied to make the + * interval-to-interval time unpredictable. + * 2. The overall interval rate is also randomly perturbed for each + * chaffing session to make the average rate unpredictable. + */ + if (starting) + rate_fuzz = arc4random_uniform(fuzz_ns); + interval_ns -= fuzz_ns; + interval_ns += arc4random_uniform(fuzz_ns) + rate_fuzz; + + tmp.tv_sec = interval_ns / (1000 * 1000 * 1000); + tmp.tv_nsec = interval_ns % (1000 * 1000 * 1000); + + timespecadd(now, &tmp, next_interval); +} + +/* + * Performs keystroke timing obfuscation. Returns non-zero if the + * output fd should be polled. + */ +static int +obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout, + int channel_did_enqueue) +{ + static int active; + static struct timespec next_interval, chaff_until; + struct timespec now, tmp; + int just_started = 0, had_keystroke = 0; + static unsigned long long nchaff; + char *stop_reason = NULL; + long long n; + + monotime_ts(&now); + + if (options.obscure_keystroke_timing_interval <= 0) + return 1; /* disabled in config */ + + if (!channel_still_open(ssh) || quit_pending) { + /* Stop if no channels left of we're waiting for one to close */ + stop_reason = "no active channels"; + } else if (ssh_packet_is_rekeying(ssh)) { + /* Stop if we're rekeying */ + stop_reason = "rekeying started"; + } else if (!ssh_packet_interactive_data_to_write(ssh) && + ssh_packet_have_data_to_write(ssh)) { + /* Stop if the output buffer has more than a few keystrokes */ + stop_reason = "output buffer filling"; + } else if (active && channel_did_enqueue && + ssh_packet_have_data_to_write(ssh)) { + /* Still in active mode and have a keystroke queued. */ + had_keystroke = 1; + } else if (active) { + if (timespeccmp(&now, &chaff_until, >=)) { + /* Stop if there have been no keystrokes for a while */ + stop_reason = "chaff time expired"; + } else if (timespeccmp(&now, &next_interval, >=)) { + /* Otherwise if we were due to send, then send chaff */ + if (send_chaff(ssh)) + nchaff++; + } + } + + if (stop_reason != NULL) { + if (active) { + debug3_f("stopping: %s (%llu chaff packets sent)", + stop_reason, nchaff); + active = 0; + } + return 1; + } + + /* + * If we're in interactive mode, and only have a small amount + * of outbound data, then we assume that the user is typing + * interactively. In this case, start quantising outbound packets to + * fixed time intervals to hide inter-keystroke timing. + */ + if (!active && ssh_packet_interactive_data_to_write(ssh) && + channel_did_enqueue && ssh_packet_have_data_to_write(ssh)) { + debug3_f("starting: interval ~%dms", + options.obscure_keystroke_timing_interval); + just_started = had_keystroke = active = 1; + nchaff = 0; + set_next_interval(&now, &next_interval, + options.obscure_keystroke_timing_interval, 1); + } + + /* Don't hold off if obfuscation inactive */ + if (!active) + return 1; + + if (had_keystroke) { + /* + * Arrange to send chaff packets for a random interval after + * the last keystroke was sent. + */ + ms_to_timespec(&tmp, SSH_KEYSTROKE_CHAFF_MIN_MS + + arc4random_uniform(SSH_KEYSTROKE_CHAFF_RNG_MS)); + timespecadd(&now, &tmp, &chaff_until); + } + + ptimeout_deadline_monotime_tsp(timeout, &next_interval); + + if (just_started) + return 1; + + /* Don't arm output fd for poll until the timing interval has elapsed */ + if (timespeccmp(&now, &next_interval, <)) + return 0; + + /* Calculate number of intervals missed since the last check */ + n = (now.tv_sec - next_interval.tv_sec) * 1000LL * 1000 * 1000; + n += now.tv_nsec - next_interval.tv_nsec; + n /= options.obscure_keystroke_timing_interval * 1000LL * 1000; + n = (n < 0) ? 1 : n + 1; + + /* Advance to the next interval */ + set_next_interval(&now, &next_interval, + options.obscure_keystroke_timing_interval * n, 0); + return 1; +} + /* * Waits until the client can do something (some data becomes available on * one of the file descriptors). */ static void client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, - u_int *npfd_allocp, u_int *npfd_activep, int rekeying, + u_int *npfd_allocp, u_int *npfd_activep, int channel_did_enqueue, int *conn_in_readyp, int *conn_out_readyp) { struct timespec timeout; - int ret; + int ret, oready; u_int p; *conn_in_readyp = *conn_out_readyp = 0; /* Prepare channel poll. First two pollfd entries are reserved */ ptimeout_init(&timeout); channel_prepare_poll(ssh, pfdp, npfd_allocp, npfd_activep, 2, &timeout); if (*npfd_activep < 2) fatal_f("bad npfd %u", *npfd_activep); /* shouldn't happen */ /* channel_prepare_poll could have closed the last channel */ if (session_closed && !channel_still_open(ssh) && !ssh_packet_have_data_to_write(ssh)) { /* clear events since we did not call poll() */ for (p = 0; p < *npfd_activep; p++) (*pfdp)[p].revents = 0; return; } + oready = obfuscate_keystroke_timing(ssh, &timeout, channel_did_enqueue); + /* Monitor server connection on reserved pollfd entries */ (*pfdp)[0].fd = connection_in; (*pfdp)[0].events = POLLIN; (*pfdp)[1].fd = connection_out; - (*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0; + (*pfdp)[1].events = (oready && ssh_packet_have_data_to_write(ssh)) ? + POLLOUT : 0; /* * Wait for something to happen. This will suspend the process until * some polled descriptor can be read, written, or has some other * event pending, or a timeout expires. */ set_control_persist_exit_time(ssh); if (control_persist_exit_time > 0) ptimeout_deadline_monotime(&timeout, control_persist_exit_time); if (options.server_alive_interval > 0) ptimeout_deadline_monotime(&timeout, server_alive_time); - if (options.rekey_interval > 0 && !rekeying) { + if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) { ptimeout_deadline_sec(&timeout, ssh_packet_get_rekey_timeout(ssh)); } - ret = poll(*pfdp, *npfd_activep, ptimeout_get_ms(&timeout)); + ret = ppoll(*pfdp, *npfd_activep, ptimeout_get_tsp(&timeout), NULL); if (ret == -1) { /* * We have to clear the events because we return. * We have to return, because the mainloop checks for the flags * set by the signal handlers. */ for (p = 0; p < *npfd_activep; p++) (*pfdp)[p].revents = 0; if (errno == EINTR) return; /* Note: we might still have data in the buffers. */ quit_message("poll: %s", strerror(errno)); return; } *conn_in_readyp = (*pfdp)[0].revents != 0; *conn_out_readyp = (*pfdp)[1].revents != 0; if (options.server_alive_interval > 0 && !*conn_in_readyp && monotime() >= server_alive_time) { /* * ServerAlive check is needed. We can't rely on the poll * timing out since traffic on the client side such as port * forwards can keep waking it up. */ server_alive_check(ssh); } } static void client_suspend_self(struct sshbuf *bin, struct sshbuf *bout, struct sshbuf *berr) { /* Flush stdout and stderr buffers. */ if (sshbuf_len(bout) > 0) atomicio(vwrite, fileno(stdout), sshbuf_mutable_ptr(bout), sshbuf_len(bout)); if (sshbuf_len(berr) > 0) atomicio(vwrite, fileno(stderr), sshbuf_mutable_ptr(berr), sshbuf_len(berr)); leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE); sshbuf_reset(bin); sshbuf_reset(bout); sshbuf_reset(berr); /* Send the suspend signal to the program itself. */ kill(getpid(), SIGTSTP); /* Reset window sizes in case they have changed */ received_window_change_signal = 1; enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE); } static void client_process_net_input(struct ssh *ssh) { int r; /* * Read input from the server, and add any such data to the buffer of * the packet subsystem. */ schedule_server_alive_check(); if ((r = ssh_packet_process_read(ssh, connection_in)) == 0) return; /* success */ if (r == SSH_ERR_SYSTEM_ERROR) { if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) return; if (errno == EPIPE) { quit_message("Connection to %s closed by remote host.", host); return; } } quit_message("Read from remote host %s: %s", host, ssh_err(r)); } static void client_status_confirm(struct ssh *ssh, int type, Channel *c, void *ctx) { struct channel_reply_ctx *cr = (struct channel_reply_ctx *)ctx; char errmsg[256]; int r, tochan; /* * If a TTY was explicitly requested, then a failure to allocate * one is fatal. */ if (cr->action == CONFIRM_TTY && (options.request_tty == REQUEST_TTY_FORCE || options.request_tty == REQUEST_TTY_YES)) cr->action = CONFIRM_CLOSE; /* XXX suppress on mux _client_ quietmode */ tochan = options.log_level >= SYSLOG_LEVEL_ERROR && c->ctl_chan != -1 && c->extended_usage == CHAN_EXTENDED_WRITE; if (type == SSH2_MSG_CHANNEL_SUCCESS) { debug2("%s request accepted on channel %d", cr->request_type, c->self); } else if (type == SSH2_MSG_CHANNEL_FAILURE) { if (tochan) { snprintf(errmsg, sizeof(errmsg), "%s request failed\r\n", cr->request_type); } else { snprintf(errmsg, sizeof(errmsg), "%s request failed on channel %d", cr->request_type, c->self); } /* If error occurred on primary session channel, then exit */ if (cr->action == CONFIRM_CLOSE && c->self == session_ident) fatal("%s", errmsg); /* * If error occurred on mux client, append to * their stderr. */ if (tochan) { debug3_f("channel %d: mux request: %s", c->self, cr->request_type); if ((r = sshbuf_put(c->extended, errmsg, strlen(errmsg))) != 0) fatal_fr(r, "sshbuf_put"); } else error("%s", errmsg); if (cr->action == CONFIRM_TTY) { /* * If a TTY allocation error occurred, then arrange * for the correct TTY to leave raw mode. */ if (c->self == session_ident) leave_raw_mode(0); else mux_tty_alloc_failed(ssh, c); } else if (cr->action == CONFIRM_CLOSE) { chan_read_failed(ssh, c); chan_write_failed(ssh, c); } } free(cr); } static void client_abandon_status_confirm(struct ssh *ssh, Channel *c, void *ctx) { free(ctx); } void client_expect_confirm(struct ssh *ssh, int id, const char *request, enum confirm_action action) { struct channel_reply_ctx *cr = xcalloc(1, sizeof(*cr)); cr->request_type = request; cr->action = action; channel_register_status_confirm(ssh, id, client_status_confirm, client_abandon_status_confirm, cr); } void client_register_global_confirm(global_confirm_cb *cb, void *ctx) { struct global_confirm *gc, *last_gc; /* Coalesce identical callbacks */ last_gc = TAILQ_LAST(&global_confirms, global_confirms); if (last_gc && last_gc->cb == cb && last_gc->ctx == ctx) { if (++last_gc->ref_count >= INT_MAX) fatal_f("last_gc->ref_count = %d", last_gc->ref_count); return; } gc = xcalloc(1, sizeof(*gc)); gc->cb = cb; gc->ctx = ctx; gc->ref_count = 1; TAILQ_INSERT_TAIL(&global_confirms, gc, entry); } /* * Returns non-zero if the client is able to handle a hostkeys-00@openssh.com * hostkey update request. */ static int can_update_hostkeys(void) { if (hostkeys_update_complete) return 0; if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK && options.batch_mode) return 0; /* won't ask in batchmode, so don't even try */ if (!options.update_hostkeys || options.num_user_hostfiles <= 0) return 0; return 1; } static void client_repledge(void) { debug3_f("enter"); /* Might be able to tighten pledge now that session is established */ if (options.control_master || options.control_path != NULL || options.forward_x11 || options.fork_after_authentication || can_update_hostkeys() || (session_ident != -1 && !session_setup_complete)) { /* Can't tighten */ return; } /* * LocalCommand and UpdateHostkeys have finished, so can get rid of * filesystem. * * XXX protocol allows a server can to change hostkeys during the * connection at rekey time that could trigger a hostkeys update * but AFAIK no implementations support this. Could improve by * forcing known_hosts to be read-only or via unveil(2). */ if (options.num_local_forwards != 0 || options.num_remote_forwards != 0 || options.num_permitted_remote_opens != 0 || options.enable_escape_commandline != 0) { /* rfwd needs inet */ debug("pledge: network"); if (pledge("stdio unix inet dns proc tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } else if (options.forward_agent != 0) { /* agent forwarding needs to open $SSH_AUTH_SOCK at will */ debug("pledge: agent"); if (pledge("stdio unix proc tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } else { debug("pledge: fork"); if (pledge("stdio proc tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } /* XXX further things to do: * * - might be able to get rid of proc if we kill ~^Z * - ssh -N (no session) * - stdio forwarding * - sessions without tty */ } static void process_cmdline(struct ssh *ssh) { void (*handler)(int); char *s, *cmd; int ok, delete = 0, local = 0, remote = 0, dynamic = 0; struct Forward fwd; memset(&fwd, 0, sizeof(fwd)); leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE); handler = ssh_signal(SIGINT, SIG_IGN); cmd = s = read_passphrase("\r\nssh> ", RP_ECHO); if (s == NULL) goto out; while (isspace((u_char)*s)) s++; if (*s == '-') s++; /* Skip cmdline '-', if any */ if (*s == '\0') goto out; if (*s == 'h' || *s == 'H' || *s == '?') { logit("Commands:"); logit(" -L[bind_address:]port:host:hostport " "Request local forward"); logit(" -R[bind_address:]port:host:hostport " "Request remote forward"); logit(" -D[bind_address:]port " "Request dynamic forward"); logit(" -KL[bind_address:]port " "Cancel local forward"); logit(" -KR[bind_address:]port " "Cancel remote forward"); logit(" -KD[bind_address:]port " "Cancel dynamic forward"); if (!options.permit_local_command) goto out; logit(" !args " "Execute local command"); goto out; } if (*s == '!' && options.permit_local_command) { s++; ssh_local_cmd(s); goto out; } if (*s == 'K') { delete = 1; s++; } if (*s == 'L') local = 1; else if (*s == 'R') remote = 1; else if (*s == 'D') dynamic = 1; else { logit("Invalid command."); goto out; } while (isspace((u_char)*++s)) ; /* XXX update list of forwards in options */ if (delete) { /* We pass 1 for dynamicfwd to restrict to 1 or 2 fields. */ if (!parse_forward(&fwd, s, 1, 0)) { logit("Bad forwarding close specification."); goto out; } if (remote) ok = channel_request_rforward_cancel(ssh, &fwd) == 0; else if (dynamic) ok = channel_cancel_lport_listener(ssh, &fwd, 0, &options.fwd_opts) > 0; else ok = channel_cancel_lport_listener(ssh, &fwd, CHANNEL_CANCEL_PORT_STATIC, &options.fwd_opts) > 0; if (!ok) { logit("Unknown port forwarding."); goto out; } logit("Canceled forwarding."); } else { /* -R specs can be both dynamic or not, so check both. */ if (remote) { if (!parse_forward(&fwd, s, 0, remote) && !parse_forward(&fwd, s, 1, remote)) { logit("Bad remote forwarding specification."); goto out; } } else if (!parse_forward(&fwd, s, dynamic, remote)) { logit("Bad local forwarding specification."); goto out; } if (local || dynamic) { if (!channel_setup_local_fwd_listener(ssh, &fwd, &options.fwd_opts)) { logit("Port forwarding failed."); goto out; } } else { if (channel_request_remote_forwarding(ssh, &fwd) < 0) { logit("Port forwarding failed."); goto out; } } logit("Forwarding port."); } out: ssh_signal(SIGINT, handler); enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE); free(cmd); free(fwd.listen_host); free(fwd.listen_path); free(fwd.connect_host); free(fwd.connect_path); } /* reasons to suppress output of an escape command in help output */ #define SUPPRESS_NEVER 0 /* never suppress, always show */ #define SUPPRESS_MUXCLIENT 1 /* don't show in mux client sessions */ #define SUPPRESS_MUXMASTER 2 /* don't show in mux master sessions */ #define SUPPRESS_SYSLOG 4 /* don't show when logging to syslog */ #define SUPPRESS_NOCMDLINE 8 /* don't show when cmdline disabled*/ struct escape_help_text { const char *cmd; const char *text; unsigned int flags; }; static struct escape_help_text esc_txt[] = { {".", "terminate session", SUPPRESS_MUXMASTER}, {".", "terminate connection (and any multiplexed sessions)", SUPPRESS_MUXCLIENT}, {"B", "send a BREAK to the remote system", SUPPRESS_NEVER}, {"C", "open a command line", SUPPRESS_MUXCLIENT|SUPPRESS_NOCMDLINE}, {"R", "request rekey", SUPPRESS_NEVER}, {"V/v", "decrease/increase verbosity (LogLevel)", SUPPRESS_MUXCLIENT}, {"^Z", "suspend ssh", SUPPRESS_MUXCLIENT}, {"#", "list forwarded connections", SUPPRESS_NEVER}, {"&", "background ssh (when waiting for connections to terminate)", SUPPRESS_MUXCLIENT}, {"?", "this message", SUPPRESS_NEVER}, }; static void print_escape_help(struct sshbuf *b, int escape_char, int mux_client, int using_stderr) { unsigned int i, suppress_flags; int r; if ((r = sshbuf_putf(b, "%c?\r\nSupported escape sequences:\r\n", escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); suppress_flags = (mux_client ? SUPPRESS_MUXCLIENT : 0) | (mux_client ? 0 : SUPPRESS_MUXMASTER) | (using_stderr ? 0 : SUPPRESS_SYSLOG) | (options.enable_escape_commandline == 0 ? SUPPRESS_NOCMDLINE : 0); for (i = 0; i < sizeof(esc_txt)/sizeof(esc_txt[0]); i++) { if (esc_txt[i].flags & suppress_flags) continue; if ((r = sshbuf_putf(b, " %c%-3s - %s\r\n", escape_char, esc_txt[i].cmd, esc_txt[i].text)) != 0) fatal_fr(r, "sshbuf_putf"); } if ((r = sshbuf_putf(b, " %c%c - send the escape character by typing it twice\r\n" "(Note that escapes are only recognized immediately after " "newline.)\r\n", escape_char, escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); } /* * Process the characters one by one. */ static int process_escapes(struct ssh *ssh, Channel *c, struct sshbuf *bin, struct sshbuf *bout, struct sshbuf *berr, char *buf, int len) { pid_t pid; int r, bytes = 0; u_int i; u_char ch; char *s; struct escape_filter_ctx *efc; if (c == NULL || c->filter_ctx == NULL || len <= 0) return 0; efc = (struct escape_filter_ctx *)c->filter_ctx; for (i = 0; i < (u_int)len; i++) { /* Get one character at a time. */ ch = buf[i]; if (efc->escape_pending) { /* We have previously seen an escape character. */ /* Clear the flag now. */ efc->escape_pending = 0; /* Process the escaped character. */ switch (ch) { case '.': /* Terminate the connection. */ if ((r = sshbuf_putf(berr, "%c.\r\n", efc->escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); if (c && c->ctl_chan != -1) { channel_force_close(ssh, c, 1); return 0; } else quit_pending = 1; return -1; case 'Z' - 64: /* XXX support this for mux clients */ if (c && c->ctl_chan != -1) { char b[16]; noescape: if (ch == 'Z' - 64) snprintf(b, sizeof b, "^Z"); else snprintf(b, sizeof b, "%c", ch); if ((r = sshbuf_putf(berr, "%c%s escape not available to " "multiplexed sessions\r\n", efc->escape_char, b)) != 0) fatal_fr(r, "sshbuf_putf"); continue; } /* Suspend the program. Inform the user */ if ((r = sshbuf_putf(berr, "%c^Z [suspend ssh]\r\n", efc->escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); /* Restore terminal modes and suspend. */ client_suspend_self(bin, bout, berr); /* We have been continued. */ continue; case 'B': if ((r = sshbuf_putf(berr, "%cB\r\n", efc->escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); channel_request_start(ssh, c->self, "break", 0); if ((r = sshpkt_put_u32(ssh, 1000)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); continue; case 'R': if (ssh->compat & SSH_BUG_NOREKEY) logit("Server does not " "support re-keying"); else need_rekeying = 1; continue; case 'V': /* FALLTHROUGH */ case 'v': if (c && c->ctl_chan != -1) goto noescape; if (!log_is_on_stderr()) { if ((r = sshbuf_putf(berr, "%c%c [Logging to syslog]\r\n", efc->escape_char, ch)) != 0) fatal_fr(r, "sshbuf_putf"); continue; } if (ch == 'V' && options.log_level > SYSLOG_LEVEL_QUIET) log_change_level(--options.log_level); if (ch == 'v' && options.log_level < SYSLOG_LEVEL_DEBUG3) log_change_level(++options.log_level); if ((r = sshbuf_putf(berr, "%c%c [LogLevel %s]\r\n", efc->escape_char, ch, log_level_name(options.log_level))) != 0) fatal_fr(r, "sshbuf_putf"); continue; case '&': if (c->ctl_chan != -1) goto noescape; /* * Detach the program (continue to serve * connections, but put in background and no * more new connections). */ /* Restore tty modes. */ leave_raw_mode( options.request_tty == REQUEST_TTY_FORCE); /* Stop listening for new connections. */ channel_stop_listening(ssh); if ((r = sshbuf_putf(berr, "%c& " "[backgrounded]\n", efc->escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); /* Fork into background. */ pid = fork(); if (pid == -1) { error("fork: %.100s", strerror(errno)); continue; } if (pid != 0) { /* This is the parent. */ /* The parent just exits. */ exit(0); } /* The child continues serving connections. */ /* fake EOF on stdin */ if ((r = sshbuf_put_u8(bin, 4)) != 0) fatal_fr(r, "sshbuf_put_u8"); return -1; case '?': print_escape_help(berr, efc->escape_char, (c && c->ctl_chan != -1), log_is_on_stderr()); continue; case '#': if ((r = sshbuf_putf(berr, "%c#\r\n", efc->escape_char)) != 0) fatal_fr(r, "sshbuf_putf"); s = channel_open_message(ssh); if ((r = sshbuf_put(berr, s, strlen(s))) != 0) fatal_fr(r, "sshbuf_put"); free(s); continue; case 'C': if (c && c->ctl_chan != -1) goto noescape; if (options.enable_escape_commandline == 0) { if ((r = sshbuf_putf(berr, "commandline disabled\r\n")) != 0) fatal_fr(r, "sshbuf_putf"); continue; } process_cmdline(ssh); continue; default: if (ch != efc->escape_char) { if ((r = sshbuf_put_u8(bin, efc->escape_char)) != 0) fatal_fr(r, "sshbuf_put_u8"); bytes++; } /* Escaped characters fall through here */ break; } } else { /* * The previous character was not an escape char. * Check if this is an escape. */ if (last_was_cr && ch == efc->escape_char) { /* * It is. Set the flag and continue to * next character. */ efc->escape_pending = 1; continue; } } /* * Normal character. Record whether it was a newline, * and append it to the buffer. */ last_was_cr = (ch == '\r' || ch == '\n'); if ((r = sshbuf_put_u8(bin, ch)) != 0) fatal_fr(r, "sshbuf_put_u8"); bytes++; } return bytes; } /* * Get packets from the connection input buffer, and process them as long as * there are packets available. * * Any unknown packets received during the actual * session cause the session to terminate. This is * intended to make debugging easier since no * confirmations are sent. Any compatible protocol * extensions must be negotiated during the * preparatory phase. */ static void client_process_buffered_input_packets(struct ssh *ssh) { ssh_dispatch_run_fatal(ssh, DISPATCH_NONBLOCK, &quit_pending); } /* scan buf[] for '~' before sending data to the peer */ /* Helper: allocate a new escape_filter_ctx and fill in its escape char */ void * client_new_escape_filter_ctx(int escape_char) { struct escape_filter_ctx *ret; ret = xcalloc(1, sizeof(*ret)); ret->escape_pending = 0; ret->escape_char = escape_char; return (void *)ret; } /* Free the escape filter context on channel free */ void client_filter_cleanup(struct ssh *ssh, int cid, void *ctx) { free(ctx); } int client_simple_escape_filter(struct ssh *ssh, Channel *c, char *buf, int len) { if (c->extended_usage != CHAN_EXTENDED_WRITE) return 0; return process_escapes(ssh, c, c->input, c->output, c->extended, buf, len); } static void client_channel_closed(struct ssh *ssh, int id, int force, void *arg) { channel_cancel_cleanup(ssh, id); session_closed = 1; leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE); } /* * Implements the interactive session with the server. This is called after * the user has been authenticated, and a command has been started on the * remote host. If escape_char != SSH_ESCAPECHAR_NONE, it is the character * used as an escape character for terminating or suspending the session. */ int client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, int ssh2_chan_id) { struct pollfd *pfd = NULL; u_int npfd_alloc = 0, npfd_active = 0; double start_time, total_time; - int r, len; + int channel_did_enqueue = 0, r, len; u_int64_t ibytes, obytes; int conn_in_ready, conn_out_ready; debug("Entering interactive session."); session_ident = ssh2_chan_id; if (options.control_master && !option_clear_or_none(options.control_path)) { debug("pledge: id"); if (pledge("stdio rpath wpath cpath unix inet dns recvfd sendfd proc exec id tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } else if (options.forward_x11 || options.permit_local_command) { debug("pledge: exec"); if (pledge("stdio rpath wpath cpath unix inet dns proc exec tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } else if (options.update_hostkeys) { debug("pledge: filesystem"); if (pledge("stdio rpath wpath cpath unix inet dns proc tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } else if (!option_clear_or_none(options.proxy_command) || options.fork_after_authentication) { debug("pledge: proc"); if (pledge("stdio cpath unix inet dns proc tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } else { debug("pledge: network"); if (pledge("stdio unix inet dns proc tty", NULL) == -1) fatal_f("pledge(): %s", strerror(errno)); } /* might be able to tighten now */ client_repledge(); start_time = monotime_double(); /* Initialize variables. */ last_was_cr = 1; exit_status = -1; connection_in = ssh_packet_get_connection_in(ssh); connection_out = ssh_packet_get_connection_out(ssh); quit_pending = 0; /* Initialize buffer. */ if ((stderr_buffer = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); client_init_dispatch(ssh); /* * Set signal handlers, (e.g. to restore non-blocking mode) * but don't overwrite SIG_IGN, matches behaviour from rsh(1) */ if (ssh_signal(SIGHUP, SIG_IGN) != SIG_IGN) ssh_signal(SIGHUP, signal_handler); if (ssh_signal(SIGINT, SIG_IGN) != SIG_IGN) ssh_signal(SIGINT, signal_handler); if (ssh_signal(SIGQUIT, SIG_IGN) != SIG_IGN) ssh_signal(SIGQUIT, signal_handler); if (ssh_signal(SIGTERM, SIG_IGN) != SIG_IGN) ssh_signal(SIGTERM, signal_handler); ssh_signal(SIGWINCH, window_change_handler); if (have_pty) enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE); if (session_ident != -1) { if (escape_char_arg != SSH_ESCAPECHAR_NONE) { channel_register_filter(ssh, session_ident, client_simple_escape_filter, NULL, client_filter_cleanup, client_new_escape_filter_ctx( escape_char_arg)); } channel_register_cleanup(ssh, session_ident, client_channel_closed, 0); } schedule_server_alive_check(); /* Main loop of the client for the interactive session mode. */ while (!quit_pending) { + channel_did_enqueue = 0; /* Process buffered packets sent by the server. */ client_process_buffered_input_packets(ssh); if (session_closed && !channel_still_open(ssh)) break; if (ssh_packet_is_rekeying(ssh)) { debug("rekeying in progress"); } else if (need_rekeying) { /* manual rekey request */ debug("need rekeying"); if ((r = kex_start_rekex(ssh)) != 0) fatal_fr(r, "kex_start_rekex"); need_rekeying = 0; } else { /* * Make packets from buffered channel data, and * enqueue them for sending to the server. */ if (ssh_packet_not_very_much_data_to_write(ssh)) - channel_output_poll(ssh); + channel_did_enqueue = channel_output_poll(ssh); /* * Check if the window size has changed, and buffer a * message about it to the server if so. */ client_check_window_change(ssh); if (quit_pending) break; } /* * Wait until we have something to do (something becomes * available on one of the descriptors). */ client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc, - &npfd_active, ssh_packet_is_rekeying(ssh), + &npfd_active, channel_did_enqueue, &conn_in_ready, &conn_out_ready); if (quit_pending) break; /* Do channel operations. */ channel_after_poll(ssh, pfd, npfd_active); /* Buffer input from the connection. */ if (conn_in_ready) client_process_net_input(ssh); if (quit_pending) break; /* A timeout may have triggered rekeying */ if ((r = ssh_packet_check_rekey(ssh)) != 0) fatal_fr(r, "cannot start rekeying"); /* * Send as much buffered packet data as possible to the * sender. */ if (conn_out_ready) { if ((r = ssh_packet_write_poll(ssh)) != 0) { sshpkt_fatal(ssh, r, "%s: ssh_packet_write_poll", __func__); } } /* * If we are a backgrounded control master, and the * timeout has expired without any active client * connections, then quit. */ if (control_persist_exit_time > 0) { if (monotime() >= control_persist_exit_time) { debug("ControlPersist timeout expired"); break; } } } free(pfd); /* Terminate the session. */ /* Stop watching for window change. */ ssh_signal(SIGWINCH, SIG_DFL); if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || (r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_BY_APPLICATION)) != 0 || (r = sshpkt_put_cstring(ssh, "disconnected by user")) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || /* language tag */ (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send disconnect"); channel_free_all(ssh); if (have_pty) leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE); /* * If there was no shell or command requested, there will be no remote * exit status to be returned. In that case, clear error code if the * connection was deliberately terminated at this end. */ if (options.session_type == SESSION_TYPE_NONE && received_signal == SIGTERM) { received_signal = 0; exit_status = 0; } if (received_signal) { verbose("Killed by signal %d.", (int) received_signal); cleanup_exit(255); } /* * In interactive mode (with pseudo tty) display a message indicating * that the connection has been closed. */ if (have_pty && options.log_level >= SYSLOG_LEVEL_INFO) quit_message("Connection to %s closed.", host); /* Output any buffered data for stderr. */ if (sshbuf_len(stderr_buffer) > 0) { len = atomicio(vwrite, fileno(stderr), (u_char *)sshbuf_ptr(stderr_buffer), sshbuf_len(stderr_buffer)); if (len < 0 || (u_int)len != sshbuf_len(stderr_buffer)) error("Write failed flushing stderr buffer."); else if ((r = sshbuf_consume(stderr_buffer, len)) != 0) fatal_fr(r, "sshbuf_consume"); } /* Clear and free any buffers. */ sshbuf_free(stderr_buffer); /* Report bytes transferred, and transfer rates. */ total_time = monotime_double() - start_time; ssh_packet_get_bytes(ssh, &ibytes, &obytes); verbose("Transferred: sent %llu, received %llu bytes, in %.1f seconds", (unsigned long long)obytes, (unsigned long long)ibytes, total_time); if (total_time > 0) verbose("Bytes per second: sent %.1f, received %.1f", obytes / total_time, ibytes / total_time); /* Return the exit status of the program. */ debug("Exit status %d", exit_status); return exit_status; } /*********/ static Channel * client_request_forwarded_tcpip(struct ssh *ssh, const char *request_type, int rchan, u_int rwindow, u_int rmaxpack) { Channel *c = NULL; struct sshbuf *b = NULL; char *listen_address, *originator_address; u_int listen_port, originator_port; int r; /* Get rest of the packet */ if ((r = sshpkt_get_cstring(ssh, &listen_address, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &listen_port)) != 0 || (r = sshpkt_get_cstring(ssh, &originator_address, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &originator_port)) != 0 || (r = sshpkt_get_end(ssh)) != 0) fatal_fr(r, "parse packet"); debug_f("listen %s port %d, originator %s port %d", listen_address, listen_port, originator_address, originator_port); if (listen_port > 0xffff) error_f("invalid listen port"); else if (originator_port > 0xffff) error_f("invalid originator port"); else { c = channel_connect_by_listen_address(ssh, listen_address, listen_port, "forwarded-tcpip", originator_address); } if (c != NULL && c->type == SSH_CHANNEL_MUX_CLIENT) { if ((b = sshbuf_new()) == NULL) { error_f("alloc reply"); goto out; } /* reconstruct and send to muxclient */ if ((r = sshbuf_put_u8(b, 0)) != 0 || /* padlen */ (r = sshbuf_put_u8(b, SSH2_MSG_CHANNEL_OPEN)) != 0 || (r = sshbuf_put_cstring(b, request_type)) != 0 || (r = sshbuf_put_u32(b, rchan)) != 0 || (r = sshbuf_put_u32(b, rwindow)) != 0 || (r = sshbuf_put_u32(b, rmaxpack)) != 0 || (r = sshbuf_put_cstring(b, listen_address)) != 0 || (r = sshbuf_put_u32(b, listen_port)) != 0 || (r = sshbuf_put_cstring(b, originator_address)) != 0 || (r = sshbuf_put_u32(b, originator_port)) != 0 || (r = sshbuf_put_stringb(c->output, b)) != 0) { error_fr(r, "compose for muxclient"); goto out; } } out: sshbuf_free(b); free(originator_address); free(listen_address); return c; } static Channel * client_request_forwarded_streamlocal(struct ssh *ssh, const char *request_type, int rchan) { Channel *c = NULL; char *listen_path; int r; /* Get the remote path. */ if ((r = sshpkt_get_cstring(ssh, &listen_path, NULL)) != 0 || (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* reserved */ (r = sshpkt_get_end(ssh)) != 0) fatal_fr(r, "parse packet"); debug_f("request: %s", listen_path); c = channel_connect_by_listen_path(ssh, listen_path, "forwarded-streamlocal@openssh.com", "forwarded-streamlocal"); free(listen_path); return c; } static Channel * client_request_x11(struct ssh *ssh, const char *request_type, int rchan) { Channel *c = NULL; char *originator; u_int originator_port; int r, sock; if (!options.forward_x11) { error("Warning: ssh server tried X11 forwarding."); error("Warning: this is probably a break-in attempt by a " "malicious server."); return NULL; } if (x11_refuse_time != 0 && monotime() >= x11_refuse_time) { verbose("Rejected X11 connection after ForwardX11Timeout " "expired"); return NULL; } if ((r = sshpkt_get_cstring(ssh, &originator, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &originator_port)) != 0 || (r = sshpkt_get_end(ssh)) != 0) fatal_fr(r, "parse packet"); /* XXX check permission */ /* XXX range check originator port? */ debug("client_request_x11: request from %s %u", originator, originator_port); free(originator); sock = x11_connect_display(ssh); if (sock < 0) return NULL; c = channel_new(ssh, "x11", SSH_CHANNEL_X11_OPEN, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); c->force_drain = 1; return c; } static Channel * client_request_agent(struct ssh *ssh, const char *request_type, int rchan) { Channel *c = NULL; int r, sock; if (!options.forward_agent) { error("Warning: ssh server tried agent forwarding."); error("Warning: this is probably a break-in attempt by a " "malicious server."); return NULL; } if (forward_agent_sock_path == NULL) { r = ssh_get_authentication_socket(&sock); } else { r = ssh_get_authentication_socket_path(forward_agent_sock_path, &sock); } if (r != 0) { if (r != SSH_ERR_AGENT_NOT_PRESENT) debug_fr(r, "ssh_get_authentication_socket"); return NULL; } if ((r = ssh_agent_bind_hostkey(sock, ssh->kex->initial_hostkey, ssh->kex->session_id, ssh->kex->initial_sig, 1)) == 0) debug_f("bound agent to hostkey"); else debug2_fr(r, "ssh_agent_bind_hostkey"); c = channel_new(ssh, "authentication agent connection", SSH_CHANNEL_OPEN, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "authentication agent connection", 1); c->force_drain = 1; return c; } char * client_request_tun_fwd(struct ssh *ssh, int tun_mode, int local_tun, int remote_tun, channel_open_fn *cb, void *cbctx) { Channel *c; int r, fd; char *ifname = NULL; if (tun_mode == SSH_TUNMODE_NO) return 0; debug("Requesting tun unit %d in mode %d", local_tun, tun_mode); /* Open local tunnel device */ if ((fd = tun_open(local_tun, tun_mode, &ifname)) == -1) { error("Tunnel device open failed."); return NULL; } debug("Tunnel forwarding using interface %s", ifname); c = channel_new(ssh, "tun", SSH_CHANNEL_OPENING, fd, fd, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); c->datagram = 1; #if defined(SSH_TUN_FILTER) if (options.tun_open == SSH_TUNMODE_POINTOPOINT) channel_register_filter(ssh, c->self, sys_tun_infilter, sys_tun_outfilter, NULL, NULL); #endif if (cb != NULL) channel_register_open_confirm(ssh, c->self, cb, cbctx); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN)) != 0 || (r = sshpkt_put_cstring(ssh, "tun@openssh.com")) != 0 || (r = sshpkt_put_u32(ssh, c->self)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window_max)) != 0 || (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0 || (r = sshpkt_put_u32(ssh, tun_mode)) != 0 || (r = sshpkt_put_u32(ssh, remote_tun)) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send reply", __func__); return ifname; } /* XXXX move to generic input handler */ static int client_input_channel_open(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = NULL; char *ctype = NULL; int r; u_int rchan; size_t len; u_int rmaxpack, rwindow; if ((r = sshpkt_get_cstring(ssh, &ctype, &len)) != 0 || (r = sshpkt_get_u32(ssh, &rchan)) != 0 || (r = sshpkt_get_u32(ssh, &rwindow)) != 0 || (r = sshpkt_get_u32(ssh, &rmaxpack)) != 0) goto out; debug("client_input_channel_open: ctype %s rchan %d win %d max %d", ctype, rchan, rwindow, rmaxpack); if (strcmp(ctype, "forwarded-tcpip") == 0) { c = client_request_forwarded_tcpip(ssh, ctype, rchan, rwindow, rmaxpack); } else if (strcmp(ctype, "forwarded-streamlocal@openssh.com") == 0) { c = client_request_forwarded_streamlocal(ssh, ctype, rchan); } else if (strcmp(ctype, "x11") == 0) { c = client_request_x11(ssh, ctype, rchan); } else if (strcmp(ctype, "auth-agent@openssh.com") == 0) { c = client_request_agent(ssh, ctype, rchan); } if (c != NULL && c->type == SSH_CHANNEL_MUX_CLIENT) { debug3("proxied to downstream: %s", ctype); } else if (c != NULL) { debug("confirm %s", ctype); c->remote_id = rchan; c->have_remote_id = 1; c->remote_window = rwindow; c->remote_maxpacket = rmaxpack; if (c->type != SSH_CHANNEL_CONNECTING) { if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, c->self)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window)) != 0 || (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send reply", __func__); } } else { debug("failure %s", ctype); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 || (r = sshpkt_put_u32(ssh, rchan)) != 0 || (r = sshpkt_put_u32(ssh, SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED)) != 0 || (r = sshpkt_put_cstring(ssh, "open failed")) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send failure", __func__); } r = 0; out: free(ctype); return r; } static int client_input_channel_req(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = NULL; char *rtype = NULL; u_char reply; u_int id, exitval; int r, success = 0; if ((r = sshpkt_get_u32(ssh, &id)) != 0) return r; if (id <= INT_MAX) c = channel_lookup(ssh, id); if (channel_proxy_upstream(c, type, seq, ssh)) return 0; if ((r = sshpkt_get_cstring(ssh, &rtype, NULL)) != 0 || (r = sshpkt_get_u8(ssh, &reply)) != 0) goto out; debug("client_input_channel_req: channel %u rtype %s reply %d", id, rtype, reply); if (c == NULL) { error("client_input_channel_req: channel %d: " "unknown channel", id); } else if (strcmp(rtype, "eow@openssh.com") == 0) { if ((r = sshpkt_get_end(ssh)) != 0) goto out; chan_rcvd_eow(ssh, c); } else if (strcmp(rtype, "exit-status") == 0) { if ((r = sshpkt_get_u32(ssh, &exitval)) != 0) goto out; if (c->ctl_chan != -1) { mux_exit_message(ssh, c, exitval); success = 1; } else if ((int)id == session_ident) { /* Record exit value of local session */ success = 1; exit_status = exitval; } else { /* Probably for a mux channel that has already closed */ debug_f("no sink for exit-status on channel %d", id); } if ((r = sshpkt_get_end(ssh)) != 0) goto out; } if (reply && c != NULL && !(c->flags & CHAN_CLOSE_SENT)) { if (!c->have_remote_id) fatal_f("channel %d: no remote_id", c->self); if ((r = sshpkt_start(ssh, success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send failure", __func__); } r = 0; out: free(rtype); return r; } struct hostkeys_update_ctx { /* The hostname and (optionally) IP address string for the server */ char *host_str, *ip_str; /* * Keys received from the server and a flag for each indicating * whether they already exist in known_hosts. * keys_match is filled in by hostkeys_find() and later (for new * keys) by client_global_hostkeys_prove_confirm(). */ struct sshkey **keys; u_int *keys_match; /* mask of HKF_MATCH_* from hostfile.h */ int *keys_verified; /* flag for new keys verified by server */ size_t nkeys, nnew, nincomplete; /* total, new keys, incomplete match */ /* * Keys that are in known_hosts, but were not present in the update * from the server (i.e. scheduled to be deleted). * Filled in by hostkeys_find(). */ struct sshkey **old_keys; size_t nold; /* Various special cases. */ int complex_hostspec; /* wildcard or manual pattern-list host name */ int ca_available; /* saw CA key for this host */ int old_key_seen; /* saw old key with other name/addr */ int other_name_seen; /* saw key with other name/addr */ }; static void hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx) { size_t i; if (ctx == NULL) return; for (i = 0; i < ctx->nkeys; i++) sshkey_free(ctx->keys[i]); free(ctx->keys); free(ctx->keys_match); free(ctx->keys_verified); for (i = 0; i < ctx->nold; i++) sshkey_free(ctx->old_keys[i]); free(ctx->old_keys); free(ctx->host_str); free(ctx->ip_str); free(ctx); } /* * Returns non-zero if a known_hosts hostname list is not of a form that * can be handled by UpdateHostkeys. These include wildcard hostnames and * hostnames lists that do not follow the form host[,ip]. */ static int hostspec_is_complex(const char *hosts) { char *cp; /* wildcard */ if (strchr(hosts, '*') != NULL || strchr(hosts, '?') != NULL) return 1; /* single host/ip = ok */ if ((cp = strchr(hosts, ',')) == NULL) return 0; /* more than two entries on the line */ if (strchr(cp + 1, ',') != NULL) return 1; /* XXX maybe parse cp+1 and ensure it is an IP? */ return 0; } /* callback to search for ctx->keys in known_hosts */ static int hostkeys_find(struct hostkey_foreach_line *l, void *_ctx) { struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx; size_t i; struct sshkey **tmp; if (l->key == NULL) return 0; if (l->status != HKF_STATUS_MATCHED) { /* Record if one of the keys appears on a non-matching line */ for (i = 0; i < ctx->nkeys; i++) { if (sshkey_equal(l->key, ctx->keys[i])) { ctx->other_name_seen = 1; debug3_f("found %s key under different " "name/addr at %s:%ld", sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum); return 0; } } return 0; } /* Don't proceed if revocation or CA markers are present */ /* XXX relax this */ if (l->marker != MRK_NONE) { debug3_f("hostkeys file %s:%ld has CA/revocation marker", l->path, l->linenum); ctx->complex_hostspec = 1; return 0; } /* If CheckHostIP is enabled, then check for mismatched hostname/addr */ if (ctx->ip_str != NULL && strchr(l->hosts, ',') != NULL) { if ((l->match & HKF_MATCH_HOST) == 0) { /* Record if address matched a different hostname. */ ctx->other_name_seen = 1; debug3_f("found address %s against different hostname " "at %s:%ld", ctx->ip_str, l->path, l->linenum); return 0; } else if ((l->match & HKF_MATCH_IP) == 0) { /* Record if hostname matched a different address. */ ctx->other_name_seen = 1; debug3_f("found hostname %s against different address " "at %s:%ld", ctx->host_str, l->path, l->linenum); } } /* * UpdateHostkeys is skipped for wildcard host names and hostnames * that contain more than two entries (ssh never writes these). */ if (hostspec_is_complex(l->hosts)) { debug3_f("hostkeys file %s:%ld complex host specification", l->path, l->linenum); ctx->complex_hostspec = 1; return 0; } /* Mark off keys we've already seen for this host */ for (i = 0; i < ctx->nkeys; i++) { if (!sshkey_equal(l->key, ctx->keys[i])) continue; debug3_f("found %s key at %s:%ld", sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum); ctx->keys_match[i] |= l->match; return 0; } /* This line contained a key that not offered by the server */ debug3_f("deprecated %s key at %s:%ld", sshkey_ssh_name(l->key), l->path, l->linenum); if ((tmp = recallocarray(ctx->old_keys, ctx->nold, ctx->nold + 1, sizeof(*ctx->old_keys))) == NULL) fatal_f("recallocarray failed nold = %zu", ctx->nold); ctx->old_keys = tmp; ctx->old_keys[ctx->nold++] = l->key; l->key = NULL; return 0; } /* callback to search for ctx->old_keys in known_hosts under other names */ static int hostkeys_check_old(struct hostkey_foreach_line *l, void *_ctx) { struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx; size_t i; int hashed; /* only care about lines that *don't* match the active host spec */ if (l->status == HKF_STATUS_MATCHED || l->key == NULL) return 0; hashed = l->match & (HKF_MATCH_HOST_HASHED|HKF_MATCH_IP_HASHED); for (i = 0; i < ctx->nold; i++) { if (!sshkey_equal(l->key, ctx->old_keys[i])) continue; debug3_f("found deprecated %s key at %s:%ld as %s", sshkey_ssh_name(ctx->old_keys[i]), l->path, l->linenum, hashed ? "[HASHED]" : l->hosts); ctx->old_key_seen = 1; break; } return 0; } /* * Check known_hosts files for deprecated keys under other names. Returns 0 * on success or -1 on failure. Updates ctx->old_key_seen if deprecated keys * exist under names other than the active hostname/IP. */ static int check_old_keys_othernames(struct hostkeys_update_ctx *ctx) { size_t i; int r; debug2_f("checking for %zu deprecated keys", ctx->nold); for (i = 0; i < options.num_user_hostfiles; i++) { debug3_f("searching %s for %s / %s", options.user_hostfiles[i], ctx->host_str, ctx->ip_str ? ctx->ip_str : "(none)"); if ((r = hostkeys_foreach(options.user_hostfiles[i], hostkeys_check_old, ctx, ctx->host_str, ctx->ip_str, HKF_WANT_PARSE_KEY, 0)) != 0) { if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { debug_f("hostkeys file %s does not exist", options.user_hostfiles[i]); continue; } error_fr(r, "hostkeys_foreach failed for %s", options.user_hostfiles[i]); return -1; } } return 0; } static void hostkey_change_preamble(LogLevel loglevel) { do_log2(loglevel, "The server has updated its host keys."); do_log2(loglevel, "These changes were verified by the server's " "existing trusted key."); } static void update_known_hosts(struct hostkeys_update_ctx *ctx) { int r, was_raw = 0, first = 1; int asking = options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK; LogLevel loglevel = asking ? SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE; char *fp, *response; size_t i; struct stat sb; for (i = 0; i < ctx->nkeys; i++) { if (!ctx->keys_verified[i]) continue; if ((fp = sshkey_fingerprint(ctx->keys[i], options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal_f("sshkey_fingerprint failed"); if (first && asking) hostkey_change_preamble(loglevel); do_log2(loglevel, "Learned new hostkey: %s %s", sshkey_type(ctx->keys[i]), fp); first = 0; free(fp); } for (i = 0; i < ctx->nold; i++) { if ((fp = sshkey_fingerprint(ctx->old_keys[i], options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal_f("sshkey_fingerprint failed"); if (first && asking) hostkey_change_preamble(loglevel); do_log2(loglevel, "Deprecating obsolete hostkey: %s %s", sshkey_type(ctx->old_keys[i]), fp); first = 0; free(fp); } if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) { if (get_saved_tio() != NULL) { leave_raw_mode(1); was_raw = 1; } response = NULL; for (i = 0; !quit_pending && i < 3; i++) { free(response); response = read_passphrase("Accept updated hostkeys? " "(yes/no): ", RP_ECHO); if (response != NULL && strcasecmp(response, "yes") == 0) break; else if (quit_pending || response == NULL || strcasecmp(response, "no") == 0) { options.update_hostkeys = 0; break; } else { do_log2(loglevel, "Please enter " "\"yes\" or \"no\""); } } if (quit_pending || i >= 3 || response == NULL) options.update_hostkeys = 0; free(response); if (was_raw) enter_raw_mode(1); } if (options.update_hostkeys == 0) return; /* * Now that all the keys are verified, we can go ahead and replace * them in known_hosts (assuming SSH_UPDATE_HOSTKEYS_ASK didn't * cancel the operation). */ for (i = 0; i < options.num_user_hostfiles; i++) { /* * NB. keys are only added to hostfiles[0], for the rest we * just delete the hostname entries. */ if (stat(options.user_hostfiles[i], &sb) != 0) { if (errno == ENOENT) { debug_f("known hosts file %s does not " "exist", options.user_hostfiles[i]); } else { error_f("known hosts file %s " "inaccessible: %s", options.user_hostfiles[i], strerror(errno)); } continue; } if ((r = hostfile_replace_entries(options.user_hostfiles[i], ctx->host_str, ctx->ip_str, i == 0 ? ctx->keys : NULL, i == 0 ? ctx->nkeys : 0, options.hash_known_hosts, 0, options.fingerprint_hash)) != 0) { error_fr(r, "hostfile_replace_entries failed for %s", options.user_hostfiles[i]); } } } static void client_global_hostkeys_prove_confirm(struct ssh *ssh, int type, u_int32_t seq, void *_ctx) { struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx; size_t i, ndone; struct sshbuf *signdata; int r, plaintype; const u_char *sig; const char *rsa_kexalg = NULL; char *alg = NULL; size_t siglen; if (ctx->nnew == 0) fatal_f("ctx->nnew == 0"); /* sanity */ if (type != SSH2_MSG_REQUEST_SUCCESS) { error("Server failed to confirm ownership of " "private host keys"); hostkeys_update_ctx_free(ctx); return; } if (sshkey_type_plain(sshkey_type_from_name( ssh->kex->hostkey_alg)) == KEY_RSA) rsa_kexalg = ssh->kex->hostkey_alg; if ((signdata = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* * Expect a signature for each of the ctx->nnew private keys we * haven't seen before. They will be in the same order as the * ctx->keys where the corresponding ctx->keys_match[i] == 0. */ for (ndone = i = 0; i < ctx->nkeys; i++) { if (ctx->keys_match[i]) continue; plaintype = sshkey_type_plain(ctx->keys[i]->type); /* Prepare data to be signed: session ID, unique string, key */ sshbuf_reset(signdata); if ( (r = sshbuf_put_cstring(signdata, "hostkeys-prove-00@openssh.com")) != 0 || (r = sshbuf_put_stringb(signdata, ssh->kex->session_id)) != 0 || (r = sshkey_puts(ctx->keys[i], signdata)) != 0) fatal_fr(r, "compose signdata"); /* Extract and verify signature */ if ((r = sshpkt_get_string_direct(ssh, &sig, &siglen)) != 0) { error_fr(r, "parse sig"); goto out; } if ((r = sshkey_get_sigtype(sig, siglen, &alg)) != 0) { error_fr(r, "server gave unintelligible signature " "for %s key %zu", sshkey_type(ctx->keys[i]), i); goto out; } /* * Special case for RSA keys: if a RSA hostkey was negotiated, * then use its signature type for verification of RSA hostkey * proofs. Otherwise, accept only RSA-SHA256/512 signatures. */ if (plaintype == KEY_RSA && rsa_kexalg == NULL && match_pattern_list(alg, HOSTKEY_PROOF_RSA_ALGS, 0) != 1) { debug_f("server used untrusted RSA signature algorithm " "%s for key %zu, disregarding", alg, i); free(alg); /* zap the key from the list */ sshkey_free(ctx->keys[i]); ctx->keys[i] = NULL; ndone++; continue; } debug3_f("verify %s key %zu using sigalg %s", sshkey_type(ctx->keys[i]), i, alg); free(alg); if ((r = sshkey_verify(ctx->keys[i], sig, siglen, sshbuf_ptr(signdata), sshbuf_len(signdata), plaintype == KEY_RSA ? rsa_kexalg : NULL, 0, NULL)) != 0) { error_fr(r, "server gave bad signature for %s key %zu", sshkey_type(ctx->keys[i]), i); goto out; } /* Key is good. Mark it as 'seen' */ ctx->keys_verified[i] = 1; ndone++; } /* Shouldn't happen */ if (ndone != ctx->nnew) fatal_f("ndone != ctx->nnew (%zu / %zu)", ndone, ctx->nnew); if ((r = sshpkt_get_end(ssh)) != 0) { error_f("protocol error"); goto out; } /* Make the edits to known_hosts */ update_known_hosts(ctx); out: hostkeys_update_ctx_free(ctx); hostkeys_update_complete = 1; client_repledge(); } /* * Returns non-zero if the key is accepted by HostkeyAlgorithms. * Made slightly less trivial by the multiple RSA signature algorithm names. */ static int key_accepted_by_hostkeyalgs(const struct sshkey *key) { const char *ktype = sshkey_ssh_name(key); const char *hostkeyalgs = options.hostkeyalgorithms; if (key->type == KEY_UNSPEC) return 0; if (key->type == KEY_RSA && (match_pattern_list("rsa-sha2-256", hostkeyalgs, 0) == 1 || match_pattern_list("rsa-sha2-512", hostkeyalgs, 0) == 1)) return 1; return match_pattern_list(ktype, hostkeyalgs, 0) == 1; } /* * Handle hostkeys-00@openssh.com global request to inform the client of all * the server's hostkeys. The keys are checked against the user's * HostkeyAlgorithms preference before they are accepted. */ static int client_input_hostkeys(struct ssh *ssh) { const u_char *blob = NULL; size_t i, len = 0; struct sshbuf *buf = NULL; struct sshkey *key = NULL, **tmp; int r, prove_sent = 0; char *fp; static int hostkeys_seen = 0; /* XXX use struct ssh */ extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */ struct hostkeys_update_ctx *ctx = NULL; u_int want; if (hostkeys_seen) fatal_f("server already sent hostkeys"); if (!can_update_hostkeys()) return 1; hostkeys_seen = 1; ctx = xcalloc(1, sizeof(*ctx)); while (ssh_packet_remaining(ssh) > 0) { sshkey_free(key); key = NULL; if ((r = sshpkt_get_string_direct(ssh, &blob, &len)) != 0) { error_fr(r, "parse key"); goto out; } if ((r = sshkey_from_blob(blob, len, &key)) != 0) { do_log2_fr(r, r == SSH_ERR_KEY_TYPE_UNKNOWN ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_ERROR, "convert key"); continue; } fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); debug3_f("received %s key %s", sshkey_type(key), fp); free(fp); if (!key_accepted_by_hostkeyalgs(key)) { debug3_f("%s key not permitted by " "HostkeyAlgorithms", sshkey_ssh_name(key)); continue; } /* Skip certs */ if (sshkey_is_cert(key)) { debug3_f("%s key is a certificate; skipping", sshkey_ssh_name(key)); continue; } /* Ensure keys are unique */ for (i = 0; i < ctx->nkeys; i++) { if (sshkey_equal(key, ctx->keys[i])) { error_f("received duplicated %s host key", sshkey_ssh_name(key)); goto out; } } /* Key is good, record it */ if ((tmp = recallocarray(ctx->keys, ctx->nkeys, ctx->nkeys + 1, sizeof(*ctx->keys))) == NULL) fatal_f("recallocarray failed nkeys = %zu", ctx->nkeys); ctx->keys = tmp; ctx->keys[ctx->nkeys++] = key; key = NULL; } if (ctx->nkeys == 0) { debug_f("server sent no hostkeys"); goto out; } if ((ctx->keys_match = calloc(ctx->nkeys, sizeof(*ctx->keys_match))) == NULL || (ctx->keys_verified = calloc(ctx->nkeys, sizeof(*ctx->keys_verified))) == NULL) fatal_f("calloc failed"); get_hostfile_hostname_ipaddr(host, options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL, options.port, &ctx->host_str, options.check_host_ip ? &ctx->ip_str : NULL); /* Find which keys we already know about. */ for (i = 0; i < options.num_user_hostfiles; i++) { debug_f("searching %s for %s / %s", options.user_hostfiles[i], ctx->host_str, ctx->ip_str ? ctx->ip_str : "(none)"); if ((r = hostkeys_foreach(options.user_hostfiles[i], hostkeys_find, ctx, ctx->host_str, ctx->ip_str, HKF_WANT_PARSE_KEY, 0)) != 0) { if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { debug_f("hostkeys file %s does not exist", options.user_hostfiles[i]); continue; } error_fr(r, "hostkeys_foreach failed for %s", options.user_hostfiles[i]); goto out; } } /* Figure out if we have any new keys to add */ ctx->nnew = ctx->nincomplete = 0; want = HKF_MATCH_HOST | ( options.check_host_ip ? HKF_MATCH_IP : 0); for (i = 0; i < ctx->nkeys; i++) { if (ctx->keys_match[i] == 0) ctx->nnew++; if ((ctx->keys_match[i] & want) != want) ctx->nincomplete++; } debug3_f("%zu server keys: %zu new, %zu retained, " "%zu incomplete match. %zu to remove", ctx->nkeys, ctx->nnew, ctx->nkeys - ctx->nnew - ctx->nincomplete, ctx->nincomplete, ctx->nold); if (ctx->nnew == 0 && ctx->nold == 0) { debug_f("no new or deprecated keys from server"); goto out; } /* Various reasons why we cannot proceed with the update */ if (ctx->complex_hostspec) { debug_f("CA/revocation marker, manual host list or wildcard " "host pattern found, skipping UserKnownHostsFile update"); goto out; } if (ctx->other_name_seen) { debug_f("host key found matching a different name/address, " "skipping UserKnownHostsFile update"); goto out; } /* * If removing keys, check whether they appear under different * names/addresses and refuse to proceed if they do. This avoids * cases such as hosts with multiple names becoming inconsistent * with regards to CheckHostIP entries. * XXX UpdateHostkeys=force to override this (and other) checks? */ if (ctx->nold != 0) { if (check_old_keys_othernames(ctx) != 0) goto out; /* error already logged */ if (ctx->old_key_seen) { debug_f("key(s) for %s%s%s exist under other names; " "skipping UserKnownHostsFile update", ctx->host_str, ctx->ip_str == NULL ? "" : ",", ctx->ip_str == NULL ? "" : ctx->ip_str); goto out; } } if (ctx->nnew == 0) { /* * We have some keys to remove or fix matching for. * We can proceed to do this without requiring a fresh proof * from the server. */ update_known_hosts(ctx); goto out; } /* * We have received previously-unseen keys from the server. * Ask the server to confirm ownership of the private halves. */ debug3_f("asking server to prove ownership for %zu keys", ctx->nnew); if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "hostkeys-prove-00@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */ fatal_fr(r, "prepare hostkeys-prove"); if ((buf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); for (i = 0; i < ctx->nkeys; i++) { if (ctx->keys_match[i]) continue; sshbuf_reset(buf); if ((r = sshkey_putb(ctx->keys[i], buf)) != 0 || (r = sshpkt_put_stringb(ssh, buf)) != 0) fatal_fr(r, "assemble hostkeys-prove"); } if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send hostkeys-prove"); client_register_global_confirm( client_global_hostkeys_prove_confirm, ctx); ctx = NULL; /* will be freed in callback */ prove_sent = 1; /* Success */ out: hostkeys_update_ctx_free(ctx); sshkey_free(key); sshbuf_free(buf); if (!prove_sent) { /* UpdateHostkeys handling completed */ hostkeys_update_complete = 1; client_repledge(); } /* * NB. Return success for all cases. The server doesn't need to know * what the client does with its hosts file. */ return 1; } static int client_input_global_request(int type, u_int32_t seq, struct ssh *ssh) { char *rtype; u_char want_reply; int r, success = 0; if ((r = sshpkt_get_cstring(ssh, &rtype, NULL)) != 0 || (r = sshpkt_get_u8(ssh, &want_reply)) != 0) goto out; debug("client_input_global_request: rtype %s want_reply %d", rtype, want_reply); if (strcmp(rtype, "hostkeys-00@openssh.com") == 0) success = client_input_hostkeys(ssh); if (want_reply) { if ((r = sshpkt_start(ssh, success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE)) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) goto out; } r = 0; out: free(rtype); return r; } static void client_send_env(struct ssh *ssh, int id, const char *name, const char *val) { int r; debug("channel %d: setting env %s = \"%s\"", id, name, val); channel_request_start(ssh, id, "env", 0); if ((r = sshpkt_put_cstring(ssh, name)) != 0 || (r = sshpkt_put_cstring(ssh, val)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send setenv"); } void client_session2_setup(struct ssh *ssh, int id, int want_tty, int want_subsystem, const char *term, struct termios *tiop, int in_fd, struct sshbuf *cmd, char **env) { size_t i, j, len; int matched, r; char *name, *val; Channel *c = NULL; debug2_f("id %d", id); if ((c = channel_lookup(ssh, id)) == NULL) fatal_f("channel %d: unknown channel", id); ssh_packet_set_interactive(ssh, want_tty, options.ip_qos_interactive, options.ip_qos_bulk); if (want_tty) { struct winsize ws; /* Store window size in the packet. */ if (ioctl(in_fd, TIOCGWINSZ, &ws) == -1) memset(&ws, 0, sizeof(ws)); channel_request_start(ssh, id, "pty-req", 1); client_expect_confirm(ssh, id, "PTY allocation", CONFIRM_TTY); if ((r = sshpkt_put_cstring(ssh, term != NULL ? term : "")) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_col)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_row)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_xpixel)) != 0 || (r = sshpkt_put_u32(ssh, (u_int)ws.ws_ypixel)) != 0) fatal_fr(r, "build pty-req"); if (tiop == NULL) tiop = get_saved_tio(); ssh_tty_make_modes(ssh, -1, tiop); if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send pty-req"); /* XXX wait for reply */ c->client_tty = 1; } /* Transfer any environment variables from client to server */ if (options.num_send_env != 0 && env != NULL) { debug("Sending environment."); for (i = 0; env[i] != NULL; i++) { /* Split */ name = xstrdup(env[i]); if ((val = strchr(name, '=')) == NULL) { free(name); continue; } *val++ = '\0'; matched = 0; for (j = 0; j < options.num_send_env; j++) { if (match_pattern(name, options.send_env[j])) { matched = 1; break; } } if (!matched) { debug3("Ignored env %s", name); free(name); continue; } client_send_env(ssh, id, name, val); free(name); } } for (i = 0; i < options.num_setenv; i++) { /* Split */ name = xstrdup(options.setenv[i]); if ((val = strchr(name, '=')) == NULL) { free(name); continue; } *val++ = '\0'; client_send_env(ssh, id, name, val); free(name); } len = sshbuf_len(cmd); if (len > 0) { if (len > 900) len = 900; if (want_subsystem) { debug("Sending subsystem: %.*s", (int)len, (const u_char*)sshbuf_ptr(cmd)); channel_request_start(ssh, id, "subsystem", 1); client_expect_confirm(ssh, id, "subsystem", CONFIRM_CLOSE); } else { debug("Sending command: %.*s", (int)len, (const u_char*)sshbuf_ptr(cmd)); channel_request_start(ssh, id, "exec", 1); client_expect_confirm(ssh, id, "exec", CONFIRM_CLOSE); } if ((r = sshpkt_put_stringb(ssh, cmd)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send command"); } else { channel_request_start(ssh, id, "shell", 1); client_expect_confirm(ssh, id, "shell", CONFIRM_CLOSE); if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send shell"); } session_setup_complete = 1; client_repledge(); } static void client_init_dispatch(struct ssh *ssh) { ssh_dispatch_init(ssh, &dispatch_protocol_error); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_DATA, &channel_input_data); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_OPEN, &client_input_channel_open); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_REQUEST, &client_input_channel_req); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_SUCCESS, &channel_input_status_confirm); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_FAILURE, &channel_input_status_confirm); ssh_dispatch_set(ssh, SSH2_MSG_GLOBAL_REQUEST, &client_input_global_request); /* rekeying */ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); /* global request reply messages */ ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_FAILURE, &client_global_request_reply); ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_SUCCESS, &client_global_request_reply); } void client_stop_mux(void) { if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); /* * If we are in persist mode, or don't have a shell, signal that we * should close when all active channels are closed. */ if (options.control_persist || options.session_type == SESSION_TYPE_NONE) { session_closed = 1; setproctitle("[stopped mux]"); } } /* client specific fatal cleanup */ void cleanup_exit(int i) { leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE); if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); ssh_kill_proxy_command(); _exit(i); } diff --git a/crypto/openssh/configure.ac b/crypto/openssh/configure.ac index 7034ebe9bf14..58a40dd174b5 100644 --- a/crypto/openssh/configure.ac +++ b/crypto/openssh/configure.ac @@ -1,5723 +1,5729 @@ # # Copyright (c) 1999-2004 Damien Miller # # 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. AC_INIT([OpenSSH], [Portable], [openssh-unix-dev@mindrot.org]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([ssh.c]) # Check for stale configure as early as possible. for i in $srcdir/configure.ac $srcdir/m4/*.m4; do if test "$i" -nt "$srcdir/configure"; then AC_MSG_ERROR([$i newer than configure, run autoreconf]) fi done AC_LANG([C]) AC_CONFIG_HEADERS([config.h]) AC_PROG_CC([cc gcc clang]) # XXX relax this after reimplementing logit() etc. AC_MSG_CHECKING([if $CC supports C99-style variadic macros]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ int f(int a, int b, int c) { return a + b + c; } #define F(a, ...) f(a, __VA_ARGS__) ]], [[return F(1, 2, -3);]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_ERROR([*** OpenSSH requires support for C99-style variadic macros]) ] ) AC_CANONICAL_HOST AC_C_BIGENDIAN # Checks for programs. AC_PROG_AWK AC_PROG_CPP AC_PROG_RANLIB AC_PROG_INSTALL AC_PROG_EGREP AC_PROG_MKDIR_P AC_CHECK_TOOLS([AR], [ar]) AC_PATH_PROG([CAT], [cat]) AC_PATH_PROG([KILL], [kill]) AC_PATH_PROG([SED], [sed]) AC_PATH_PROG([TEST_MINUS_S_SH], [bash]) AC_PATH_PROG([TEST_MINUS_S_SH], [ksh]) AC_PATH_PROG([TEST_MINUS_S_SH], [sh]) AC_PATH_PROG([SH], [bash]) AC_PATH_PROG([SH], [ksh]) AC_PATH_PROG([SH], [sh]) AC_PATH_PROG([GROFF], [groff]) AC_PATH_PROG([NROFF], [nroff awf]) AC_PATH_PROG([MANDOC], [mandoc]) AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no]) AC_SUBST([TEST_SHELL], [sh]) dnl select manpage formatter to be used to build "cat" format pages. if test "x$MANDOC" != "x" ; then MANFMT="$MANDOC" elif test "x$NROFF" != "x" ; then MANFMT="$NROFF -mandoc" elif test "x$GROFF" != "x" ; then MANFMT="$GROFF -mandoc -Tascii" else AC_MSG_WARN([no manpage formatter found]) MANFMT="false" fi AC_SUBST([MANFMT]) dnl for buildpkg.sh AC_PATH_PROG([PATH_GROUPADD_PROG], [groupadd], [groupadd], [/usr/sbin${PATH_SEPARATOR}/etc]) AC_PATH_PROG([PATH_USERADD_PROG], [useradd], [useradd], [/usr/sbin${PATH_SEPARATOR}/etc]) AC_CHECK_PROG([MAKE_PACKAGE_SUPPORTED], [pkgmk], [yes], [no]) if test -x /sbin/sh; then AC_SUBST([STARTUP_SCRIPT_SHELL], [/sbin/sh]) else AC_SUBST([STARTUP_SCRIPT_SHELL], [/bin/sh]) fi # System features AC_SYS_LARGEFILE if test -z "$AR" ; then AC_MSG_ERROR([*** 'ar' missing, please install or fix your \$PATH ***]) fi AC_PATH_PROG([PATH_PASSWD_PROG], [passwd]) if test ! -z "$PATH_PASSWD_PROG" ; then AC_DEFINE_UNQUOTED([_PATH_PASSWD_PROG], ["$PATH_PASSWD_PROG"], [Full path of your "passwd" program]) fi dnl Since autoconf doesn't support it very well, we no longer allow users to dnl override LD, however keeping the hook here for now in case there's a use dnl use case we overlooked and someone needs to re-enable it. Unless a good dnl reason is found we'll be removing this in future. LD="$CC" AC_SUBST([LD]) AC_C_INLINE AC_CHECK_DECL([LLONG_MAX], [have_llong_max=1], , [#include ]) AC_CHECK_DECL([LONG_LONG_MAX], [have_long_long_max=1], , [#include ]) AC_CHECK_DECL([SYSTR_POLICY_KILL], [have_systr_policy_kill=1], , [ #include #include #include ]) AC_CHECK_DECL([RLIMIT_NPROC], [AC_DEFINE([HAVE_RLIMIT_NPROC], [], [sys/resource.h has RLIMIT_NPROC])], , [ #include #include ]) AC_CHECK_DECL([PR_SET_NO_NEW_PRIVS], [have_linux_no_new_privs=1], , [ #include #include ]) openssl=yes openssl_bin=openssl AC_ARG_WITH([openssl], [ --without-openssl Disable use of OpenSSL; use only limited internal crypto **EXPERIMENTAL** ], [ if test "x$withval" = "xno" ; then openssl=no openssl_bin="" fi ] ) AC_MSG_CHECKING([whether OpenSSL will be used for cryptography]) if test "x$openssl" = "xyes" ; then AC_MSG_RESULT([yes]) AC_DEFINE_UNQUOTED([WITH_OPENSSL], [1], [use libcrypto for cryptography]) else AC_MSG_RESULT([no]) fi use_stack_protector=1 use_toolchain_hardening=1 AC_ARG_WITH([stackprotect], [ --without-stackprotect Don't use compiler's stack protection], [ if test "x$withval" = "xno"; then use_stack_protector=0 fi ]) AC_ARG_WITH([hardening], [ --without-hardening Don't use toolchain hardening flags], [ if test "x$withval" = "xno"; then use_toolchain_hardening=0 fi ]) # We use -Werror for the tests only so that we catch warnings like "this is # on by default" for things like -fPIE. AC_MSG_CHECKING([if $CC supports -Werror]) saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Werror" AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int main(void) { return 0; }]])], [ AC_MSG_RESULT([yes]) WERROR="-Werror"], [ AC_MSG_RESULT([no]) WERROR="" ] ) CFLAGS="$saved_CFLAGS" if test "$GCC" = "yes" || test "$GCC" = "egcs"; then AC_MSG_CHECKING([gcc version]) GCC_VER=`$CC -v 2>&1 | $AWK '/gcc version /{print $3}'` case "$GCC_VER" in 1.*) no_attrib_nonnull=1 ;; 2.8* | 2.9*) no_attrib_nonnull=1 ;; 2.*) no_attrib_nonnull=1 ;; *) ;; esac AC_MSG_RESULT([$GCC_VER]) AC_MSG_CHECKING([clang version]) - CLANG_VER=`$CC -v 2>&1 | $AWK '/clang version /{print $3}'` + ver="`$CC -v 2>&1`" + if echo "$ver" | grep "Apple" >/dev/null; then + CLANG_VER="apple-`echo "$ver" | \ + awk '/Apple LLVM/ {print $4"-"$5}'`" + else + CLANG_VER=`echo "$ver" | $AWK '/clang version /{print $3}'` + fi AC_MSG_RESULT([$CLANG_VER]) OSSH_CHECK_CFLAG_COMPILE([-pipe]) OSSH_CHECK_CFLAG_COMPILE([-Wunknown-warning-option]) OSSH_CHECK_CFLAG_COMPILE([-Wno-error=format-truncation]) OSSH_CHECK_CFLAG_COMPILE([-Qunused-arguments]) OSSH_CHECK_CFLAG_COMPILE([-Wall]) OSSH_CHECK_CFLAG_COMPILE([-Wextra]) OSSH_CHECK_CFLAG_COMPILE([-Wpointer-arith]) OSSH_CHECK_CFLAG_COMPILE([-Wuninitialized]) OSSH_CHECK_CFLAG_COMPILE([-Wsign-compare]) OSSH_CHECK_CFLAG_COMPILE([-Wformat-security]) OSSH_CHECK_CFLAG_COMPILE([-Wsizeof-pointer-memaccess]) OSSH_CHECK_CFLAG_COMPILE([-Wpointer-sign], [-Wno-pointer-sign]) OSSH_CHECK_CFLAG_COMPILE([-Wunused-parameter], [-Wno-unused-parameter]) OSSH_CHECK_CFLAG_COMPILE([-Wunused-result], [-Wno-unused-result]) OSSH_CHECK_CFLAG_COMPILE([-Wimplicit-fallthrough]) OSSH_CHECK_CFLAG_COMPILE([-Wmisleading-indentation]) OSSH_CHECK_CFLAG_COMPILE([-Wbitwise-instead-of-logical]) OSSH_CHECK_CFLAG_COMPILE([-fno-strict-aliasing]) if test "x$use_toolchain_hardening" = "x1"; then OSSH_CHECK_CFLAG_COMPILE([-mretpoline]) # clang OSSH_CHECK_LDFLAG_LINK([-Wl,-z,retpolineplt]) OSSH_CHECK_CFLAG_COMPILE([-D_FORTIFY_SOURCE=2]) OSSH_CHECK_LDFLAG_LINK([-Wl,-z,relro]) OSSH_CHECK_LDFLAG_LINK([-Wl,-z,now]) OSSH_CHECK_LDFLAG_LINK([-Wl,-z,noexecstack]) # NB. -ftrapv expects certain support functions to be present in # the compiler library (libgcc or similar) to detect integer operations # that can overflow. We must check that the result of enabling it # actually links. The test program compiled/linked includes a number # of integer operations that should exercise this. OSSH_CHECK_CFLAG_LINK([-ftrapv]) # clang 15 seems to have a bug in -fzero-call-used-regs=all. See # https://bugzilla.mindrot.org/show_bug.cgi?id=3475 and # https://github.com/llvm/llvm-project/issues/59242 case "$CLANG_VER" in - 15.*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=used]) ;; + 15.*|apple*) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=used]) ;; *) OSSH_CHECK_CFLAG_COMPILE([-fzero-call-used-regs=all]) ;; esac OSSH_CHECK_CFLAG_COMPILE([-ftrivial-auto-var-init=zero]) fi AC_MSG_CHECKING([if $CC accepts -fno-builtin-memset]) saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -fno-builtin-memset" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char b[10]; memset(b, 0, sizeof(b)); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) CFLAGS="$saved_CFLAGS" ] ) # -fstack-protector-all doesn't always work for some GCC versions # and/or platforms, so we test if we can. If it's not supported # on a given platform gcc will emit a warning so we use -Werror. if test "x$use_stack_protector" = "x1"; then for t in -fstack-protector-strong -fstack-protector-all \ -fstack-protector; do AC_MSG_CHECKING([if $CC supports $t]) saved_CFLAGS="$CFLAGS" saved_LDFLAGS="$LDFLAGS" CFLAGS="$CFLAGS $t -Werror" LDFLAGS="$LDFLAGS $t -Werror" AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include int func (int t) {char b[100]; snprintf(b,sizeof b,"%d",t); return t;} ]], [[ char x[256]; snprintf(x, sizeof(x), "XXX%d", func(1)); ]])], [ AC_MSG_RESULT([yes]) CFLAGS="$saved_CFLAGS $t" LDFLAGS="$saved_LDFLAGS $t" AC_MSG_CHECKING([if $t works]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include int func (int t) {char b[100]; snprintf(b,sizeof b,"%d",t); return t;} ]], [[ char x[256]; snprintf(x, sizeof(x), "XXX%d", func(1)); ]])], [ AC_MSG_RESULT([yes]) break ], [ AC_MSG_RESULT([no]) ], [ AC_MSG_WARN([cross compiling: cannot test]) break ] ) ], [ AC_MSG_RESULT([no]) ] ) CFLAGS="$saved_CFLAGS" LDFLAGS="$saved_LDFLAGS" done fi if test -z "$have_llong_max"; then # retry LLONG_MAX with -std=gnu99, needed on some Linuxes unset ac_cv_have_decl_LLONG_MAX saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -std=gnu99" AC_CHECK_DECL([LLONG_MAX], [have_llong_max=1], [CFLAGS="$saved_CFLAGS"], [#include ] ) fi fi AC_MSG_CHECKING([if compiler allows __attribute__ on return types]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[ #include __attribute__((__unused__)) static void foo(void){return;}]], [[ exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_DEFINE(NO_ATTRIBUTE_ON_RETURN_TYPE, 1, [compiler does not accept __attribute__ on return types]) ] ) AC_MSG_CHECKING([if compiler allows __attribute__ prototype args]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[ #include typedef void foo(const char *, ...) __attribute__((format(printf, 1, 2)));]], [[ exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_DEFINE(NO_ATTRIBUTE_ON_PROTOTYPE_ARGS, 1, [compiler does not accept __attribute__ on prototype args]) ] ) AC_MSG_CHECKING([if compiler supports variable length arrays]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[#include ]], [[ int i; for (i=0; i<3; i++){int a[i]; a[i-1]=0;} exit(0); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE(VARIABLE_LENGTH_ARRAYS, [1], [compiler supports variable length arrays]) ], [ AC_MSG_RESULT([no]) ] ) AC_MSG_CHECKING([if compiler accepts variable declarations after code]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[#include ]], [[ int a; a = 1; int b = 1; exit(a-b); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE(VARIABLE_DECLARATION_AFTER_CODE, [1], [compiler variable declarations after code]) ], [ AC_MSG_RESULT([no]) ] ) if test "x$no_attrib_nonnull" != "x1" ; then AC_DEFINE([HAVE_ATTRIBUTE__NONNULL__], [1], [Have attribute nonnull]) fi AC_ARG_WITH([rpath], [ --without-rpath Disable auto-added -R linker paths], [ if test "x$withval" = "xno" ; then rpath_opt="" elif test "x$withval" = "xyes" ; then rpath_opt="-R" else rpath_opt="$withval" fi ] ) # Allow user to specify flags AC_ARG_WITH([cflags], [ --with-cflags Specify additional flags to pass to compiler], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then CFLAGS="$CFLAGS $withval" fi ] ) AC_ARG_WITH([cflags-after], [ --with-cflags-after Specify additional flags to pass to compiler after configure], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then CFLAGS_AFTER="$withval" fi ] ) AC_ARG_WITH([cppflags], [ --with-cppflags Specify additional flags to pass to preprocessor] , [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then CPPFLAGS="$CPPFLAGS $withval" fi ] ) AC_ARG_WITH([ldflags], [ --with-ldflags Specify additional flags to pass to linker], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then LDFLAGS="$LDFLAGS $withval" fi ] ) AC_ARG_WITH([ldflags-after], [ --with-ldflags-after Specify additional flags to pass to linker after configure], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then LDFLAGS_AFTER="$withval" fi ] ) AC_ARG_WITH([libs], [ --with-libs Specify additional libraries to link with], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then LIBS="$LIBS $withval" fi ] ) AC_ARG_WITH([Werror], [ --with-Werror Build main code with -Werror], [ if test -n "$withval" && test "x$withval" != "xno"; then werror_flags="-Werror" if test "x${withval}" != "xyes"; then werror_flags="$withval" fi fi ] ) dnl On some old platforms, sys/stat.h requires sys/types.h, but autoconf-2.71's dnl AC_CHECK_INCLUDES_DEFAULT checks for them in the opposite order. If we dnl haven't detected it, recheck. if test "x$ac_cv_header_sys_stat_h" != "xyes"; then unset ac_cv_header_sys_stat_h AC_CHECK_HEADERS([sys/stat.h]) fi AC_CHECK_HEADERS([ \ blf.h \ bstring.h \ crypt.h \ crypto/sha2.h \ dirent.h \ endian.h \ elf.h \ err.h \ features.h \ fcntl.h \ floatingpoint.h \ fnmatch.h \ getopt.h \ glob.h \ ia.h \ iaf.h \ ifaddrs.h \ inttypes.h \ langinfo.h \ limits.h \ locale.h \ login.h \ maillock.h \ ndir.h \ net/if_tun.h \ netdb.h \ netgroup.h \ pam/pam_appl.h \ paths.h \ poll.h \ pty.h \ readpassphrase.h \ rpc/types.h \ security/pam_appl.h \ sha2.h \ shadow.h \ stddef.h \ stdint.h \ string.h \ strings.h \ sys/bitypes.h \ sys/byteorder.h \ sys/bsdtty.h \ sys/cdefs.h \ sys/dir.h \ sys/file.h \ sys/mman.h \ sys/label.h \ sys/ndir.h \ sys/param.h \ sys/poll.h \ sys/prctl.h \ sys/procctl.h \ sys/pstat.h \ sys/ptrace.h \ sys/random.h \ sys/select.h \ sys/stream.h \ sys/stropts.h \ sys/strtio.h \ sys/statvfs.h \ sys/sysmacros.h \ sys/time.h \ sys/timers.h \ sys/vfs.h \ time.h \ tmpdir.h \ ttyent.h \ ucred.h \ unistd.h \ usersec.h \ util.h \ utime.h \ utmp.h \ utmpx.h \ vis.h \ wchar.h \ ]) # On some platforms (eg SunOS4) sys/audit.h requires sys/[time|types|label.h] # to be included first. AC_CHECK_HEADERS([sys/audit.h], [], [], [ #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_LABEL_H # include #endif ]) # sys/capsicum.h requires sys/types.h AC_CHECK_HEADERS([sys/capsicum.h capsicum_helpers.h], [], [], [ #ifdef HAVE_SYS_TYPES_H # include #endif ]) AC_MSG_CHECKING([for caph_cache_tzdata]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include ]], [[caph_cache_tzdata();]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_CAPH_CACHE_TZDATA], [1], [Define if you have caph_cache_tzdata]) ], [ AC_MSG_RESULT([no]) ] ) # net/route.h requires sys/socket.h and sys/types.h. # sys/sysctl.h also requires sys/param.h AC_CHECK_HEADERS([net/route.h sys/sysctl.h], [], [], [ #ifdef HAVE_SYS_TYPES_H # include #endif #include #include ]) # lastlog.h requires sys/time.h to be included first on Solaris AC_CHECK_HEADERS([lastlog.h], [], [], [ #ifdef HAVE_SYS_TIME_H # include #endif ]) # sys/ptms.h requires sys/stream.h to be included first on Solaris AC_CHECK_HEADERS([sys/ptms.h], [], [], [ #ifdef HAVE_SYS_STREAM_H # include #endif ]) # login_cap.h requires sys/types.h on NetBSD AC_CHECK_HEADERS([login_cap.h], [], [], [ #include ]) # older BSDs need sys/param.h before sys/mount.h AC_CHECK_HEADERS([sys/mount.h], [], [], [ #include ]) # Android requires sys/socket.h to be included before sys/un.h AC_CHECK_HEADERS([sys/un.h], [], [], [ #include #include ]) # Messages for features tested for in target-specific section SIA_MSG="no" SPC_MSG="no" SP_MSG="no" SPP_MSG="no" # Support for Solaris/Illumos privileges (this test is used by both # the --with-solaris-privs option and --with-sandbox=solaris). SOLARIS_PRIVS="no" # Check for some target-specific stuff case "$host" in *-*-aix*) # Some versions of VAC won't allow macro redefinitions at # -qlanglevel=ansi, and autoconf 2.60 sometimes insists on using that # particularly with older versions of vac or xlc. # It also throws errors about null macro arguments, but these are # not fatal. AC_MSG_CHECKING([if compiler allows macro redefinitions]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[ #define testmacro foo #define testmacro bar]], [[ exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) CC="`echo $CC | sed 's/-qlanglvl\=ansi//g'`" CFLAGS="`echo $CFLAGS | sed 's/-qlanglvl\=ansi//g'`" CPPFLAGS="`echo $CPPFLAGS | sed 's/-qlanglvl\=ansi//g'`" ] ) AC_MSG_CHECKING([how to specify blibpath for linker ($LD)]) if (test -z "$blibpath"); then blibpath="/usr/lib:/lib" fi saved_LDFLAGS="$LDFLAGS" if test "$GCC" = "yes"; then flags="-Wl,-blibpath: -Wl,-rpath, -blibpath:" else flags="-blibpath: -Wl,-blibpath: -Wl,-rpath," fi for tryflags in $flags ;do if (test -z "$blibflags"); then LDFLAGS="$saved_LDFLAGS $tryflags$blibpath" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [blibflags=$tryflags], []) fi done if (test -z "$blibflags"); then AC_MSG_RESULT([not found]) AC_MSG_ERROR([*** must be able to specify blibpath on AIX - check config.log]) else AC_MSG_RESULT([$blibflags]) fi LDFLAGS="$saved_LDFLAGS" dnl Check for authenticate. Might be in libs.a on older AIXes AC_CHECK_FUNC([authenticate], [AC_DEFINE([WITH_AIXAUTHENTICATE], [1], [Define if you want to enable AIX4's authenticate function])], [AC_CHECK_LIB([s], [authenticate], [ AC_DEFINE([WITH_AIXAUTHENTICATE]) LIBS="$LIBS -ls" ]) ]) dnl Check for various auth function declarations in headers. AC_CHECK_DECLS([authenticate, loginrestrictions, loginsuccess, passwdexpired, setauthdb], , , [#include ]) dnl Check if loginfailed is declared and takes 4 arguments (AIX >= 5.2) AC_CHECK_DECLS([loginfailed], [AC_MSG_CHECKING([if loginfailed takes 4 arguments]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ (void)loginfailed("user","host","tty",0); ]])], [AC_MSG_RESULT([yes]) AC_DEFINE([AIX_LOGINFAILED_4ARG], [1], [Define if your AIX loginfailed() function takes 4 arguments (AIX >= 5.2)])], [AC_MSG_RESULT([no]) ])], [], [#include ] ) AC_CHECK_FUNCS([getgrset setauthdb]) AC_CHECK_DECL([F_CLOSEM], AC_DEFINE([HAVE_FCNTL_CLOSEM], [1], [Use F_CLOSEM fcntl for closefrom]), [], [ #include #include ] ) check_for_aix_broken_getaddrinfo=1 AC_DEFINE([SETEUID_BREAKS_SETUID], [1], [Define if your platform breaks doing a seteuid before a setuid]) AC_DEFINE([BROKEN_SETREUID], [1], [Define if your setreuid() is broken]) AC_DEFINE([BROKEN_SETREGID], [1], [Define if your setregid() is broken]) dnl AIX handles lastlog as part of its login message AC_DEFINE([DISABLE_LASTLOG], [1], [Define if you don't want to use lastlog]) AC_DEFINE([LOGIN_NEEDS_UTMPX], [1], [Some systems need a utmpx entry for /bin/login to work]) AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV], [Define to a Set Process Title type if your system is supported by bsd-setproctitle.c]) AC_DEFINE([SSHPAM_CHAUTHTOK_NEEDS_RUID], [1], [AIX 5.2 and 5.3 (and presumably newer) require this]) AC_DEFINE([PTY_ZEROREAD], [1], [read(1) can return 0 for a non-closed fd]) AC_DEFINE([PLATFORM_SYS_DIR_UID], 2, [System dirs owned by bin (uid 2)]) AC_DEFINE([BROKEN_STRNDUP], 1, [strndup broken, see APAR IY61211]) AC_DEFINE([BROKEN_STRNLEN], 1, [strnlen broken, see APAR IY62551]) ;; *-*-android*) AC_DEFINE([DISABLE_UTMP], [1], [Define if you don't want to use utmp]) AC_DEFINE([DISABLE_WTMP], [1], [Define if you don't want to use wtmp]) ;; *-*-cygwin*) LIBS="$LIBS /usr/lib/textreadmode.o" AC_DEFINE([HAVE_CYGWIN], [1], [Define if you are on Cygwin]) AC_DEFINE([USE_PIPES], [1], [Use PIPES instead of a socketpair()]) AC_DEFINE([NO_UID_RESTORATION_TEST], [1], [Define to disable UID restoration test]) AC_DEFINE([DISABLE_SHADOW], [1], [Define if you want to disable shadow passwords]) AC_DEFINE([NO_X11_UNIX_SOCKETS], [1], [Define if X11 doesn't support AF_UNIX sockets on that system]) AC_DEFINE([DISABLE_FD_PASSING], [1], [Define if your platform needs to skip post auth file descriptor passing]) AC_DEFINE([SSH_IOBUFSZ], [65535], [Windows is sensitive to read buffer size]) AC_DEFINE([FILESYSTEM_NO_BACKSLASH], [1], [File names may not contain backslash characters]) # Cygwin defines optargs, optargs as declspec(dllimport) for historical # reasons which cause compile warnings, so we disable those warnings. OSSH_CHECK_CFLAG_COMPILE([-Wno-attributes]) ;; *-*-dgux*) AC_DEFINE([IP_TOS_IS_BROKEN], [1], [Define if your system choked on IP TOS setting]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) ;; *-*-darwin*) use_pie=auto AC_MSG_CHECKING([if we have working getaddrinfo]) AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include int main(void) { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) exit(0); else exit(1); } ]])], [AC_MSG_RESULT([working])], [AC_MSG_RESULT([buggy]) AC_DEFINE([BROKEN_GETADDRINFO], [1], [getaddrinfo is broken (if present)]) ], [AC_MSG_RESULT([assume it is working])]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([BROKEN_GLOB], [1], [OS X glob does not do what we expect]) AC_DEFINE_UNQUOTED([BIND_8_COMPAT], [1], [Define if your resolver libs need this for getrrsetbyname]) AC_DEFINE([SSH_TUN_FREEBSD], [1], [Open tunnel devices the FreeBSD way]) AC_DEFINE([SSH_TUN_COMPAT_AF], [1], [Use tunnel device compatibility to OpenBSD]) AC_DEFINE([SSH_TUN_PREPEND_AF], [1], [Prepend the address family to IP tunnel traffic]) m4_pattern_allow([AU_IPv]) AC_CHECK_DECL([AU_IPv4], [], AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) [#include ] AC_DEFINE([LASTLOG_WRITE_PUTUTXLINE], [1], [Define if pututxline updates lastlog too]) ) AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV], [Define to a Set Process Title type if your system is supported by bsd-setproctitle.c]) AC_CHECK_FUNCS([sandbox_init]) AC_CHECK_HEADERS([sandbox.h]) AC_CHECK_LIB([sandbox], [sandbox_apply], [ SSHDLIBS="$SSHDLIBS -lsandbox" ]) # proc_pidinfo()-based closefrom() replacement. AC_CHECK_HEADERS([libproc.h]) AC_CHECK_FUNCS([proc_pidinfo]) # poll(2) is broken for character-special devices (at least). # cf. Apple bug 3710161 (not public, but searchable) AC_DEFINE([BROKEN_POLL], [1], [System poll(2) implementation is broken]) ;; *-*-dragonfly*) SSHDLIBS="$SSHDLIBS" TEST_MALLOC_OPTIONS="AFGJPRX" ;; *-*-haiku*) LIBS="$LIBS -lbsd " CFLAGS="$CFLAGS -D_BSD_SOURCE" AC_CHECK_LIB([network], [socket]) AC_DEFINE([HAVE_U_INT64_T]) AC_DEFINE([DISABLE_UTMPX], [1], [no utmpx]) MANTYPE=man ;; *-*-hpux*) # first we define all of the options common to all HP-UX releases CPPFLAGS="$CPPFLAGS -D_HPUX_SOURCE -D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED=1" IPADDR_IN_DISPLAY=yes AC_DEFINE([USE_PIPES]) AC_DEFINE([LOGIN_NEEDS_UTMPX]) AC_DEFINE([LOCKED_PASSWD_STRING], ["*"], [String used in /etc/passwd to denote locked account]) AC_DEFINE([SPT_TYPE], [SPT_PSTAT]) AC_DEFINE([PLATFORM_SYS_DIR_UID], 2, [System dirs owned by bin (uid 2)]) maildir="/var/mail" LIBS="$LIBS -lsec" AC_CHECK_LIB([xnet], [t_error], , [AC_MSG_ERROR([*** -lxnet needed on HP-UX - check config.log ***])]) # next, we define all of the options specific to major releases case "$host" in *-*-hpux10*) if test -z "$GCC"; then CFLAGS="$CFLAGS -Ae" fi AC_DEFINE([BROKEN_GETLINE], [1], [getline is not what we expect]) ;; *-*-hpux11*) AC_DEFINE([PAM_SUN_CODEBASE], [1], [Define if you are using Solaris-derived PAM which passes pam_messages to the conversation function with an extra level of indirection]) AC_DEFINE([DISABLE_UTMP], [1], [Define if you don't want to use utmp]) AC_DEFINE([USE_BTMP], [1], [Use btmp to log bad logins]) check_for_hpux_broken_getaddrinfo=1 check_for_conflicting_getspnam=1 ;; esac # lastly, we define options specific to minor releases case "$host" in *-*-hpux10.26) AC_DEFINE([HAVE_SECUREWARE], [1], [Define if you have SecureWare-based protected password database]) disable_ptmx_check=yes LIBS="$LIBS -lsecpw" ;; esac ;; *-*-irix5*) PATH="$PATH:/usr/etc" AC_DEFINE([BROKEN_INET_NTOA], [1], [Define if you system's inet_ntoa is busted (e.g. Irix gcc issue)]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([WITH_ABBREV_NO_TTY], [1], [Define if you shouldn't strip 'tty' from your ttyname in [uw]tmp]) AC_DEFINE([LOCKED_PASSWD_STRING], ["*LK*"]) ;; *-*-irix6*) PATH="$PATH:/usr/etc" AC_DEFINE([WITH_IRIX_ARRAY], [1], [Define if you have/want arrays (cluster-wide session management, not C arrays)]) AC_DEFINE([WITH_IRIX_PROJECT], [1], [Define if you want IRIX project management]) AC_DEFINE([WITH_IRIX_AUDIT], [1], [Define if you want IRIX audit trails]) AC_CHECK_FUNC([jlimit_startjob], [AC_DEFINE([WITH_IRIX_JOBS], [1], [Define if you want IRIX kernel jobs])]) AC_DEFINE([BROKEN_INET_NTOA]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([BROKEN_UPDWTMPX], [1], [updwtmpx is broken (if present)]) AC_DEFINE([WITH_ABBREV_NO_TTY]) AC_DEFINE([LOCKED_PASSWD_STRING], ["*LK*"]) ;; *-*-k*bsd*-gnu | *-*-kopensolaris*-gnu) AC_DEFINE([PAM_TTY_KLUDGE]) AC_DEFINE([LOCKED_PASSWD_PREFIX], ["!"]) AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV]) AC_DEFINE([_PATH_BTMP], ["/var/log/btmp"], [log for bad login attempts]) AC_DEFINE([USE_BTMP], [1], [Use btmp to log bad logins]) ;; *-*-linux*) no_dev_ptmx=1 use_pie=auto check_for_openpty_ctty_bug=1 dnl Target SUSv3/POSIX.1-2001 plus BSD specifics. dnl _DEFAULT_SOURCE is the new name for _BSD_SOURCE dnl _GNU_SOURCE is needed for setres*id prototypes. CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_DEFAULT_SOURCE -D_GNU_SOURCE" AC_DEFINE([BROKEN_CLOSEFROM], [1], [broken in chroots on older kernels]) AC_DEFINE([PAM_TTY_KLUDGE], [1], [Work around problematic Linux PAM modules handling of PAM_TTY]) AC_DEFINE([LOCKED_PASSWD_PREFIX], ["!"], [String used in /etc/passwd to denote locked account]) AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV]) AC_DEFINE([LINK_OPNOTSUPP_ERRNO], [EPERM], [Define to whatever link() returns for "not supported" if it doesn't return EOPNOTSUPP.]) AC_DEFINE([_PATH_BTMP], ["/var/log/btmp"], [log for bad login attempts]) AC_DEFINE([USE_BTMP]) AC_DEFINE([LINUX_OOM_ADJUST], [1], [Adjust Linux out-of-memory killer]) inet6_default_4in6=yes case `uname -r` in 1.*|2.0.*) AC_DEFINE([BROKEN_CMSG_TYPE], [1], [Define if cmsg_type is not passed correctly]) ;; esac # tun(4) forwarding compat code AC_CHECK_HEADERS([linux/if_tun.h]) if test "x$ac_cv_header_linux_if_tun_h" = "xyes" ; then AC_DEFINE([SSH_TUN_LINUX], [1], [Open tunnel devices the Linux tun/tap way]) AC_DEFINE([SSH_TUN_COMPAT_AF], [1], [Use tunnel device compatibility to OpenBSD]) AC_DEFINE([SSH_TUN_PREPEND_AF], [1], [Prepend the address family to IP tunnel traffic]) fi AC_CHECK_HEADER([linux/if.h], AC_DEFINE([SYS_RDOMAIN_LINUX], [1], [Support routing domains using Linux VRF]), [], [ #ifdef HAVE_SYS_TYPES_H # include #endif ]) AC_CHECK_HEADERS([linux/seccomp.h linux/filter.h linux/audit.h], [], [], [#include ]) # Obtain MIPS ABI case "$host" in mips*) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if _MIPS_SIM != _ABIO32 #error #endif ]])],[mips_abi="o32"],[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if _MIPS_SIM != _ABIN32 #error #endif ]])],[mips_abi="n32"],[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if _MIPS_SIM != _ABI64 #error #endif ]])],[mips_abi="n64"],[AC_MSG_ERROR([unknown MIPS ABI]) ]) ]) ]) ;; esac AC_MSG_CHECKING([for seccomp architecture]) seccomp_audit_arch= case "$host" in x86_64-*) seccomp_audit_arch=AUDIT_ARCH_X86_64 ;; i*86-*) seccomp_audit_arch=AUDIT_ARCH_I386 ;; arm*-*) seccomp_audit_arch=AUDIT_ARCH_ARM ;; aarch64*-*) seccomp_audit_arch=AUDIT_ARCH_AARCH64 ;; s390x-*) seccomp_audit_arch=AUDIT_ARCH_S390X ;; s390-*) seccomp_audit_arch=AUDIT_ARCH_S390 ;; powerpc-*) seccomp_audit_arch=AUDIT_ARCH_PPC ;; powerpc64-*) seccomp_audit_arch=AUDIT_ARCH_PPC64 ;; powerpc64le-*) seccomp_audit_arch=AUDIT_ARCH_PPC64LE ;; mips-*) seccomp_audit_arch=AUDIT_ARCH_MIPS ;; mipsel-*) seccomp_audit_arch=AUDIT_ARCH_MIPSEL ;; mips64-*) case "$mips_abi" in "n32") seccomp_audit_arch=AUDIT_ARCH_MIPS64N32 ;; "n64") seccomp_audit_arch=AUDIT_ARCH_MIPS64 ;; esac ;; mips64el-*) case "$mips_abi" in "n32") seccomp_audit_arch=AUDIT_ARCH_MIPSEL64N32 ;; "n64") seccomp_audit_arch=AUDIT_ARCH_MIPSEL64 ;; esac ;; riscv64-*) seccomp_audit_arch=AUDIT_ARCH_RISCV64 ;; esac if test "x$seccomp_audit_arch" != "x" ; then AC_MSG_RESULT(["$seccomp_audit_arch"]) AC_DEFINE_UNQUOTED([SECCOMP_AUDIT_ARCH], [$seccomp_audit_arch], [Specify the system call convention in use]) else AC_MSG_RESULT([architecture not supported]) fi ;; *-*-minix) AC_DEFINE([SETEUID_BREAKS_SETUID]) # poll(2) seems to choke on /dev/null; "Bad file descriptor" AC_DEFINE([BROKEN_POLL], [1], [System poll(2) implementation is broken]) ;; mips-sony-bsd|mips-sony-newsos4) AC_DEFINE([NEED_SETPGRP], [1], [Need setpgrp to acquire controlling tty]) SONY=1 ;; *-*-netbsd*) if test "x$withval" != "xno" ; then rpath_opt="-R" fi CPPFLAGS="$CPPFLAGS -D_OPENBSD_SOURCE" AC_DEFINE([SSH_TUN_FREEBSD], [1], [Open tunnel devices the FreeBSD way]) AC_CHECK_HEADER([net/if_tap.h], , AC_DEFINE([SSH_TUN_NO_L2], [1], [No layer 2 tunnel support])) AC_DEFINE([SSH_TUN_PREPEND_AF], [1], [Prepend the address family to IP tunnel traffic]) TEST_MALLOC_OPTIONS="AJRX" AC_DEFINE([BROKEN_READ_COMPARISON], [1], [NetBSD read function is sometimes redirected, breaking atomicio comparisons against it]) ;; *-*-freebsd*) SKIP_DISABLE_LASTLOG_DEFINE=yes AC_DEFINE([LOCKED_PASSWD_PREFIX], ["*LOCKED*"], [Account locked with pw(1)]) AC_DEFINE([SSH_TUN_FREEBSD], [1], [Open tunnel devices the FreeBSD way]) AC_CHECK_HEADER([net/if_tap.h], , AC_DEFINE([SSH_TUN_NO_L2], [1], [No layer 2 tunnel support])) AC_DEFINE([BROKEN_GLOB], [1], [FreeBSD glob does not do what we need]) TEST_MALLOC_OPTIONS="AJRX" # Preauth crypto occasionally uses file descriptors for crypto offload # and will crash if they cannot be opened. AC_DEFINE([SANDBOX_SKIP_RLIMIT_NOFILE], [1], [define if setrlimit RLIMIT_NOFILE breaks things]) case "$host" in *-*-freebsd9.*|*-*-freebsd10.*) # Capsicum on 9 and 10 do not allow ppoll() so don't auto-enable. disable_capsicum=yes esac ;; *-*-bsdi*) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) ;; *-next-*) conf_lastlog_location="/usr/adm/lastlog" conf_utmp_location=/etc/utmp conf_wtmp_location=/usr/adm/wtmp maildir=/usr/spool/mail AC_DEFINE([HAVE_NEXT], [1], [Define if you are on NeXT]) AC_DEFINE([USE_PIPES]) AC_DEFINE([BROKEN_SAVED_UIDS], [1], [Needed for NeXT]) ;; *-*-openbsd*) use_pie=auto AC_DEFINE([HAVE_ATTRIBUTE__SENTINEL__], [1], [OpenBSD's gcc has sentinel]) AC_DEFINE([HAVE_ATTRIBUTE__BOUNDED__], [1], [OpenBSD's gcc has bounded]) AC_DEFINE([SSH_TUN_OPENBSD], [1], [Open tunnel devices the OpenBSD way]) AC_DEFINE([SYSLOG_R_SAFE_IN_SIGHAND], [1], [syslog_r function is safe to use in in a signal handler]) TEST_MALLOC_OPTIONS="AFGJPRX" ;; *-*-solaris*) if test "x$withval" != "xno" ; then rpath_opt="-R" fi AC_DEFINE([PAM_SUN_CODEBASE]) AC_DEFINE([LOGIN_NEEDS_UTMPX]) AC_DEFINE([PAM_TTY_KLUDGE]) AC_DEFINE([SSHPAM_CHAUTHTOK_NEEDS_RUID], [1], [Define if pam_chauthtok wants real uid set to the unpriv'ed user]) AC_DEFINE([LOCKED_PASSWD_STRING], ["*LK*"]) # Pushing STREAMS modules will cause sshd to acquire a controlling tty. AC_DEFINE([SSHD_ACQUIRES_CTTY], [1], [Define if sshd somehow reacquires a controlling TTY after setsid()]) AC_DEFINE([PASSWD_NEEDS_USERNAME], [1], [must supply username to passwd in case the name is longer than 8 chars]) AC_DEFINE([BROKEN_TCGETATTR_ICANON], [1], [tcgetattr with ICANON may hang]) external_path_file=/etc/default/login # hardwire lastlog location (can't detect it on some versions) conf_lastlog_location="/var/adm/lastlog" AC_MSG_CHECKING([for obsolete utmp and wtmp in solaris2.x]) sol2ver=`echo "$host"| sed -e 's/.*[[0-9]]\.//'` if test "$sol2ver" -ge 8; then AC_MSG_RESULT([yes]) AC_DEFINE([DISABLE_UTMP]) AC_DEFINE([DISABLE_WTMP], [1], [Define if you don't want to use wtmp]) else AC_MSG_RESULT([no]) fi AC_CHECK_FUNCS([setpflags]) AC_CHECK_FUNCS([setppriv]) AC_CHECK_FUNCS([priv_basicset]) AC_CHECK_HEADERS([priv.h]) AC_ARG_WITH([solaris-contracts], [ --with-solaris-contracts Enable Solaris process contracts (experimental)], [ AC_CHECK_LIB([contract], [ct_tmpl_activate], [ AC_DEFINE([USE_SOLARIS_PROCESS_CONTRACTS], [1], [Define if you have Solaris process contracts]) LIBS="$LIBS -lcontract" SPC_MSG="yes" ], ) ], ) AC_ARG_WITH([solaris-projects], [ --with-solaris-projects Enable Solaris projects (experimental)], [ AC_CHECK_LIB([project], [setproject], [ AC_DEFINE([USE_SOLARIS_PROJECTS], [1], [Define if you have Solaris projects]) LIBS="$LIBS -lproject" SP_MSG="yes" ], ) ], ) AC_ARG_WITH([solaris-privs], [ --with-solaris-privs Enable Solaris/Illumos privileges (experimental)], [ AC_MSG_CHECKING([for Solaris/Illumos privilege support]) if test "x$ac_cv_func_setppriv" = "xyes" -a \ "x$ac_cv_header_priv_h" = "xyes" ; then SOLARIS_PRIVS=yes AC_MSG_RESULT([found]) AC_DEFINE([NO_UID_RESTORATION_TEST], [1], [Define to disable UID restoration test]) AC_DEFINE([USE_SOLARIS_PRIVS], [1], [Define if you have Solaris privileges]) SPP_MSG="yes" else AC_MSG_RESULT([not found]) AC_MSG_ERROR([*** must have support for Solaris privileges to use --with-solaris-privs]) fi ], ) TEST_SHELL=$SHELL # let configure find us a capable shell ;; *-*-sunos4*) CPPFLAGS="$CPPFLAGS -DSUNOS4" AC_CHECK_FUNCS([getpwanam]) AC_DEFINE([PAM_SUN_CODEBASE]) conf_utmp_location=/etc/utmp conf_wtmp_location=/var/adm/wtmp conf_lastlog_location=/var/adm/lastlog AC_DEFINE([USE_PIPES]) AC_DEFINE([DISABLE_UTMPX], [1], [no utmpx]) ;; *-ncr-sysv*) LIBS="$LIBS -lc89" AC_DEFINE([USE_PIPES]) AC_DEFINE([SSHD_ACQUIRES_CTTY]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) ;; *-sni-sysv*) # /usr/ucblib MUST NOT be searched on ReliantUNIX AC_CHECK_LIB([dl], [dlsym], ,) # -lresolv needs to be at the end of LIBS or DNS lookups break AC_CHECK_LIB([resolv], [res_query], [ LIBS="$LIBS -lresolv" ]) IPADDR_IN_DISPLAY=yes AC_DEFINE([USE_PIPES]) AC_DEFINE([IP_TOS_IS_BROKEN]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([SSHD_ACQUIRES_CTTY]) external_path_file=/etc/default/login # /usr/ucblib/libucb.a no longer needed on ReliantUNIX # Attention: always take care to bind libsocket and libnsl before libc, # otherwise you will find lots of "SIOCGPGRP errno 22" on syslog ;; # UnixWare 1.x, UnixWare 2.x, and others based on code from Univel. *-*-sysv4.2*) AC_DEFINE([USE_PIPES]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([PASSWD_NEEDS_USERNAME], [1], [must supply username to passwd]) AC_DEFINE([LOCKED_PASSWD_STRING], ["*LK*"]) TEST_SHELL=$SHELL # let configure find us a capable shell ;; # UnixWare 7.x, OpenUNIX 8 *-*-sysv5*) CPPFLAGS="$CPPFLAGS -Dvsnprintf=_xvsnprintf -Dsnprintf=_xsnprintf" AC_DEFINE([UNIXWARE_LONG_PASSWORDS], [1], [Support passwords > 8 chars]) AC_DEFINE([USE_PIPES]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_GETADDRINFO]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([PASSWD_NEEDS_USERNAME]) AC_DEFINE([BROKEN_TCGETATTR_ICANON]) TEST_SHELL=$SHELL # let configure find us a capable shell case "$host" in *-*-sysv5SCO_SV*) # SCO OpenServer 6.x maildir=/var/spool/mail AC_DEFINE([BROKEN_UPDWTMPX]) AC_CHECK_LIB([prot], [getluid], [ LIBS="$LIBS -lprot" AC_CHECK_FUNCS([getluid setluid], , , [-lprot]) ], , ) ;; *) AC_DEFINE([LOCKED_PASSWD_STRING], ["*LK*"]) ;; esac ;; *-*-sysv*) ;; # SCO UNIX and OEM versions of SCO UNIX *-*-sco3.2v4*) AC_MSG_ERROR("This Platform is no longer supported.") ;; # SCO OpenServer 5.x *-*-sco3.2v5*) if test -z "$GCC"; then CFLAGS="$CFLAGS -belf" fi LIBS="$LIBS -lprot -lx -ltinfo -lm" no_dev_ptmx=1 AC_DEFINE([USE_PIPES]) AC_DEFINE([HAVE_SECUREWARE]) AC_DEFINE([DISABLE_SHADOW]) AC_DEFINE([DISABLE_FD_PASSING]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_GETADDRINFO]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([WITH_ABBREV_NO_TTY]) AC_DEFINE([BROKEN_UPDWTMPX]) AC_DEFINE([PASSWD_NEEDS_USERNAME]) AC_CHECK_FUNCS([getluid setluid]) MANTYPE=man TEST_SHELL=$SHELL # let configure find us a capable shell SKIP_DISABLE_LASTLOG_DEFINE=yes ;; *-dec-osf*) AC_MSG_CHECKING([for Digital Unix SIA]) no_osfsia="" AC_ARG_WITH([osfsia], [ --with-osfsia Enable Digital Unix SIA], [ if test "x$withval" = "xno" ; then AC_MSG_RESULT([disabled]) no_osfsia=1 fi ], ) if test -z "$no_osfsia" ; then if test -f /etc/sia/matrix.conf; then AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_OSF_SIA], [1], [Define if you have Digital Unix Security Integration Architecture]) AC_DEFINE([DISABLE_LOGIN], [1], [Define if you don't want to use your system's login() call]) AC_DEFINE([DISABLE_FD_PASSING]) LIBS="$LIBS -lsecurity -ldb -lm -laud" SIA_MSG="yes" else AC_MSG_RESULT([no]) AC_DEFINE([LOCKED_PASSWD_SUBSTR], ["Nologin"], [String used in /etc/passwd to denote locked account]) fi fi AC_DEFINE([BROKEN_GETADDRINFO]) AC_DEFINE([SETEUID_BREAKS_SETUID]) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([BROKEN_READV_COMPARISON], [1], [Can't do comparisons on readv]) ;; *-*-nto-qnx*) AC_DEFINE([USE_PIPES]) AC_DEFINE([NO_X11_UNIX_SOCKETS]) AC_DEFINE([DISABLE_LASTLOG]) AC_DEFINE([SSHD_ACQUIRES_CTTY]) AC_DEFINE([BROKEN_SHADOW_EXPIRE], [1], [QNX shadow support is broken]) enable_etc_default_login=no # has incompatible /etc/default/login case "$host" in *-*-nto-qnx6*) AC_DEFINE([DISABLE_FD_PASSING]) ;; esac ;; *-*-ultrix*) AC_DEFINE([BROKEN_GETGROUPS], [1], [getgroups(0,NULL) will return -1]) AC_DEFINE([NEED_SETPGRP], [1], [Need setpgrp to for controlling tty]) AC_DEFINE([HAVE_SYS_SYSLOG_H], [1], [Force use of sys/syslog.h on Ultrix]) AC_DEFINE([DISABLE_UTMPX], [1], [Disable utmpx]) # DISABLE_FD_PASSING so that we call setpgrp as root, otherwise we # don't get a controlling tty. AC_DEFINE([DISABLE_FD_PASSING], [1], [Need to call setpgrp as root]) # On Ultrix some headers are not protected against multiple includes, # so we create wrappers and put it where the compiler will find it. AC_MSG_WARN([creating compat wrappers for headers]) mkdir -p netinet for header in netinet/ip.h netdb.h resolv.h; do name=`echo $header | tr 'a-z/.' 'A-Z__'` cat >$header < ]], [[ exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([*** compiler cannot create working executables, check config.log ***]) ], [ AC_MSG_WARN([cross compiling: not checking compiler sanity]) ] ) dnl Checks for header files. # Checks for libraries. AC_CHECK_FUNC([setsockopt], , [AC_CHECK_LIB([socket], [setsockopt])]) dnl IRIX and Solaris 2.5.1 have dirname() in libgen AC_CHECK_FUNCS([dirname], [AC_CHECK_HEADERS([libgen.h])] , [ AC_CHECK_LIB([gen], [dirname], [ AC_CACHE_CHECK([for broken dirname], ac_cv_have_broken_dirname, [ save_LIBS="$LIBS" LIBS="$LIBS -lgen" AC_RUN_IFELSE( [AC_LANG_SOURCE([[ #include #include #include int main(int argc, char **argv) { char *s, buf[32]; strncpy(buf,"/etc", 32); s = dirname(buf); if (!s || strncmp(s, "/", 32) != 0) { exit(1); } else { exit(0); } } ]])], [ ac_cv_have_broken_dirname="no" ], [ ac_cv_have_broken_dirname="yes" ], [ ac_cv_have_broken_dirname="no" ], ) LIBS="$save_LIBS" ]) if test "x$ac_cv_have_broken_dirname" = "xno" ; then LIBS="$LIBS -lgen" AC_DEFINE([HAVE_DIRNAME]) AC_CHECK_HEADERS([libgen.h]) fi ]) ]) AC_CHECK_FUNC([getspnam], , [AC_CHECK_LIB([gen], [getspnam], [LIBS="$LIBS -lgen"])]) AC_SEARCH_LIBS([basename], [gen], [AC_DEFINE([HAVE_BASENAME], [1], [Define if you have the basename function.])]) dnl zlib defaults to enabled zlib=yes AC_ARG_WITH([zlib], [ --with-zlib=PATH Use zlib in PATH], [ if test "x$withval" = "xno" ; then zlib=no elif test "x$withval" != "xyes"; then if test -d "$withval/lib"; then if test -n "${rpath_opt}"; then LDFLAGS="-L${withval}/lib ${rpath_opt}${withval}/lib ${LDFLAGS}" else LDFLAGS="-L${withval}/lib ${LDFLAGS}" fi else if test -n "${rpath_opt}"; then LDFLAGS="-L${withval} ${rpath_opt}${withval} ${LDFLAGS}" else LDFLAGS="-L${withval} ${LDFLAGS}" fi fi if test -d "$withval/include"; then CPPFLAGS="-I${withval}/include ${CPPFLAGS}" else CPPFLAGS="-I${withval} ${CPPFLAGS}" fi fi ] ) # These libraries are needed for anything that links in the channel code. CHANNELLIBS="" AC_MSG_CHECKING([for zlib]) if test "x${zlib}" = "xno"; then AC_MSG_RESULT([no]) else saved_LIBS="$LIBS" CHANNELLIBS="$CHANNELLIBS -lz" AC_MSG_RESULT([yes]) AC_DEFINE([WITH_ZLIB], [1], [Enable zlib]) AC_CHECK_HEADER([zlib.h], ,[AC_MSG_ERROR([*** zlib.h missing - please install first or check config.log ***])]) AC_CHECK_LIB([z], [deflate], [], [ saved_CPPFLAGS="$CPPFLAGS" saved_LDFLAGS="$LDFLAGS" dnl Check default zlib install dir if test -n "${rpath_opt}"; then LDFLAGS="-L/usr/local/lib ${rpath_opt}/usr/local/lib ${saved_LDFLAGS}" else LDFLAGS="-L/usr/local/lib ${saved_LDFLAGS}" fi CPPFLAGS="-I/usr/local/include ${saved_CPPFLAGS}" AC_TRY_LINK_FUNC([deflate], [AC_DEFINE([HAVE_LIBZ])], [ AC_MSG_ERROR([*** zlib missing - please install first or check config.log ***]) ] ) ] ) AC_ARG_WITH([zlib-version-check], [ --without-zlib-version-check Disable zlib version check], [ if test "x$withval" = "xno" ; then zlib_check_nonfatal=1 fi ] ) AC_MSG_CHECKING([for possibly buggy zlib]) AC_RUN_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]], [[ int a=0, b=0, c=0, d=0, n, v; n = sscanf(ZLIB_VERSION, "%d.%d.%d.%d", &a, &b, &c, &d); - if (n != 3 && n != 4) + if (n < 1) exit(1); v = a*1000000 + b*10000 + c*100 + d; fprintf(stderr, "found zlib version %s (%d)\n", ZLIB_VERSION, v); /* 1.1.4 is OK */ if (a == 1 && b == 1 && c >= 4) exit(0); /* 1.2.3 and up are OK */ if (v >= 1020300) exit(0); exit(2); ]])], AC_MSG_RESULT([no]), [ AC_MSG_RESULT([yes]) if test -z "$zlib_check_nonfatal" ; then AC_MSG_ERROR([*** zlib too old - check config.log *** Your reported zlib version has known security problems. It's possible your vendor has fixed these problems without changing the version number. If you are sure this is the case, you can disable the check by running "./configure --without-zlib-version-check". If you are in doubt, upgrade zlib to version 1.2.3 or greater. See http://www.gzip.org/zlib/ for details.]) else AC_MSG_WARN([zlib version may have security problems]) fi ], [ AC_MSG_WARN([cross compiling: not checking zlib version]) ] ) LIBS="$saved_LIBS" fi dnl UnixWare 2.x AC_CHECK_FUNC([strcasecmp], [], [ AC_CHECK_LIB([resolv], [strcasecmp], [LIBS="$LIBS -lresolv"]) ] ) AC_CHECK_FUNCS([utimes], [], [ AC_CHECK_LIB([c89], [utimes], [AC_DEFINE([HAVE_UTIMES]) LIBS="$LIBS -lc89"]) ] ) dnl Checks for libutil functions AC_CHECK_HEADERS([bsd/libutil.h libutil.h]) AC_SEARCH_LIBS([fmt_scaled], [util bsd]) AC_SEARCH_LIBS([scan_scaled], [util bsd]) AC_SEARCH_LIBS([login], [util bsd]) AC_SEARCH_LIBS([logout], [util bsd]) AC_SEARCH_LIBS([logwtmp], [util bsd]) AC_SEARCH_LIBS([openpty], [util bsd]) AC_SEARCH_LIBS([updwtmp], [util bsd]) AC_CHECK_FUNCS([fmt_scaled scan_scaled login logout openpty updwtmp logwtmp]) # On some platforms, inet_ntop and gethostbyname may be found in libresolv # or libnsl. AC_SEARCH_LIBS([inet_ntop], [resolv nsl]) AC_SEARCH_LIBS([gethostbyname], [resolv nsl]) # Some Linux distribtions ship the BSD libc hashing functions in # separate libraries. AC_SEARCH_LIBS([SHA256Update], [md bsd]) # "Particular Function Checks" # see https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Particular-Functions.html AC_FUNC_STRFTIME AC_FUNC_MALLOC AC_FUNC_REALLOC # autoconf doesn't have AC_FUNC_CALLOC so fake it if malloc returns NULL; AC_MSG_CHECKING([if calloc(0, N) returns non-null]) AC_RUN_IFELSE( [AC_LANG_PROGRAM( [[ #include ]], [[ void *p = calloc(0, 1); exit(p == NULL); ]] )], [ func_calloc_0_nonnull=yes ], [ func_calloc_0_nonnull=no ], [ AC_MSG_WARN([cross compiling: assuming same as malloc]) func_calloc_0_nonnull="$ac_cv_func_malloc_0_nonnull"] ) AC_MSG_RESULT([$func_calloc_0_nonnull]) if test "x$func_calloc_0_nonnull" = "xyes"; then AC_DEFINE(HAVE_CALLOC, 1, [calloc(0, x) returns non-null]) else AC_DEFINE(HAVE_CALLOC, 0, [calloc(0, x) returns NULL]) AC_DEFINE(calloc, rpl_calloc, [Define to rpl_calloc if the replacement function should be used.]) fi # Check for ALTDIRFUNC glob() extension AC_MSG_CHECKING([for GLOB_ALTDIRFUNC support]) AC_EGREP_CPP([FOUNDIT], [ #include #ifdef GLOB_ALTDIRFUNC FOUNDIT #endif ], [ AC_DEFINE([GLOB_HAS_ALTDIRFUNC], [1], [Define if your system glob() function has the GLOB_ALTDIRFUNC extension]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ] ) # Check for g.gl_matchc glob() extension AC_MSG_CHECKING([for gl_matchc field in glob_t]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ glob_t g; g.gl_matchc = 1; ]])], [ AC_DEFINE([GLOB_HAS_GL_MATCHC], [1], [Define if your system glob() function has gl_matchc options in glob_t]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) # Check for g.gl_statv glob() extension AC_MSG_CHECKING([for gl_statv and GLOB_KEEPSTAT extensions for glob]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ #ifndef GLOB_KEEPSTAT #error "glob does not support GLOB_KEEPSTAT extension" #endif glob_t g; g.gl_statv = NULL; ]])], [ AC_DEFINE([GLOB_HAS_GL_STATV], [1], [Define if your system glob() function has gl_statv options in glob_t]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) AC_CHECK_DECLS([GLOB_NOMATCH], , , [#include ]) AC_CHECK_DECL([VIS_ALL], , AC_DEFINE(BROKEN_STRNVIS, 1, [missing VIS_ALL]), [#include ]) AC_MSG_CHECKING([whether struct dirent allocates space for d_name]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]], [[ struct dirent d; exit(sizeof(d.d_name)<=sizeof(char)); ]])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_ONE_BYTE_DIRENT_D_NAME], [1], [Define if your struct dirent expects you to allocate extra space for d_name]) ], [ AC_MSG_WARN([cross compiling: assuming BROKEN_ONE_BYTE_DIRENT_D_NAME]) AC_DEFINE([BROKEN_ONE_BYTE_DIRENT_D_NAME]) ] ) AC_MSG_CHECKING([for /proc/pid/fd directory]) if test -d "/proc/$$/fd" ; then AC_DEFINE([HAVE_PROC_PID], [1], [Define if you have /proc/$pid/fd]) AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi # Check whether user wants TCP wrappers support TCPW_MSG="no" AC_ARG_WITH([tcp-wrappers], [ --with-tcp-wrappers[[=PATH]] Enable tcpwrappers support (optionally in PATH)], [ if test "x$withval" != "xno" ; then saved_LIBS="$LIBS" saved_LDFLAGS="$LDFLAGS" saved_CPPFLAGS="$CPPFLAGS" if test -n "${withval}" && \ test "x${withval}" != "xyes"; then if test -d "${withval}/lib"; then if test -n "${need_dash_r}"; then LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" else LDFLAGS="-L${withval}/lib ${LDFLAGS}" fi else if test -n "${need_dash_r}"; then LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" else LDFLAGS="-L${withval} ${LDFLAGS}" fi fi if test -d "${withval}/include"; then CPPFLAGS="-I${withval}/include ${CPPFLAGS}" else CPPFLAGS="-I${withval} ${CPPFLAGS}" fi fi LIBS="-lwrap $LIBS" AC_MSG_CHECKING([for libwrap]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include int deny_severity = 0, allow_severity = 0; ]], [[ hosts_access(0); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([LIBWRAP], [1], [Define if you want TCP Wrappers support]) SSHDLIBS="$SSHDLIBS -lwrap" TCPW_MSG="yes" ], [ AC_MSG_ERROR([*** libwrap missing]) ]) LIBS="$saved_LIBS" fi ] ) # Check whether user wants to use ldns LDNS_MSG="no" AC_ARG_WITH(ldns, [ --with-ldns[[=PATH]] Use ldns for DNSSEC support (optionally in PATH)], [ ldns="" if test "x$withval" = "xyes" ; then AC_PATH_TOOL([LDNSCONFIG], [ldns-config], [no]) if test "x$LDNSCONFIG" = "xno"; then LIBS="-lldns $LIBS" ldns=yes else LIBS="$LIBS `$LDNSCONFIG --libs`" CPPFLAGS="$CPPFLAGS `$LDNSCONFIG --cflags`" ldns=yes fi elif test "x$withval" != "xno" ; then CPPFLAGS="$CPPFLAGS -I${withval}/include" LDFLAGS="$LDFLAGS -L${withval}/lib" LIBS="-lldns $LIBS" ldns=yes fi # Verify that it works. if test "x$ldns" = "xyes" ; then AC_DEFINE(HAVE_LDNS, 1, [Define if you want ldns support]) LDNS_MSG="yes" AC_MSG_CHECKING([for ldns support]) AC_LINK_IFELSE( [AC_LANG_SOURCE([[ #include #include #ifdef HAVE_STDINT_H # include #endif #include int main(void) { ldns_status status = ldns_verify_trusted(NULL, NULL, NULL, NULL); status=LDNS_STATUS_OK; exit(0); } ]]) ], [AC_MSG_RESULT(yes)], [ AC_MSG_RESULT(no) AC_MSG_ERROR([** Incomplete or missing ldns libraries.]) ]) fi ]) # Check whether user wants libedit support LIBEDIT_MSG="no" AC_ARG_WITH([libedit], [ --with-libedit[[=PATH]] Enable libedit support for sftp], [ if test "x$withval" != "xno" ; then if test "x$withval" = "xyes" ; then if test "x$PKGCONFIG" != "xno"; then AC_MSG_CHECKING([if $PKGCONFIG knows about libedit]) if "$PKGCONFIG" libedit; then AC_MSG_RESULT([yes]) use_pkgconfig_for_libedit=yes else AC_MSG_RESULT([no]) fi fi else CPPFLAGS="$CPPFLAGS -I${withval}/include" if test -n "${rpath_opt}"; then LDFLAGS="-L${withval}/lib ${rpath_opt}${withval}/lib ${LDFLAGS}" else LDFLAGS="-L${withval}/lib ${LDFLAGS}" fi fi if test "x$use_pkgconfig_for_libedit" = "xyes"; then LIBEDIT=`$PKGCONFIG --libs libedit` CPPFLAGS="$CPPFLAGS `$PKGCONFIG --cflags libedit`" else LIBEDIT="-ledit -lcurses" fi OTHERLIBS=`echo $LIBEDIT | sed 's/-ledit//'` AC_CHECK_LIB([edit], [el_init], [ AC_DEFINE([USE_LIBEDIT], [1], [Use libedit for sftp]) LIBEDIT_MSG="yes" AC_SUBST([LIBEDIT]) ], [ AC_MSG_ERROR([libedit not found]) ], [ $OTHERLIBS ] ) AC_MSG_CHECKING([if libedit version is compatible]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[ #include #include ]], [[ int i = H_SETSIZE; el_init("", NULL, NULL, NULL); exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([libedit version is not compatible]) ] ) fi ] ) AUDIT_MODULE=none AC_ARG_WITH([audit], [ --with-audit=module Enable audit support (modules=debug,bsm,linux)], [ AC_MSG_CHECKING([for supported audit module]) case "$withval" in bsm) AC_MSG_RESULT([bsm]) AUDIT_MODULE=bsm dnl Checks for headers, libs and functions AC_CHECK_HEADERS([bsm/audit.h], [], [AC_MSG_ERROR([BSM enabled and bsm/audit.h not found])], [ #ifdef HAVE_TIME_H # include #endif ] ) AC_CHECK_LIB([bsm], [getaudit], [], [AC_MSG_ERROR([BSM enabled and required library not found])]) AC_CHECK_FUNCS([getaudit], [], [AC_MSG_ERROR([BSM enabled and required function not found])]) # These are optional AC_CHECK_FUNCS([getaudit_addr aug_get_machine]) AC_DEFINE([USE_BSM_AUDIT], [1], [Use BSM audit module]) if test "$sol2ver" -ge 11; then SSHDLIBS="$SSHDLIBS -lscf" AC_DEFINE([BROKEN_BSM_API], [1], [The system has incomplete BSM API]) fi ;; linux) AC_MSG_RESULT([linux]) AUDIT_MODULE=linux dnl Checks for headers, libs and functions AC_CHECK_HEADERS([libaudit.h]) SSHDLIBS="$SSHDLIBS -laudit" AC_DEFINE([USE_LINUX_AUDIT], [1], [Use Linux audit module]) ;; debug) AUDIT_MODULE=debug AC_MSG_RESULT([debug]) AC_DEFINE([SSH_AUDIT_EVENTS], [1], [Use audit debugging module]) ;; no) AC_MSG_RESULT([no]) ;; *) AC_MSG_ERROR([Unknown audit module $withval]) ;; esac ] ) AC_ARG_WITH([pie], [ --with-pie Build Position Independent Executables if possible], [ if test "x$withval" = "xno"; then use_pie=no fi if test "x$withval" = "xyes"; then use_pie=yes fi ] ) if test "x$use_pie" = "x"; then use_pie=no fi if test "x$use_toolchain_hardening" != "x1" && test "x$use_pie" = "xauto"; then # Turn off automatic PIE when toolchain hardening is off. use_pie=no fi if test "x$use_pie" = "xauto"; then # Automatic PIE requires gcc >= 4.x AC_MSG_CHECKING([for gcc >= 4.x]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ #if !defined(__GNUC__) || __GNUC__ < 4 #error gcc is too old #endif ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) use_pie=no ] ) fi if test "x$use_pie" != "xno"; then SAVED_CFLAGS="$CFLAGS" SAVED_LDFLAGS="$LDFLAGS" OSSH_CHECK_CFLAG_COMPILE([-fPIE]) OSSH_CHECK_LDFLAG_LINK([-pie]) # We use both -fPIE and -pie or neither. AC_MSG_CHECKING([whether both -fPIE and -pie are supported]) if echo "x $CFLAGS" | grep ' -fPIE' >/dev/null 2>&1 && \ echo "x $LDFLAGS" | grep ' -pie' >/dev/null 2>&1 ; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) CFLAGS="$SAVED_CFLAGS" LDFLAGS="$SAVED_LDFLAGS" fi fi AC_MSG_CHECKING([whether -fPIC is accepted]) SAVED_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -fPIC" AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [[ #include ]], [[ exit(0); ]] )], [AC_MSG_RESULT([yes]) PICFLAG="-fPIC"; ], [AC_MSG_RESULT([no]) PICFLAG=""; ]) CFLAGS="$SAVED_CFLAGS" AC_SUBST([PICFLAG]) dnl Checks for library functions. Please keep in alphabetical order AC_CHECK_FUNCS([ \ auth_hostok \ auth_timeok \ Blowfish_initstate \ Blowfish_expandstate \ Blowfish_expand0state \ Blowfish_stream2word \ SHA256Update \ SHA384Update \ SHA512Update \ asprintf \ b64_ntop \ __b64_ntop \ b64_pton \ __b64_pton \ bcopy \ bcrypt_pbkdf \ bindresvport_sa \ blf_enc \ bzero \ cap_rights_limit \ clock \ closefrom \ close_range \ dirfd \ endgrent \ err \ errx \ explicit_bzero \ explicit_memset \ fchmod \ fchmodat \ fchown \ fchownat \ flock \ fnmatch \ freeaddrinfo \ freezero \ fstatfs \ fstatvfs \ futimes \ getaddrinfo \ getcwd \ getentropy \ getgrouplist \ getline \ getnameinfo \ getopt \ getpagesize \ getpeereid \ getpeerucred \ getpgid \ _getpty \ getrlimit \ getrandom \ getsid \ getttyent \ glob \ group_from_gid \ inet_aton \ inet_ntoa \ inet_ntop \ innetgr \ killpg \ llabs \ localtime_r \ login_getcapbool \ login_getpwclass \ memmem \ memmove \ memset_s \ mkdtemp \ ngetaddrinfo \ nsleep \ ogetaddrinfo \ openlog_r \ pledge \ poll \ ppoll \ prctl \ procctl \ pselect \ pstat \ raise \ readpassphrase \ reallocarray \ realpath \ recvmsg \ recallocarray \ rresvport_af \ sendmsg \ setdtablesize \ setegid \ setenv \ seteuid \ setgroupent \ setgroups \ setlinebuf \ setlogin \ setpassent\ setpcred \ setproctitle \ setregid \ setreuid \ setrlimit \ setsid \ setvbuf \ sigaction \ sigvec \ snprintf \ socketpair \ statfs \ statvfs \ strcasestr \ strdup \ strerror \ strlcat \ strlcpy \ strmode \ strndup \ strnlen \ strnvis \ strptime \ strsignal \ strtonum \ strtoll \ strtoul \ strtoull \ swap32 \ sysconf \ tcgetpgrp \ timegm \ timingsafe_bcmp \ truncate \ unsetenv \ updwtmpx \ utimensat \ user_from_uid \ usleep \ vasprintf \ vsnprintf \ waitpid \ warn \ ]) AC_CHECK_DECLS([bzero, memmem]) dnl Wide character support. AC_CHECK_FUNCS([mblen mbtowc nl_langinfo wcwidth]) TEST_SSH_UTF8=${TEST_SSH_UTF8:=yes} AC_MSG_CHECKING([for utf8 locale support]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include ]], [[ char *loc = setlocale(LC_CTYPE, "en_US.UTF-8"); if (loc != NULL) exit(0); exit(1); ]])], AC_MSG_RESULT(yes), [AC_MSG_RESULT(no) TEST_SSH_UTF8=no], AC_MSG_WARN([cross compiling: assuming yes]) ) AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[ #include ]], [[ return (isblank('a')); ]])], [AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).]) ]) disable_pkcs11= AC_ARG_ENABLE([pkcs11], [ --disable-pkcs11 disable PKCS#11 support code [no]], [ if test "x$enableval" = "xno" ; then disable_pkcs11=1 fi ] ) disable_sk= AC_ARG_ENABLE([security-key], [ --disable-security-key disable U2F/FIDO support code [no]], [ if test "x$enableval" = "xno" ; then disable_sk=1 fi ] ) enable_sk_internal= AC_ARG_WITH([security-key-builtin], [ --with-security-key-builtin include builtin U2F/FIDO support], [ enable_sk_internal=$withval ] ) AC_SEARCH_LIBS([dlopen], [dl]) AC_CHECK_FUNCS([dlopen]) AC_CHECK_DECL([RTLD_NOW], [], [], [#include ]) # IRIX has a const char return value for gai_strerror() AC_CHECK_FUNCS([gai_strerror], [ AC_DEFINE([HAVE_GAI_STRERROR]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include const char *gai_strerror(int); ]], [[ char *str; str = gai_strerror(0); ]])], [ AC_DEFINE([HAVE_CONST_GAI_STRERROR_PROTO], [1], [Define if gai_strerror() returns const char *])], [])]) AC_SEARCH_LIBS([nanosleep], [rt posix4], [AC_DEFINE([HAVE_NANOSLEEP], [1], [Some systems put nanosleep outside of libc])]) AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME], [1], [Have clock_gettime])]) dnl check if we need -D_REENTRANT for localtime_r declaration. AC_CHECK_DECL([localtime_r], [], [ saved_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS -D_REENTRANT" unset ac_cv_have_decl_localtime_r AC_CHECK_DECL([localtime_r], [], [ CPPFLAGS="$saved_CPPFLAGS" ], [ #include ] ) ], [ #include ] ) dnl Make sure prototypes are defined for these before using them. AC_CHECK_DECL([strsep], [AC_CHECK_FUNCS([strsep])], [], [ #ifdef HAVE_STRING_H # include #endif ]) dnl tcsendbreak might be a macro AC_CHECK_DECL([tcsendbreak], [AC_DEFINE([HAVE_TCSENDBREAK])], [AC_CHECK_FUNCS([tcsendbreak])], [#include ] ) AC_CHECK_DECLS([h_errno], , ,[#include ]) AC_CHECK_DECLS([SHUT_RD, getpeereid], , , [ #include #include #include ]) AC_CHECK_DECLS([O_NONBLOCK], , , [ #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_FCNTL_H # include #endif ]) AC_CHECK_DECLS([ftruncate, getentropy], , , [ #include #include ]) AC_CHECK_DECLS([readv, writev], , , [ #include #include #include ]) AC_CHECK_DECLS([MAXSYMLINKS], , , [ #include ]) AC_CHECK_DECLS([offsetof], , , [ #include ]) # extra bits for select(2) AC_CHECK_DECLS([howmany, NFDBITS], [], [], [[ #include #include #ifdef HAVE_SYS_SYSMACROS_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif ]]) AC_CHECK_TYPES([fd_mask], [], [], [[ #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif ]]) AC_CHECK_FUNCS([setresuid], [ dnl Some platorms have setresuid that isn't implemented, test for this AC_MSG_CHECKING([if setresuid seems to work]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]], [[ errno=0; setresuid(0,0,0); if (errno==ENOSYS) exit(1); else exit(0); ]])], [AC_MSG_RESULT([yes])], [AC_DEFINE([BROKEN_SETRESUID], [1], [Define if your setresuid() is broken]) AC_MSG_RESULT([not implemented])], [AC_MSG_WARN([cross compiling: not checking setresuid])] ) ]) AC_CHECK_FUNCS([setresgid], [ dnl Some platorms have setresgid that isn't implemented, test for this AC_MSG_CHECKING([if setresgid seems to work]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]], [[ errno=0; setresgid(0,0,0); if (errno==ENOSYS) exit(1); else exit(0); ]])], [AC_MSG_RESULT([yes])], [AC_DEFINE([BROKEN_SETRESGID], [1], [Define if your setresgid() is broken]) AC_MSG_RESULT([not implemented])], [AC_MSG_WARN([cross compiling: not checking setresuid])] ) ]) AC_MSG_CHECKING([for working fflush(NULL)]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include ]], [[fflush(NULL); exit(0);]])], AC_MSG_RESULT([yes]), [AC_MSG_RESULT([no]) AC_DEFINE([FFLUSH_NULL_BUG], [1], [define if fflush(NULL) does not work])], AC_MSG_WARN([cross compiling: assuming working]) ) dnl Checks for time functions AC_CHECK_FUNCS([gettimeofday time]) dnl Checks for utmp functions AC_CHECK_FUNCS([endutent getutent getutid getutline pututline setutent]) AC_CHECK_FUNCS([utmpname]) dnl Checks for utmpx functions AC_CHECK_FUNCS([endutxent getutxent getutxid getutxline getutxuser pututxline]) AC_CHECK_FUNCS([setutxdb setutxent utmpxname]) dnl Checks for lastlog functions AC_CHECK_FUNCS([getlastlogxbyname]) AC_CHECK_FUNC([daemon], [AC_DEFINE([HAVE_DAEMON], [1], [Define if your libraries define daemon()])], [AC_CHECK_LIB([bsd], [daemon], [LIBS="$LIBS -lbsd"; AC_DEFINE([HAVE_DAEMON])])] ) AC_CHECK_FUNC([getpagesize], [AC_DEFINE([HAVE_GETPAGESIZE], [1], [Define if your libraries define getpagesize()])], [AC_CHECK_LIB([ucb], [getpagesize], [LIBS="$LIBS -lucb"; AC_DEFINE([HAVE_GETPAGESIZE])])] ) # Check for broken snprintf if test "x$ac_cv_func_snprintf" = "xyes" ; then AC_MSG_CHECKING([whether snprintf correctly terminates long strings]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include ]], [[ char b[5]; snprintf(b,5,"123456789"); exit(b[4]!='\0'); ]])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_SNPRINTF], [1], [Define if your snprintf is busted]) AC_MSG_WARN([****** Your snprintf() function is broken, complain to your vendor]) ], [ AC_MSG_WARN([cross compiling: Assuming working snprintf()]) ] ) fi if test "x$ac_cv_func_snprintf" = "xyes" ; then AC_MSG_CHECKING([whether snprintf understands %zu]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include ]], [[ size_t a = 1, b = 2; char z[128]; snprintf(z, sizeof z, "%zu%zu", a, b); exit(strcmp(z, "12")); ]])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_SNPRINTF], [1], [snprintf does not understand %zu]) ], [ AC_MSG_WARN([cross compiling: Assuming working snprintf()]) ] ) fi # We depend on vsnprintf returning the right thing on overflow: the # number of characters it tried to create (as per SUSv3) if test "x$ac_cv_func_vsnprintf" = "xyes" ; then AC_MSG_CHECKING([whether vsnprintf returns correct values on overflow]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include int x_snprintf(char *str, size_t count, const char *fmt, ...) { size_t ret; va_list ap; va_start(ap, fmt); ret = vsnprintf(str, count, fmt, ap); va_end(ap); return ret; } ]], [[ char x[1]; if (x_snprintf(x, 1, "%s %d", "hello", 12345) != 11) return 1; if (x_snprintf(NULL, 0, "%s %d", "hello", 12345) != 11) return 1; return 0; ]])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_SNPRINTF], [1], [Define if your snprintf is busted]) AC_MSG_WARN([****** Your vsnprintf() function is broken, complain to your vendor]) ], [ AC_MSG_WARN([cross compiling: Assuming working vsnprintf()]) ] ) fi # On systems where [v]snprintf is broken, but is declared in stdio, # check that the fmt argument is const char * or just char *. # This is only useful for when BROKEN_SNPRINTF AC_MSG_CHECKING([whether snprintf can declare const char *fmt]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include int snprintf(char *a, size_t b, const char *c, ...) { return 0; } ]], [[ snprintf(0, 0, 0); ]])], [AC_MSG_RESULT([yes]) AC_DEFINE([SNPRINTF_CONST], [const], [Define as const if snprintf() can declare const char *fmt])], [AC_MSG_RESULT([no]) AC_DEFINE([SNPRINTF_CONST], [/* not const */])]) # Check for missing getpeereid (or equiv) support NO_PEERCHECK="" if test "x$ac_cv_func_getpeereid" != "xyes" -a "x$ac_cv_func_getpeerucred" != "xyes"; then AC_MSG_CHECKING([whether system supports SO_PEERCRED getsockopt]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[int i = SO_PEERCRED;]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_SO_PEERCRED], [1], [Have PEERCRED socket option]) ], [AC_MSG_RESULT([no]) NO_PEERCHECK=1 ]) fi dnl make sure that openpty does not reacquire controlling terminal if test ! -z "$check_for_openpty_ctty_bug"; then AC_MSG_CHECKING([if openpty correctly handles controlling tty]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #ifdef HAVE_PTY_H # include #endif #include #include #include ]], [[ pid_t pid; int fd, ptyfd, ttyfd, status; pid = fork(); if (pid < 0) { /* failed */ exit(1); } else if (pid > 0) { /* parent */ waitpid(pid, &status, 0); if (WIFEXITED(status)) exit(WEXITSTATUS(status)); else exit(2); } else { /* child */ close(0); close(1); close(2); setsid(); openpty(&ptyfd, &ttyfd, NULL, NULL, NULL); fd = open("/dev/tty", O_RDWR | O_NOCTTY); if (fd >= 0) exit(3); /* Acquired ctty: broken */ else exit(0); /* Did not acquire ctty: OK */ } ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_DEFINE([SSHD_ACQUIRES_CTTY]) ], [ AC_MSG_RESULT([cross-compiling, assuming yes]) ] ) fi if test "x$ac_cv_func_getaddrinfo" = "xyes" && \ test "x$check_for_hpux_broken_getaddrinfo" = "x1"; then AC_MSG_CHECKING([if getaddrinfo seems to work]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #include #define TEST_PORT "2222" ]], [[ int err, sock; struct addrinfo *gai_ai, *ai, hints; char ntop[NI_MAXHOST], strport[NI_MAXSERV], *name = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; err = getaddrinfo(name, TEST_PORT, &hints, &gai_ai); if (err != 0) { fprintf(stderr, "getaddrinfo failed (%s)", gai_strerror(err)); exit(1); } for (ai = gai_ai; ai != NULL; ai = ai->ai_next) { if (ai->ai_family != AF_INET6) continue; err = getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV); if (err != 0) { if (err == EAI_SYSTEM) perror("getnameinfo EAI_SYSTEM"); else fprintf(stderr, "getnameinfo failed: %s\n", gai_strerror(err)); exit(2); } sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) perror("socket"); if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { if (errno == EBADF) exit(3); } } exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_GETADDRINFO]) ], [ AC_MSG_RESULT([cross-compiling, assuming yes]) ] ) fi if test "x$ac_cv_func_getaddrinfo" = "xyes" && \ test "x$check_for_aix_broken_getaddrinfo" = "x1"; then AC_MSG_CHECKING([if getaddrinfo seems to work]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #include #define TEST_PORT "2222" ]], [[ int err, sock; struct addrinfo *gai_ai, *ai, hints; char ntop[NI_MAXHOST], strport[NI_MAXSERV], *name = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; err = getaddrinfo(name, TEST_PORT, &hints, &gai_ai); if (err != 0) { fprintf(stderr, "getaddrinfo failed (%s)", gai_strerror(err)); exit(1); } for (ai = gai_ai; ai != NULL; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; err = getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV); if (ai->ai_family == AF_INET && err != 0) { perror("getnameinfo"); exit(2); } } exit(0); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([AIX_GETNAMEINFO_HACK], [1], [Define if you have a getaddrinfo that fails for the all-zeros IPv6 address]) ], [ AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_GETADDRINFO]) ], [ AC_MSG_RESULT([cross-compiling, assuming no]) ] ) fi if test "x$ac_cv_func_getaddrinfo" = "xyes"; then AC_CHECK_DECLS(AI_NUMERICSERV, , , [#include #include #include ]) fi if test "x$check_for_conflicting_getspnam" = "x1"; then AC_MSG_CHECKING([for conflicting getspnam in shadow.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ exit(0); ]])], [ AC_MSG_RESULT([no]) ], [ AC_MSG_RESULT([yes]) AC_DEFINE([GETSPNAM_CONFLICTING_DEFS], [1], [Conflicting defs for getspnam]) ] ) fi dnl NetBSD added an strnvis and unfortunately made it incompatible with the dnl existing one in OpenBSD and Linux's libbsd (the former having existed dnl for over ten years). Despite this incompatibility being reported during dnl development (see http://gnats.netbsd.org/44977) they still shipped it. dnl Even more unfortunately FreeBSD and later MacOS picked up this incompatible dnl implementation. Try to detect this mess, and assume the only safe option dnl if we're cross compiling. dnl dnl OpenBSD, 2001: strnvis(char *dst, const char *src, size_t dlen, int flag); dnl NetBSD: 2012, strnvis(char *dst, size_t dlen, const char *src, int flag); if test "x$ac_cv_func_strnvis" = "xyes"; then AC_MSG_CHECKING([for working strnvis]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include static void sighandler(int sig) { _exit(1); } ]], [[ char dst[16]; signal(SIGSEGV, sighandler); if (strnvis(dst, "src", 4, 0) && strcmp(dst, "src") == 0) exit(0); exit(1) ]])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_DEFINE([BROKEN_STRNVIS], [1], [strnvis detected broken])], [AC_MSG_WARN([cross compiling: assuming broken]) AC_DEFINE([BROKEN_STRNVIS], [1], [strnvis assumed broken])] ) fi AC_MSG_CHECKING([if SA_RESTARTed signals interrupt select()]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #ifdef HAVE_SYS_SELECT # include #endif #include #include #include #include #include static void sighandler(int sig) { } ]], [[ int r; pid_t pid; struct sigaction sa; sa.sa_handler = sighandler; sa.sa_flags = SA_RESTART; (void)sigaction(SIGTERM, &sa, NULL); if ((pid = fork()) == 0) { /* child */ pid = getppid(); sleep(1); kill(pid, SIGTERM); sleep(1); if (getppid() == pid) /* if parent did not exit, shoot it */ kill(pid, SIGKILL); exit(0); } else { /* parent */ r = select(0, NULL, NULL, NULL, NULL); } exit(r == -1 ? 0 : 1); ]])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_DEFINE([NO_SA_RESTART], [1], [SA_RESTARTed signals do no interrupt select])], [AC_MSG_WARN([cross compiling: assuming yes])] ) AC_CHECK_FUNCS([getpgrp],[ AC_MSG_CHECKING([if getpgrp accepts zero args]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[$ac_includes_default]], [[ getpgrp(); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([GETPGRP_VOID], [1], [getpgrp takes zero args])], [ AC_MSG_RESULT([no]) AC_DEFINE([GETPGRP_VOID], [0], [getpgrp takes one arg])] ) ]) # Search for OpenSSL saved_CPPFLAGS="$CPPFLAGS" saved_LDFLAGS="$LDFLAGS" openssl_bin_PATH="$PATH" AC_ARG_WITH([ssl-dir], [ --with-ssl-dir=PATH Specify path to OpenSSL installation ], [ if test "x$openssl" = "xno" ; then AC_MSG_ERROR([cannot use --with-ssl-dir when OpenSSL disabled]) fi if test "x$withval" != "xno" ; then case "$withval" in # Relative paths ./*|../*) withval="`pwd`/$withval" esac if test -d "$withval/lib"; then libcrypto_path="${withval}/lib" elif test -d "$withval/lib64"; then libcrypto_path="$withval/lib64" else # Built but not installed libcrypto_path="${withval}" fi if test -n "${rpath_opt}"; then LDFLAGS="-L${libcrypto_path} ${rpath_opt}${libcrypto_path} ${LDFLAGS}" else LDFLAGS="-L${libcrypto_path} ${LDFLAGS}" fi if test -d "$withval/include"; then CPPFLAGS="-I${withval}/include ${CPPFLAGS}" else CPPFLAGS="-I${withval} ${CPPFLAGS}" fi openssl_bin_PATH="${PATH}${PATH_SEPARATOR}${withval}/bin${PATH_SEPARATOR}${withval}/apps" fi ] ) AC_PATH_PROGS([openssl_bin], openssl, [], [$openssl_bin_PATH]) AC_SUBST(OPENSSL_BIN, [${openssl_bin}]) AC_ARG_WITH([openssl-header-check], [ --without-openssl-header-check Disable OpenSSL version consistency check], [ if test "x$withval" = "xno" ; then openssl_check_nonfatal=1 fi ] ) openssl_engine=no AC_ARG_WITH([ssl-engine], [ --with-ssl-engine Enable OpenSSL (hardware) ENGINE support ], [ if test "x$withval" != "xno" ; then if test "x$openssl" = "xno" ; then AC_MSG_ERROR([cannot use --with-ssl-engine when OpenSSL disabled]) fi openssl_engine=yes fi ] ) nocrypto_saved_LIBS="$LIBS" if test "x$openssl" = "xyes" ; then LIBS="-lcrypto $LIBS" CHANNELLIBS="-lcrypto $CHANNELLIBS" AC_TRY_LINK_FUNC([RAND_add], , [AC_MSG_ERROR([*** working libcrypto not found, check config.log])]) AC_CHECK_HEADER([openssl/opensslv.h], , [AC_MSG_ERROR([*** OpenSSL headers missing - please install first or check config.log ***])]) # Determine OpenSSL header version AC_MSG_CHECKING([OpenSSL header version]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #define DATA "conftest.sslincver" ]], [[ FILE *fd; int rc; fd = fopen(DATA,"w"); if(fd == NULL) exit(1); if ((rc = fprintf(fd, "%08lx (%s)\n", (unsigned long)OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_TEXT)) < 0) exit(1); exit(0); ]])], [ ssl_header_ver=`cat conftest.sslincver` AC_MSG_RESULT([$ssl_header_ver]) ], [ AC_MSG_RESULT([not found]) AC_MSG_ERROR([OpenSSL version header not found.]) ], [ AC_MSG_WARN([cross compiling: not checking]) ] ) # Determining OpenSSL library version is version dependent. AC_CHECK_FUNCS([OpenSSL_version OpenSSL_version_num]) # Determine OpenSSL library version AC_MSG_CHECKING([OpenSSL library version]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #define DATA "conftest.ssllibver" ]], [[ FILE *f; /* We need these legacy bits to warn for old libcrypto */ #ifndef OPENSSL_VERSION # define OPENSSL_VERSION SSLEAY_VERSION #endif #ifndef HAVE_OPENSSL_VERSION # define OpenSSL_version SSLeay_version #endif #ifndef HAVE_OPENSSL_VERSION_NUM # define OpenSSL_version_num SSLeay #endif if ((f = fopen(DATA, "w")) == NULL) exit(1); if (fprintf(f, "%08lx (%s)", (unsigned long)OpenSSL_version_num(), OpenSSL_version(OPENSSL_VERSION)) < 0) exit(1); #ifdef LIBRESSL_VERSION_NUMBER if (fprintf(f, " libressl-%08lx", LIBRESSL_VERSION_NUMBER) < 0) exit(1); #endif if (fputc('\n', f) == EOF || fclose(f) == EOF) exit(1); exit(0); ]])], [ sslver=`cat conftest.ssllibver` ssl_showver=`echo "$sslver" | sed 's/ libressl-.*//'` # Check version is supported. case "$sslver" in 100*|10100*) # 1.0.x, 1.1.0x AC_MSG_ERROR([OpenSSL >= 1.1.1 required (have "$ssl_showver")]) ;; 101*) ;; # 1.1.x 200*) # LibreSSL lver=`echo "$sslver" | sed 's/.*libressl-//'` case "$lver" in 2*|300*) # 2.x, 3.0.0 AC_MSG_ERROR([LibreSSL >= 3.1.0 required (have "$ssl_showver")]) ;; *) ;; # Assume all other versions are good. esac ;; 300*) # OpenSSL 3; we use the 1.1x API CPPFLAGS="$CPPFLAGS -DOPENSSL_API_COMPAT=0x10100000L" ;; 301*|302*) # OpenSSL development branch; request 1.1x API CPPFLAGS="$CPPFLAGS -DOPENSSL_API_COMPAT=0x10100000L" ;; *) AC_MSG_ERROR([Unknown/unsupported OpenSSL version ("$ssl_showver")]) ;; esac AC_MSG_RESULT([$ssl_showver]) ], [ AC_MSG_RESULT([not found]) AC_MSG_ERROR([OpenSSL library not found.]) ], [ AC_MSG_WARN([cross compiling: not checking]) ] ) case "$host" in x86_64-*) case "$sslver" in 3000004*) AC_MSG_ERROR([OpenSSL 3.0.4 has a potential RCE in its RSA implementation (CVE-2022-2274)]) ;; esac esac # Sanity check OpenSSL headers AC_MSG_CHECKING([whether OpenSSL's headers match the library]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include ]], [[ exit(OpenSSL_version_num() == OPENSSL_VERSION_NUMBER ? 0 : 1); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) if test "x$openssl_check_nonfatal" = "x"; then AC_MSG_ERROR([Your OpenSSL headers do not match your library. Check config.log for details. If you are sure your installation is consistent, you can disable the check by running "./configure --without-openssl-header-check". Also see contrib/findssl.sh for help identifying header/library mismatches. ]) else AC_MSG_WARN([Your OpenSSL headers do not match your library. Check config.log for details. Also see contrib/findssl.sh for help identifying header/library mismatches.]) fi ], [ AC_MSG_WARN([cross compiling: not checking]) ] ) AC_MSG_CHECKING([if programs using OpenSSL functions will link]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include ]], [[ ERR_load_crypto_strings(); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) LIBS="$LIBS -ldl" AC_MSG_CHECKING([if programs using OpenSSL need -ldl]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include ]], [[ ERR_load_crypto_strings(); ]])], [ AC_MSG_RESULT([yes]) CHANNELLIBS="$CHANNELLIBS -ldl" ], [ AC_MSG_RESULT([no]) ] ) ] ) AC_CHECK_FUNCS([ \ BN_is_prime_ex \ DES_crypt \ DSA_generate_parameters_ex \ EVP_DigestFinal_ex \ EVP_DigestInit_ex \ EVP_MD_CTX_cleanup \ EVP_MD_CTX_copy_ex \ EVP_MD_CTX_init \ HMAC_CTX_init \ RSA_generate_key_ex \ RSA_get_default_method \ ]) # OpenSSL_add_all_algorithms may be a macro. AC_CHECK_FUNC(OpenSSL_add_all_algorithms, AC_DEFINE(HAVE_OPENSSL_ADD_ALL_ALGORITHMS, 1, [as a function]), AC_CHECK_DECL(OpenSSL_add_all_algorithms, AC_DEFINE(HAVE_OPENSSL_ADD_ALL_ALGORITHMS, 1, [as a macro]), , [[#include ]] ) ) # LibreSSL/OpenSSL API differences AC_CHECK_FUNCS([ \ EVP_CIPHER_CTX_iv \ EVP_CIPHER_CTX_iv_noconst \ EVP_CIPHER_CTX_get_iv \ EVP_CIPHER_CTX_get_updated_iv \ EVP_CIPHER_CTX_set_iv \ ]) if test "x$openssl_engine" = "xyes" ; then AC_MSG_CHECKING([for OpenSSL ENGINE support]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ ENGINE_load_builtin_engines(); ENGINE_register_all_complete(); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([USE_OPENSSL_ENGINE], [1], [Enable OpenSSL engine support]) ], [ AC_MSG_ERROR([OpenSSL ENGINE support not found]) ]) fi # Check for OpenSSL without EVP_aes_{192,256}_cbc AC_MSG_CHECKING([whether OpenSSL has crippled AES support]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]], [[ exit(EVP_aes_192_cbc() == NULL || EVP_aes_256_cbc() == NULL); ]])], [ AC_MSG_RESULT([no]) ], [ AC_MSG_RESULT([yes]) AC_DEFINE([OPENSSL_LOBOTOMISED_AES], [1], [libcrypto is missing AES 192 and 256 bit functions]) ] ) AC_MSG_CHECKING([if EVP_DigestUpdate returns an int]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]], [[ if(EVP_DigestUpdate(NULL, NULL,0)) exit(0); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_DEFINE([OPENSSL_EVP_DIGESTUPDATE_VOID], [1], [Define if EVP_DigestUpdate returns void]) ] ) # Check for various EVP support in OpenSSL AC_CHECK_FUNCS([EVP_sha256 EVP_sha384 EVP_sha512 EVP_chacha20]) # Check complete ECC support in OpenSSL AC_MSG_CHECKING([whether OpenSSL has NID_X9_62_prime256v1]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #include ]], [[ EC_KEY *e = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); const EVP_MD *m = EVP_sha256(); /* We need this too */ ]])], [ AC_MSG_RESULT([yes]) enable_nistp256=1 ], [ AC_MSG_RESULT([no]) ] ) AC_MSG_CHECKING([whether OpenSSL has NID_secp384r1]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #include ]], [[ EC_KEY *e = EC_KEY_new_by_curve_name(NID_secp384r1); const EVP_MD *m = EVP_sha384(); /* We need this too */ ]])], [ AC_MSG_RESULT([yes]) enable_nistp384=1 ], [ AC_MSG_RESULT([no]) ] ) AC_MSG_CHECKING([whether OpenSSL has NID_secp521r1]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #include ]], [[ EC_KEY *e = EC_KEY_new_by_curve_name(NID_secp521r1); const EVP_MD *m = EVP_sha512(); /* We need this too */ ]])], [ AC_MSG_RESULT([yes]) AC_MSG_CHECKING([if OpenSSL's NID_secp521r1 is functional]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #include #include #include #include ]],[[ EC_KEY *e = EC_KEY_new_by_curve_name(NID_secp521r1); const EVP_MD *m = EVP_sha512(); /* We need this too */ exit(e == NULL || m == NULL); ]])], [ AC_MSG_RESULT([yes]) enable_nistp521=1 ], [ AC_MSG_RESULT([no]) ], [ AC_MSG_WARN([cross-compiling: assuming yes]) enable_nistp521=1 ] )], AC_MSG_RESULT([no]) ) if test x$enable_nistp256 = x1 || test x$enable_nistp384 = x1 || \ test x$enable_nistp521 = x1; then AC_DEFINE(OPENSSL_HAS_ECC, [1], [OpenSSL has ECC]) AC_CHECK_FUNCS([EC_KEY_METHOD_new]) openssl_ecc=yes else openssl_ecc=no fi if test x$enable_nistp256 = x1; then AC_DEFINE([OPENSSL_HAS_NISTP256], [1], [libcrypto has NID_X9_62_prime256v1]) else unsupported_algorithms="$unsupported_algorithms \ ecdsa-sha2-nistp256 \ ecdh-sha2-nistp256 \ ecdsa-sha2-nistp256-cert-v01@openssh.com" fi if test x$enable_nistp384 = x1; then AC_DEFINE([OPENSSL_HAS_NISTP384], [1], [libcrypto has NID_secp384r1]) else unsupported_algorithms="$unsupported_algorithms \ ecdsa-sha2-nistp384 \ ecdh-sha2-nistp384 \ ecdsa-sha2-nistp384-cert-v01@openssh.com" fi if test x$enable_nistp521 = x1; then AC_DEFINE([OPENSSL_HAS_NISTP521], [1], [libcrypto has NID_secp521r1]) else unsupported_algorithms="$unsupported_algorithms \ ecdh-sha2-nistp521 \ ecdsa-sha2-nistp521 \ ecdsa-sha2-nistp521-cert-v01@openssh.com" fi fi # PKCS11/U2F depend on OpenSSL and dlopen(). enable_pkcs11=yes enable_sk=yes if test "x$openssl" != "xyes" ; then enable_pkcs11="disabled; missing libcrypto" fi if test "x$ac_cv_func_dlopen" != "xyes" ; then enable_pkcs11="disabled; missing dlopen(3)" enable_sk="disabled; missing dlopen(3)" fi if test "x$ac_cv_have_decl_RTLD_NOW" != "xyes" ; then enable_pkcs11="disabled; missing RTLD_NOW" enable_sk="disabled; missing RTLD_NOW" fi if test ! -z "$disable_pkcs11" ; then enable_pkcs11="disabled by user" fi if test ! -z "$disable_sk" ; then enable_sk="disabled by user" fi AC_MSG_CHECKING([whether to enable PKCS11]) if test "x$enable_pkcs11" = "xyes" ; then AC_DEFINE([ENABLE_PKCS11], [], [Enable for PKCS#11 support]) fi AC_MSG_RESULT([$enable_pkcs11]) AC_MSG_CHECKING([whether to enable U2F]) if test "x$enable_sk" = "xyes" ; then AC_DEFINE([ENABLE_SK], [], [Enable for U2F/FIDO support]) AC_SUBST(SK_DUMMY_LIBRARY, [regress/misc/sk-dummy/sk-dummy.so]) else # Do not try to build sk-dummy library. AC_SUBST(SK_DUMMY_LIBRARY, [""]) fi AC_MSG_RESULT([$enable_sk]) # Now check for built-in security key support. if test "x$enable_sk" = "xyes" -a "x$enable_sk_internal" != "xno" ; then use_pkgconfig_for_libfido2= if test "x$PKGCONFIG" != "xno"; then AC_MSG_CHECKING([if $PKGCONFIG knows about libfido2]) if "$PKGCONFIG" libfido2; then AC_MSG_RESULT([yes]) use_pkgconfig_for_libfido2=yes else AC_MSG_RESULT([no]) fi fi if test "x$use_pkgconfig_for_libfido2" = "xyes"; then LIBFIDO2=`$PKGCONFIG --libs libfido2` CPPFLAGS="$CPPFLAGS `$PKGCONFIG --cflags libfido2`" else LIBFIDO2="-lprivatefido2 -lprivatecbor" fi OTHERLIBS=`echo $LIBFIDO2 | sed 's/-lfido2//'` fido2_error= AC_CHECK_LIB([privatefido2], [fido_init], [ ], [ fido2_error="missing/unusable libfido2" ], [ $OTHERLIBS ] ) AC_CHECK_HEADER([fido.h], [], [ fido2_error="missing fido.h from libfido2" ]) AC_CHECK_HEADER([fido/credman.h], [], [ fido2_error="missing fido/credman.h from libfido2" ], [ #include ] ) AC_MSG_CHECKING([for usable libfido2 installation]) if test ! -z "$fido2_error" ; then AC_MSG_RESULT([$fido2_error]) if test "x$enable_sk_internal" = "xyes" ; then AC_MSG_ERROR([No usable libfido2 library/headers found]) fi LIBFIDO2="" else AC_MSG_RESULT([yes]) AC_SUBST([LIBFIDO2]) AC_DEFINE([ENABLE_SK_INTERNAL], [], [Enable for built-in U2F/FIDO support]) enable_sk="built-in" saved_LIBS="$LIBS" LIBS="$LIBFIDO2 $LIBS" AC_CHECK_FUNCS([ \ fido_assert_set_clientdata \ fido_cred_prot \ fido_cred_set_prot \ fido_cred_set_clientdata \ fido_dev_get_touch_begin \ fido_dev_get_touch_status \ fido_dev_supports_cred_prot \ fido_dev_is_winhello \ ]) LIBS="$saved_LIBS" fi fi AC_CHECK_FUNCS([ \ arc4random \ arc4random_buf \ arc4random_stir \ arc4random_uniform \ ]) ### Configure cryptographic random number support # Check whether OpenSSL seeds itself if test "x$openssl" = "xyes" ; then AC_MSG_CHECKING([whether OpenSSL's PRNG is internally seeded]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]], [[ exit(RAND_status() == 1 ? 0 : 1); ]])], [ OPENSSL_SEEDS_ITSELF=yes AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ], [ AC_MSG_WARN([cross compiling: assuming yes]) # This is safe, since we will fatal() at runtime if # OpenSSL is not seeded correctly. OPENSSL_SEEDS_ITSELF=yes ] ) fi # PRNGD TCP socket AC_ARG_WITH([prngd-port], [ --with-prngd-port=PORT read entropy from PRNGD/EGD TCP localhost:PORT], [ case "$withval" in no) withval="" ;; [[0-9]]*) ;; *) AC_MSG_ERROR([You must specify a numeric port number for --with-prngd-port]) ;; esac if test ! -z "$withval" ; then PRNGD_PORT="$withval" AC_DEFINE_UNQUOTED([PRNGD_PORT], [$PRNGD_PORT], [Port number of PRNGD/EGD random number socket]) fi ] ) # PRNGD Unix domain socket AC_ARG_WITH([prngd-socket], [ --with-prngd-socket=FILE read entropy from PRNGD/EGD socket FILE (default=/var/run/egd-pool)], [ case "$withval" in yes) withval="/var/run/egd-pool" ;; no) withval="" ;; /*) ;; *) AC_MSG_ERROR([You must specify an absolute path to the entropy socket]) ;; esac if test ! -z "$withval" ; then if test ! -z "$PRNGD_PORT" ; then AC_MSG_ERROR([You may not specify both a PRNGD/EGD port and socket]) fi if test ! -r "$withval" ; then AC_MSG_WARN([Entropy socket is not readable]) fi PRNGD_SOCKET="$withval" AC_DEFINE_UNQUOTED([PRNGD_SOCKET], ["$PRNGD_SOCKET"], [Location of PRNGD/EGD random number socket]) fi ], [ # Check for existing socket only if we don't have a random device already if test "x$OPENSSL_SEEDS_ITSELF" != "xyes" ; then AC_MSG_CHECKING([for PRNGD/EGD socket]) # Insert other locations here for sock in /var/run/egd-pool /dev/egd-pool /etc/entropy; do if test -r $sock && $TEST_MINUS_S_SH -c "test -S $sock -o -p $sock" ; then PRNGD_SOCKET="$sock" AC_DEFINE_UNQUOTED([PRNGD_SOCKET], ["$PRNGD_SOCKET"]) break; fi done if test ! -z "$PRNGD_SOCKET" ; then AC_MSG_RESULT([$PRNGD_SOCKET]) else AC_MSG_RESULT([not found]) fi fi ] ) # Which randomness source do we use? if test ! -z "$PRNGD_PORT" ; then RAND_MSG="PRNGd port $PRNGD_PORT" elif test ! -z "$PRNGD_SOCKET" ; then RAND_MSG="PRNGd socket $PRNGD_SOCKET" elif test ! -z "$OPENSSL_SEEDS_ITSELF" ; then AC_DEFINE([OPENSSL_PRNG_ONLY], [1], [Define if you want the OpenSSL internally seeded PRNG only]) RAND_MSG="OpenSSL internal ONLY" elif test "x$openssl" = "xno" ; then AC_MSG_WARN([OpenSSH will use /dev/urandom as a source of random numbers. It will fail if this device is not supported or accessible]) else AC_MSG_ERROR([OpenSSH has no source of random numbers. Please configure OpenSSL with an entropy source or re-run configure using one of the --with-prngd-port or --with-prngd-socket options]) fi LIBS="$nocrypto_saved_LIBS" saved_LIBS="$LIBS" AC_CHECK_LIB([iaf], [ia_openinfo], [ LIBS="$LIBS -liaf" AC_CHECK_FUNCS([set_id], [SSHDLIBS="$SSHDLIBS -liaf" AC_DEFINE([HAVE_LIBIAF], [1], [Define if system has libiaf that supports set_id]) ]) ]) LIBS="$saved_LIBS" # Check for crypt() in libcrypt. If we have it, we only need it for sshd. saved_LIBS="$LIBS" AC_CHECK_LIB([crypt], [crypt], [ LIBS="-lcrypt $LIBS" SSHDLIBS="-lcrypt $SSHDLIBS" ]) AC_CHECK_FUNCS([crypt]) LIBS="$saved_LIBS" # Check for PAM libs PAM_MSG="no" AC_ARG_WITH([pam], [ --with-pam Enable PAM support ], [ if test "x$withval" != "xno" ; then if test "x$ac_cv_header_security_pam_appl_h" != "xyes" && \ test "x$ac_cv_header_pam_pam_appl_h" != "xyes" ; then AC_MSG_ERROR([PAM headers not found]) fi saved_LIBS="$LIBS" AC_CHECK_LIB([dl], [dlopen], , ) AC_CHECK_LIB([pam], [pam_set_item], , [AC_MSG_ERROR([*** libpam missing])]) AC_CHECK_FUNCS([pam_getenvlist]) AC_CHECK_FUNCS([pam_putenv]) LIBS="$saved_LIBS" PAM_MSG="yes" SSHDLIBS="$SSHDLIBS -lpam" AC_DEFINE([USE_PAM], [1], [Define if you want to enable PAM support]) if test $ac_cv_lib_dl_dlopen = yes; then case "$LIBS" in *-ldl*) # libdl already in LIBS ;; *) SSHDLIBS="$SSHDLIBS -ldl" ;; esac fi fi ] ) AC_ARG_WITH([pam-service], [ --with-pam-service=name Specify PAM service name ], [ if test "x$withval" != "xno" && \ test "x$withval" != "xyes" ; then AC_DEFINE_UNQUOTED([SSHD_PAM_SERVICE], ["$withval"], [sshd PAM service name]) fi ] ) # Check for older PAM if test "x$PAM_MSG" = "xyes" ; then # Check PAM strerror arguments (old PAM) AC_MSG_CHECKING([whether pam_strerror takes only one argument]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #if defined(HAVE_SECURITY_PAM_APPL_H) #include #elif defined (HAVE_PAM_PAM_APPL_H) #include #endif ]], [[ (void)pam_strerror((pam_handle_t *)NULL, -1); ]])], [AC_MSG_RESULT([no])], [ AC_DEFINE([HAVE_OLD_PAM], [1], [Define if you have an old version of PAM which takes only one argument to pam_strerror]) AC_MSG_RESULT([yes]) PAM_MSG="yes (old library)" ]) fi case "$host" in *-*-cygwin*) SSH_PRIVSEP_USER=CYGWIN_SSH_PRIVSEP_USER ;; *) SSH_PRIVSEP_USER=sshd ;; esac AC_ARG_WITH([privsep-user], [ --with-privsep-user=user Specify non-privileged user for privilege separation], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then SSH_PRIVSEP_USER=$withval fi ] ) if test "x$SSH_PRIVSEP_USER" = "xCYGWIN_SSH_PRIVSEP_USER" ; then AC_DEFINE_UNQUOTED([SSH_PRIVSEP_USER], [CYGWIN_SSH_PRIVSEP_USER], [Cygwin function to fetch non-privileged user for privilege separation]) else AC_DEFINE_UNQUOTED([SSH_PRIVSEP_USER], ["$SSH_PRIVSEP_USER"], [non-privileged user for privilege separation]) fi AC_SUBST([SSH_PRIVSEP_USER]) if test "x$have_linux_no_new_privs" = "x1" ; then AC_CHECK_DECL([SECCOMP_MODE_FILTER], [have_seccomp_filter=1], , [ #include #include ]) fi if test "x$have_seccomp_filter" = "x1" ; then AC_MSG_CHECKING([kernel for seccomp_filter support]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include #include #include ]], [[ int i = $seccomp_audit_arch; errno = 0; prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, NULL, 0, 0); exit(errno == EFAULT ? 0 : 1); ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) # Disable seccomp filter as a target have_seccomp_filter=0 ] ) fi AC_CHECK_MEMBERS([struct pollfd.fd], [], [], [[ #include #ifdef HAVE_POLL_H #include #endif #ifdef HAVE_SYS_POLL_H #include #endif ]]) AC_CHECK_TYPES([nfds_t], , , [ #include #ifdef HAVE_POLL_H #include #endif #ifdef HAVE_SYS_POLL_H #include #endif ]) # Decide which sandbox style to use sandbox_arg="" AC_ARG_WITH([sandbox], [ --with-sandbox=style Specify privilege separation sandbox (no, capsicum, darwin, rlimit, seccomp_filter, systrace, pledge)], [ if test "x$withval" = "xyes" ; then sandbox_arg="" else sandbox_arg="$withval" fi ] ) if test "x$sandbox_arg" != "xno"; then # POSIX specifies that poll() "shall fail with EINVAL if the nfds argument # is greater than OPEN_MAX". On some platforms that includes implementions # of select in userspace on top of poll() so check both work with rlimit # NOFILES so check that both work before enabling the rlimit sandbox. AC_MSG_CHECKING([if select and/or poll works with descriptor rlimit]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #ifdef HAVE_SYS_TIME_H # include #endif #include #ifdef HAVE_SYS_SELECT_H # include #endif #ifdef HAVE_POLL_H # include #elif HAVE_SYS_POLL_H # include #endif #include #include #include ]],[[ struct rlimit rl_zero; int fd, r; fd_set fds; struct timeval tv; #ifdef HAVE_POLL struct pollfd pfd; #endif fd = open("/dev/null", O_RDONLY); FD_ZERO(&fds); FD_SET(fd, &fds); rl_zero.rlim_cur = rl_zero.rlim_max = 0; setrlimit(RLIMIT_FSIZE, &rl_zero); setrlimit(RLIMIT_NOFILE, &rl_zero); tv.tv_sec = 1; tv.tv_usec = 0; r = select(fd+1, &fds, NULL, NULL, &tv); if (r == -1) exit(1); #ifdef HAVE_POLL pfd.fd = fd; pfd.events = POLLIN; r = poll(&pfd, 1, 1); if (r == -1) exit(2); #endif exit(0); ]])], [AC_MSG_RESULT([yes]) select_works_with_rlimit=yes], [AC_MSG_RESULT([no]) select_works_with_rlimit=no], [AC_MSG_WARN([cross compiling: assuming no]) select_works_with_rlimit=no] ) AC_MSG_CHECKING([if setrlimit(RLIMIT_NOFILE,{0,0}) works]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include ]],[[ struct rlimit rl_zero; int r; rl_zero.rlim_cur = rl_zero.rlim_max = 0; r = setrlimit(RLIMIT_NOFILE, &rl_zero); exit (r == -1 ? 1 : 0); ]])], [AC_MSG_RESULT([yes]) rlimit_nofile_zero_works=yes], [AC_MSG_RESULT([no]) rlimit_nofile_zero_works=no], [AC_MSG_WARN([cross compiling: assuming yes]) rlimit_nofile_zero_works=yes] ) AC_MSG_CHECKING([if setrlimit RLIMIT_FSIZE works]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include ]],[[ struct rlimit rl_zero; rl_zero.rlim_cur = rl_zero.rlim_max = 0; exit(setrlimit(RLIMIT_FSIZE, &rl_zero) != 0); ]])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_DEFINE(SANDBOX_SKIP_RLIMIT_FSIZE, 1, [setrlimit RLIMIT_FSIZE works])], [AC_MSG_WARN([cross compiling: assuming yes])] ) fi if test "x$sandbox_arg" = "xpledge" || \ ( test -z "$sandbox_arg" && test "x$ac_cv_func_pledge" = "xyes" ) ; then test "x$ac_cv_func_pledge" != "xyes" && \ AC_MSG_ERROR([pledge sandbox requires pledge(2) support]) SANDBOX_STYLE="pledge" AC_DEFINE([SANDBOX_PLEDGE], [1], [Sandbox using pledge(2)]) elif test "x$sandbox_arg" = "xsystrace" || \ ( test -z "$sandbox_arg" && test "x$have_systr_policy_kill" = "x1" ) ; then test "x$have_systr_policy_kill" != "x1" && \ AC_MSG_ERROR([systrace sandbox requires systrace headers and SYSTR_POLICY_KILL support]) SANDBOX_STYLE="systrace" AC_DEFINE([SANDBOX_SYSTRACE], [1], [Sandbox using systrace(4)]) elif test "x$sandbox_arg" = "xdarwin" || \ ( test -z "$sandbox_arg" && test "x$ac_cv_func_sandbox_init" = "xyes" && \ test "x$ac_cv_header_sandbox_h" = "xyes") ; then test "x$ac_cv_func_sandbox_init" != "xyes" -o \ "x$ac_cv_header_sandbox_h" != "xyes" && \ AC_MSG_ERROR([Darwin seatbelt sandbox requires sandbox.h and sandbox_init function]) SANDBOX_STYLE="darwin" AC_DEFINE([SANDBOX_DARWIN], [1], [Sandbox using Darwin sandbox_init(3)]) elif test "x$sandbox_arg" = "xseccomp_filter" || \ ( test -z "$sandbox_arg" && \ test "x$have_seccomp_filter" = "x1" && \ test "x$ac_cv_header_elf_h" = "xyes" && \ test "x$ac_cv_header_linux_audit_h" = "xyes" && \ test "x$ac_cv_header_linux_filter_h" = "xyes" && \ test "x$seccomp_audit_arch" != "x" && \ test "x$have_linux_no_new_privs" = "x1" && \ test "x$ac_cv_func_prctl" = "xyes" ) ; then test "x$seccomp_audit_arch" = "x" && \ AC_MSG_ERROR([seccomp_filter sandbox not supported on $host]) test "x$have_linux_no_new_privs" != "x1" && \ AC_MSG_ERROR([seccomp_filter sandbox requires PR_SET_NO_NEW_PRIVS]) test "x$have_seccomp_filter" != "x1" && \ AC_MSG_ERROR([seccomp_filter sandbox requires seccomp headers]) test "x$ac_cv_func_prctl" != "xyes" && \ AC_MSG_ERROR([seccomp_filter sandbox requires prctl function]) SANDBOX_STYLE="seccomp_filter" AC_DEFINE([SANDBOX_SECCOMP_FILTER], [1], [Sandbox using seccomp filter]) elif test "x$sandbox_arg" = "xcapsicum" || \ ( test -z "$sandbox_arg" && \ test "x$disable_capsicum" != "xyes" && \ test "x$ac_cv_header_sys_capsicum_h" = "xyes" && \ test "x$ac_cv_func_cap_rights_limit" = "xyes") ; then test "x$ac_cv_header_sys_capsicum_h" != "xyes" && \ AC_MSG_ERROR([capsicum sandbox requires sys/capsicum.h header]) test "x$ac_cv_func_cap_rights_limit" != "xyes" && \ AC_MSG_ERROR([capsicum sandbox requires cap_rights_limit function]) SANDBOX_STYLE="capsicum" AC_DEFINE([SANDBOX_CAPSICUM], [1], [Sandbox using capsicum]) elif test "x$sandbox_arg" = "xrlimit" || \ ( test -z "$sandbox_arg" && test "x$ac_cv_func_setrlimit" = "xyes" && \ test "x$select_works_with_rlimit" = "xyes" && \ test "x$rlimit_nofile_zero_works" = "xyes" ) ; then test "x$ac_cv_func_setrlimit" != "xyes" && \ AC_MSG_ERROR([rlimit sandbox requires setrlimit function]) test "x$select_works_with_rlimit" != "xyes" && \ AC_MSG_ERROR([rlimit sandbox requires select to work with rlimit]) SANDBOX_STYLE="rlimit" AC_DEFINE([SANDBOX_RLIMIT], [1], [Sandbox using setrlimit(2)]) elif test "x$sandbox_arg" = "xsolaris" || \ ( test -z "$sandbox_arg" && test "x$SOLARIS_PRIVS" = "xyes" ) ; then SANDBOX_STYLE="solaris" AC_DEFINE([SANDBOX_SOLARIS], [1], [Sandbox using Solaris/Illumos privileges]) elif test -z "$sandbox_arg" || test "x$sandbox_arg" = "xno" || \ test "x$sandbox_arg" = "xnone" || test "x$sandbox_arg" = "xnull" ; then SANDBOX_STYLE="none" AC_DEFINE([SANDBOX_NULL], [1], [no privsep sandboxing]) else AC_MSG_ERROR([unsupported --with-sandbox]) fi # Cheap hack to ensure NEWS-OS libraries are arranged right. if test ! -z "$SONY" ; then LIBS="$LIBS -liberty"; fi # Check for long long datatypes AC_CHECK_TYPES([long long, unsigned long long, long double]) # Check datatype sizes AC_CHECK_SIZEOF([short int]) AC_CHECK_SIZEOF([int]) AC_CHECK_SIZEOF([long int]) AC_CHECK_SIZEOF([long long int]) AC_CHECK_SIZEOF([time_t], [], [[ #include #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_TIME_H # include #endif ]] ) # Sanity check long long for some platforms (AIX) if test "x$ac_cv_sizeof_long_long_int" = "x4" ; then ac_cv_sizeof_long_long_int=0 fi # compute LLONG_MIN and LLONG_MAX if we don't know them. if test -z "$have_llong_max" && test -z "$have_long_long_max"; then AC_MSG_CHECKING([for max value of long long]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include /* Why is this so damn hard? */ #ifdef __GNUC__ # undef __GNUC__ #endif #define __USE_ISOC99 #include #define DATA "conftest.llminmax" #define my_abs(a) ((a) < 0 ? ((a) * -1) : (a)) /* * printf in libc on some platforms (eg old Tru64) does not understand %lld so * we do this the hard way. */ static int fprint_ll(FILE *f, long long n) { unsigned int i; int l[sizeof(long long) * 8]; if (n < 0) if (fprintf(f, "-") < 0) return -1; for (i = 0; n != 0; i++) { l[i] = my_abs(n % 10); n /= 10; } do { if (fprintf(f, "%d", l[--i]) < 0) return -1; } while (i != 0); if (fprintf(f, " ") < 0) return -1; return 0; } ]], [[ FILE *f; long long i, llmin, llmax = 0; if((f = fopen(DATA,"w")) == NULL) exit(1); #if defined(LLONG_MIN) && defined(LLONG_MAX) fprintf(stderr, "Using system header for LLONG_MIN and LLONG_MAX\n"); llmin = LLONG_MIN; llmax = LLONG_MAX; #else fprintf(stderr, "Calculating LLONG_MIN and LLONG_MAX\n"); /* This will work on one's complement and two's complement */ for (i = 1; i > llmax; i <<= 1, i++) llmax = i; llmin = llmax + 1LL; /* wrap */ #endif /* Sanity check */ if (llmin + 1 < llmin || llmin - 1 < llmin || llmax + 1 > llmax || llmax - 1 > llmax || llmin == llmax || llmin == 0 || llmax == 0 || llmax < LONG_MAX || llmin > LONG_MIN) { fprintf(f, "unknown unknown\n"); exit(2); } if (fprint_ll(f, llmin) < 0) exit(3); if (fprint_ll(f, llmax) < 0) exit(4); if (fclose(f) < 0) exit(5); exit(0); ]])], [ llong_min=`$AWK '{print $1}' conftest.llminmax` llong_max=`$AWK '{print $2}' conftest.llminmax` AC_MSG_RESULT([$llong_max]) AC_DEFINE_UNQUOTED([LLONG_MAX], [${llong_max}LL], [max value of long long calculated by configure]) AC_MSG_CHECKING([for min value of long long]) AC_MSG_RESULT([$llong_min]) AC_DEFINE_UNQUOTED([LLONG_MIN], [${llong_min}LL], [min value of long long calculated by configure]) ], [ AC_MSG_RESULT([not found]) ], [ AC_MSG_WARN([cross compiling: not checking]) ] ) fi AC_CHECK_DECLS([UINT32_MAX], , , [[ #ifdef HAVE_SYS_LIMITS_H # include #endif #ifdef HAVE_LIMITS_H # include #endif #ifdef HAVE_STDINT_H # include #endif ]]) # More checks for data types AC_CACHE_CHECK([for u_int type], ac_cv_have_u_int, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ u_int a; a = 1;]])], [ ac_cv_have_u_int="yes" ], [ ac_cv_have_u_int="no" ]) ]) if test "x$ac_cv_have_u_int" = "xyes" ; then AC_DEFINE([HAVE_U_INT], [1], [define if you have u_int data type]) have_u_int=1 fi AC_CACHE_CHECK([for intXX_t types], ac_cv_have_intxx_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ int8_t a; int16_t b; int32_t c; a = b = c = 1;]])], [ ac_cv_have_intxx_t="yes" ], [ ac_cv_have_intxx_t="no" ]) ]) if test "x$ac_cv_have_intxx_t" = "xyes" ; then AC_DEFINE([HAVE_INTXX_T], [1], [define if you have intxx_t data type]) have_intxx_t=1 fi if (test -z "$have_intxx_t" && \ test "x$ac_cv_header_stdint_h" = "xyes") then AC_MSG_CHECKING([for intXX_t types in stdint.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ int8_t a; int16_t b; int32_t c; a = b = c = 1;]])], [ AC_DEFINE([HAVE_INTXX_T]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) fi AC_CACHE_CHECK([for int64_t type], ac_cv_have_int64_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #ifdef HAVE_STDINT_H # include #endif #include #ifdef HAVE_SYS_BITYPES_H # include #endif ]], [[ int64_t a; a = 1; ]])], [ ac_cv_have_int64_t="yes" ], [ ac_cv_have_int64_t="no" ]) ]) if test "x$ac_cv_have_int64_t" = "xyes" ; then AC_DEFINE([HAVE_INT64_T], [1], [define if you have int64_t data type]) fi AC_CACHE_CHECK([for u_intXX_t types], ac_cv_have_u_intxx_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ u_int8_t a; u_int16_t b; u_int32_t c; a = b = c = 1;]])], [ ac_cv_have_u_intxx_t="yes" ], [ ac_cv_have_u_intxx_t="no" ]) ]) if test "x$ac_cv_have_u_intxx_t" = "xyes" ; then AC_DEFINE([HAVE_U_INTXX_T], [1], [define if you have u_intxx_t data type]) have_u_intxx_t=1 fi if test -z "$have_u_intxx_t" ; then AC_MSG_CHECKING([for u_intXX_t types in sys/socket.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ u_int8_t a; u_int16_t b; u_int32_t c; a = b = c = 1;]])], [ AC_DEFINE([HAVE_U_INTXX_T]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) fi AC_CACHE_CHECK([for u_int64_t types], ac_cv_have_u_int64_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ u_int64_t a; a = 1;]])], [ ac_cv_have_u_int64_t="yes" ], [ ac_cv_have_u_int64_t="no" ]) ]) if test "x$ac_cv_have_u_int64_t" = "xyes" ; then AC_DEFINE([HAVE_U_INT64_T], [1], [define if you have u_int64_t data type]) have_u_int64_t=1 fi if (test -z "$have_u_int64_t" && \ test "x$ac_cv_header_sys_bitypes_h" = "xyes") then AC_MSG_CHECKING([for u_int64_t type in sys/bitypes.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ u_int64_t a; a = 1]])], [ AC_DEFINE([HAVE_U_INT64_T]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) fi if test -z "$have_u_intxx_t" ; then AC_CACHE_CHECK([for uintXX_t types], ac_cv_have_uintxx_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ uint8_t a; uint16_t b; uint32_t c; a = b = c = 1; ]])], [ ac_cv_have_uintxx_t="yes" ], [ ac_cv_have_uintxx_t="no" ]) ]) if test "x$ac_cv_have_uintxx_t" = "xyes" ; then AC_DEFINE([HAVE_UINTXX_T], [1], [define if you have uintxx_t data type]) fi fi if (test -z "$have_uintxx_t" && \ test "x$ac_cv_header_stdint_h" = "xyes") then AC_MSG_CHECKING([for uintXX_t types in stdint.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ uint8_t a; uint16_t b; uint32_t c; a = b = c = 1;]])], [ AC_DEFINE([HAVE_UINTXX_T]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) fi if (test -z "$have_uintxx_t" && \ test "x$ac_cv_header_inttypes_h" = "xyes") then AC_MSG_CHECKING([for uintXX_t types in inttypes.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ uint8_t a; uint16_t b; uint32_t c; a = b = c = 1;]])], [ AC_DEFINE([HAVE_UINTXX_T]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) fi if (test -z "$have_u_intxx_t" || test -z "$have_intxx_t" && \ test "x$ac_cv_header_sys_bitypes_h" = "xyes") then AC_MSG_CHECKING([for intXX_t and u_intXX_t types in sys/bitypes.h]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ int8_t a; int16_t b; int32_t c; u_int8_t e; u_int16_t f; u_int32_t g; a = b = c = e = f = g = 1; ]])], [ AC_DEFINE([HAVE_U_INTXX_T]) AC_DEFINE([HAVE_INTXX_T]) AC_MSG_RESULT([yes]) ], [AC_MSG_RESULT([no]) ]) fi AC_CACHE_CHECK([for u_char], ac_cv_have_u_char, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ u_char foo; foo = 125; ]])], [ ac_cv_have_u_char="yes" ], [ ac_cv_have_u_char="no" ]) ]) if test "x$ac_cv_have_u_char" = "xyes" ; then AC_DEFINE([HAVE_U_CHAR], [1], [define if you have u_char data type]) fi AC_CHECK_TYPES([intmax_t, uintmax_t], , , [ #include #ifdef HAVE_STDINT_H # include #endif ]) TYPE_SOCKLEN_T AC_CHECK_TYPES([sig_atomic_t, sighandler_t], , , [#include ]) AC_CHECK_TYPES([fsblkcnt_t, fsfilcnt_t], , , [ #include #ifdef HAVE_SYS_BITYPES_H #include #endif #ifdef HAVE_SYS_STATFS_H #include #endif #ifdef HAVE_SYS_STATVFS_H #include #endif ]) AC_CHECK_MEMBERS([struct statfs.f_files, struct statfs.f_flags], [], [], [[ #include #include #ifdef HAVE_SYS_BITYPES_H #include #endif #ifdef HAVE_SYS_STATFS_H #include #endif #ifdef HAVE_SYS_STATVFS_H #include #endif #ifdef HAVE_SYS_VFS_H #include #endif #ifdef HAVE_SYS_MOUNT_H #include #endif ]]) AC_CHECK_TYPES([in_addr_t, in_port_t], , , [#include #include ]) AC_CACHE_CHECK([for size_t], ac_cv_have_size_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ size_t foo; foo = 1235; ]])], [ ac_cv_have_size_t="yes" ], [ ac_cv_have_size_t="no" ]) ]) if test "x$ac_cv_have_size_t" = "xyes" ; then AC_DEFINE([HAVE_SIZE_T], [1], [define if you have size_t data type]) fi AC_CACHE_CHECK([for ssize_t], ac_cv_have_ssize_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ ssize_t foo; foo = 1235; ]])], [ ac_cv_have_ssize_t="yes" ], [ ac_cv_have_ssize_t="no" ]) ]) if test "x$ac_cv_have_ssize_t" = "xyes" ; then AC_DEFINE([HAVE_SSIZE_T], [1], [define if you have ssize_t data type]) fi AC_CACHE_CHECK([for clock_t], ac_cv_have_clock_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ clock_t foo; foo = 1235; ]])], [ ac_cv_have_clock_t="yes" ], [ ac_cv_have_clock_t="no" ]) ]) if test "x$ac_cv_have_clock_t" = "xyes" ; then AC_DEFINE([HAVE_CLOCK_T], [1], [define if you have clock_t data type]) fi AC_CACHE_CHECK([for sa_family_t], ac_cv_have_sa_family_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ sa_family_t foo; foo = 1235; ]])], [ ac_cv_have_sa_family_t="yes" ], [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]], [[ sa_family_t foo; foo = 1235; ]])], [ ac_cv_have_sa_family_t="yes" ], [ ac_cv_have_sa_family_t="no" ] ) ]) ]) if test "x$ac_cv_have_sa_family_t" = "xyes" ; then AC_DEFINE([HAVE_SA_FAMILY_T], [1], [define if you have sa_family_t data type]) fi AC_CACHE_CHECK([for pid_t], ac_cv_have_pid_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ pid_t foo; foo = 1235; ]])], [ ac_cv_have_pid_t="yes" ], [ ac_cv_have_pid_t="no" ]) ]) if test "x$ac_cv_have_pid_t" = "xyes" ; then AC_DEFINE([HAVE_PID_T], [1], [define if you have pid_t data type]) fi AC_CACHE_CHECK([for mode_t], ac_cv_have_mode_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ mode_t foo; foo = 1235; ]])], [ ac_cv_have_mode_t="yes" ], [ ac_cv_have_mode_t="no" ]) ]) if test "x$ac_cv_have_mode_t" = "xyes" ; then AC_DEFINE([HAVE_MODE_T], [1], [define if you have mode_t data type]) fi AC_CACHE_CHECK([for struct sockaddr_storage], ac_cv_have_struct_sockaddr_storage, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ struct sockaddr_storage s; ]])], [ ac_cv_have_struct_sockaddr_storage="yes" ], [ ac_cv_have_struct_sockaddr_storage="no" ]) ]) if test "x$ac_cv_have_struct_sockaddr_storage" = "xyes" ; then AC_DEFINE([HAVE_STRUCT_SOCKADDR_STORAGE], [1], [define if you have struct sockaddr_storage data type]) fi AC_CACHE_CHECK([for struct sockaddr_in6], ac_cv_have_struct_sockaddr_in6, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ struct sockaddr_in6 s; s.sin6_family = 0; ]])], [ ac_cv_have_struct_sockaddr_in6="yes" ], [ ac_cv_have_struct_sockaddr_in6="no" ]) ]) if test "x$ac_cv_have_struct_sockaddr_in6" = "xyes" ; then AC_DEFINE([HAVE_STRUCT_SOCKADDR_IN6], [1], [define if you have struct sockaddr_in6 data type]) fi AC_CACHE_CHECK([for struct in6_addr], ac_cv_have_struct_in6_addr, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ struct in6_addr s; s.s6_addr[0] = 0; ]])], [ ac_cv_have_struct_in6_addr="yes" ], [ ac_cv_have_struct_in6_addr="no" ]) ]) if test "x$ac_cv_have_struct_in6_addr" = "xyes" ; then AC_DEFINE([HAVE_STRUCT_IN6_ADDR], [1], [define if you have struct in6_addr data type]) dnl Now check for sin6_scope_id AC_CHECK_MEMBERS([struct sockaddr_in6.sin6_scope_id], , , [ #ifdef HAVE_SYS_TYPES_H #include #endif #include ]) fi AC_CACHE_CHECK([for struct addrinfo], ac_cv_have_struct_addrinfo, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]], [[ struct addrinfo s; s.ai_flags = AI_PASSIVE; ]])], [ ac_cv_have_struct_addrinfo="yes" ], [ ac_cv_have_struct_addrinfo="no" ]) ]) if test "x$ac_cv_have_struct_addrinfo" = "xyes" ; then AC_DEFINE([HAVE_STRUCT_ADDRINFO], [1], [define if you have struct addrinfo data type]) fi AC_CACHE_CHECK([for struct timeval], ac_cv_have_struct_timeval, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ struct timeval tv; tv.tv_sec = 1;]])], [ ac_cv_have_struct_timeval="yes" ], [ ac_cv_have_struct_timeval="no" ]) ]) if test "x$ac_cv_have_struct_timeval" = "xyes" ; then AC_DEFINE([HAVE_STRUCT_TIMEVAL], [1], [define if you have struct timeval]) have_struct_timeval=1 fi AC_CACHE_CHECK([for struct timespec], ac_cv_have_struct_timespec, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_TIME_H # include #endif ]], [[ struct timespec ts; ts.tv_sec = 1;]])], [ ac_cv_have_struct_timespec="yes" ], [ ac_cv_have_struct_timespec="no" ]) ]) if test "x$ac_cv_have_struct_timespec" = "xyes" ; then AC_DEFINE([HAVE_STRUCT_TIMESPEC], [1], [define if you have struct timespec]) have_struct_timespec=1 fi # We need int64_t or else certain parts of the compile will fail. if test "x$ac_cv_have_int64_t" = "xno" && \ test "x$ac_cv_sizeof_long_int" != "x8" && \ test "x$ac_cv_sizeof_long_long_int" = "x0" ; then echo "OpenSSH requires int64_t support. Contact your vendor or install" echo "an alternative compiler (I.E., GCC) before continuing." echo "" exit 1; else dnl test snprintf (broken on SCO w/gcc) AC_RUN_IFELSE( [AC_LANG_SOURCE([[ #include #include #include #ifdef HAVE_SNPRINTF int main(void) { char buf[50]; char expected_out[50]; int mazsize = 50 ; #if (SIZEOF_LONG_INT == 8) long int num = 0x7fffffffffffffff; #else long long num = 0x7fffffffffffffffll; #endif strcpy(expected_out, "9223372036854775807"); snprintf(buf, mazsize, "%lld", num); if(strcmp(buf, expected_out) != 0) exit(1); exit(0); } #else int main(void) { exit(0); } #endif ]])], [ true ], [ AC_DEFINE([BROKEN_SNPRINTF]) ], AC_MSG_WARN([cross compiling: Assuming working snprintf()]) ) fi dnl Checks for structure members OSSH_CHECK_HEADER_FOR_FIELD([ut_host], [utmp.h], [HAVE_HOST_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_host], [utmpx.h], [HAVE_HOST_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([syslen], [utmpx.h], [HAVE_SYSLEN_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_pid], [utmp.h], [HAVE_PID_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_type], [utmp.h], [HAVE_TYPE_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_type], [utmpx.h], [HAVE_TYPE_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_tv], [utmp.h], [HAVE_TV_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_id], [utmp.h], [HAVE_ID_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_id], [utmpx.h], [HAVE_ID_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_addr], [utmp.h], [HAVE_ADDR_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_addr], [utmpx.h], [HAVE_ADDR_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_addr_v6], [utmp.h], [HAVE_ADDR_V6_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_addr_v6], [utmpx.h], [HAVE_ADDR_V6_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_exit], [utmp.h], [HAVE_EXIT_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_time], [utmp.h], [HAVE_TIME_IN_UTMP]) OSSH_CHECK_HEADER_FOR_FIELD([ut_time], [utmpx.h], [HAVE_TIME_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_tv], [utmpx.h], [HAVE_TV_IN_UTMPX]) OSSH_CHECK_HEADER_FOR_FIELD([ut_ss], [utmpx.h], [HAVE_SS_IN_UTMPX]) AC_CHECK_MEMBERS([struct stat.st_blksize]) AC_CHECK_MEMBERS([struct stat.st_mtim]) AC_CHECK_MEMBERS([struct stat.st_mtime]) AC_CHECK_MEMBERS([struct passwd.pw_gecos, struct passwd.pw_class, struct passwd.pw_change, struct passwd.pw_expire], [], [], [[ #include #include ]]) AC_CHECK_MEMBER([struct __res_state.retrans], [], [AC_DEFINE([__res_state], [state], [Define if we don't have struct __res_state in resolv.h])], [[ #include #if HAVE_SYS_TYPES_H # include #endif #include #include #include ]]) AC_CHECK_MEMBER([struct sockaddr_in.sin_len], [AC_DEFINE([SOCK_HAS_LEN], [1], [sockaddr_in has sin_len])], [], [AC_LANG_SOURCE([[ #include #include #include ]])] ) AC_CACHE_CHECK([for ss_family field in struct sockaddr_storage], ac_cv_have_ss_family_in_struct_ss, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ struct sockaddr_storage s; s.ss_family = 1; ]])], [ ac_cv_have_ss_family_in_struct_ss="yes" ], [ ac_cv_have_ss_family_in_struct_ss="no" ]) ]) if test "x$ac_cv_have_ss_family_in_struct_ss" = "xyes" ; then AC_DEFINE([HAVE_SS_FAMILY_IN_SS], [1], [Fields in struct sockaddr_storage]) fi AC_CACHE_CHECK([for __ss_family field in struct sockaddr_storage], ac_cv_have___ss_family_in_struct_ss, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ struct sockaddr_storage s; s.__ss_family = 1; ]])], [ ac_cv_have___ss_family_in_struct_ss="yes" ], [ ac_cv_have___ss_family_in_struct_ss="no" ]) ]) if test "x$ac_cv_have___ss_family_in_struct_ss" = "xyes" ; then AC_DEFINE([HAVE___SS_FAMILY_IN_SS], [1], [Fields in struct sockaddr_storage]) fi dnl make sure we're using the real structure members and not defines AC_CACHE_CHECK([for msg_accrights field in struct msghdr], ac_cv_have_accrights_in_msghdr, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include ]], [[ #ifdef msg_accrights #error "msg_accrights is a macro" exit(1); #endif struct msghdr m; m.msg_accrights = 0; exit(0); ]])], [ ac_cv_have_accrights_in_msghdr="yes" ], [ ac_cv_have_accrights_in_msghdr="no" ] ) ]) if test "x$ac_cv_have_accrights_in_msghdr" = "xyes" ; then AC_DEFINE([HAVE_ACCRIGHTS_IN_MSGHDR], [1], [Define if your system uses access rights style file descriptor passing]) fi AC_MSG_CHECKING([if struct statvfs.f_fsid is integral type]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_MOUNT_H #include #endif #ifdef HAVE_SYS_STATVFS_H #include #endif ]], [[ struct statvfs s; s.f_fsid = 0; ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_MSG_CHECKING([if fsid_t has member val]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ fsid_t t; t.val[0] = 0; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([FSID_HAS_VAL], [1], [fsid_t has member val]) ], [ AC_MSG_RESULT([no]) ]) AC_MSG_CHECKING([if f_fsid has member __val]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ fsid_t t; t.__val[0] = 0; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([FSID_HAS___VAL], [1], [fsid_t has member __val]) ], [ AC_MSG_RESULT([no]) ]) ]) AC_CACHE_CHECK([for msg_control field in struct msghdr], ac_cv_have_control_in_msghdr, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include ]], [[ #ifdef msg_control #error "msg_control is a macro" exit(1); #endif struct msghdr m; m.msg_control = 0; exit(0); ]])], [ ac_cv_have_control_in_msghdr="yes" ], [ ac_cv_have_control_in_msghdr="no" ] ) ]) if test "x$ac_cv_have_control_in_msghdr" = "xyes" ; then AC_DEFINE([HAVE_CONTROL_IN_MSGHDR], [1], [Define if your system uses ancillary data style file descriptor passing]) fi AC_CACHE_CHECK([if libc defines __progname], ac_cv_libc_defines___progname, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ extern char *__progname; printf("%s", __progname); ]])], [ ac_cv_libc_defines___progname="yes" ], [ ac_cv_libc_defines___progname="no" ]) ]) if test "x$ac_cv_libc_defines___progname" = "xyes" ; then AC_DEFINE([HAVE___PROGNAME], [1], [Define if libc defines __progname]) fi AC_CACHE_CHECK([whether $CC implements __FUNCTION__], ac_cv_cc_implements___FUNCTION__, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ printf("%s", __FUNCTION__); ]])], [ ac_cv_cc_implements___FUNCTION__="yes" ], [ ac_cv_cc_implements___FUNCTION__="no" ]) ]) if test "x$ac_cv_cc_implements___FUNCTION__" = "xyes" ; then AC_DEFINE([HAVE___FUNCTION__], [1], [Define if compiler implements __FUNCTION__]) fi AC_CACHE_CHECK([whether $CC implements __func__], ac_cv_cc_implements___func__, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ printf("%s", __func__); ]])], [ ac_cv_cc_implements___func__="yes" ], [ ac_cv_cc_implements___func__="no" ]) ]) if test "x$ac_cv_cc_implements___func__" = "xyes" ; then AC_DEFINE([HAVE___func__], [1], [Define if compiler implements __func__]) fi AC_CACHE_CHECK([whether va_copy exists], ac_cv_have_va_copy, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include va_list x,y; ]], [[ va_copy(x,y); ]])], [ ac_cv_have_va_copy="yes" ], [ ac_cv_have_va_copy="no" ]) ]) if test "x$ac_cv_have_va_copy" = "xyes" ; then AC_DEFINE([HAVE_VA_COPY], [1], [Define if va_copy exists]) fi AC_CACHE_CHECK([whether __va_copy exists], ac_cv_have___va_copy, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include va_list x,y; ]], [[ __va_copy(x,y); ]])], [ ac_cv_have___va_copy="yes" ], [ ac_cv_have___va_copy="no" ]) ]) if test "x$ac_cv_have___va_copy" = "xyes" ; then AC_DEFINE([HAVE___VA_COPY], [1], [Define if __va_copy exists]) fi AC_CACHE_CHECK([whether getopt has optreset support], ac_cv_have_getopt_optreset, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ extern int optreset; optreset = 0; ]])], [ ac_cv_have_getopt_optreset="yes" ], [ ac_cv_have_getopt_optreset="no" ]) ]) if test "x$ac_cv_have_getopt_optreset" = "xyes" ; then AC_DEFINE([HAVE_GETOPT_OPTRESET], [1], [Define if your getopt(3) defines and uses optreset]) fi AC_CACHE_CHECK([if libc defines sys_errlist], ac_cv_libc_defines_sys_errlist, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ extern const char *const sys_errlist[]; printf("%s", sys_errlist[0]);]])], [ ac_cv_libc_defines_sys_errlist="yes" ], [ ac_cv_libc_defines_sys_errlist="no" ]) ]) if test "x$ac_cv_libc_defines_sys_errlist" = "xyes" ; then AC_DEFINE([HAVE_SYS_ERRLIST], [1], [Define if your system defines sys_errlist[]]) fi AC_CACHE_CHECK([if libc defines sys_nerr], ac_cv_libc_defines_sys_nerr, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ extern int sys_nerr; printf("%i", sys_nerr);]])], [ ac_cv_libc_defines_sys_nerr="yes" ], [ ac_cv_libc_defines_sys_nerr="no" ]) ]) if test "x$ac_cv_libc_defines_sys_nerr" = "xyes" ; then AC_DEFINE([HAVE_SYS_NERR], [1], [Define if your system defines sys_nerr]) fi # Check libraries needed by DNS fingerprint support AC_SEARCH_LIBS([getrrsetbyname], [resolv], [AC_DEFINE([HAVE_GETRRSETBYNAME], [1], [Define if getrrsetbyname() exists])], [ # Needed by our getrrsetbyname() AC_SEARCH_LIBS([res_query], [resolv]) AC_SEARCH_LIBS([dn_expand], [resolv]) AC_MSG_CHECKING([if res_query will link]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include #include ]], [[ res_query (0, 0, 0, 0, 0); ]])], AC_MSG_RESULT([yes]), [AC_MSG_RESULT([no]) saved_LIBS="$LIBS" LIBS="$LIBS -lresolv" AC_MSG_CHECKING([for res_query in -lresolv]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include #include ]], [[ res_query (0, 0, 0, 0, 0); ]])], [AC_MSG_RESULT([yes])], [LIBS="$saved_LIBS" AC_MSG_RESULT([no])]) ]) AC_CHECK_FUNCS([_getshort _getlong]) AC_CHECK_DECLS([_getshort, _getlong], , , [#include #include ]) AC_CHECK_MEMBER([HEADER.ad], [AC_DEFINE([HAVE_HEADER_AD], [1], [Define if HEADER.ad exists in arpa/nameser.h])], , [#include ]) ]) AC_MSG_CHECKING([if struct __res_state _res is an extern]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #if HAVE_SYS_TYPES_H # include #endif #include #include #include extern struct __res_state _res; ]], [[ struct __res_state *volatile p = &_res; /* force resolution of _res */ return 0; ]],)], [AC_MSG_RESULT([yes]) AC_DEFINE([HAVE__RES_EXTERN], [1], [Define if you have struct __res_state _res as an extern]) ], [ AC_MSG_RESULT([no]) ] ) # Check whether user wants SELinux support SELINUX_MSG="no" LIBSELINUX="" AC_ARG_WITH([selinux], [ --with-selinux Enable SELinux support], [ if test "x$withval" != "xno" ; then save_LIBS="$LIBS" AC_DEFINE([WITH_SELINUX], [1], [Define if you want SELinux support.]) SELINUX_MSG="yes" AC_CHECK_HEADER([selinux/selinux.h], , AC_MSG_ERROR([SELinux support requires selinux.h header])) AC_CHECK_LIB([selinux], [setexeccon], [ LIBSELINUX="-lselinux" LIBS="$LIBS -lselinux" ], AC_MSG_ERROR([SELinux support requires libselinux library])) AC_CHECK_FUNCS([getseuserbyname get_default_context_with_level]) LIBS="$save_LIBS $LIBSELINUX" fi ] ) AC_SUBST([SSHDLIBS]) # Check whether user wants Kerberos 5 support KRB5_MSG="no" AC_ARG_WITH([kerberos5], [ --with-kerberos5=PATH Enable Kerberos 5 support], [ if test "x$withval" != "xno" ; then if test "x$withval" = "xyes" ; then KRB5ROOT="/usr/local" else KRB5ROOT=${withval} fi AC_DEFINE([KRB5], [1], [Define if you want Kerberos 5 support]) KRB5_MSG="yes" use_pkgconfig_for_krb5= if test "x$PKGCONFIG" != "xno"; then AC_MSG_CHECKING([if $PKGCONFIG knows about kerberos5]) if "$PKGCONFIG" krb5; then AC_MSG_RESULT([yes]) use_pkgconfig_for_krb5=yes else AC_MSG_RESULT([no]) fi fi if test "x$use_pkgconfig_for_krb5" = "xyes"; then K5CFLAGS=`$PKGCONFIG --cflags krb5` K5LIBS=`$PKGCONFIG --libs krb5` CPPFLAGS="$CPPFLAGS $K5CFLAGS" AC_MSG_CHECKING([for gssapi support]) if "$PKGCONFIG" krb5-gssapi; then AC_MSG_RESULT([yes]) AC_DEFINE([GSSAPI], [1], [Define this if you want GSSAPI support in the version 2 protocol]) GSSCFLAGS="`$PKGCONFIG --cflags krb5-gssapi`" GSSLIBS="`$PKGCONFIG --libs krb5-gssapi`" CPPFLAGS="$CPPFLAGS $GSSCFLAGS" else AC_MSG_RESULT([no]) fi AC_MSG_CHECKING([whether we are using Heimdal]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *tmp = heimdal_version; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HEIMDAL], [1], [Define this if you are using the Heimdal version of Kerberos V5]) ], [AC_MSG_RESULT([no]) ]) else AC_PATH_TOOL([KRB5CONF], [krb5-config], [$KRB5ROOT/bin/krb5-config], [$KRB5ROOT/bin:$PATH]) if test -x $KRB5CONF ; then K5CFLAGS="`$KRB5CONF --cflags`" K5LIBS="`$KRB5CONF --libs`" CPPFLAGS="$CPPFLAGS $K5CFLAGS" AC_MSG_CHECKING([for gssapi support]) if $KRB5CONF | grep gssapi >/dev/null ; then AC_MSG_RESULT([yes]) AC_DEFINE([GSSAPI], [1], [Define this if you want GSSAPI support in the version 2 protocol]) GSSCFLAGS="`$KRB5CONF --cflags gssapi`" GSSLIBS="`$KRB5CONF --libs gssapi`" CPPFLAGS="$CPPFLAGS $GSSCFLAGS" else AC_MSG_RESULT([no]) fi AC_MSG_CHECKING([whether we are using Heimdal]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *tmp = heimdal_version; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HEIMDAL], [1], [Define this if you are using the Heimdal version of Kerberos V5]) ], [AC_MSG_RESULT([no]) ]) else CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include" LDFLAGS="$LDFLAGS -L${KRB5ROOT}/lib" AC_MSG_CHECKING([whether we are using Heimdal]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *tmp = heimdal_version; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HEIMDAL]) K5LIBS="-lkrb5" K5LIBS="$K5LIBS -lcom_err -lasn1" AC_CHECK_LIB([roken], [net_write], [K5LIBS="$K5LIBS -lroken"]) AC_CHECK_LIB([des], [des_cbc_encrypt], [K5LIBS="$K5LIBS -ldes"]) ], [ AC_MSG_RESULT([no]) K5LIBS="-lkrb5 -lk5crypto -lcom_err" ]) AC_SEARCH_LIBS([dn_expand], [resolv]) AC_CHECK_LIB([gssapi_krb5], [gss_init_sec_context], [ AC_DEFINE([GSSAPI]) GSSLIBS="-lgssapi_krb5" ], [ AC_CHECK_LIB([gssapi], [gss_init_sec_context], [ AC_DEFINE([GSSAPI]) GSSLIBS="-lgssapi" ], [ AC_CHECK_LIB([gss], [gss_init_sec_context], [ AC_DEFINE([GSSAPI]) GSSLIBS="-lgss" ], AC_MSG_WARN([Cannot find any suitable gss-api library - build may fail])) ]) ]) AC_CHECK_HEADER([gssapi.h], , [ unset ac_cv_header_gssapi_h CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include/gssapi" AC_CHECK_HEADERS([gssapi.h], , AC_MSG_WARN([Cannot find any suitable gss-api header - build may fail]) ) ] ) oldCPP="$CPPFLAGS" CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include/gssapi" AC_CHECK_HEADER([gssapi_krb5.h], , [ CPPFLAGS="$oldCPP" ]) fi fi if test -n "${rpath_opt}" ; then LDFLAGS="$LDFLAGS ${rpath_opt}${KRB5ROOT}/lib" fi if test ! -z "$blibpath" ; then blibpath="$blibpath:${KRB5ROOT}/lib" fi AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h]) AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h]) AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h]) AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1], [Define this if you want to use libkafs' AFS support])]) AC_CHECK_DECLS([GSS_C_NT_HOSTBASED_SERVICE], [], [], [[ #ifdef HAVE_GSSAPI_H # include #elif defined(HAVE_GSSAPI_GSSAPI_H) # include #endif #ifdef HAVE_GSSAPI_GENERIC_H # include #elif defined(HAVE_GSSAPI_GSSAPI_GENERIC_H) # include #endif ]]) saved_LIBS="$LIBS" LIBS="$LIBS $K5LIBS" AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message]) LIBS="$saved_LIBS" fi ] ) AC_SUBST([GSSLIBS]) AC_SUBST([K5LIBS]) AC_SUBST([CHANNELLIBS]) # Looking for programs, paths and files PRIVSEP_PATH=/var/empty AC_ARG_WITH([privsep-path], [ --with-privsep-path=xxx Path for privilege separation chroot (default=/var/empty)], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then PRIVSEP_PATH=$withval fi ] ) AC_SUBST([PRIVSEP_PATH]) AC_ARG_WITH([xauth], [ --with-xauth=PATH Specify path to xauth program ], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then xauth_path=$withval fi ], [ TestPath="$PATH" TestPath="${TestPath}${PATH_SEPARATOR}/usr/X/bin" TestPath="${TestPath}${PATH_SEPARATOR}/usr/bin/X11" TestPath="${TestPath}${PATH_SEPARATOR}/usr/X11R6/bin" TestPath="${TestPath}${PATH_SEPARATOR}/usr/openwin/bin" AC_PATH_PROG([xauth_path], [xauth], , [$TestPath]) if (test ! -z "$xauth_path" && test -x "/usr/openwin/bin/xauth") ; then xauth_path="/usr/openwin/bin/xauth" fi ] ) STRIP_OPT=-s AC_ARG_ENABLE([strip], [ --disable-strip Disable calling strip(1) on install], [ if test "x$enableval" = "xno" ; then STRIP_OPT= fi ] ) AC_SUBST([STRIP_OPT]) if test -z "$xauth_path" ; then XAUTH_PATH="undefined" AC_SUBST([XAUTH_PATH]) else AC_DEFINE_UNQUOTED([XAUTH_PATH], ["$xauth_path"], [Define if xauth is found in your path]) XAUTH_PATH=$xauth_path AC_SUBST([XAUTH_PATH]) fi dnl # --with-maildir=/path/to/mail gets top priority. dnl # if maildir is set in the platform case statement above we use that. dnl # Otherwise we run a program to get the dir from system headers. dnl # We first look for _PATH_MAILDIR then MAILDIR then _PATH_MAIL dnl # If we find _PATH_MAILDIR we do nothing because that is what dnl # session.c expects anyway. Otherwise we set to the value found dnl # stripping any trailing slash. If for some strage reason our program dnl # does not find what it needs, we default to /var/spool/mail. # Check for mail directory AC_ARG_WITH([maildir], [ --with-maildir=/path/to/mail Specify your system mail directory], [ if test "X$withval" != X && test "x$withval" != xno && \ test "x${withval}" != xyes; then AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["$withval"], [Set this to your mail directory if you do not have _PATH_MAILDIR]) fi ],[ if test "X$maildir" != "X"; then AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["$maildir"]) else AC_MSG_CHECKING([Discovering system mail directory]) AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ #include #include #include #ifdef HAVE_PATHS_H #include #endif #ifdef HAVE_MAILLOCK_H #include #endif #define DATA "conftest.maildir" ]], [[ FILE *fd; int rc; fd = fopen(DATA,"w"); if(fd == NULL) exit(1); #if defined (_PATH_MAILDIR) if ((rc = fprintf(fd ,"_PATH_MAILDIR:%s\n", _PATH_MAILDIR)) <0) exit(1); #elif defined (MAILDIR) if ((rc = fprintf(fd ,"MAILDIR:%s\n", MAILDIR)) <0) exit(1); #elif defined (_PATH_MAIL) if ((rc = fprintf(fd ,"_PATH_MAIL:%s\n", _PATH_MAIL)) <0) exit(1); #else exit (2); #endif exit(0); ]])], [ maildir_what=`awk -F: '{print $1}' conftest.maildir` maildir=`awk -F: '{print $2}' conftest.maildir \ | sed 's|/$||'` AC_MSG_RESULT([Using: $maildir from $maildir_what]) if test "x$maildir_what" != "x_PATH_MAILDIR"; then AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["$maildir"]) fi ], [ if test "X$ac_status" = "X2";then # our test program didn't find it. Default to /var/spool/mail AC_MSG_RESULT([Using: default value of /var/spool/mail]) AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["/var/spool/mail"]) else AC_MSG_RESULT([*** not found ***]) fi ], [ AC_MSG_WARN([cross compiling: use --with-maildir=/path/to/mail]) ] ) fi ] ) # maildir if test ! -z "$cross_compiling" && test "x$cross_compiling" = "xyes"; then AC_MSG_WARN([cross compiling: Disabling /dev/ptmx test]) disable_ptmx_check=yes fi if test -z "$no_dev_ptmx" ; then if test "x$disable_ptmx_check" != "xyes" ; then AC_CHECK_FILE(["/dev/ptmx"], [ AC_DEFINE_UNQUOTED([HAVE_DEV_PTMX], [1], [Define if you have /dev/ptmx]) have_dev_ptmx=1 ] ) fi fi if test ! -z "$cross_compiling" && test "x$cross_compiling" != "xyes"; then AC_CHECK_FILE(["/dev/ptc"], [ AC_DEFINE_UNQUOTED([HAVE_DEV_PTS_AND_PTC], [1], [Define if you have /dev/ptc]) have_dev_ptc=1 ] ) else AC_MSG_WARN([cross compiling: Disabling /dev/ptc test]) fi # Options from here on. Some of these are preset by platform above AC_ARG_WITH([mantype], [ --with-mantype=man|cat|doc Set man page type], [ case "$withval" in man|cat|doc) MANTYPE=$withval ;; *) AC_MSG_ERROR([invalid man type: $withval]) ;; esac ] ) if test -z "$MANTYPE"; then if ${MANDOC} ${srcdir}/ssh.1 >/dev/null 2>&1; then MANTYPE=doc elif ${NROFF} -mdoc ${srcdir}/ssh.1 >/dev/null 2>&1; then MANTYPE=doc elif ${NROFF} -man ${srcdir}/ssh.1 >/dev/null 2>&1; then MANTYPE=man else MANTYPE=cat fi fi AC_SUBST([MANTYPE]) if test "$MANTYPE" = "doc"; then mansubdir=man; else mansubdir=$MANTYPE; fi AC_SUBST([mansubdir]) # Whether to disable shadow password support AC_ARG_WITH([shadow], [ --without-shadow Disable shadow password support], [ if test "x$withval" = "xno" ; then AC_DEFINE([DISABLE_SHADOW]) disable_shadow=yes fi ] ) if test -z "$disable_shadow" ; then AC_MSG_CHECKING([if the systems has expire shadow information]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include struct spwd sp; ]], [[ sp.sp_expire = sp.sp_lstchg = sp.sp_inact = 0; ]])], [ sp_expire_available=yes ], [ ]) if test "x$sp_expire_available" = "xyes" ; then AC_MSG_RESULT([yes]) AC_DEFINE([HAS_SHADOW_EXPIRE], [1], [Define if you want to use shadow password expire field]) else AC_MSG_RESULT([no]) fi fi # Use ip address instead of hostname in $DISPLAY if test ! -z "$IPADDR_IN_DISPLAY" ; then DISPLAY_HACK_MSG="yes" AC_DEFINE([IPADDR_IN_DISPLAY], [1], [Define if you need to use IP address instead of hostname in $DISPLAY]) else DISPLAY_HACK_MSG="no" AC_ARG_WITH([ipaddr-display], [ --with-ipaddr-display Use ip address instead of hostname in $DISPLAY], [ if test "x$withval" != "xno" ; then AC_DEFINE([IPADDR_IN_DISPLAY]) DISPLAY_HACK_MSG="yes" fi ] ) fi # check for /etc/default/login and use it if present. AC_ARG_ENABLE([etc-default-login], [ --disable-etc-default-login Disable using PATH from /etc/default/login [no]], [ if test "x$enableval" = "xno"; then AC_MSG_NOTICE([/etc/default/login handling disabled]) etc_default_login=no else etc_default_login=yes fi ], [ if test ! -z "$cross_compiling" && test "x$cross_compiling" = "xyes"; then AC_MSG_WARN([cross compiling: not checking /etc/default/login]) etc_default_login=no else etc_default_login=yes fi ] ) if test "x$etc_default_login" != "xno"; then AC_CHECK_FILE(["/etc/default/login"], [ external_path_file=/etc/default/login ]) if test "x$external_path_file" = "x/etc/default/login"; then AC_DEFINE([HAVE_ETC_DEFAULT_LOGIN], [1], [Define if your system has /etc/default/login]) fi fi dnl BSD systems use /etc/login.conf so --with-default-path= has no effect if test $ac_cv_func_login_getcapbool = "yes" && \ test $ac_cv_header_login_cap_h = "yes" ; then external_path_file=/etc/login.conf fi # Whether to mess with the default path SERVER_PATH_MSG="(default)" AC_ARG_WITH([default-path], [ --with-default-path= Specify default $PATH environment for server], [ if test "x$external_path_file" = "x/etc/login.conf" ; then AC_MSG_WARN([ --with-default-path=PATH has no effect on this system. Edit /etc/login.conf instead.]) elif test "x$withval" != "xno" ; then if test ! -z "$external_path_file" ; then AC_MSG_WARN([ --with-default-path=PATH will only be used if PATH is not defined in $external_path_file .]) fi user_path="$withval" SERVER_PATH_MSG="$withval" fi ], [ if test "x$external_path_file" = "x/etc/login.conf" ; then AC_MSG_WARN([Make sure the path to scp is in /etc/login.conf]) else if test ! -z "$external_path_file" ; then AC_MSG_WARN([ If PATH is defined in $external_path_file, ensure the path to scp is included, otherwise scp will not work.]) fi AC_RUN_IFELSE( [AC_LANG_PROGRAM([[ /* find out what STDPATH is */ #include #include #ifdef HAVE_PATHS_H # include #endif #ifndef _PATH_STDPATH # ifdef _PATH_USERPATH /* Irix */ # define _PATH_STDPATH _PATH_USERPATH # else # define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin" # endif #endif #include #include #include #define DATA "conftest.stdpath" ]], [[ FILE *fd; int rc; fd = fopen(DATA,"w"); if(fd == NULL) exit(1); if ((rc = fprintf(fd,"%s", _PATH_STDPATH)) < 0) exit(1); exit(0); ]])], [ user_path=`cat conftest.stdpath` ], [ user_path="/usr/bin:/bin:/usr/sbin:/sbin" ], [ user_path="/usr/bin:/bin:/usr/sbin:/sbin" ] ) # make sure $bindir is in USER_PATH so scp will work t_bindir="${bindir}" while echo "${t_bindir}" | egrep '\$\{|NONE/' >/dev/null 2>&1; do t_bindir=`eval echo ${t_bindir}` case $t_bindir in NONE/*) t_bindir=`echo $t_bindir | sed "s~NONE~$prefix~"` ;; esac case $t_bindir in NONE/*) t_bindir=`echo $t_bindir | sed "s~NONE~$ac_default_prefix~"` ;; esac done echo $user_path | grep ":$t_bindir" > /dev/null 2>&1 if test $? -ne 0 ; then echo $user_path | grep "^$t_bindir" > /dev/null 2>&1 if test $? -ne 0 ; then user_path=$user_path:$t_bindir AC_MSG_RESULT([Adding $t_bindir to USER_PATH so scp will work]) fi fi fi ] ) if test "x$external_path_file" != "x/etc/login.conf" ; then AC_DEFINE_UNQUOTED([USER_PATH], ["$user_path"], [Specify default $PATH]) AC_SUBST([user_path]) fi # Set superuser path separately to user path AC_ARG_WITH([superuser-path], [ --with-superuser-path= Specify different path for super-user], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then AC_DEFINE_UNQUOTED([SUPERUSER_PATH], ["$withval"], [Define if you want a different $PATH for the superuser]) superuser_path=$withval fi ] ) AC_MSG_CHECKING([if we need to convert IPv4 in IPv6-mapped addresses]) IPV4_IN6_HACK_MSG="no" AC_ARG_WITH(4in6, [ --with-4in6 Check for and convert IPv4 in IPv6 mapped addresses], [ if test "x$withval" != "xno" ; then AC_MSG_RESULT([yes]) AC_DEFINE([IPV4_IN_IPV6], [1], [Detect IPv4 in IPv6 mapped addresses and treat as IPv4]) IPV4_IN6_HACK_MSG="yes" else AC_MSG_RESULT([no]) fi ], [ if test "x$inet6_default_4in6" = "xyes"; then AC_MSG_RESULT([yes (default)]) AC_DEFINE([IPV4_IN_IPV6]) IPV4_IN6_HACK_MSG="yes" else AC_MSG_RESULT([no (default)]) fi ] ) # Whether to enable BSD auth support BSD_AUTH_MSG=no AC_ARG_WITH([bsd-auth], [ --with-bsd-auth Enable BSD auth support], [ if test "x$withval" != "xno" ; then AC_DEFINE([BSD_AUTH], [1], [Define if you have BSD auth support]) BSD_AUTH_MSG=yes fi ] ) # Where to place sshd.pid piddir=/var/run # make sure the directory exists if test ! -d $piddir ; then piddir=`eval echo ${sysconfdir}` case $piddir in NONE/*) piddir=`echo $piddir | sed "s~NONE~$ac_default_prefix~"` ;; esac fi AC_ARG_WITH([pid-dir], [ --with-pid-dir=PATH Specify location of sshd.pid file], [ if test -n "$withval" && test "x$withval" != "xno" && \ test "x${withval}" != "xyes"; then piddir=$withval if test ! -d $piddir ; then AC_MSG_WARN([** no $piddir directory on this system **]) fi fi ] ) AC_DEFINE_UNQUOTED([_PATH_SSH_PIDDIR], ["$piddir"], [Specify location of ssh.pid]) AC_SUBST([piddir]) dnl allow user to disable some login recording features AC_ARG_ENABLE([lastlog], [ --disable-lastlog disable use of lastlog even if detected [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_LASTLOG]) fi ] ) AC_ARG_ENABLE([utmp], [ --disable-utmp disable use of utmp even if detected [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_UTMP]) fi ] ) AC_ARG_ENABLE([utmpx], [ --disable-utmpx disable use of utmpx even if detected [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_UTMPX], [1], [Define if you don't want to use utmpx]) fi ] ) AC_ARG_ENABLE([wtmp], [ --disable-wtmp disable use of wtmp even if detected [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_WTMP]) fi ] ) AC_ARG_ENABLE([wtmpx], [ --disable-wtmpx disable use of wtmpx even if detected [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_WTMPX], [1], [Define if you don't want to use wtmpx]) fi ] ) AC_ARG_ENABLE([libutil], [ --disable-libutil disable use of libutil (login() etc.) [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_LOGIN]) fi ] ) AC_ARG_ENABLE([pututline], [ --disable-pututline disable use of pututline() etc. ([uw]tmp) [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_PUTUTLINE], [1], [Define if you don't want to use pututline() etc. to write [uw]tmp]) fi ] ) AC_ARG_ENABLE([pututxline], [ --disable-pututxline disable use of pututxline() etc. ([uw]tmpx) [no]], [ if test "x$enableval" = "xno" ; then AC_DEFINE([DISABLE_PUTUTXLINE], [1], [Define if you don't want to use pututxline() etc. to write [uw]tmpx]) fi ] ) AC_ARG_WITH([lastlog], [ --with-lastlog=FILE|DIR specify lastlog location [common locations]], [ if test "x$withval" = "xno" ; then AC_DEFINE([DISABLE_LASTLOG]) elif test -n "$withval" && test "x${withval}" != "xyes"; then conf_lastlog_location=$withval fi ] ) dnl lastlog, [uw]tmpx? detection dnl NOTE: set the paths in the platform section to avoid the dnl need for command-line parameters dnl lastlog and [uw]tmp are subject to a file search if all else fails dnl lastlog detection dnl NOTE: the code itself will detect if lastlog is a directory AC_MSG_CHECKING([if your system defines LASTLOG_FILE]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #ifdef HAVE_LASTLOG_H # include #endif #ifdef HAVE_PATHS_H # include #endif #ifdef HAVE_LOGIN_H # include #endif ]], [[ char *lastlog = LASTLOG_FILE; ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_MSG_CHECKING([if your system defines _PATH_LASTLOG]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #ifdef HAVE_LASTLOG_H # include #endif #ifdef HAVE_PATHS_H # include #endif ]], [[ char *lastlog = _PATH_LASTLOG; ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) system_lastlog_path=no ]) ]) if test -z "$conf_lastlog_location"; then if test x"$system_lastlog_path" = x"no" ; then for f in /var/log/lastlog /usr/adm/lastlog /var/adm/lastlog /etc/security/lastlog ; do if (test -d "$f" || test -f "$f") ; then conf_lastlog_location=$f fi done if test -z "$conf_lastlog_location"; then AC_MSG_WARN([** Cannot find lastlog **]) dnl Don't define DISABLE_LASTLOG - that means we don't try wtmp/wtmpx fi fi fi if test -n "$conf_lastlog_location"; then AC_DEFINE_UNQUOTED([CONF_LASTLOG_FILE], ["$conf_lastlog_location"], [Define if you want to specify the path to your lastlog file]) fi dnl utmp detection AC_MSG_CHECKING([if your system defines UTMP_FILE]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #ifdef HAVE_PATHS_H # include #endif ]], [[ char *utmp = UTMP_FILE; ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) system_utmp_path=no ]) if test -z "$conf_utmp_location"; then if test x"$system_utmp_path" = x"no" ; then for f in /etc/utmp /usr/adm/utmp /var/run/utmp; do if test -f $f ; then conf_utmp_location=$f fi done if test -z "$conf_utmp_location"; then AC_DEFINE([DISABLE_UTMP]) fi fi fi if test -n "$conf_utmp_location"; then AC_DEFINE_UNQUOTED([CONF_UTMP_FILE], ["$conf_utmp_location"], [Define if you want to specify the path to your utmp file]) fi dnl wtmp detection AC_MSG_CHECKING([if your system defines WTMP_FILE]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #ifdef HAVE_PATHS_H # include #endif ]], [[ char *wtmp = WTMP_FILE; ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) system_wtmp_path=no ]) if test -z "$conf_wtmp_location"; then if test x"$system_wtmp_path" = x"no" ; then for f in /usr/adm/wtmp /var/log/wtmp; do if test -f $f ; then conf_wtmp_location=$f fi done if test -z "$conf_wtmp_location"; then AC_DEFINE([DISABLE_WTMP]) fi fi fi if test -n "$conf_wtmp_location"; then AC_DEFINE_UNQUOTED([CONF_WTMP_FILE], ["$conf_wtmp_location"], [Define if you want to specify the path to your wtmp file]) fi dnl wtmpx detection AC_MSG_CHECKING([if your system defines WTMPX_FILE]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #ifdef HAVE_UTMPX_H #include #endif #ifdef HAVE_PATHS_H # include #endif ]], [[ char *wtmpx = WTMPX_FILE; ]])], [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) system_wtmpx_path=no ]) if test -z "$conf_wtmpx_location"; then if test x"$system_wtmpx_path" = x"no" ; then AC_DEFINE([DISABLE_WTMPX]) fi else AC_DEFINE_UNQUOTED([CONF_WTMPX_FILE], ["$conf_wtmpx_location"], [Define if you want to specify the path to your wtmpx file]) fi if test ! -z "$blibpath" ; then LDFLAGS="$LDFLAGS $blibflags$blibpath" AC_MSG_WARN([Please check and edit blibpath in LDFLAGS in Makefile]) fi AC_CHECK_MEMBER([struct lastlog.ll_line], [], [ if test x$SKIP_DISABLE_LASTLOG_DEFINE != "xyes" ; then AC_DEFINE([DISABLE_LASTLOG]) fi ], [ #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_UTMP_H #include #endif #ifdef HAVE_UTMPX_H #include #endif #ifdef HAVE_LASTLOG_H #include #endif ]) AC_CHECK_MEMBER([struct utmp.ut_line], [], [ AC_DEFINE([DISABLE_UTMP]) AC_DEFINE([DISABLE_WTMP]) ], [ #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_UTMP_H #include #endif #ifdef HAVE_UTMPX_H #include #endif #ifdef HAVE_LASTLOG_H #include #endif ]) dnl Adding -Werror to CFLAGS early prevents configure tests from running. dnl Add now. CFLAGS="$CFLAGS $werror_flags" if test "x$ac_cv_func_getaddrinfo" != "xyes" ; then TEST_SSH_IPV6=no else TEST_SSH_IPV6=yes fi AC_CHECK_DECL([BROKEN_GETADDRINFO], [TEST_SSH_IPV6=no]) AC_SUBST([TEST_SSH_IPV6], [$TEST_SSH_IPV6]) AC_SUBST([TEST_SSH_UTF8], [$TEST_SSH_UTF8]) AC_SUBST([TEST_MALLOC_OPTIONS], [$TEST_MALLOC_OPTIONS]) AC_SUBST([UNSUPPORTED_ALGORITHMS], [$unsupported_algorithms]) AC_SUBST([DEPEND], [$(cat $srcdir/.depend)]) CFLAGS="${CFLAGS} ${CFLAGS_AFTER}" LDFLAGS="${LDFLAGS} ${LDFLAGS_AFTER}" # Make a copy of CFLAGS/LDFLAGS without PIE options. LDFLAGS_NOPIE=`echo "$LDFLAGS" | sed 's/ -pie//'` CFLAGS_NOPIE=`echo "$CFLAGS" | sed 's/ -fPIE//'` AC_SUBST([LDFLAGS_NOPIE]) AC_SUBST([CFLAGS_NOPIE]) AC_EXEEXT AC_CONFIG_FILES([Makefile buildpkg.sh opensshd.init openssh.xml \ openbsd-compat/Makefile openbsd-compat/regress/Makefile \ survey.sh]) AC_OUTPUT # Print summary of options # Someone please show me a better way :) A=`eval echo ${prefix}` ; A=`eval echo ${A}` B=`eval echo ${bindir}` ; B=`eval echo ${B}` C=`eval echo ${sbindir}` ; C=`eval echo ${C}` D=`eval echo ${sysconfdir}` ; D=`eval echo ${D}` E=`eval echo ${libexecdir}/ssh-askpass` ; E=`eval echo ${E}` F=`eval echo ${mandir}/${mansubdir}X` ; F=`eval echo ${F}` G=`eval echo ${piddir}` ; G=`eval echo ${G}` H=`eval echo ${PRIVSEP_PATH}` ; H=`eval echo ${H}` I=`eval echo ${user_path}` ; I=`eval echo ${I}` J=`eval echo ${superuser_path}` ; J=`eval echo ${J}` echo "" echo "OpenSSH has been configured with the following options:" echo " User binaries: $B" echo " System binaries: $C" echo " Configuration files: $D" echo " Askpass program: $E" echo " Manual pages: $F" echo " PID file: $G" echo " Privilege separation chroot path: $H" if test "x$external_path_file" = "x/etc/login.conf" ; then echo " At runtime, sshd will use the path defined in $external_path_file" echo " Make sure the path to scp is present, otherwise scp will not work" else echo " sshd default user PATH: $I" if test ! -z "$external_path_file"; then echo " (If PATH is set in $external_path_file it will be used instead. If" echo " used, ensure the path to scp is present, otherwise scp will not work.)" fi fi if test ! -z "$superuser_path" ; then echo " sshd superuser user PATH: $J" fi echo " Manpage format: $MANTYPE" echo " PAM support: $PAM_MSG" echo " OSF SIA support: $SIA_MSG" echo " KerberosV support: $KRB5_MSG" echo " SELinux support: $SELINUX_MSG" echo " TCP Wrappers support: $TCPW_MSG" echo " libedit support: $LIBEDIT_MSG" echo " libldns support: $LDNS_MSG" echo " Solaris process contract support: $SPC_MSG" echo " Solaris project support: $SP_MSG" echo " Solaris privilege support: $SPP_MSG" echo " IP address in \$DISPLAY hack: $DISPLAY_HACK_MSG" echo " Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG" echo " BSD Auth support: $BSD_AUTH_MSG" echo " Random number source: $RAND_MSG" echo " Privsep sandbox style: $SANDBOX_STYLE" echo " PKCS#11 support: $enable_pkcs11" echo " U2F/FIDO support: $enable_sk" echo "" echo " Host: ${host}" echo " Compiler: ${CC}" echo " Compiler flags: ${CFLAGS}" echo "Preprocessor flags: ${CPPFLAGS}" echo " Linker flags: ${LDFLAGS}" echo " Libraries: ${LIBS}" if test ! -z "${CHANNELLIBS}"; then echo " +for channels: ${CHANNELLIBS}" fi if test ! -z "${LIBFIDO2}"; then echo " +for FIDO2: ${LIBFIDO2}" fi if test ! -z "${SSHDLIBS}"; then echo " +for sshd: ${SSHDLIBS}" fi echo "" if test "x$MAKE_PACKAGE_SUPPORTED" = "xyes" ; then echo "SVR4 style packages are supported with \"make package\"" echo "" fi if test "x$PAM_MSG" = "xyes" ; then echo "PAM is enabled. You may need to install a PAM control file " echo "for sshd, otherwise password authentication may fail. " echo "Example PAM control files can be found in the contrib/ " echo "subdirectory" echo "" fi if test ! -z "$NO_PEERCHECK" ; then echo "WARNING: the operating system that you are using does not" echo "appear to support getpeereid(), getpeerucred() or the" echo "SO_PEERCRED getsockopt() option. These facilities are used to" echo "enforce security checks to prevent unauthorised connections to" echo "ssh-agent. Their absence increases the risk that a malicious" echo "user can connect to your agent." echo "" fi if test "$AUDIT_MODULE" = "bsm" ; then echo "WARNING: BSM audit support is currently considered EXPERIMENTAL." echo "See the Solaris section in README.platform for details." fi diff --git a/crypto/openssh/contrib/redhat/openssh.spec b/crypto/openssh/contrib/redhat/openssh.spec index 7d6fe3cfd2fe..7a167d4456d1 100644 --- a/crypto/openssh/contrib/redhat/openssh.spec +++ b/crypto/openssh/contrib/redhat/openssh.spec @@ -1,850 +1,850 @@ -%global ver 9.4p1 +%global ver 9.5p1 %global rel 1%{?dist} # OpenSSH privilege separation requires a user & group ID %global sshd_uid 74 %global sshd_gid 74 # Version of ssh-askpass %global aversion 1.2.4.1 # Do we want to disable building of x11-askpass? (1=yes 0=no) %global no_x11_askpass 0 # Do we want to disable building of gnome-askpass? (1=yes 0=no) %global no_gnome_askpass 0 # Do we want to link against a static libcrypto? (1=yes 0=no) %global static_libcrypto 0 # Do we want smartcard support (1=yes 0=no) %global scard 0 # Use GTK2 instead of GNOME in gnome-ssh-askpass %global gtk2 1 # Use build6x options for older RHEL builds # RHEL 7 not yet supported %if 0%{?rhel} > 6 %global build6x 0 %else %global build6x 1 %endif %if 0%{?fedora} >= 26 %global compat_openssl 1 %else %global compat_openssl 0 %endif # Do we want kerberos5 support (1=yes 0=no) %global kerberos5 1 # Reserve options to override askpass settings with: # rpm -ba|--rebuild --define 'skip_xxx 1' %{?skip_x11_askpass:%global no_x11_askpass 1} %{?skip_gnome_askpass:%global no_gnome_askpass 1} # Add option to build without GTK2 for older platforms with only GTK+. # RedHat <= 7.2 and Red Hat Advanced Server 2.1 are examples. # rpm -ba|--rebuild --define 'no_gtk2 1' %{?no_gtk2:%global gtk2 0} # Is this a build for RHL 6.x or earlier? %{?build_6x:%global build6x 1} # If this is RHL 6.x, the default configuration has sysconfdir in /usr/etc. %if %{build6x} %global _sysconfdir /etc %endif # Options for static OpenSSL link: # rpm -ba|--rebuild --define "static_openssl 1" %{?static_openssl:%global static_libcrypto 1} # Options for Smartcard support: (needs libsectok and openssl-engine) # rpm -ba|--rebuild --define "smartcard 1" %{?smartcard:%global scard 1} # Is this a build for the rescue CD (without PAM)? (1=yes 0=no) %global rescue 0 %{?build_rescue:%global rescue 1} # Turn off some stuff for resuce builds %if %{rescue} %global kerberos5 0 %endif Summary: The OpenSSH implementation of SSH protocol version 2. Name: openssh Version: %{ver} %if %{rescue} Release: %{rel}rescue %else Release: %{rel} %endif URL: https://www.openssh.com/portable.html Source0: https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz Source1: http://www.jmknoble.net/software/x11-ssh-askpass/x11-ssh-askpass-%{aversion}.tar.gz License: BSD Group: Applications/Internet BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot Obsoletes: ssh %if %{build6x} PreReq: initscripts >= 5.00 %else Requires: initscripts >= 5.20 %endif BuildRequires: perl %if %{compat_openssl} BuildRequires: compat-openssl10-devel %else BuildRequires: openssl-devel >= 1.0.1 BuildRequires: openssl-devel < 1.1 %endif BuildRequires: /bin/login %if ! %{build6x} BuildRequires: glibc-devel, pam %else BuildRequires: /usr/include/security/pam_appl.h %endif %if ! %{no_x11_askpass} BuildRequires: /usr/include/X11/Xlib.h # Xt development tools BuildRequires: libXt-devel # Provides xmkmf BuildRequires: imake # Rely on relatively recent gtk BuildRequires: gtk2-devel %endif %if ! %{no_gnome_askpass} BuildRequires: pkgconfig %endif %if %{kerberos5} BuildRequires: krb5-devel BuildRequires: krb5-libs %endif %package clients Summary: OpenSSH clients. Requires: openssh = %{version}-%{release} Group: Applications/Internet Obsoletes: ssh-clients %package server Summary: The OpenSSH server daemon. Group: System Environment/Daemons Obsoletes: ssh-server Requires: openssh = %{version}-%{release}, chkconfig >= 0.9 %if ! %{build6x} Requires: /etc/pam.d/system-auth %endif %package askpass Summary: A passphrase dialog for OpenSSH and X. Group: Applications/Internet Requires: openssh = %{version}-%{release} Obsoletes: ssh-extras %package askpass-gnome Summary: A passphrase dialog for OpenSSH, X, and GNOME. Group: Applications/Internet Requires: openssh = %{version}-%{release} Obsoletes: ssh-extras %description SSH (Secure SHell) is a program for logging into and executing commands on a remote machine. SSH is intended to replace rlogin and rsh, and to provide secure encrypted communications between two untrusted hosts over an insecure network. X11 connections and arbitrary TCP/IP ports can also be forwarded over the secure channel. OpenSSH is OpenBSD's version of the last free version of SSH, bringing it up to date in terms of security and features, as well as removing all patented algorithms to separate libraries. This package includes the core files necessary for both the OpenSSH client and server. To make this package useful, you should also install openssh-clients, openssh-server, or both. %description clients OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package includes the clients necessary to make encrypted connections to SSH servers. You'll also need to install the openssh package on OpenSSH clients. %description server OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package contains the secure shell daemon (sshd). The sshd daemon allows SSH clients to securely connect to your SSH server. You also need to have the openssh package installed. %description askpass OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package contains an X11 passphrase dialog for OpenSSH. %description askpass-gnome OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package contains an X11 passphrase dialog for OpenSSH and the GNOME GUI desktop environment. %prep %if ! %{no_x11_askpass} %setup -q -a 1 %else %setup -q %endif %build %if %{rescue} CFLAGS="$RPM_OPT_FLAGS -Os"; export CFLAGS %endif %configure \ --sysconfdir=%{_sysconfdir}/ssh \ --libexecdir=%{_libexecdir}/openssh \ --datadir=%{_datadir}/openssh \ --with-default-path=/usr/local/bin:/bin:/usr/bin \ --with-superuser-path=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin \ --with-privsep-path=%{_var}/empty/sshd \ --mandir=%{_mandir} \ --with-mantype=man \ --disable-strip \ %if %{scard} --with-smartcard \ %endif %if %{rescue} --without-pam \ %else --with-pam \ %endif %if %{kerberos5} --with-kerberos5=$K5DIR \ %endif %if %{static_libcrypto} perl -pi -e "s|-lcrypto|%{_libdir}/libcrypto.a|g" Makefile %endif make %if ! %{no_x11_askpass} pushd x11-ssh-askpass-%{aversion} %configure --libexecdir=%{_libexecdir}/openssh xmkmf -a make popd %endif # Define a variable to toggle gnome1/gtk2 building. This is necessary # because RPM doesn't handle nested %if statements. %if %{gtk2} gtk2=yes %else gtk2=no %endif %if ! %{no_gnome_askpass} pushd contrib if [ $gtk2 = yes ] ; then make gnome-ssh-askpass2 mv gnome-ssh-askpass2 gnome-ssh-askpass else make gnome-ssh-askpass1 mv gnome-ssh-askpass1 gnome-ssh-askpass fi popd %endif %install rm -rf $RPM_BUILD_ROOT mkdir -p -m755 $RPM_BUILD_ROOT%{_sysconfdir}/ssh mkdir -p -m755 $RPM_BUILD_ROOT%{_libexecdir}/openssh mkdir -p -m755 $RPM_BUILD_ROOT%{_var}/empty/sshd make install DESTDIR=$RPM_BUILD_ROOT install -d $RPM_BUILD_ROOT/etc/pam.d/ install -d $RPM_BUILD_ROOT/etc/rc.d/init.d install -d $RPM_BUILD_ROOT%{_libexecdir}/openssh %if %{build6x} install -m644 contrib/redhat/sshd.pam.old $RPM_BUILD_ROOT/etc/pam.d/sshd %else install -m644 contrib/redhat/sshd.pam $RPM_BUILD_ROOT/etc/pam.d/sshd %endif install -m755 contrib/redhat/sshd.init $RPM_BUILD_ROOT/etc/rc.d/init.d/sshd %if ! %{no_x11_askpass} install x11-ssh-askpass-%{aversion}/x11-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/x11-ssh-askpass ln -s x11-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/ssh-askpass %endif %if ! %{no_gnome_askpass} install contrib/gnome-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/gnome-ssh-askpass %endif %if ! %{scard} rm -f $RPM_BUILD_ROOT/usr/share/openssh/Ssh.bin %endif %if ! %{no_gnome_askpass} install -m 755 -d $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/ install -m 755 contrib/redhat/gnome-ssh-askpass.csh $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/ install -m 755 contrib/redhat/gnome-ssh-askpass.sh $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/ %endif perl -pi -e "s|$RPM_BUILD_ROOT||g" $RPM_BUILD_ROOT%{_mandir}/man*/* %clean rm -rf $RPM_BUILD_ROOT %triggerun server -- ssh-server if [ "$1" != 0 -a -r /var/run/sshd.pid ] ; then touch /var/run/sshd.restart fi %triggerun server -- openssh-server < 2.5.0p1 # Count the number of HostKey and HostDsaKey statements we have. gawk 'BEGIN {IGNORECASE=1} /^hostkey/ || /^hostdsakey/ {sawhostkey = sawhostkey + 1} END {exit sawhostkey}' /etc/ssh/sshd_config # And if we only found one, we know the client was relying on the old default # behavior, which loaded the the SSH2 DSA host key when HostDsaKey wasn't # specified. Now that HostKey is used for both SSH1 and SSH2 keys, specifying # one nullifies the default, which would have loaded both. if [ $? -eq 1 ] ; then echo HostKey /etc/ssh/ssh_host_rsa_key >> /etc/ssh/sshd_config echo HostKey /etc/ssh/ssh_host_dsa_key >> /etc/ssh/sshd_config fi %triggerpostun server -- ssh-server if [ "$1" != 0 ] ; then /sbin/chkconfig --add sshd if test -f /var/run/sshd.restart ; then rm -f /var/run/sshd.restart /sbin/service sshd start > /dev/null 2>&1 || : fi fi %pre server %{_sbindir}/groupadd -r -g %{sshd_gid} sshd 2>/dev/null || : %{_sbindir}/useradd -d /var/empty/sshd -s /bin/false -u %{sshd_uid} \ -g sshd -M -r sshd 2>/dev/null || : %post server /sbin/chkconfig --add sshd %postun server /sbin/service sshd condrestart > /dev/null 2>&1 || : %preun server if [ "$1" = 0 ] then /sbin/service sshd stop > /dev/null 2>&1 || : /sbin/chkconfig --del sshd fi %files %defattr(-,root,root) %doc CREDITS ChangeLog INSTALL LICENCE OVERVIEW README* PROTOCOL* TODO %attr(0755,root,root) %{_bindir}/scp %attr(0644,root,root) %{_mandir}/man1/scp.1* %attr(0755,root,root) %dir %{_sysconfdir}/ssh %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/moduli %if ! %{rescue} %attr(0755,root,root) %{_bindir}/ssh-keygen %attr(0644,root,root) %{_mandir}/man1/ssh-keygen.1* %attr(0755,root,root) %dir %{_libexecdir}/openssh %attr(4711,root,root) %{_libexecdir}/openssh/ssh-keysign %attr(0755,root,root) %{_libexecdir}/openssh/ssh-pkcs11-helper %attr(0755,root,root) %{_libexecdir}/openssh/ssh-sk-helper %attr(0644,root,root) %{_mandir}/man8/ssh-keysign.8* %attr(0644,root,root) %{_mandir}/man8/ssh-pkcs11-helper.8* %attr(0644,root,root) %{_mandir}/man8/ssh-sk-helper.8* %endif %if %{scard} %attr(0755,root,root) %dir %{_datadir}/openssh %attr(0644,root,root) %{_datadir}/openssh/Ssh.bin %endif %files clients %defattr(-,root,root) %attr(0755,root,root) %{_bindir}/ssh %attr(0644,root,root) %{_mandir}/man1/ssh.1* %attr(0644,root,root) %{_mandir}/man5/ssh_config.5* %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ssh/ssh_config %if ! %{rescue} %attr(2755,root,nobody) %{_bindir}/ssh-agent %attr(0755,root,root) %{_bindir}/ssh-add %attr(0755,root,root) %{_bindir}/ssh-keyscan %attr(0755,root,root) %{_bindir}/sftp %attr(0644,root,root) %{_mandir}/man1/ssh-agent.1* %attr(0644,root,root) %{_mandir}/man1/ssh-add.1* %attr(0644,root,root) %{_mandir}/man1/ssh-keyscan.1* %attr(0644,root,root) %{_mandir}/man1/sftp.1* %endif %if ! %{rescue} %files server %defattr(-,root,root) %dir %attr(0111,root,root) %{_var}/empty/sshd %attr(0755,root,root) %{_sbindir}/sshd %attr(0755,root,root) %{_libexecdir}/openssh/sftp-server %attr(0644,root,root) %{_mandir}/man8/sshd.8* %attr(0644,root,root) %{_mandir}/man5/moduli.5* %attr(0644,root,root) %{_mandir}/man5/sshd_config.5* %attr(0644,root,root) %{_mandir}/man8/sftp-server.8* %attr(0755,root,root) %dir %{_sysconfdir}/ssh %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/sshd_config %attr(0600,root,root) %config(noreplace) /etc/pam.d/sshd %attr(0755,root,root) %config /etc/rc.d/init.d/sshd %endif %if ! %{no_x11_askpass} %files askpass %defattr(-,root,root) %doc x11-ssh-askpass-%{aversion}/README %doc x11-ssh-askpass-%{aversion}/ChangeLog %doc x11-ssh-askpass-%{aversion}/SshAskpass*.ad %{_libexecdir}/openssh/ssh-askpass %attr(0755,root,root) %{_libexecdir}/openssh/x11-ssh-askpass %endif %if ! %{no_gnome_askpass} %files askpass-gnome %defattr(-,root,root) %attr(0755,root,root) %config %{_sysconfdir}/profile.d/gnome-ssh-askpass.* %attr(0755,root,root) %{_libexecdir}/openssh/gnome-ssh-askpass %endif %changelog * Thu Oct 28 2021 Damien Miller - Remove remaining traces of --with-md5-passwords * Mon Jul 20 2020 Damien Miller - Add ssh-sk-helper and corresponding manual page. * Sat Feb 10 2018 Darren Tucker - Update openssl-devel dependency to match current requirements. - Handle Fedora >=6 openssl 1.0 compat libs. - Remove SSH1 from description. - Don't strip binaries at build time so that debuginfo package can be created. * Sun Nov 16 2014 Nico Kadel-Garcia - Add '--mandir' and '--with-mantype' for RHEL 5 compatibility - Add 'dist' option to 'ver' so package names reflect OS at build time - Always include x11-ssh-askpass tarball in SRPM - Add openssh-x11-aspass BuildRequires for libXT-devel, imake, gtk2-devel - Discard 'K5DIR' reporting, not usable inside 'mock' for RHEL 5 compatibility - Discard obsolete '--with-rsh' configure option - Update openssl-devel dependency to 0.9.8f, as found in autoconf * Wed Jul 14 2010 Tim Rice - test for skip_x11_askpass (line 77) should have been for no_x11_askpass * Mon Jun 2 2003 Damien Miller - Remove noip6 option. This may be controlled at run-time in client config file using new AddressFamily directive * Mon May 12 2003 Damien Miller - Don't install profile.d scripts when not building with GNOME/GTK askpass (patch from bet@rahul.net) * Tue Oct 01 2002 Damien Miller - Install ssh-agent setgid nobody to prevent ptrace() key theft attacks * Mon Sep 30 2002 Damien Miller - Use contrib/ Makefile for building askpass programs * Fri Jun 21 2002 Damien Miller - Merge in spec changes from seba@iq.pl (Sebastian Pachuta) - Add new {ssh,sshd}_config.5 manpages - Add new ssh-keysign program and remove setuid from ssh client * Fri May 10 2002 Damien Miller - Merge in spec changes from RedHat, reorgansie a little - Add Privsep user, group and directory * Thu Mar 7 2002 Nalin Dahyabhai 3.1p1-2 - bump and grind (through the build system) * Thu Mar 7 2002 Nalin Dahyabhai 3.1p1-1 - require sharutils for building (mindrot #137) - require db1-devel only when building for 6.x (#55105), which probably won't work anyway (3.1 requires OpenSSL 0.9.6 to build), but what the heck - require pam-devel by file (not by package name) again - add Markus's patch to compile with OpenSSL 0.9.5a (from http://bugzilla.mindrot.org/show_bug.cgi?id=141) and apply it if we're building for 6.x * Thu Mar 7 2002 Nalin Dahyabhai 3.1p1-0 - update to 3.1p1 * Tue Mar 5 2002 Nalin Dahyabhai SNAP-20020305 - update to SNAP-20020305 - drop debug patch, fixed upstream * Wed Feb 20 2002 Nalin Dahyabhai SNAP-20020220 - update to SNAP-20020220 for testing purposes (you've been warned, if there's anything to be warned about, gss patches won't apply, I don't mind) * Wed Feb 13 2002 Nalin Dahyabhai 3.0.2p1-3 - add patches from Simon Wilkinson and Nicolas Williams for GSSAPI key exchange, authentication, and named key support * Wed Jan 23 2002 Nalin Dahyabhai 3.0.2p1-2 - remove dependency on db1-devel, which has just been swallowed up whole by gnome-libs-devel * Sat Dec 29 2001 Nalin Dahyabhai - adjust build dependencies so that build6x actually works right (fix from Hugo van der Kooij) * Tue Dec 4 2001 Nalin Dahyabhai 3.0.2p1-1 - update to 3.0.2p1 * Fri Nov 16 2001 Nalin Dahyabhai 3.0.1p1-1 - update to 3.0.1p1 * Tue Nov 13 2001 Nalin Dahyabhai - update to current CVS (not for use in distribution) * Thu Nov 8 2001 Nalin Dahyabhai 3.0p1-1 - merge some of Damien Miller changes from the upstream 3.0p1 spec file and init script * Wed Nov 7 2001 Nalin Dahyabhai - update to 3.0p1 - update to x11-ssh-askpass 1.2.4.1 - change build dependency on a file from pam-devel to the pam-devel package - replace primes with moduli * Thu Sep 27 2001 Nalin Dahyabhai 2.9p2-9 - incorporate fix from Markus Friedl's advisory for IP-based authorization bugs * Thu Sep 13 2001 Bernhard Rosenkraenzer 2.9p2-8 - Merge changes to rescue build from current sysadmin survival cd * Thu Sep 6 2001 Nalin Dahyabhai 2.9p2-7 - fix scp's server's reporting of file sizes, and build with the proper preprocessor define to get large-file capable open(), stat(), etc. (sftp has been doing this correctly all along) (#51827) - configure without --with-ipv4-default on RHL 7.x and newer (#45987,#52247) - pull cvs patch to fix support for /etc/nologin for non-PAM logins (#47298) - mark profile.d scriptlets as config files (#42337) - refer to Jason Stone's mail for zsh workaround for exit-hanging quasi-bug - change a couple of log() statements to debug() statements (#50751) - pull cvs patch to add -t flag to sshd (#28611) - clear fd_sets correctly (one bit per FD, not one byte per FD) (#43221) * Mon Aug 20 2001 Nalin Dahyabhai 2.9p2-6 - add db1-devel as a BuildPrerequisite (noted by Hans Ecke) * Thu Aug 16 2001 Nalin Dahyabhai - pull cvs patch to fix remote port forwarding with protocol 2 * Thu Aug 9 2001 Nalin Dahyabhai - pull cvs patch to add session initialization to no-pty sessions - pull cvs patch to not cut off challengeresponse auth needlessly - refuse to do X11 forwarding if xauth isn't there, handy if you enable it by default on a system that doesn't have X installed (#49263) * Wed Aug 8 2001 Nalin Dahyabhai - don't apply patches to code we don't intend to build (spotted by Matt Galgoci) * Mon Aug 6 2001 Nalin Dahyabhai - pass OPTIONS correctly to initlog (#50151) * Wed Jul 25 2001 Nalin Dahyabhai - switch to x11-ssh-askpass 1.2.2 * Wed Jul 11 2001 Nalin Dahyabhai - rebuild in new environment * Mon Jun 25 2001 Nalin Dahyabhai - disable the gssapi patch * Mon Jun 18 2001 Nalin Dahyabhai - update to 2.9p2 - refresh to a new version of the gssapi patch * Thu Jun 7 2001 Nalin Dahyabhai - change Copyright: BSD to License: BSD - add Markus Friedl's unverified patch for the cookie file deletion problem so that we can verify it - drop patch to check if xauth is present (was folded into cookie patch) - don't apply gssapi patches for the errata candidate - clear supplemental groups list at startup * Fri May 25 2001 Nalin Dahyabhai - fix an error parsing the new default sshd_config - add a fix from Markus Friedl (via openssh-unix-dev) for ssh-keygen not dealing with comments right * Thu May 24 2001 Nalin Dahyabhai - add in Simon Wilkinson's GSSAPI patch to give it some testing in-house, to be removed before the next beta cycle because it's a big departure from the upstream version * Thu May 3 2001 Nalin Dahyabhai - finish marking strings in the init script for translation - modify init script to source /etc/sysconfig/sshd and pass $OPTIONS to sshd at startup (change merged from openssh.com init script, originally by Pekka Savola) - refuse to do X11 forwarding if xauth isn't there, handy if you enable it by default on a system that doesn't have X installed * Wed May 2 2001 Nalin Dahyabhai - update to 2.9 - drop various patches that came from or went upstream or to or from CVS * Wed Apr 18 2001 Nalin Dahyabhai - only require initscripts 5.00 on 6.2 (reported by Peter Bieringer) * Sun Apr 8 2001 Preston Brown - remove explicit openssl requirement, fixes builddistro issue - make initscript stop() function wait until sshd really dead to avoid races in condrestart * Mon Apr 2 2001 Nalin Dahyabhai - mention that challengereponse supports PAM, so disabling password doesn't limit users to pubkey and rsa auth (#34378) - bypass the daemon() function in the init script and call initlog directly, because daemon() won't start a daemon it detects is already running (like open connections) - require the version of openssl we had when we were built * Fri Mar 23 2001 Nalin Dahyabhai - make do_pam_setcred() smart enough to know when to establish creds and when to reinitialize them - add in a couple of other fixes from Damien for inclusion in the errata * Thu Mar 22 2001 Nalin Dahyabhai - update to 2.5.2p2 - call setcred() again after initgroups, because the "creds" could actually be group memberships * Tue Mar 20 2001 Nalin Dahyabhai - update to 2.5.2p1 (includes endianness fixes in the rijndael implementation) - don't enable challenge-response by default until we find a way to not have too many userauth requests (we may make up to six pubkey and up to three password attempts as it is) - remove build dependency on rsh to match openssh.com's packages more closely * Sat Mar 3 2001 Nalin Dahyabhai - remove dependency on openssl -- would need to be too precise * Fri Mar 2 2001 Nalin Dahyabhai - rebuild in new environment * Mon Feb 26 2001 Nalin Dahyabhai - Revert the patch to move pam_open_session. - Init script and spec file changes from Pekka Savola. (#28750) - Patch sftp to recognize '-o protocol' arguments. (#29540) * Thu Feb 22 2001 Nalin Dahyabhai - Chuck the closing patch. - Add a trigger to add host keys for protocol 2 to the config file, now that configuration file syntax requires us to specify it with HostKey if we specify any other HostKey values, which we do. * Tue Feb 20 2001 Nalin Dahyabhai - Redo patch to move pam_open_session after the server setuid()s to the user. - Rework the nopam patch to use be picked up by autoconf. * Mon Feb 19 2001 Nalin Dahyabhai - Update for 2.5.1p1. - Add init script mods from Pekka Savola. - Tweak the init script to match the CVS contrib script more closely. - Redo patch to ssh-add to try to adding both identity and id_dsa to also try adding id_rsa. * Fri Feb 16 2001 Nalin Dahyabhai - Update for 2.5.0p1. - Use $RPM_OPT_FLAGS instead of -O when building gnome-ssh-askpass - Resync with parts of Damien Miller's openssh.spec from CVS, including update of x11 askpass to 1.2.0. - Only require openssl (don't prereq) because we generate keys in the init script now. * Tue Feb 13 2001 Nalin Dahyabhai - Don't open a PAM session until we've forked and become the user (#25690). - Apply Andrew Bartlett's patch for letting pam_authenticate() know which host the user is attempting a login from. - Resync with parts of Damien Miller's openssh.spec from CVS. - Don't expose KbdInt responses in debug messages (from CVS). - Detect and handle errors in rsa_{public,private}_decrypt (from CVS). * Wed Feb 7 2001 Trond Eivind Glomsrxd - i18n-tweak to initscript. * Tue Jan 23 2001 Nalin Dahyabhai - More gettextizing. - Close all files after going into daemon mode (needs more testing). - Extract patch from CVS to handle auth banners (in the client). - Extract patch from CVS to handle compat weirdness. * Fri Jan 19 2001 Nalin Dahyabhai - Finish with the gettextizing. * Thu Jan 18 2001 Nalin Dahyabhai - Fix a bug in auth2-pam.c (#23877) - Gettextize the init script. * Wed Dec 20 2000 Nalin Dahyabhai - Incorporate a switch for using PAM configs for 6.x, just in case. * Tue Dec 5 2000 Nalin Dahyabhai - Incorporate Bero's changes for a build specifically for rescue CDs. * Wed Nov 29 2000 Nalin Dahyabhai - Don't treat pam_setcred() failure as fatal unless pam_authenticate() has succeeded, to allow public-key authentication after a failure with "none" authentication. (#21268) * Tue Nov 28 2000 Nalin Dahyabhai - Update to x11-askpass 1.1.1. (#21301) - Don't second-guess fixpaths, which causes paths to get fixed twice. (#21290) * Mon Nov 27 2000 Nalin Dahyabhai - Merge multiple PAM text messages into subsequent prompts when possible when doing keyboard-interactive authentication. * Sun Nov 26 2000 Nalin Dahyabhai - Disable the built-in MD5 password support. We're using PAM. - Take a crack at doing keyboard-interactive authentication with PAM, and enable use of it in the default client configuration so that the client will try it when the server disallows password authentication. - Build with debugging flags. Build root policies strip all binaries anyway. * Tue Nov 21 2000 Nalin Dahyabhai - Use DESTDIR instead of %%makeinstall. - Remove /usr/X11R6/bin from the path-fixing patch. * Mon Nov 20 2000 Nalin Dahyabhai - Add the primes file from the latest snapshot to the main package (#20884). - Add the dev package to the prereq list (#19984). - Remove the default path and mimic login's behavior in the server itself. * Fri Nov 17 2000 Nalin Dahyabhai - Resync with conditional options in Damien Miller's .spec file for an errata. - Change libexecdir from %%{_libexecdir}/ssh to %%{_libexecdir}/openssh. * Tue Nov 7 2000 Nalin Dahyabhai - Update to OpenSSH 2.3.0p1. - Update to x11-askpass 1.1.0. - Enable keyboard-interactive authentication. * Mon Oct 30 2000 Nalin Dahyabhai - Update to ssh-askpass-x11 1.0.3. - Change authentication related messages to be private (#19966). * Tue Oct 10 2000 Nalin Dahyabhai - Patch ssh-keygen to be able to list signatures for DSA public key files it generates. * Thu Oct 5 2000 Nalin Dahyabhai - Add BuildRequires on /usr/include/security/pam_appl.h to be sure we always build PAM authentication in. - Try setting SSH_ASKPASS if gnome-ssh-askpass is installed. - Clean out no-longer-used patches. - Patch ssh-add to try to add both identity and id_dsa, and to error only when neither exists. * Mon Oct 2 2000 Nalin Dahyabhai - Update x11-askpass to 1.0.2. (#17835) - Add BuildRequiress for /bin/login and /usr/bin/rsh so that configure will always find them in the right place. (#17909) - Set the default path to be the same as the one supplied by /bin/login, but add /usr/X11R6/bin. (#17909) - Try to handle obsoletion of ssh-server more cleanly. Package names are different, but init script name isn't. (#17865) * Wed Sep 6 2000 Nalin Dahyabhai - Update to 2.2.0p1. (#17835) - Tweak the init script to allow proper restarting. (#18023) * Wed Aug 23 2000 Nalin Dahyabhai - Update to 20000823 snapshot. - Change subpackage requirements from %%{version} to %%{version}-%%{release} - Back out the pipe patch. * Mon Jul 17 2000 Nalin Dahyabhai - Update to 2.1.1p4, which includes fixes for config file parsing problems. - Move the init script back. - Add Damien's quick fix for wackiness. * Wed Jul 12 2000 Nalin Dahyabhai - Update to 2.1.1p3, which includes fixes for X11 forwarding and strtok(). * Thu Jul 6 2000 Nalin Dahyabhai - Move condrestart to server postun. - Move key generation to init script. - Actually use the right patch for moving the key generation to the init script. - Clean up the init script a bit. * Wed Jul 5 2000 Nalin Dahyabhai - Fix X11 forwarding, from mail post by Chan Shih-Ping Richard. * Sun Jul 2 2000 Nalin Dahyabhai - Update to 2.1.1p2. - Use of strtok() considered harmful. * Sat Jul 1 2000 Nalin Dahyabhai - Get the build root out of the man pages. * Thu Jun 29 2000 Nalin Dahyabhai - Add and use condrestart support in the init script. - Add newer initscripts as a prereq. * Tue Jun 27 2000 Nalin Dahyabhai - Build in new environment (release 2) - Move -clients subpackage to Applications/Internet group * Fri Jun 9 2000 Nalin Dahyabhai - Update to 2.2.1p1 * Sat Jun 3 2000 Nalin Dahyabhai - Patch to build with neither RSA nor RSAref. - Miscellaneous FHS-compliance tweaks. - Fix for possibly-compressed man pages. * Wed Mar 15 2000 Damien Miller - Updated for new location - Updated for new gnome-ssh-askpass build * Sun Dec 26 1999 Damien Miller - Added Jim Knoble's askpass * Mon Nov 15 1999 Damien Miller - Split subpackages further based on patch from jim knoble * Sat Nov 13 1999 Damien Miller - Added 'Obsoletes' directives * Tue Nov 09 1999 Damien Miller - Use make install - Subpackages * Mon Nov 08 1999 Damien Miller - Added links for slogin - Fixed perms on manpages * Sat Oct 30 1999 Damien Miller - Renamed init script * Fri Oct 29 1999 Damien Miller - Back to old binary names * Thu Oct 28 1999 Damien Miller - Use autoconf - New binary names * Wed Oct 27 1999 Damien Miller - Initial RPMification, based on Jan "Yenya" Kasprzak's spec. diff --git a/crypto/openssh/contrib/suse/openssh.spec b/crypto/openssh/contrib/suse/openssh.spec index 77736276638c..921d0f5a4d67 100644 --- a/crypto/openssh/contrib/suse/openssh.spec +++ b/crypto/openssh/contrib/suse/openssh.spec @@ -1,245 +1,245 @@ # Default values for additional components %define build_x11_askpass 1 # Define the UID/GID to use for privilege separation %define sshd_gid 65 %define sshd_uid 71 # The version of x11-ssh-askpass to use %define xversion 1.2.4.1 # Allow the ability to override defaults with -D skip_xxx=1 %{?skip_x11_askpass:%define build_x11_askpass 0} Summary: OpenSSH, a free Secure Shell (SSH) protocol implementation Name: openssh -Version: 9.4p1 +Version: 9.5p1 URL: https://www.openssh.com/ Release: 1 Source0: openssh-%{version}.tar.gz Source1: x11-ssh-askpass-%{xversion}.tar.gz License: BSD Group: Productivity/Networking/SSH BuildRoot: %{_tmppath}/openssh-%{version}-buildroot PreReq: openssl Obsoletes: ssh Provides: ssh # # (Build[ing] Prereq[uisites] only work for RPM 2.95 and newer.) # building prerequisites -- stuff for # OpenSSL (openssl-devel), # and Gnome (glibdev, gtkdev, and gnlibsd) # BuildPrereq: openssl BuildPrereq: zlib-devel #BuildPrereq: glibdev #BuildPrereq: gtkdev #BuildPrereq: gnlibsd %package askpass Summary: A passphrase dialog for OpenSSH and the X window System. Group: Productivity/Networking/SSH Requires: openssh = %{version} Obsoletes: ssh-extras Provides: openssh:${_libdir}/ssh/ssh-askpass %if %{build_x11_askpass} BuildPrereq: XFree86-devel %endif %description Ssh (Secure Shell) is a program for logging into a remote machine and for executing commands in a remote machine. It is intended to replace rlogin and rsh, and provide secure encrypted communications between two untrusted hosts over an insecure network. X11 connections and arbitrary TCP/IP ports can also be forwarded over the secure channel. OpenSSH is OpenBSD's rework of the last free version of SSH, bringing it up to date in terms of security and features, as well as removing all patented algorithms to separate libraries (OpenSSL). This package includes all files necessary for both the OpenSSH client and server. %description askpass Ssh (Secure Shell) is a program for logging into a remote machine and for executing commands in a remote machine. It is intended to replace rlogin and rsh, and provide secure encrypted communications between two untrusted hosts over an insecure network. X11 connections and arbitrary TCP/IP ports can also be forwarded over the secure channel. OpenSSH is OpenBSD's rework of the last free version of SSH, bringing it up to date in terms of security and features, as well as removing all patented algorithms to separate libraries (OpenSSL). This package contains an X Window System passphrase dialog for OpenSSH. %changelog * Mon Jul 20 2020 Damien Miller - Add ssh-sk-helper and corresponding manual page. * Wed Oct 26 2005 Iain Morgan - Removed accidental inclusion of --without-zlib-version-check * Tue Oct 25 2005 Iain Morgan - Overhaul to deal with newer versions of SuSE and OpenSSH * Mon Jun 12 2000 Damien Miller - Glob manpages to catch compressed files * Wed Mar 15 2000 Damien Miller - Updated for new location - Updated for new gnome-ssh-askpass build * Sun Dec 26 1999 Chris Saia - Made symlink to gnome-ssh-askpass called ssh-askpass * Wed Nov 24 1999 Chris Saia - Removed patches that included /etc/pam.d/sshd, /sbin/init.d/rc.sshd, and /var/adm/fillup-templates/rc.config.sshd, since Damien merged these into his released tarfile - Changed permissions on ssh_config in the install procedure to 644 from 600 even though it was correct in the %files section and thus right in the RPMs - Postinstall script for the server now only prints "Generating SSH host key..." if we need to actually do this, in order to eliminate a confusing message if an SSH host key is already in place - Marked all manual pages as %doc(umentation) * Mon Nov 22 1999 Chris Saia - Added flag to configure daemon with TCP Wrappers support - Added building prerequisites (works in RPM 3.0 and newer) * Thu Nov 18 1999 Chris Saia - Made this package correct for SuSE. - Changed instances of pam_pwdb.so to pam_unix.so, since it works more properly with SuSE, and lib_pwdb.so isn't installed by default. * Mon Nov 15 1999 Damien Miller - Split subpackages further based on patch from jim knoble * Sat Nov 13 1999 Damien Miller - Added 'Obsoletes' directives * Tue Nov 09 1999 Damien Miller - Use make install - Subpackages * Mon Nov 08 1999 Damien Miller - Added links for slogin - Fixed perms on manpages * Sat Oct 30 1999 Damien Miller - Renamed init script * Fri Oct 29 1999 Damien Miller - Back to old binary names * Thu Oct 28 1999 Damien Miller - Use autoconf - New binary names * Wed Oct 27 1999 Damien Miller - Initial RPMification, based on Jan "Yenya" Kasprzak's spec. %prep %if %{build_x11_askpass} %setup -q -a 1 %else %setup -q %endif %build CFLAGS="$RPM_OPT_FLAGS" \ %configure --prefix=/usr \ --sysconfdir=%{_sysconfdir}/ssh \ --mandir=%{_mandir} \ --with-privsep-path=/var/lib/empty \ --with-pam \ --libexecdir=%{_libdir}/ssh make %if %{build_x11_askpass} cd x11-ssh-askpass-%{xversion} %configure --mandir=/usr/X11R6/man \ --libexecdir=%{_libdir}/ssh xmkmf -a make cd .. %endif %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT/ install -d $RPM_BUILD_ROOT/etc/pam.d/ install -d $RPM_BUILD_ROOT/etc/init.d/ install -d $RPM_BUILD_ROOT/var/adm/fillup-templates install -m644 contrib/sshd.pam.generic $RPM_BUILD_ROOT/etc/pam.d/sshd install -m744 contrib/suse/rc.sshd $RPM_BUILD_ROOT/etc/init.d/sshd install -m744 contrib/suse/sysconfig.ssh \ $RPM_BUILD_ROOT/var/adm/fillup-templates %if %{build_x11_askpass} cd x11-ssh-askpass-%{xversion} make install install.man BINDIR=%{_libdir}/ssh DESTDIR=$RPM_BUILD_ROOT/ rm -f $RPM_BUILD_ROOT/usr/share/Ssh.bin %endif %clean rm -rf $RPM_BUILD_ROOT %pre /usr/sbin/groupadd -g %{sshd_gid} -o -r sshd 2> /dev/null || : /usr/sbin/useradd -r -o -g sshd -u %{sshd_uid} -s /bin/false -c "SSH Privilege Separation User" -d /var/lib/sshd sshd 2> /dev/null || : %post /usr/bin/ssh-keygen -A %{fillup_and_insserv -n -y ssh sshd} %run_permissions %verifyscript %verify_permissions -e /etc/ssh/sshd_config -e /etc/ssh/ssh_config -e /usr/bin/ssh %preun %stop_on_removal sshd %postun %restart_on_update sshd %{insserv_cleanup} %files %defattr(-,root,root) %doc ChangeLog OVERVIEW README* PROTOCOL* %doc TODO CREDITS LICENCE %attr(0755,root,root) %dir %{_sysconfdir}/ssh %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ssh/ssh_config %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/sshd_config %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/moduli %attr(0644,root,root) %config(noreplace) /etc/pam.d/sshd %attr(0755,root,root) %config /etc/init.d/sshd %attr(0755,root,root) %{_bindir}/ssh-keygen %attr(0755,root,root) %{_bindir}/scp %attr(0755,root,root) %{_bindir}/ssh %attr(0755,root,root) %{_bindir}/ssh-agent %attr(0755,root,root) %{_bindir}/ssh-add %attr(0755,root,root) %{_bindir}/ssh-keyscan %attr(0755,root,root) %{_bindir}/sftp %attr(0755,root,root) %{_sbindir}/sshd %attr(0755,root,root) %dir %{_libdir}/ssh %attr(0755,root,root) %{_libdir}/ssh/sftp-server %attr(4711,root,root) %{_libdir}/ssh/ssh-keysign %attr(0755,root,root) %{_libdir}/ssh/ssh-pkcs11-helper %attr(0755,root,root) %{_libdir}/ssh/ssh-sk-helper %attr(0644,root,root) %doc %{_mandir}/man1/scp.1* %attr(0644,root,root) %doc %{_mandir}/man1/sftp.1* %attr(0644,root,root) %doc %{_mandir}/man1/ssh.1* %attr(0644,root,root) %doc %{_mandir}/man1/ssh-add.1* %attr(0644,root,root) %doc %{_mandir}/man1/ssh-agent.1* %attr(0644,root,root) %doc %{_mandir}/man1/ssh-keygen.1* %attr(0644,root,root) %doc %{_mandir}/man1/ssh-keyscan.1* %attr(0644,root,root) %doc %{_mandir}/man5/moduli.5* %attr(0644,root,root) %doc %{_mandir}/man5/ssh_config.5* %attr(0644,root,root) %doc %{_mandir}/man5/sshd_config.5* %attr(0644,root,root) %doc %{_mandir}/man8/sftp-server.8* %attr(0644,root,root) %doc %{_mandir}/man8/ssh-keysign.8* %attr(0644,root,root) %doc %{_mandir}/man8/ssh-pkcs11-helper.8* %attr(0644,root,root) %doc %{_mandir}/man8/ssh-sk-helper.8* %attr(0644,root,root) %doc %{_mandir}/man8/sshd.8* %attr(0644,root,root) /var/adm/fillup-templates/sysconfig.ssh %if %{build_x11_askpass} %files askpass %defattr(-,root,root) %doc x11-ssh-askpass-%{xversion}/README %doc x11-ssh-askpass-%{xversion}/ChangeLog %doc x11-ssh-askpass-%{xversion}/SshAskpass*.ad %attr(0755,root,root) %{_libdir}/ssh/ssh-askpass %attr(0755,root,root) %{_libdir}/ssh/x11-ssh-askpass %attr(0644,root,root) %doc /usr/X11R6/man/man1/ssh-askpass.1x* %attr(0644,root,root) %doc /usr/X11R6/man/man1/x11-ssh-askpass.1x* %attr(0644,root,root) %config /usr/X11R6/lib/X11/app-defaults/SshAskpass %endif diff --git a/crypto/openssh/kex.c b/crypto/openssh/kex.c index fd04bb0b5c10..8ff92f2a2d5e 100644 --- a/crypto/openssh/kex.c +++ b/crypto/openssh/kex.c @@ -1,1477 +1,1498 @@ -/* $OpenBSD: kex.c,v 1.179 2023/08/18 01:37:41 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.181 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #ifdef WITH_OPENSSL #include #include #endif #include "ssh.h" #include "ssh2.h" #include "atomicio.h" #include "version.h" #include "packet.h" #include "compat.h" #include "cipher.h" #include "sshkey.h" #include "kex.h" #include "log.h" #include "mac.h" #include "match.h" #include "misc.h" #include "dispatch.h" #include "monitor.h" #include "myproposal.h" #include "ssherr.h" #include "sshbuf.h" #include "digest.h" #include "xmalloc.h" /* prototype */ static int kex_choose_conf(struct ssh *); static int kex_input_newkeys(int, u_int32_t, struct ssh *); static const char * const proposal_names[PROPOSAL_MAX] = { "KEX algorithms", "host key algorithms", "ciphers ctos", "ciphers stoc", "MACs ctos", "MACs stoc", "compression ctos", "compression stoc", "languages ctos", "languages stoc", }; struct kexalg { char *name; u_int type; int ec_nid; int hash_alg; }; static const struct kexalg kexalgs[] = { #ifdef WITH_OPENSSL { KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, { KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, { KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, { KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, { KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512 }, { KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, #ifdef HAVE_EVP_SHA256 { KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256 }, #endif /* HAVE_EVP_SHA256 */ #ifdef OPENSSL_HAS_ECC { KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2, NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, { KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1, SSH_DIGEST_SHA384 }, # ifdef OPENSSL_HAS_NISTP521 { KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1, SSH_DIGEST_SHA512 }, # endif /* OPENSSL_HAS_NISTP521 */ #endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ #if defined(HAVE_EVP_SHA256) || !defined(WITH_OPENSSL) { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, #ifdef USE_SNTRUP761X25519 { KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0, SSH_DIGEST_SHA512 }, #endif #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ { NULL, 0, -1, -1}, }; char * kex_alg_list(char sep) { char *ret = NULL, *tmp; size_t nlen, rlen = 0; const struct kexalg *k; for (k = kexalgs; k->name != NULL; k++) { if (ret != NULL) ret[rlen++] = sep; nlen = strlen(k->name); if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { free(ret); return NULL; } ret = tmp; memcpy(ret + rlen, k->name, nlen + 1); rlen += nlen; } return ret; } static const struct kexalg * kex_alg_by_name(const char *name) { const struct kexalg *k; for (k = kexalgs; k->name != NULL; k++) { if (strcmp(k->name, name) == 0) return k; } return NULL; } /* Validate KEX method name list */ int kex_names_valid(const char *names) { char *s, *cp, *p; if (names == NULL || strcmp(names, "") == 0) return 0; if ((s = cp = strdup(names)) == NULL) return 0; for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { if (kex_alg_by_name(p) == NULL) { error("Unsupported KEX algorithm \"%.100s\"", p); free(s); return 0; } } debug3("kex names ok: [%s]", names); free(s); return 1; } /* * Concatenate algorithm names, avoiding duplicates in the process. * Caller must free returned string. */ char * kex_names_cat(const char *a, const char *b) { char *ret = NULL, *tmp = NULL, *cp, *p, *m; size_t len; if (a == NULL || *a == '\0') return strdup(b); if (b == NULL || *b == '\0') return strdup(a); if (strlen(b) > 1024*1024) return NULL; len = strlen(a) + strlen(b) + 2; if ((tmp = cp = strdup(b)) == NULL || (ret = calloc(1, len)) == NULL) { free(tmp); return NULL; } strlcpy(ret, a, len); for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { if ((m = match_list(ret, p, NULL)) != NULL) { free(m); continue; /* Algorithm already present */ } if (strlcat(ret, ",", len) >= len || strlcat(ret, p, len) >= len) { free(tmp); free(ret); return NULL; /* Shouldn't happen */ } } free(tmp); return ret; } /* * Assemble a list of algorithms from a default list and a string from a * configuration file. The user-provided string may begin with '+' to * indicate that it should be appended to the default, '-' that the * specified names should be removed, or '^' that they should be placed * at the head. */ int kex_assemble_names(char **listp, const char *def, const char *all) { char *cp, *tmp, *patterns; char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL; int r = SSH_ERR_INTERNAL_ERROR; if (listp == NULL || def == NULL || all == NULL) return SSH_ERR_INVALID_ARGUMENT; if (*listp == NULL || **listp == '\0') { if ((*listp = strdup(def)) == NULL) return SSH_ERR_ALLOC_FAIL; return 0; } list = *listp; *listp = NULL; if (*list == '+') { /* Append names to default list */ if ((tmp = kex_names_cat(def, list + 1)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } free(list); list = tmp; } else if (*list == '-') { /* Remove names from default list */ if ((*listp = match_filter_denylist(def, list + 1)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } free(list); /* filtering has already been done */ return 0; } else if (*list == '^') { /* Place names at head of default list */ if ((tmp = kex_names_cat(list + 1, def)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } free(list); list = tmp; } else { /* Explicit list, overrides default - just use "list" as is */ } /* * The supplied names may be a pattern-list. For the -list case, * the patterns are applied above. For the +list and explicit list * cases we need to do it now. */ ret = NULL; if ((patterns = opatterns = strdup(list)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } /* Apply positive (i.e. non-negated) patterns from the list */ while ((cp = strsep(&patterns, ",")) != NULL) { if (*cp == '!') { /* negated matches are not supported here */ r = SSH_ERR_INVALID_ARGUMENT; goto fail; } free(matching); if ((matching = match_filter_allowlist(all, cp)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } if ((tmp = kex_names_cat(ret, matching)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } free(ret); ret = tmp; } if (ret == NULL || *ret == '\0') { /* An empty name-list is an error */ /* XXX better error code? */ r = SSH_ERR_INVALID_ARGUMENT; goto fail; } /* success */ *listp = ret; ret = NULL; r = 0; fail: free(matching); free(opatterns); free(list); free(ret); return r; } /* * Fill out a proposal array with dynamically allocated values, which may * be modified as required for compatibility reasons. * Any of the options may be NULL, in which case the default is used. * Array contents must be freed by calling kex_proposal_free_entries. */ void kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX], const char *kexalgos, const char *ciphers, const char *macs, const char *comp, const char *hkalgs) { const char *defpropserver[PROPOSAL_MAX] = { KEX_SERVER }; const char *defpropclient[PROPOSAL_MAX] = { KEX_CLIENT }; const char **defprop = ssh->kex->server ? defpropserver : defpropclient; u_int i; if (prop == NULL) fatal_f("proposal missing"); for (i = 0; i < PROPOSAL_MAX; i++) { switch(i) { case PROPOSAL_KEX_ALGS: prop[i] = compat_kex_proposal(ssh, kexalgos ? kexalgos : defprop[i]); break; case PROPOSAL_ENC_ALGS_CTOS: case PROPOSAL_ENC_ALGS_STOC: prop[i] = xstrdup(ciphers ? ciphers : defprop[i]); break; case PROPOSAL_MAC_ALGS_CTOS: case PROPOSAL_MAC_ALGS_STOC: prop[i] = xstrdup(macs ? macs : defprop[i]); break; case PROPOSAL_COMP_ALGS_CTOS: case PROPOSAL_COMP_ALGS_STOC: prop[i] = xstrdup(comp ? comp : defprop[i]); break; case PROPOSAL_SERVER_HOST_KEY_ALGS: prop[i] = xstrdup(hkalgs ? hkalgs : defprop[i]); break; default: prop[i] = xstrdup(defprop[i]); } } } void kex_proposal_free_entries(char *prop[PROPOSAL_MAX]) { u_int i; for (i = 0; i < PROPOSAL_MAX; i++) free(prop[i]); } /* put algorithm proposal into buffer */ int kex_prop2buf(struct sshbuf *b, char *proposal[PROPOSAL_MAX]) { u_int i; int r; sshbuf_reset(b); /* * add a dummy cookie, the cookie will be overwritten by * kex_send_kexinit(), each time a kexinit is set */ for (i = 0; i < KEX_COOKIE_LEN; i++) { if ((r = sshbuf_put_u8(b, 0)) != 0) return r; } for (i = 0; i < PROPOSAL_MAX; i++) { if ((r = sshbuf_put_cstring(b, proposal[i])) != 0) return r; } if ((r = sshbuf_put_u8(b, 0)) != 0 || /* first_kex_packet_follows */ (r = sshbuf_put_u32(b, 0)) != 0) /* uint32 reserved */ return r; return 0; } /* parse buffer and return algorithm proposal */ int kex_buf2prop(struct sshbuf *raw, int *first_kex_follows, char ***propp) { struct sshbuf *b = NULL; u_char v; u_int i; char **proposal = NULL; int r; *propp = NULL; if ((proposal = calloc(PROPOSAL_MAX, sizeof(char *))) == NULL) return SSH_ERR_ALLOC_FAIL; if ((b = sshbuf_fromb(raw)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_consume(b, KEX_COOKIE_LEN)) != 0) { /* skip cookie */ error_fr(r, "consume cookie"); goto out; } /* extract kex init proposal strings */ for (i = 0; i < PROPOSAL_MAX; i++) { if ((r = sshbuf_get_cstring(b, &(proposal[i]), NULL)) != 0) { error_fr(r, "parse proposal %u", i); goto out; } debug2("%s: %s", proposal_names[i], proposal[i]); } /* first kex follows / reserved */ if ((r = sshbuf_get_u8(b, &v)) != 0 || /* first_kex_follows */ (r = sshbuf_get_u32(b, &i)) != 0) { /* reserved */ error_fr(r, "parse"); goto out; } if (first_kex_follows != NULL) *first_kex_follows = v; debug2("first_kex_follows %d ", v); debug2("reserved %u ", i); r = 0; *propp = proposal; out: if (r != 0 && proposal != NULL) kex_prop_free(proposal); sshbuf_free(b); return r; } void kex_prop_free(char **proposal) { u_int i; if (proposal == NULL) return; for (i = 0; i < PROPOSAL_MAX; i++) free(proposal[i]); free(proposal); } int kex_protocol_error(int type, u_int32_t seq, struct ssh *ssh) { int r; error("kex protocol error: type %d seq %u", type, seq); if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 || (r = sshpkt_put_u32(ssh, seq)) != 0 || (r = sshpkt_send(ssh)) != 0) return r; return 0; } static void kex_reset_dispatch(struct ssh *ssh) { ssh_dispatch_range(ssh, SSH2_MSG_TRANSPORT_MIN, SSH2_MSG_TRANSPORT_MAX, &kex_protocol_error); } static int kex_send_ext_info(struct ssh *ssh) { int r; char *algs; debug("Sending SSH2_MSG_EXT_INFO"); if ((algs = sshkey_alg_list(0, 1, 1, ',')) == NULL) return SSH_ERR_ALLOC_FAIL; /* XXX filter algs list by allowed pubkey/hostbased types */ if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 || - (r = sshpkt_put_u32(ssh, 2)) != 0 || + (r = sshpkt_put_u32(ssh, 3)) != 0 || (r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || (r = sshpkt_put_cstring(ssh, algs)) != 0 || (r = sshpkt_put_cstring(ssh, "publickey-hostbound@openssh.com")) != 0 || (r = sshpkt_put_cstring(ssh, "0")) != 0 || + (r = sshpkt_put_cstring(ssh, "ping@openssh.com")) != 0 || + (r = sshpkt_put_cstring(ssh, "0")) != 0 || (r = sshpkt_send(ssh)) != 0) { error_fr(r, "compose"); goto out; } /* success */ r = 0; out: free(algs); return r; } int kex_send_newkeys(struct ssh *ssh) { int r; kex_reset_dispatch(ssh); if ((r = sshpkt_start(ssh, SSH2_MSG_NEWKEYS)) != 0 || (r = sshpkt_send(ssh)) != 0) return r; debug("SSH2_MSG_NEWKEYS sent"); ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) if ((r = kex_send_ext_info(ssh)) != 0) return r; debug("expecting SSH2_MSG_NEWKEYS"); return 0; } +/* Check whether an ext_info value contains the expected version string */ +static int +kex_ext_info_check_ver(struct kex *kex, const char *name, + const u_char *val, size_t len, const char *want_ver, u_int flag) +{ + if (memchr(val, '\0', len) != NULL) { + error("SSH2_MSG_EXT_INFO: %s value contains nul byte", name); + return SSH_ERR_INVALID_FORMAT; + } + debug_f("%s=<%s>", name, val); + if (strcmp(val, want_ver) == 0) + kex->flags |= flag; + else + debug_f("unsupported version of %s extension", name); + return 0; +} + int kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) { struct kex *kex = ssh->kex; u_int32_t i, ninfo; char *name; u_char *val; size_t vlen; int r; debug("SSH2_MSG_EXT_INFO received"); ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_protocol_error); if ((r = sshpkt_get_u32(ssh, &ninfo)) != 0) return r; if (ninfo >= 1024) { error("SSH2_MSG_EXT_INFO with too many entries, expected " "<=1024, received %u", ninfo); return SSH_ERR_INVALID_FORMAT; } for (i = 0; i < ninfo; i++) { if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0) return r; if ((r = sshpkt_get_string(ssh, &val, &vlen)) != 0) { free(name); return r; } if (strcmp(name, "server-sig-algs") == 0) { /* Ensure no \0 lurking in value */ if (memchr(val, '\0', vlen) != NULL) { error_f("nul byte in %s", name); + free(name); + free(val); return SSH_ERR_INVALID_FORMAT; } debug_f("%s=<%s>", name, val); kex->server_sig_algs = val; val = NULL; } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { - /* XXX refactor */ - /* Ensure no \0 lurking in value */ - if (memchr(val, '\0', vlen) != NULL) { - error_f("nul byte in %s", name); - return SSH_ERR_INVALID_FORMAT; + if ((r = kex_ext_info_check_ver(kex, name, val, vlen, + "0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) { + free(name); + free(val); + return r; } - debug_f("%s=<%s>", name, val); - if (strcmp(val, "0") == 0) - kex->flags |= KEX_HAS_PUBKEY_HOSTBOUND; - else { - debug_f("unsupported version of %s extension", - name); + } else if (strcmp(name, "ping@openssh.com") == 0) { + if ((r = kex_ext_info_check_ver(kex, name, val, vlen, + "0", KEX_HAS_PING)) != 0) { + free(name); + free(val); + return r; } } else debug_f("%s (unrecognised)", name); free(name); free(val); } return sshpkt_get_end(ssh); } static int kex_input_newkeys(int type, u_int32_t seq, struct ssh *ssh) { struct kex *kex = ssh->kex; int r; debug("SSH2_MSG_NEWKEYS received"); ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_protocol_error); ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); if ((r = sshpkt_get_end(ssh)) != 0) return r; if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0) return r; kex->done = 1; kex->flags &= ~KEX_INITIAL; sshbuf_reset(kex->peer); /* sshbuf_reset(kex->my); */ kex->flags &= ~KEX_INIT_SENT; free(kex->name); kex->name = NULL; return 0; } int kex_send_kexinit(struct ssh *ssh) { u_char *cookie; struct kex *kex = ssh->kex; int r; if (kex == NULL) { error_f("no kex"); return SSH_ERR_INTERNAL_ERROR; } if (kex->flags & KEX_INIT_SENT) return 0; kex->done = 0; /* generate a random cookie */ if (sshbuf_len(kex->my) < KEX_COOKIE_LEN) { error_f("bad kex length: %zu < %d", sshbuf_len(kex->my), KEX_COOKIE_LEN); return SSH_ERR_INVALID_FORMAT; } if ((cookie = sshbuf_mutable_ptr(kex->my)) == NULL) { error_f("buffer error"); return SSH_ERR_INTERNAL_ERROR; } arc4random_buf(cookie, KEX_COOKIE_LEN); if ((r = sshpkt_start(ssh, SSH2_MSG_KEXINIT)) != 0 || (r = sshpkt_putb(ssh, kex->my)) != 0 || (r = sshpkt_send(ssh)) != 0) { error_fr(r, "compose reply"); return r; } debug("SSH2_MSG_KEXINIT sent"); kex->flags |= KEX_INIT_SENT; return 0; } int kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) { struct kex *kex = ssh->kex; const u_char *ptr; u_int i; size_t dlen; int r; debug("SSH2_MSG_KEXINIT received"); if (kex == NULL) { error_f("no kex"); return SSH_ERR_INTERNAL_ERROR; } ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL); ptr = sshpkt_ptr(ssh, &dlen); if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0) return r; /* discard packet */ for (i = 0; i < KEX_COOKIE_LEN; i++) { if ((r = sshpkt_get_u8(ssh, NULL)) != 0) { error_fr(r, "discard cookie"); return r; } } for (i = 0; i < PROPOSAL_MAX; i++) { if ((r = sshpkt_get_string(ssh, NULL, NULL)) != 0) { error_fr(r, "discard proposal"); return r; } } /* * XXX RFC4253 sec 7: "each side MAY guess" - currently no supported * KEX method has the server move first, but a server might be using * a custom method or one that we otherwise don't support. We should * be prepared to remember first_kex_follows here so we can eat a * packet later. * XXX2 - RFC4253 is kind of ambiguous on what first_kex_follows means * for cases where the server *doesn't* go first. I guess we should * ignore it when it is set for these cases, which is what we do now. */ if ((r = sshpkt_get_u8(ssh, NULL)) != 0 || /* first_kex_follows */ (r = sshpkt_get_u32(ssh, NULL)) != 0 || /* reserved */ (r = sshpkt_get_end(ssh)) != 0) return r; if (!(kex->flags & KEX_INIT_SENT)) if ((r = kex_send_kexinit(ssh)) != 0) return r; if ((r = kex_choose_conf(ssh)) != 0) return r; if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) return (kex->kex[kex->kex_type])(ssh); error_f("unknown kex type %u", kex->kex_type); return SSH_ERR_INTERNAL_ERROR; } struct kex * kex_new(void) { struct kex *kex; if ((kex = calloc(1, sizeof(*kex))) == NULL || (kex->peer = sshbuf_new()) == NULL || (kex->my = sshbuf_new()) == NULL || (kex->client_version = sshbuf_new()) == NULL || (kex->server_version = sshbuf_new()) == NULL || (kex->session_id = sshbuf_new()) == NULL) { kex_free(kex); return NULL; } return kex; } void kex_free_newkeys(struct newkeys *newkeys) { if (newkeys == NULL) return; if (newkeys->enc.key) { explicit_bzero(newkeys->enc.key, newkeys->enc.key_len); free(newkeys->enc.key); newkeys->enc.key = NULL; } if (newkeys->enc.iv) { explicit_bzero(newkeys->enc.iv, newkeys->enc.iv_len); free(newkeys->enc.iv); newkeys->enc.iv = NULL; } free(newkeys->enc.name); explicit_bzero(&newkeys->enc, sizeof(newkeys->enc)); free(newkeys->comp.name); explicit_bzero(&newkeys->comp, sizeof(newkeys->comp)); mac_clear(&newkeys->mac); if (newkeys->mac.key) { explicit_bzero(newkeys->mac.key, newkeys->mac.key_len); free(newkeys->mac.key); newkeys->mac.key = NULL; } free(newkeys->mac.name); explicit_bzero(&newkeys->mac, sizeof(newkeys->mac)); freezero(newkeys, sizeof(*newkeys)); } void kex_free(struct kex *kex) { u_int mode; if (kex == NULL) return; #ifdef WITH_OPENSSL DH_free(kex->dh); #ifdef OPENSSL_HAS_ECC EC_KEY_free(kex->ec_client_key); #endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ for (mode = 0; mode < MODE_MAX; mode++) { kex_free_newkeys(kex->newkeys[mode]); kex->newkeys[mode] = NULL; } sshbuf_free(kex->peer); sshbuf_free(kex->my); sshbuf_free(kex->client_version); sshbuf_free(kex->server_version); sshbuf_free(kex->client_pub); sshbuf_free(kex->session_id); sshbuf_free(kex->initial_sig); sshkey_free(kex->initial_hostkey); free(kex->failed_choice); free(kex->hostkey_alg); free(kex->name); free(kex); } int kex_ready(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) { int r; if ((r = kex_prop2buf(ssh->kex->my, proposal)) != 0) return r; ssh->kex->flags = KEX_INITIAL; kex_reset_dispatch(ssh); ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); return 0; } int kex_setup(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) { int r; if ((r = kex_ready(ssh, proposal)) != 0) return r; if ((r = kex_send_kexinit(ssh)) != 0) { /* we start */ kex_free(ssh->kex); ssh->kex = NULL; return r; } return 0; } /* * Request key re-exchange, returns 0 on success or a ssherr.h error * code otherwise. Must not be called if KEX is incomplete or in-progress. */ int kex_start_rekex(struct ssh *ssh) { if (ssh->kex == NULL) { error_f("no kex"); return SSH_ERR_INTERNAL_ERROR; } if (ssh->kex->done == 0) { error_f("requested twice"); return SSH_ERR_INTERNAL_ERROR; } ssh->kex->done = 0; return kex_send_kexinit(ssh); } static int choose_enc(struct sshenc *enc, char *client, char *server) { char *name = match_list(client, server, NULL); if (name == NULL) return SSH_ERR_NO_CIPHER_ALG_MATCH; if ((enc->cipher = cipher_by_name(name)) == NULL) { error_f("unsupported cipher %s", name); free(name); return SSH_ERR_INTERNAL_ERROR; } enc->name = name; enc->enabled = 0; enc->iv = NULL; enc->iv_len = cipher_ivlen(enc->cipher); enc->key = NULL; enc->key_len = cipher_keylen(enc->cipher); enc->block_size = cipher_blocksize(enc->cipher); return 0; } static int choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server) { char *name = match_list(client, server, NULL); if (name == NULL) return SSH_ERR_NO_MAC_ALG_MATCH; if (mac_setup(mac, name) < 0) { error_f("unsupported MAC %s", name); free(name); return SSH_ERR_INTERNAL_ERROR; } mac->name = name; mac->key = NULL; mac->enabled = 0; return 0; } static int choose_comp(struct sshcomp *comp, char *client, char *server) { char *name = match_list(client, server, NULL); if (name == NULL) return SSH_ERR_NO_COMPRESS_ALG_MATCH; #ifdef WITH_ZLIB if (strcmp(name, "zlib@openssh.com") == 0) { comp->type = COMP_DELAYED; } else if (strcmp(name, "zlib") == 0) { comp->type = COMP_ZLIB; } else #endif /* WITH_ZLIB */ if (strcmp(name, "none") == 0) { comp->type = COMP_NONE; } else { error_f("unsupported compression scheme %s", name); free(name); return SSH_ERR_INTERNAL_ERROR; } comp->name = name; return 0; } static int choose_kex(struct kex *k, char *client, char *server) { const struct kexalg *kexalg; k->name = match_list(client, server, NULL); debug("kex: algorithm: %s", k->name ? k->name : "(no match)"); if (k->name == NULL) return SSH_ERR_NO_KEX_ALG_MATCH; if ((kexalg = kex_alg_by_name(k->name)) == NULL) { error_f("unsupported KEX method %s", k->name); return SSH_ERR_INTERNAL_ERROR; } k->kex_type = kexalg->type; k->hash_alg = kexalg->hash_alg; k->ec_nid = kexalg->ec_nid; return 0; } static int choose_hostkeyalg(struct kex *k, char *client, char *server) { free(k->hostkey_alg); k->hostkey_alg = match_list(client, server, NULL); debug("kex: host key algorithm: %s", k->hostkey_alg ? k->hostkey_alg : "(no match)"); if (k->hostkey_alg == NULL) return SSH_ERR_NO_HOSTKEY_ALG_MATCH; k->hostkey_type = sshkey_type_from_name(k->hostkey_alg); if (k->hostkey_type == KEY_UNSPEC) { error_f("unsupported hostkey algorithm %s", k->hostkey_alg); return SSH_ERR_INTERNAL_ERROR; } k->hostkey_nid = sshkey_ecdsa_nid_from_name(k->hostkey_alg); return 0; } static int proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX]) { static int check[] = { PROPOSAL_KEX_ALGS, PROPOSAL_SERVER_HOST_KEY_ALGS, -1 }; int *idx; char *p; for (idx = &check[0]; *idx != -1; idx++) { if ((p = strchr(my[*idx], ',')) != NULL) *p = '\0'; if ((p = strchr(peer[*idx], ',')) != NULL) *p = '\0'; if (strcmp(my[*idx], peer[*idx]) != 0) { debug2("proposal mismatch: my %s peer %s", my[*idx], peer[*idx]); return (0); } } debug2("proposals match"); return (1); } /* returns non-zero if proposal contains any algorithm from algs */ static int has_any_alg(const char *proposal, const char *algs) { char *cp; if ((cp = match_list(proposal, algs, NULL)) == NULL) return 0; free(cp); return 1; } static int kex_choose_conf(struct ssh *ssh) { struct kex *kex = ssh->kex; struct newkeys *newkeys; char **my = NULL, **peer = NULL; char **cprop, **sprop; int nenc, nmac, ncomp; u_int mode, ctos, need, dh_need, authlen; int r, first_kex_follows; debug2("local %s KEXINIT proposal", kex->server ? "server" : "client"); if ((r = kex_buf2prop(kex->my, NULL, &my)) != 0) goto out; debug2("peer %s KEXINIT proposal", kex->server ? "client" : "server"); if ((r = kex_buf2prop(kex->peer, &first_kex_follows, &peer)) != 0) goto out; if (kex->server) { cprop=peer; sprop=my; } else { cprop=my; sprop=peer; } /* Check whether client supports ext_info_c */ if (kex->server && (kex->flags & KEX_INITIAL)) { char *ext; ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL); kex->ext_info_c = (ext != NULL); free(ext); } /* Check whether client supports rsa-sha2 algorithms */ if (kex->server && (kex->flags & KEX_INITIAL)) { if (has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com")) kex->flags |= KEX_RSA_SHA2_256_SUPPORTED; if (has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com")) kex->flags |= KEX_RSA_SHA2_512_SUPPORTED; } /* Algorithm Negotiation */ if ((r = choose_kex(kex, cprop[PROPOSAL_KEX_ALGS], sprop[PROPOSAL_KEX_ALGS])) != 0) { kex->failed_choice = peer[PROPOSAL_KEX_ALGS]; peer[PROPOSAL_KEX_ALGS] = NULL; goto out; } if ((r = choose_hostkeyalg(kex, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS], sprop[PROPOSAL_SERVER_HOST_KEY_ALGS])) != 0) { kex->failed_choice = peer[PROPOSAL_SERVER_HOST_KEY_ALGS]; peer[PROPOSAL_SERVER_HOST_KEY_ALGS] = NULL; goto out; } for (mode = 0; mode < MODE_MAX; mode++) { if ((newkeys = calloc(1, sizeof(*newkeys))) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } kex->newkeys[mode] = newkeys; ctos = (!kex->server && mode == MODE_OUT) || (kex->server && mode == MODE_IN); nenc = ctos ? PROPOSAL_ENC_ALGS_CTOS : PROPOSAL_ENC_ALGS_STOC; nmac = ctos ? PROPOSAL_MAC_ALGS_CTOS : PROPOSAL_MAC_ALGS_STOC; ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC; if ((r = choose_enc(&newkeys->enc, cprop[nenc], sprop[nenc])) != 0) { kex->failed_choice = peer[nenc]; peer[nenc] = NULL; goto out; } authlen = cipher_authlen(newkeys->enc.cipher); /* ignore mac for authenticated encryption */ if (authlen == 0 && (r = choose_mac(ssh, &newkeys->mac, cprop[nmac], sprop[nmac])) != 0) { kex->failed_choice = peer[nmac]; peer[nmac] = NULL; goto out; } if ((r = choose_comp(&newkeys->comp, cprop[ncomp], sprop[ncomp])) != 0) { kex->failed_choice = peer[ncomp]; peer[ncomp] = NULL; goto out; } debug("kex: %s cipher: %s MAC: %s compression: %s", ctos ? "client->server" : "server->client", newkeys->enc.name, authlen == 0 ? newkeys->mac.name : "", newkeys->comp.name); } need = dh_need = 0; for (mode = 0; mode < MODE_MAX; mode++) { newkeys = kex->newkeys[mode]; need = MAXIMUM(need, newkeys->enc.key_len); need = MAXIMUM(need, newkeys->enc.block_size); need = MAXIMUM(need, newkeys->enc.iv_len); need = MAXIMUM(need, newkeys->mac.key_len); dh_need = MAXIMUM(dh_need, cipher_seclen(newkeys->enc.cipher)); dh_need = MAXIMUM(dh_need, newkeys->enc.block_size); dh_need = MAXIMUM(dh_need, newkeys->enc.iv_len); dh_need = MAXIMUM(dh_need, newkeys->mac.key_len); } /* XXX need runden? */ kex->we_need = need; kex->dh_need = dh_need; /* ignore the next message if the proposals do not match */ if (first_kex_follows && !proposals_match(my, peer)) ssh->dispatch_skip_packets = 1; r = 0; out: kex_prop_free(my); kex_prop_free(peer); return r; } static int derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen, const struct sshbuf *shared_secret, u_char **keyp) { struct kex *kex = ssh->kex; struct ssh_digest_ctx *hashctx = NULL; char c = id; u_int have; size_t mdsz; u_char *digest; int r; if ((mdsz = ssh_digest_bytes(kex->hash_alg)) == 0) return SSH_ERR_INVALID_ARGUMENT; if ((digest = calloc(1, ROUNDUP(need, mdsz))) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } /* K1 = HASH(K || H || "A" || session_id) */ if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL || ssh_digest_update_buffer(hashctx, shared_secret) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, &c, 1) != 0 || ssh_digest_update_buffer(hashctx, kex->session_id) != 0 || ssh_digest_final(hashctx, digest, mdsz) != 0) { r = SSH_ERR_LIBCRYPTO_ERROR; error_f("KEX hash failed"); goto out; } ssh_digest_free(hashctx); hashctx = NULL; /* * expand key: * Kn = HASH(K || H || K1 || K2 || ... || Kn-1) * Key = K1 || K2 || ... || Kn */ for (have = mdsz; need > have; have += mdsz) { if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL || ssh_digest_update_buffer(hashctx, shared_secret) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, digest, have) != 0 || ssh_digest_final(hashctx, digest + have, mdsz) != 0) { error_f("KDF failed"); r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } ssh_digest_free(hashctx); hashctx = NULL; } #ifdef DEBUG_KEX fprintf(stderr, "key '%c'== ", c); dump_digest("key", digest, need); #endif *keyp = digest; digest = NULL; r = 0; out: free(digest); ssh_digest_free(hashctx); return r; } #define NKEYS 6 int kex_derive_keys(struct ssh *ssh, u_char *hash, u_int hashlen, const struct sshbuf *shared_secret) { struct kex *kex = ssh->kex; u_char *keys[NKEYS]; u_int i, j, mode, ctos; int r; /* save initial hash as session id */ if ((kex->flags & KEX_INITIAL) != 0) { if (sshbuf_len(kex->session_id) != 0) { error_f("already have session ID at kex"); return SSH_ERR_INTERNAL_ERROR; } if ((r = sshbuf_put(kex->session_id, hash, hashlen)) != 0) return r; } else if (sshbuf_len(kex->session_id) == 0) { error_f("no session ID in rekex"); return SSH_ERR_INTERNAL_ERROR; } for (i = 0; i < NKEYS; i++) { if ((r = derive_key(ssh, 'A'+i, kex->we_need, hash, hashlen, shared_secret, &keys[i])) != 0) { for (j = 0; j < i; j++) free(keys[j]); return r; } } for (mode = 0; mode < MODE_MAX; mode++) { ctos = (!kex->server && mode == MODE_OUT) || (kex->server && mode == MODE_IN); kex->newkeys[mode]->enc.iv = keys[ctos ? 0 : 1]; kex->newkeys[mode]->enc.key = keys[ctos ? 2 : 3]; kex->newkeys[mode]->mac.key = keys[ctos ? 4 : 5]; } return 0; } int kex_load_hostkey(struct ssh *ssh, struct sshkey **prvp, struct sshkey **pubp) { struct kex *kex = ssh->kex; *pubp = NULL; *prvp = NULL; if (kex->load_host_public_key == NULL || kex->load_host_private_key == NULL) { error_f("missing hostkey loader"); return SSH_ERR_INVALID_ARGUMENT; } *pubp = kex->load_host_public_key(kex->hostkey_type, kex->hostkey_nid, ssh); *prvp = kex->load_host_private_key(kex->hostkey_type, kex->hostkey_nid, ssh); if (*pubp == NULL) return SSH_ERR_NO_HOSTKEY_LOADED; return 0; } int kex_verify_host_key(struct ssh *ssh, struct sshkey *server_host_key) { struct kex *kex = ssh->kex; if (kex->verify_host_key == NULL) { error_f("missing hostkey verifier"); return SSH_ERR_INVALID_ARGUMENT; } if (server_host_key->type != kex->hostkey_type || (kex->hostkey_type == KEY_ECDSA && server_host_key->ecdsa_nid != kex->hostkey_nid)) return SSH_ERR_KEY_TYPE_MISMATCH; if (kex->verify_host_key(server_host_key, ssh) == -1) return SSH_ERR_SIGNATURE_INVALID; return 0; } #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void dump_digest(const char *msg, const u_char *digest, int len) { fprintf(stderr, "%s\n", msg); sshbuf_dump_data(digest, len, stderr); } #endif /* * Send a plaintext error message to the peer, suffixed by \r\n. * Only used during banner exchange, and there only for the server. */ static void send_error(struct ssh *ssh, char *msg) { char *crnl = "\r\n"; if (!ssh->kex->server) return; if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), msg, strlen(msg)) != strlen(msg) || atomicio(vwrite, ssh_packet_get_connection_out(ssh), crnl, strlen(crnl)) != strlen(crnl)) error_f("write: %.100s", strerror(errno)); } /* * Sends our identification string and waits for the peer's. Will block for * up to timeout_ms (or indefinitely if timeout_ms <= 0). * Returns on 0 success or a ssherr.h code on failure. */ int kex_exchange_identification(struct ssh *ssh, int timeout_ms, const char *version_addendum) { int remote_major, remote_minor, mismatch, oerrno = 0; size_t len, n; int r, expect_nl; u_char c; struct sshbuf *our_version = ssh->kex->server ? ssh->kex->server_version : ssh->kex->client_version; struct sshbuf *peer_version = ssh->kex->server ? ssh->kex->client_version : ssh->kex->server_version; char *our_version_string = NULL, *peer_version_string = NULL; char *cp, *remote_version = NULL; /* Prepare and send our banner */ sshbuf_reset(our_version); if (version_addendum != NULL && *version_addendum == '\0') version_addendum = NULL; if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, version_addendum == NULL ? "" : " ", version_addendum == NULL ? "" : version_addendum)) != 0) { oerrno = errno; error_fr(r, "sshbuf_putf"); goto out; } if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)) != sshbuf_len(our_version)) { oerrno = errno; debug_f("write: %.100s", strerror(errno)); r = SSH_ERR_SYSTEM_ERROR; goto out; } if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ oerrno = errno; error_fr(r, "sshbuf_consume_end"); goto out; } our_version_string = sshbuf_dup_string(our_version); if (our_version_string == NULL) { error_f("sshbuf_dup_string failed"); r = SSH_ERR_ALLOC_FAIL; goto out; } debug("Local version string %.100s", our_version_string); /* Read other side's version identification. */ for (n = 0; ; n++) { if (n >= SSH_MAX_PRE_BANNER_LINES) { send_error(ssh, "No SSH identification string " "received."); error_f("No SSH version received in first %u lines " "from server", SSH_MAX_PRE_BANNER_LINES); r = SSH_ERR_INVALID_FORMAT; goto out; } sshbuf_reset(peer_version); expect_nl = 0; for (;;) { if (timeout_ms > 0) { r = waitrfd(ssh_packet_get_connection_in(ssh), &timeout_ms, NULL); if (r == -1 && errno == ETIMEDOUT) { send_error(ssh, "Timed out waiting " "for SSH identification string."); error("Connection timed out during " "banner exchange"); r = SSH_ERR_CONN_TIMEOUT; goto out; } else if (r == -1) { oerrno = errno; error_f("%s", strerror(errno)); r = SSH_ERR_SYSTEM_ERROR; goto out; } } len = atomicio(read, ssh_packet_get_connection_in(ssh), &c, 1); if (len != 1 && errno == EPIPE) { - error_f("Connection closed by remote host"); + verbose_f("Connection closed by remote host"); r = SSH_ERR_CONN_CLOSED; goto out; } else if (len != 1) { oerrno = errno; error_f("read: %.100s", strerror(errno)); r = SSH_ERR_SYSTEM_ERROR; goto out; } if (c == '\r') { expect_nl = 1; continue; } if (c == '\n') break; if (c == '\0' || expect_nl) { - error_f("banner line contains invalid " + verbose_f("banner line contains invalid " "characters"); goto invalid; } if ((r = sshbuf_put_u8(peer_version, c)) != 0) { oerrno = errno; error_fr(r, "sshbuf_put"); goto out; } if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) { - error_f("banner line too long"); + verbose_f("banner line too long"); goto invalid; } } /* Is this an actual protocol banner? */ if (sshbuf_len(peer_version) > 4 && memcmp(sshbuf_ptr(peer_version), "SSH-", 4) == 0) break; /* If not, then just log the line and continue */ if ((cp = sshbuf_dup_string(peer_version)) == NULL) { error_f("sshbuf_dup_string failed"); r = SSH_ERR_ALLOC_FAIL; goto out; } /* Do not accept lines before the SSH ident from a client */ if (ssh->kex->server) { - error_f("client sent invalid protocol identifier " + verbose_f("client sent invalid protocol identifier " "\"%.256s\"", cp); free(cp); goto invalid; } debug_f("banner line %zu: %s", n, cp); free(cp); } peer_version_string = sshbuf_dup_string(peer_version); if (peer_version_string == NULL) fatal_f("sshbuf_dup_string failed"); /* XXX must be same size for sscanf */ if ((remote_version = calloc(1, sshbuf_len(peer_version))) == NULL) { error_f("calloc failed"); r = SSH_ERR_ALLOC_FAIL; goto out; } /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ if (sscanf(peer_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) { error("Bad remote protocol version identification: '%.100s'", peer_version_string); invalid: send_error(ssh, "Invalid SSH identification string."); r = SSH_ERR_INVALID_FORMAT; goto out; } debug("Remote protocol version %d.%d, remote software version %.100s", remote_major, remote_minor, remote_version); compat_banner(ssh, remote_version); mismatch = 0; switch (remote_major) { case 2: break; case 1: if (remote_minor != 99) mismatch = 1; break; default: mismatch = 1; break; } if (mismatch) { error("Protocol major versions differ: %d vs. %d", PROTOCOL_MAJOR_2, remote_major); send_error(ssh, "Protocol major versions differ."); r = SSH_ERR_NO_PROTOCOL_VERSION; goto out; } if (ssh->kex->server && (ssh->compat & SSH_BUG_PROBE) != 0) { logit("probed from %s port %d with %s. Don't panic.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), peer_version_string); r = SSH_ERR_CONN_CLOSED; /* XXX */ goto out; } if (ssh->kex->server && (ssh->compat & SSH_BUG_SCANNER) != 0) { logit("scanned from %s port %d with %s. Don't panic.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), peer_version_string); r = SSH_ERR_CONN_CLOSED; /* XXX */ goto out; } /* success */ r = 0; out: free(our_version_string); free(peer_version_string); free(remote_version); if (r == SSH_ERR_SYSTEM_ERROR) errno = oerrno; return r; } diff --git a/crypto/openssh/kex.h b/crypto/openssh/kex.h index 8b54e3f4b912..5f7ef784eec9 100644 --- a/crypto/openssh/kex.h +++ b/crypto/openssh/kex.h @@ -1,269 +1,270 @@ -/* $OpenBSD: kex.h,v 1.118 2023/03/06 12:14:48 dtucker Exp $ */ +/* $OpenBSD: kex.h,v 1.119 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef KEX_H #define KEX_H #include "mac.h" #include "crypto_api.h" #ifdef WITH_OPENSSL # include # include # include # ifdef OPENSSL_HAS_ECC # include # else /* OPENSSL_HAS_ECC */ # define EC_KEY void # define EC_GROUP void # define EC_POINT void # endif /* OPENSSL_HAS_ECC */ #else /* WITH_OPENSSL */ # define DH void # define BIGNUM void # define EC_KEY void # define EC_GROUP void # define EC_POINT void #endif /* WITH_OPENSSL */ #define KEX_COOKIE_LEN 16 #define KEX_DH1 "diffie-hellman-group1-sha1" #define KEX_DH14_SHA1 "diffie-hellman-group14-sha1" #define KEX_DH14_SHA256 "diffie-hellman-group14-sha256" #define KEX_DH16_SHA512 "diffie-hellman-group16-sha512" #define KEX_DH18_SHA512 "diffie-hellman-group18-sha512" #define KEX_DHGEX_SHA1 "diffie-hellman-group-exchange-sha1" #define KEX_DHGEX_SHA256 "diffie-hellman-group-exchange-sha256" #define KEX_ECDH_SHA2_NISTP256 "ecdh-sha2-nistp256" #define KEX_ECDH_SHA2_NISTP384 "ecdh-sha2-nistp384" #define KEX_ECDH_SHA2_NISTP521 "ecdh-sha2-nistp521" #define KEX_CURVE25519_SHA256 "curve25519-sha256" #define KEX_CURVE25519_SHA256_OLD "curve25519-sha256@libssh.org" #define KEX_SNTRUP761X25519_SHA512 "sntrup761x25519-sha512@openssh.com" #define COMP_NONE 0 /* pre-auth compression (COMP_ZLIB) is only supported in the client */ #define COMP_ZLIB 1 #define COMP_DELAYED 2 #define CURVE25519_SIZE 32 enum kex_init_proposals { PROPOSAL_KEX_ALGS, PROPOSAL_SERVER_HOST_KEY_ALGS, PROPOSAL_ENC_ALGS_CTOS, PROPOSAL_ENC_ALGS_STOC, PROPOSAL_MAC_ALGS_CTOS, PROPOSAL_MAC_ALGS_STOC, PROPOSAL_COMP_ALGS_CTOS, PROPOSAL_COMP_ALGS_STOC, PROPOSAL_LANG_CTOS, PROPOSAL_LANG_STOC, PROPOSAL_MAX }; enum kex_modes { MODE_IN, MODE_OUT, MODE_MAX }; enum kex_exchange { KEX_DH_GRP1_SHA1, KEX_DH_GRP14_SHA1, KEX_DH_GRP14_SHA256, KEX_DH_GRP16_SHA512, KEX_DH_GRP18_SHA512, KEX_DH_GEX_SHA1, KEX_DH_GEX_SHA256, KEX_ECDH_SHA2, KEX_C25519_SHA256, KEX_KEM_SNTRUP761X25519_SHA512, KEX_MAX }; /* kex->flags */ #define KEX_INIT_SENT 0x0001 #define KEX_INITIAL 0x0002 #define KEX_HAS_PUBKEY_HOSTBOUND 0x0004 #define KEX_RSA_SHA2_256_SUPPORTED 0x0008 /* only set in server for now */ #define KEX_RSA_SHA2_512_SUPPORTED 0x0010 /* only set in server for now */ +#define KEX_HAS_PING 0x0020 struct sshenc { char *name; const struct sshcipher *cipher; int enabled; u_int key_len; u_int iv_len; u_int block_size; u_char *key; u_char *iv; }; struct sshcomp { u_int type; int enabled; char *name; }; struct newkeys { struct sshenc enc; struct sshmac mac; struct sshcomp comp; }; struct ssh; struct sshbuf; struct kex { struct newkeys *newkeys[MODE_MAX]; u_int we_need; u_int dh_need; int server; char *name; char *hostkey_alg; int hostkey_type; int hostkey_nid; u_int kex_type; char *server_sig_algs; int ext_info_c; struct sshbuf *my; struct sshbuf *peer; struct sshbuf *client_version; struct sshbuf *server_version; struct sshbuf *session_id; struct sshbuf *initial_sig; struct sshkey *initial_hostkey; sig_atomic_t done; u_int flags; int hash_alg; int ec_nid; char *failed_choice; int (*verify_host_key)(struct sshkey *, struct ssh *); struct sshkey *(*load_host_public_key)(int, int, struct ssh *); struct sshkey *(*load_host_private_key)(int, int, struct ssh *); int (*host_key_index)(struct sshkey *, int, struct ssh *); int (*sign)(struct ssh *, struct sshkey *, struct sshkey *, u_char **, size_t *, const u_char *, size_t, const char *); int (*kex[KEX_MAX])(struct ssh *); /* kex specific state */ DH *dh; /* DH */ u_int min, max, nbits; /* GEX */ EC_KEY *ec_client_key; /* ECDH */ const EC_GROUP *ec_group; /* ECDH */ u_char c25519_client_key[CURVE25519_SIZE]; /* 25519 + KEM */ u_char c25519_client_pubkey[CURVE25519_SIZE]; /* 25519 */ u_char sntrup761_client_key[crypto_kem_sntrup761_SECRETKEYBYTES]; /* KEM */ struct sshbuf *client_pub; }; int kex_names_valid(const char *); char *kex_alg_list(char); char *kex_names_cat(const char *, const char *); int kex_assemble_names(char **, const char *, const char *); void kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX], const char *, const char *, const char *, const char *, const char *); void kex_proposal_free_entries(char *prop[PROPOSAL_MAX]); int kex_exchange_identification(struct ssh *, int, const char *); struct kex *kex_new(void); int kex_ready(struct ssh *, char *[PROPOSAL_MAX]); int kex_setup(struct ssh *, char *[PROPOSAL_MAX]); void kex_free_newkeys(struct newkeys *); void kex_free(struct kex *); int kex_buf2prop(struct sshbuf *, int *, char ***); int kex_prop2buf(struct sshbuf *, char *proposal[PROPOSAL_MAX]); void kex_prop_free(char **); int kex_load_hostkey(struct ssh *, struct sshkey **, struct sshkey **); int kex_verify_host_key(struct ssh *, struct sshkey *); int kex_send_kexinit(struct ssh *); int kex_input_kexinit(int, u_int32_t, struct ssh *); int kex_input_ext_info(int, u_int32_t, struct ssh *); int kex_protocol_error(int, u_int32_t, struct ssh *); int kex_derive_keys(struct ssh *, u_char *, u_int, const struct sshbuf *); int kex_send_newkeys(struct ssh *); int kex_start_rekex(struct ssh *); int kexgex_client(struct ssh *); int kexgex_server(struct ssh *); int kex_gen_client(struct ssh *); int kex_gen_server(struct ssh *); int kex_dh_keypair(struct kex *); int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, struct sshbuf **); int kex_dh_dec(struct kex *, const struct sshbuf *, struct sshbuf **); int kex_ecdh_keypair(struct kex *); int kex_ecdh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, struct sshbuf **); int kex_ecdh_dec(struct kex *, const struct sshbuf *, struct sshbuf **); int kex_c25519_keypair(struct kex *); int kex_c25519_enc(struct kex *, const struct sshbuf *, struct sshbuf **, struct sshbuf **); int kex_c25519_dec(struct kex *, const struct sshbuf *, struct sshbuf **); int kex_kem_sntrup761x25519_keypair(struct kex *); int kex_kem_sntrup761x25519_enc(struct kex *, const struct sshbuf *, struct sshbuf **, struct sshbuf **); int kex_kem_sntrup761x25519_dec(struct kex *, const struct sshbuf *, struct sshbuf **); int kex_dh_keygen(struct kex *); int kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *); int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *, const struct sshbuf *, const struct sshbuf *, const struct sshbuf *, int, int, int, const BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, const u_char *, size_t, u_char *, size_t *); void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); int kexc25519_shared_key(const u_char key[CURVE25519_SIZE], const u_char pub[CURVE25519_SIZE], struct sshbuf *out) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); int kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE], const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void dump_digest(const char *, const u_char *, int); #endif #if !defined(WITH_OPENSSL) || !defined(OPENSSL_HAS_ECC) # undef EC_KEY # undef EC_GROUP # undef EC_POINT #endif #endif diff --git a/crypto/openssh/misc.c b/crypto/openssh/misc.c index 956587035517..42582c61829a 100644 --- a/crypto/openssh/misc.c +++ b/crypto/openssh/misc.c @@ -1,3017 +1,3028 @@ -/* $OpenBSD: misc.c,v 1.186 2023/08/18 01:37:41 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.187 2023/08/28 03:31:16 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. * Copyright (c) 2004 Henning Brauer * * 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 "includes.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBGEN_H # include #endif #ifdef HAVE_POLL_H #include #endif #ifdef HAVE_NLIST_H #include #endif #include #include #include #ifdef HAVE_STDINT_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H # include #include #include #endif #ifdef SSH_TUN_OPENBSD #include #endif #include "xmalloc.h" #include "misc.h" #include "log.h" #include "ssh.h" #include "sshbuf.h" #include "ssherr.h" #include "platform.h" /* remove newline at end of string */ char * chop(char *s) { char *t = s; while (*t) { if (*t == '\n' || *t == '\r') { *t = '\0'; return s; } t++; } return s; } /* remove whitespace from end of string */ void rtrim(char *s) { size_t i; if ((i = strlen(s)) == 0) return; for (i--; i > 0; i--) { if (isspace((unsigned char)s[i])) s[i] = '\0'; } } /* set/unset filedescriptor to non-blocking */ int set_nonblock(int fd) { int val; val = fcntl(fd, F_GETFL); if (val == -1) { error("fcntl(%d, F_GETFL): %s", fd, strerror(errno)); return (-1); } if (val & O_NONBLOCK) { debug3("fd %d is O_NONBLOCK", fd); return (0); } debug2("fd %d setting O_NONBLOCK", fd); val |= O_NONBLOCK; if (fcntl(fd, F_SETFL, val) == -1) { debug("fcntl(%d, F_SETFL, O_NONBLOCK): %s", fd, strerror(errno)); return (-1); } return (0); } int unset_nonblock(int fd) { int val; val = fcntl(fd, F_GETFL); if (val == -1) { error("fcntl(%d, F_GETFL): %s", fd, strerror(errno)); return (-1); } if (!(val & O_NONBLOCK)) { debug3("fd %d is not O_NONBLOCK", fd); return (0); } debug("fd %d clearing O_NONBLOCK", fd); val &= ~O_NONBLOCK; if (fcntl(fd, F_SETFL, val) == -1) { debug("fcntl(%d, F_SETFL, ~O_NONBLOCK): %s", fd, strerror(errno)); return (-1); } return (0); } const char * ssh_gai_strerror(int gaierr) { if (gaierr == EAI_SYSTEM && errno != 0) return strerror(errno); return gai_strerror(gaierr); } /* disable nagle on socket */ void set_nodelay(int fd) { int opt; socklen_t optlen; optlen = sizeof opt; if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) { debug("getsockopt TCP_NODELAY: %.100s", strerror(errno)); return; } if (opt == 1) { debug2("fd %d is TCP_NODELAY", fd); return; } opt = 1; debug2("fd %d setting TCP_NODELAY", fd); if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) error("setsockopt TCP_NODELAY: %.100s", strerror(errno)); } /* Allow local port reuse in TIME_WAIT */ int set_reuseaddr(int fd) { int on = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno)); return -1; } return 0; } /* Get/set routing domain */ char * get_rdomain(int fd) { #if defined(HAVE_SYS_GET_RDOMAIN) return sys_get_rdomain(fd); #elif defined(__OpenBSD__) int rtable; char *ret; socklen_t len = sizeof(rtable); if (getsockopt(fd, SOL_SOCKET, SO_RTABLE, &rtable, &len) == -1) { error("Failed to get routing domain for fd %d: %s", fd, strerror(errno)); return NULL; } xasprintf(&ret, "%d", rtable); return ret; #else /* defined(__OpenBSD__) */ return NULL; #endif } int set_rdomain(int fd, const char *name) { #if defined(HAVE_SYS_SET_RDOMAIN) return sys_set_rdomain(fd, name); #elif defined(__OpenBSD__) int rtable; const char *errstr; if (name == NULL) return 0; /* default table */ rtable = (int)strtonum(name, 0, 255, &errstr); if (errstr != NULL) { /* Shouldn't happen */ error("Invalid routing domain \"%s\": %s", name, errstr); return -1; } if (setsockopt(fd, SOL_SOCKET, SO_RTABLE, &rtable, sizeof(rtable)) == -1) { error("Failed to set routing domain %d on fd %d: %s", rtable, fd, strerror(errno)); return -1; } return 0; #else /* defined(__OpenBSD__) */ error("Setting routing domain is not supported on this platform"); return -1; #endif } int get_sock_af(int fd) { struct sockaddr_storage to; socklen_t tolen = sizeof(to); memset(&to, 0, sizeof(to)); if (getsockname(fd, (struct sockaddr *)&to, &tolen) == -1) return -1; #ifdef IPV4_IN_IPV6 if (to.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)&to)->sin6_addr)) return AF_INET; #endif return to.ss_family; } void set_sock_tos(int fd, int tos) { #ifndef IP_TOS_IS_BROKEN int af; switch ((af = get_sock_af(fd))) { case -1: /* assume not a socket */ break; case AF_INET: # ifdef IP_TOS debug3_f("set socket %d IP_TOS 0x%02x", fd, tos); if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) { error("setsockopt socket %d IP_TOS %d: %s", fd, tos, strerror(errno)); } # endif /* IP_TOS */ break; case AF_INET6: # ifdef IPV6_TCLASS debug3_f("set socket %d IPV6_TCLASS 0x%02x", fd, tos); if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == -1) { error("setsockopt socket %d IPV6_TCLASS %d: %s", fd, tos, strerror(errno)); } # endif /* IPV6_TCLASS */ break; default: debug2_f("unsupported socket family %d", af); break; } #endif /* IP_TOS_IS_BROKEN */ } /* * Wait up to *timeoutp milliseconds for events on fd. Updates * *timeoutp with time remaining. * Returns 0 if fd ready or -1 on timeout or error (see errno). */ static int waitfd(int fd, int *timeoutp, short events, volatile sig_atomic_t *stop) { struct pollfd pfd; struct timespec timeout; int oerrno, r; sigset_t nsigset, osigset; if (timeoutp && *timeoutp == -1) timeoutp = NULL; pfd.fd = fd; pfd.events = events; ptimeout_init(&timeout); if (timeoutp != NULL) ptimeout_deadline_ms(&timeout, *timeoutp); if (stop != NULL) sigfillset(&nsigset); for (; timeoutp == NULL || *timeoutp >= 0;) { if (stop != NULL) { sigprocmask(SIG_BLOCK, &nsigset, &osigset); if (*stop) { sigprocmask(SIG_SETMASK, &osigset, NULL); errno = EINTR; return -1; } } r = ppoll(&pfd, 1, ptimeout_get_tsp(&timeout), stop != NULL ? &osigset : NULL); oerrno = errno; if (stop != NULL) sigprocmask(SIG_SETMASK, &osigset, NULL); if (timeoutp) *timeoutp = ptimeout_get_ms(&timeout); errno = oerrno; if (r > 0) return 0; else if (r == -1 && errno != EAGAIN && errno != EINTR) return -1; else if (r == 0) break; } /* timeout */ errno = ETIMEDOUT; return -1; } /* * Wait up to *timeoutp milliseconds for fd to be readable. Updates * *timeoutp with time remaining. * Returns 0 if fd ready or -1 on timeout or error (see errno). */ int waitrfd(int fd, int *timeoutp, volatile sig_atomic_t *stop) { return waitfd(fd, timeoutp, POLLIN, stop); } /* * Attempt a non-blocking connect(2) to the specified address, waiting up to * *timeoutp milliseconds for the connection to complete. If the timeout is * <=0, then wait indefinitely. * * Returns 0 on success or -1 on failure. */ int timeout_connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen, int *timeoutp) { int optval = 0; socklen_t optlen = sizeof(optval); /* No timeout: just do a blocking connect() */ if (timeoutp == NULL || *timeoutp <= 0) return connect(sockfd, serv_addr, addrlen); set_nonblock(sockfd); for (;;) { if (connect(sockfd, serv_addr, addrlen) == 0) { /* Succeeded already? */ unset_nonblock(sockfd); return 0; } else if (errno == EINTR) continue; else if (errno != EINPROGRESS) return -1; break; } if (waitfd(sockfd, timeoutp, POLLIN | POLLOUT, NULL) == -1) return -1; /* Completed or failed */ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { debug("getsockopt: %s", strerror(errno)); return -1; } if (optval != 0) { errno = optval; return -1; } unset_nonblock(sockfd); return 0; } /* Characters considered whitespace in strsep calls. */ #define WHITESPACE " \t\r\n" #define QUOTE "\"" /* return next token in configuration line */ static char * strdelim_internal(char **s, int split_equals) { char *old; int wspace = 0; if (*s == NULL) return NULL; old = *s; *s = strpbrk(*s, split_equals ? WHITESPACE QUOTE "=" : WHITESPACE QUOTE); if (*s == NULL) return (old); if (*s[0] == '\"') { memmove(*s, *s + 1, strlen(*s)); /* move nul too */ /* Find matching quote */ if ((*s = strpbrk(*s, QUOTE)) == NULL) { return (NULL); /* no matching quote */ } else { *s[0] = '\0'; *s += strspn(*s + 1, WHITESPACE) + 1; return (old); } } /* Allow only one '=' to be skipped */ if (split_equals && *s[0] == '=') wspace = 1; *s[0] = '\0'; /* Skip any extra whitespace after first token */ *s += strspn(*s + 1, WHITESPACE) + 1; if (split_equals && *s[0] == '=' && !wspace) *s += strspn(*s + 1, WHITESPACE) + 1; return (old); } /* * Return next token in configuration line; splts on whitespace or a * single '=' character. */ char * strdelim(char **s) { return strdelim_internal(s, 1); } /* * Return next token in configuration line; splts on whitespace only. */ char * strdelimw(char **s) { return strdelim_internal(s, 0); } struct passwd * pwcopy(struct passwd *pw) { struct passwd *copy = xcalloc(1, sizeof(*copy)); copy->pw_name = xstrdup(pw->pw_name); copy->pw_passwd = xstrdup(pw->pw_passwd == NULL ? "*" : pw->pw_passwd); #ifdef HAVE_STRUCT_PASSWD_PW_GECOS copy->pw_gecos = xstrdup(pw->pw_gecos); #endif copy->pw_uid = pw->pw_uid; copy->pw_gid = pw->pw_gid; #ifdef HAVE_STRUCT_PASSWD_PW_EXPIRE copy->pw_expire = pw->pw_expire; #endif #ifdef HAVE_STRUCT_PASSWD_PW_CHANGE copy->pw_change = pw->pw_change; #endif #ifdef HAVE_STRUCT_PASSWD_PW_CLASS copy->pw_class = xstrdup(pw->pw_class); #endif copy->pw_dir = xstrdup(pw->pw_dir); copy->pw_shell = xstrdup(pw->pw_shell); return copy; } /* * Convert ASCII string to TCP/IP port number. * Port must be >=0 and <=65535. * Return -1 if invalid. */ int a2port(const char *s) { struct servent *se; long long port; const char *errstr; port = strtonum(s, 0, 65535, &errstr); if (errstr == NULL) return (int)port; if ((se = getservbyname(s, "tcp")) != NULL) return ntohs(se->s_port); return -1; } int a2tun(const char *s, int *remote) { const char *errstr = NULL; char *sp, *ep; int tun; if (remote != NULL) { *remote = SSH_TUNID_ANY; sp = xstrdup(s); if ((ep = strchr(sp, ':')) == NULL) { free(sp); return (a2tun(s, NULL)); } ep[0] = '\0'; ep++; *remote = a2tun(ep, NULL); tun = a2tun(sp, NULL); free(sp); return (*remote == SSH_TUNID_ERR ? *remote : tun); } if (strcasecmp(s, "any") == 0) return (SSH_TUNID_ANY); tun = strtonum(s, 0, SSH_TUNID_MAX, &errstr); if (errstr != NULL) return (SSH_TUNID_ERR); return (tun); } #define SECONDS 1 #define MINUTES (SECONDS * 60) #define HOURS (MINUTES * 60) #define DAYS (HOURS * 24) #define WEEKS (DAYS * 7) /* * Convert a time string into seconds; format is * a sequence of: * time[qualifier] * * Valid time qualifiers are: * seconds * s|S seconds * m|M minutes * h|H hours * d|D days * w|W weeks * * Examples: * 90m 90 minutes * 1h30m 90 minutes * 2d 2 days * 1w 1 week * * Return -1 if time string is invalid. */ int convtime(const char *s) { long total, secs, multiplier; const char *p; char *endp; errno = 0; total = 0; p = s; if (p == NULL || *p == '\0') return -1; while (*p) { secs = strtol(p, &endp, 10); if (p == endp || (errno == ERANGE && (secs == INT_MIN || secs == INT_MAX)) || secs < 0) return -1; multiplier = 1; switch (*endp++) { case '\0': endp--; break; case 's': case 'S': break; case 'm': case 'M': multiplier = MINUTES; break; case 'h': case 'H': multiplier = HOURS; break; case 'd': case 'D': multiplier = DAYS; break; case 'w': case 'W': multiplier = WEEKS; break; default: return -1; } if (secs > INT_MAX / multiplier) return -1; secs *= multiplier; if (total > INT_MAX - secs) return -1; total += secs; if (total < 0) return -1; p = endp; } return total; } #define TF_BUFS 8 #define TF_LEN 9 const char * fmt_timeframe(time_t t) { char *buf; static char tfbuf[TF_BUFS][TF_LEN]; /* ring buffer */ static int idx = 0; unsigned int sec, min, hrs, day; unsigned long long week; buf = tfbuf[idx++]; if (idx == TF_BUFS) idx = 0; week = t; sec = week % 60; week /= 60; min = week % 60; week /= 60; hrs = week % 24; week /= 24; day = week % 7; week /= 7; if (week > 0) snprintf(buf, TF_LEN, "%02lluw%01ud%02uh", week, day, hrs); else if (day > 0) snprintf(buf, TF_LEN, "%01ud%02uh%02um", day, hrs, min); else snprintf(buf, TF_LEN, "%02u:%02u:%02u", hrs, min, sec); return (buf); } /* * Returns a standardized host+port identifier string. * Caller must free returned string. */ char * put_host_port(const char *host, u_short port) { char *hoststr; if (port == 0 || port == SSH_DEFAULT_PORT) return(xstrdup(host)); if (asprintf(&hoststr, "[%s]:%d", host, (int)port) == -1) fatal("put_host_port: asprintf: %s", strerror(errno)); debug3("put_host_port: %s", hoststr); return hoststr; } /* * Search for next delimiter between hostnames/addresses and ports. * Argument may be modified (for termination). * Returns *cp if parsing succeeds. * *cp is set to the start of the next field, if one was found. * The delimiter char, if present, is stored in delim. * If this is the last field, *cp is set to NULL. */ char * hpdelim2(char **cp, char *delim) { char *s, *old; if (cp == NULL || *cp == NULL) return NULL; old = s = *cp; if (*s == '[') { if ((s = strchr(s, ']')) == NULL) return NULL; else s++; } else if ((s = strpbrk(s, ":/")) == NULL) s = *cp + strlen(*cp); /* skip to end (see first case below) */ switch (*s) { case '\0': *cp = NULL; /* no more fields*/ break; case ':': case '/': if (delim != NULL) *delim = *s; *s = '\0'; /* terminate */ *cp = s + 1; break; default: return NULL; } return old; } /* The common case: only accept colon as delimiter. */ char * hpdelim(char **cp) { char *r, delim = '\0'; r = hpdelim2(cp, &delim); if (delim == '/') return NULL; return r; } char * cleanhostname(char *host) { if (*host == '[' && host[strlen(host) - 1] == ']') { host[strlen(host) - 1] = '\0'; return (host + 1); } else return host; } char * colon(char *cp) { int flag = 0; if (*cp == ':') /* Leading colon is part of file name. */ return NULL; if (*cp == '[') flag = 1; for (; *cp; ++cp) { if (*cp == '@' && *(cp+1) == '[') flag = 1; if (*cp == ']' && *(cp+1) == ':' && flag) return (cp+1); if (*cp == ':' && !flag) return (cp); if (*cp == '/') return NULL; } return NULL; } /* * Parse a [user@]host:[path] string. * Caller must free returned user, host and path. * Any of the pointer return arguments may be NULL (useful for syntax checking). * If user was not specified then *userp will be set to NULL. * If host was not specified then *hostp will be set to NULL. * If path was not specified then *pathp will be set to ".". * Returns 0 on success, -1 on failure. */ int parse_user_host_path(const char *s, char **userp, char **hostp, char **pathp) { char *user = NULL, *host = NULL, *path = NULL; char *sdup, *tmp; int ret = -1; if (userp != NULL) *userp = NULL; if (hostp != NULL) *hostp = NULL; if (pathp != NULL) *pathp = NULL; sdup = xstrdup(s); /* Check for remote syntax: [user@]host:[path] */ if ((tmp = colon(sdup)) == NULL) goto out; /* Extract optional path */ *tmp++ = '\0'; if (*tmp == '\0') tmp = "."; path = xstrdup(tmp); /* Extract optional user and mandatory host */ tmp = strrchr(sdup, '@'); if (tmp != NULL) { *tmp++ = '\0'; host = xstrdup(cleanhostname(tmp)); if (*sdup != '\0') user = xstrdup(sdup); } else { host = xstrdup(cleanhostname(sdup)); user = NULL; } /* Success */ if (userp != NULL) { *userp = user; user = NULL; } if (hostp != NULL) { *hostp = host; host = NULL; } if (pathp != NULL) { *pathp = path; path = NULL; } ret = 0; out: free(sdup); free(user); free(host); free(path); return ret; } /* * Parse a [user@]host[:port] string. * Caller must free returned user and host. * Any of the pointer return arguments may be NULL (useful for syntax checking). * If user was not specified then *userp will be set to NULL. * If port was not specified then *portp will be -1. * Returns 0 on success, -1 on failure. */ int parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) { char *sdup, *cp, *tmp; char *user = NULL, *host = NULL; int port = -1, ret = -1; if (userp != NULL) *userp = NULL; if (hostp != NULL) *hostp = NULL; if (portp != NULL) *portp = -1; if ((sdup = tmp = strdup(s)) == NULL) return -1; /* Extract optional username */ if ((cp = strrchr(tmp, '@')) != NULL) { *cp = '\0'; if (*tmp == '\0') goto out; if ((user = strdup(tmp)) == NULL) goto out; tmp = cp + 1; } /* Extract mandatory hostname */ if ((cp = hpdelim(&tmp)) == NULL || *cp == '\0') goto out; host = xstrdup(cleanhostname(cp)); /* Convert and verify optional port */ if (tmp != NULL && *tmp != '\0') { if ((port = a2port(tmp)) <= 0) goto out; } /* Success */ if (userp != NULL) { *userp = user; user = NULL; } if (hostp != NULL) { *hostp = host; host = NULL; } if (portp != NULL) *portp = port; ret = 0; out: free(sdup); free(user); free(host); return ret; } /* * Converts a two-byte hex string to decimal. * Returns the decimal value or -1 for invalid input. */ static int hexchar(const char *s) { unsigned char result[2]; int i; for (i = 0; i < 2; i++) { if (s[i] >= '0' && s[i] <= '9') result[i] = (unsigned char)(s[i] - '0'); else if (s[i] >= 'a' && s[i] <= 'f') result[i] = (unsigned char)(s[i] - 'a') + 10; else if (s[i] >= 'A' && s[i] <= 'F') result[i] = (unsigned char)(s[i] - 'A') + 10; else return -1; } return (result[0] << 4) | result[1]; } /* * Decode an url-encoded string. * Returns a newly allocated string on success or NULL on failure. */ static char * urldecode(const char *src) { char *ret, *dst; int ch; size_t srclen; if ((srclen = strlen(src)) >= SIZE_MAX) fatal_f("input too large"); ret = xmalloc(srclen + 1); for (dst = ret; *src != '\0'; src++) { switch (*src) { case '+': *dst++ = ' '; break; case '%': if (!isxdigit((unsigned char)src[1]) || !isxdigit((unsigned char)src[2]) || (ch = hexchar(src + 1)) == -1) { free(ret); return NULL; } *dst++ = ch; src += 2; break; default: *dst++ = *src; break; } } *dst = '\0'; return ret; } /* * Parse an (scp|ssh|sftp)://[user@]host[:port][/path] URI. * See https://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04 * Either user or path may be url-encoded (but not host or port). * Caller must free returned user, host and path. * Any of the pointer return arguments may be NULL (useful for syntax checking) * but the scheme must always be specified. * If user was not specified then *userp will be set to NULL. * If port was not specified then *portp will be -1. * If path was not specified then *pathp will be set to NULL. * Returns 0 on success, 1 if non-uri/wrong scheme, -1 on error/invalid uri. */ int parse_uri(const char *scheme, const char *uri, char **userp, char **hostp, int *portp, char **pathp) { char *uridup, *cp, *tmp, ch; char *user = NULL, *host = NULL, *path = NULL; int port = -1, ret = -1; size_t len; len = strlen(scheme); if (strncmp(uri, scheme, len) != 0 || strncmp(uri + len, "://", 3) != 0) return 1; uri += len + 3; if (userp != NULL) *userp = NULL; if (hostp != NULL) *hostp = NULL; if (portp != NULL) *portp = -1; if (pathp != NULL) *pathp = NULL; uridup = tmp = xstrdup(uri); /* Extract optional ssh-info (username + connection params) */ if ((cp = strchr(tmp, '@')) != NULL) { char *delim; *cp = '\0'; /* Extract username and connection params */ if ((delim = strchr(tmp, ';')) != NULL) { /* Just ignore connection params for now */ *delim = '\0'; } if (*tmp == '\0') { /* Empty username */ goto out; } if ((user = urldecode(tmp)) == NULL) goto out; tmp = cp + 1; } /* Extract mandatory hostname */ if ((cp = hpdelim2(&tmp, &ch)) == NULL || *cp == '\0') goto out; host = xstrdup(cleanhostname(cp)); if (!valid_domain(host, 0, NULL)) goto out; if (tmp != NULL && *tmp != '\0') { if (ch == ':') { /* Convert and verify port. */ if ((cp = strchr(tmp, '/')) != NULL) *cp = '\0'; if ((port = a2port(tmp)) <= 0) goto out; tmp = cp ? cp + 1 : NULL; } if (tmp != NULL && *tmp != '\0') { /* Extract optional path */ if ((path = urldecode(tmp)) == NULL) goto out; } } /* Success */ if (userp != NULL) { *userp = user; user = NULL; } if (hostp != NULL) { *hostp = host; host = NULL; } if (portp != NULL) *portp = port; if (pathp != NULL) { *pathp = path; path = NULL; } ret = 0; out: free(uridup); free(user); free(host); free(path); return ret; } /* function to assist building execv() arguments */ void addargs(arglist *args, char *fmt, ...) { va_list ap; char *cp; u_int nalloc; int r; va_start(ap, fmt); r = vasprintf(&cp, fmt, ap); va_end(ap); if (r == -1) fatal_f("argument too long"); nalloc = args->nalloc; if (args->list == NULL) { nalloc = 32; args->num = 0; } else if (args->num > (256 * 1024)) fatal_f("too many arguments"); else if (args->num >= args->nalloc) fatal_f("arglist corrupt"); else if (args->num+2 >= nalloc) nalloc *= 2; args->list = xrecallocarray(args->list, args->nalloc, nalloc, sizeof(char *)); args->nalloc = nalloc; args->list[args->num++] = cp; args->list[args->num] = NULL; } void replacearg(arglist *args, u_int which, char *fmt, ...) { va_list ap; char *cp; int r; va_start(ap, fmt); r = vasprintf(&cp, fmt, ap); va_end(ap); if (r == -1) fatal_f("argument too long"); if (args->list == NULL || args->num >= args->nalloc) fatal_f("arglist corrupt"); if (which >= args->num) fatal_f("tried to replace invalid arg %d >= %d", which, args->num); free(args->list[which]); args->list[which] = cp; } void freeargs(arglist *args) { u_int i; if (args == NULL) return; if (args->list != NULL && args->num < args->nalloc) { for (i = 0; i < args->num; i++) free(args->list[i]); free(args->list); } args->nalloc = args->num = 0; args->list = NULL; } /* * Expands tildes in the file name. Returns data allocated by xmalloc. * Warning: this calls getpw*. */ int tilde_expand(const char *filename, uid_t uid, char **retp) { char *ocopy = NULL, *copy, *s = NULL; const char *path = NULL, *user = NULL; struct passwd *pw; size_t len; int ret = -1, r, slash; *retp = NULL; if (*filename != '~') { *retp = xstrdup(filename); return 0; } ocopy = copy = xstrdup(filename + 1); if (*copy == '\0') /* ~ */ path = NULL; else if (*copy == '/') { copy += strspn(copy, "/"); if (*copy == '\0') path = NULL; /* ~/ */ else path = copy; /* ~/path */ } else { user = copy; if ((path = strchr(copy, '/')) != NULL) { copy[path - copy] = '\0'; path++; path += strspn(path, "/"); if (*path == '\0') /* ~user/ */ path = NULL; /* else ~user/path */ } /* else ~user */ } if (user != NULL) { if ((pw = getpwnam(user)) == NULL) { error_f("No such user %s", user); goto out; } } else if ((pw = getpwuid(uid)) == NULL) { error_f("No such uid %ld", (long)uid); goto out; } /* Make sure directory has a trailing '/' */ slash = (len = strlen(pw->pw_dir)) == 0 || pw->pw_dir[len - 1] != '/'; if ((r = xasprintf(&s, "%s%s%s", pw->pw_dir, slash ? "/" : "", path != NULL ? path : "")) <= 0) { error_f("xasprintf failed"); goto out; } if (r >= PATH_MAX) { error_f("Path too long"); goto out; } /* success */ ret = 0; *retp = s; s = NULL; out: free(s); free(ocopy); return ret; } char * tilde_expand_filename(const char *filename, uid_t uid) { char *ret; if (tilde_expand(filename, uid, &ret) != 0) cleanup_exit(255); return ret; } /* * Expand a string with a set of %[char] escapes and/or ${ENVIRONMENT} * substitutions. A number of escapes may be specified as * (char *escape_chars, char *replacement) pairs. The list must be terminated * by a NULL escape_char. Returns replaced string in memory allocated by * xmalloc which the caller must free. */ static char * vdollar_percent_expand(int *parseerror, int dollar, int percent, const char *string, va_list ap) { #define EXPAND_MAX_KEYS 16 u_int num_keys = 0, i; struct { const char *key; const char *repl; } keys[EXPAND_MAX_KEYS]; struct sshbuf *buf; int r, missingvar = 0; char *ret = NULL, *var, *varend, *val; size_t len; if ((buf = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if (parseerror == NULL) fatal_f("null parseerror arg"); *parseerror = 1; /* Gather keys if we're doing percent expansion. */ if (percent) { for (num_keys = 0; num_keys < EXPAND_MAX_KEYS; num_keys++) { keys[num_keys].key = va_arg(ap, char *); if (keys[num_keys].key == NULL) break; keys[num_keys].repl = va_arg(ap, char *); if (keys[num_keys].repl == NULL) { fatal_f("NULL replacement for token %s", keys[num_keys].key); } } if (num_keys == EXPAND_MAX_KEYS && va_arg(ap, char *) != NULL) fatal_f("too many keys"); if (num_keys == 0) fatal_f("percent expansion without token list"); } /* Expand string */ for (i = 0; *string != '\0'; string++) { /* Optionally process ${ENVIRONMENT} expansions. */ if (dollar && string[0] == '$' && string[1] == '{') { string += 2; /* skip over '${' */ if ((varend = strchr(string, '}')) == NULL) { error_f("environment variable '%s' missing " "closing '}'", string); goto out; } len = varend - string; if (len == 0) { error_f("zero-length environment variable"); goto out; } var = xmalloc(len + 1); (void)strlcpy(var, string, len + 1); if ((val = getenv(var)) == NULL) { error_f("env var ${%s} has no value", var); missingvar = 1; } else { debug3_f("expand ${%s} -> '%s'", var, val); if ((r = sshbuf_put(buf, val, strlen(val))) !=0) fatal_fr(r, "sshbuf_put ${}"); } free(var); string += len; continue; } /* * Process percent expansions if we have a list of TOKENs. * If we're not doing percent expansion everything just gets * appended here. */ if (*string != '%' || !percent) { append: if ((r = sshbuf_put_u8(buf, *string)) != 0) fatal_fr(r, "sshbuf_put_u8 %%"); continue; } string++; /* %% case */ if (*string == '%') goto append; if (*string == '\0') { error_f("invalid format"); goto out; } for (i = 0; i < num_keys; i++) { if (strchr(keys[i].key, *string) != NULL) { if ((r = sshbuf_put(buf, keys[i].repl, strlen(keys[i].repl))) != 0) fatal_fr(r, "sshbuf_put %%-repl"); break; } } if (i >= num_keys) { error_f("unknown key %%%c", *string); goto out; } } if (!missingvar && (ret = sshbuf_dup_string(buf)) == NULL) fatal_f("sshbuf_dup_string failed"); *parseerror = 0; out: sshbuf_free(buf); return *parseerror ? NULL : ret; #undef EXPAND_MAX_KEYS } /* * Expand only environment variables. * Note that although this function is variadic like the other similar * functions, any such arguments will be unused. */ char * dollar_expand(int *parseerr, const char *string, ...) { char *ret; int err; va_list ap; va_start(ap, string); ret = vdollar_percent_expand(&err, 1, 0, string, ap); va_end(ap); if (parseerr != NULL) *parseerr = err; return ret; } /* * Returns expanded string or NULL if a specified environment variable is * not defined, or calls fatal if the string is invalid. */ char * percent_expand(const char *string, ...) { char *ret; int err; va_list ap; va_start(ap, string); ret = vdollar_percent_expand(&err, 0, 1, string, ap); va_end(ap); if (err) fatal_f("failed"); return ret; } /* * Returns expanded string or NULL if a specified environment variable is * not defined, or calls fatal if the string is invalid. */ char * percent_dollar_expand(const char *string, ...) { char *ret; int err; va_list ap; va_start(ap, string); ret = vdollar_percent_expand(&err, 1, 1, string, ap); va_end(ap); if (err) fatal_f("failed"); return ret; } int tun_open(int tun, int mode, char **ifname) { #if defined(CUSTOM_SYS_TUN_OPEN) return (sys_tun_open(tun, mode, ifname)); #elif defined(SSH_TUN_OPENBSD) struct ifreq ifr; char name[100]; int fd = -1, sock; const char *tunbase = "tun"; if (ifname != NULL) *ifname = NULL; if (mode == SSH_TUNMODE_ETHERNET) tunbase = "tap"; /* Open the tunnel device */ if (tun <= SSH_TUNID_MAX) { snprintf(name, sizeof(name), "/dev/%s%d", tunbase, tun); fd = open(name, O_RDWR); } else if (tun == SSH_TUNID_ANY) { for (tun = 100; tun >= 0; tun--) { snprintf(name, sizeof(name), "/dev/%s%d", tunbase, tun); if ((fd = open(name, O_RDWR)) >= 0) break; } } else { debug_f("invalid tunnel %u", tun); return -1; } if (fd == -1) { debug_f("%s open: %s", name, strerror(errno)); return -1; } debug_f("%s mode %d fd %d", name, mode, fd); /* Bring interface up if it is not already */ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", tunbase, tun); if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) goto failed; if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) { debug_f("get interface %s flags: %s", ifr.ifr_name, strerror(errno)); goto failed; } if (!(ifr.ifr_flags & IFF_UP)) { ifr.ifr_flags |= IFF_UP; if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) { debug_f("activate interface %s: %s", ifr.ifr_name, strerror(errno)); goto failed; } } if (ifname != NULL) *ifname = xstrdup(ifr.ifr_name); close(sock); return fd; failed: if (fd >= 0) close(fd); if (sock >= 0) close(sock); return -1; #else error("Tunnel interfaces are not supported on this platform"); return (-1); #endif } void sanitise_stdfd(void) { int nullfd, dupfd; if ((nullfd = dupfd = open(_PATH_DEVNULL, O_RDWR)) == -1) { fprintf(stderr, "Couldn't open /dev/null: %s\n", strerror(errno)); exit(1); } while (++dupfd <= STDERR_FILENO) { /* Only populate closed fds. */ if (fcntl(dupfd, F_GETFL) == -1 && errno == EBADF) { if (dup2(nullfd, dupfd) == -1) { fprintf(stderr, "dup2: %s\n", strerror(errno)); exit(1); } } } if (nullfd > STDERR_FILENO) close(nullfd); } char * tohex(const void *vp, size_t l) { const u_char *p = (const u_char *)vp; char b[3], *r; size_t i, hl; if (l > 65536) return xstrdup("tohex: length > 65536"); hl = l * 2 + 1; r = xcalloc(1, hl); for (i = 0; i < l; i++) { snprintf(b, sizeof(b), "%02x", p[i]); strlcat(r, b, hl); } return (r); } /* * Extend string *sp by the specified format. If *sp is not NULL (or empty), * then the separator 'sep' will be prepended before the formatted arguments. * Extended strings are heap allocated. */ void xextendf(char **sp, const char *sep, const char *fmt, ...) { va_list ap; char *tmp1, *tmp2; va_start(ap, fmt); xvasprintf(&tmp1, fmt, ap); va_end(ap); if (*sp == NULL || **sp == '\0') { free(*sp); *sp = tmp1; return; } xasprintf(&tmp2, "%s%s%s", *sp, sep == NULL ? "" : sep, tmp1); free(tmp1); free(*sp); *sp = tmp2; } u_int64_t get_u64(const void *vp) { const u_char *p = (const u_char *)vp; u_int64_t v; v = (u_int64_t)p[0] << 56; v |= (u_int64_t)p[1] << 48; v |= (u_int64_t)p[2] << 40; v |= (u_int64_t)p[3] << 32; v |= (u_int64_t)p[4] << 24; v |= (u_int64_t)p[5] << 16; v |= (u_int64_t)p[6] << 8; v |= (u_int64_t)p[7]; return (v); } u_int32_t get_u32(const void *vp) { const u_char *p = (const u_char *)vp; u_int32_t v; v = (u_int32_t)p[0] << 24; v |= (u_int32_t)p[1] << 16; v |= (u_int32_t)p[2] << 8; v |= (u_int32_t)p[3]; return (v); } u_int32_t get_u32_le(const void *vp) { const u_char *p = (const u_char *)vp; u_int32_t v; v = (u_int32_t)p[0]; v |= (u_int32_t)p[1] << 8; v |= (u_int32_t)p[2] << 16; v |= (u_int32_t)p[3] << 24; return (v); } u_int16_t get_u16(const void *vp) { const u_char *p = (const u_char *)vp; u_int16_t v; v = (u_int16_t)p[0] << 8; v |= (u_int16_t)p[1]; return (v); } void put_u64(void *vp, u_int64_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)(v >> 56) & 0xff; p[1] = (u_char)(v >> 48) & 0xff; p[2] = (u_char)(v >> 40) & 0xff; p[3] = (u_char)(v >> 32) & 0xff; p[4] = (u_char)(v >> 24) & 0xff; p[5] = (u_char)(v >> 16) & 0xff; p[6] = (u_char)(v >> 8) & 0xff; p[7] = (u_char)v & 0xff; } void put_u32(void *vp, u_int32_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)(v >> 24) & 0xff; p[1] = (u_char)(v >> 16) & 0xff; p[2] = (u_char)(v >> 8) & 0xff; p[3] = (u_char)v & 0xff; } void put_u32_le(void *vp, u_int32_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)v & 0xff; p[1] = (u_char)(v >> 8) & 0xff; p[2] = (u_char)(v >> 16) & 0xff; p[3] = (u_char)(v >> 24) & 0xff; } void put_u16(void *vp, u_int16_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)(v >> 8) & 0xff; p[1] = (u_char)v & 0xff; } void ms_subtract_diff(struct timeval *start, int *ms) { struct timeval diff, finish; monotime_tv(&finish); timersub(&finish, start, &diff); *ms -= (diff.tv_sec * 1000) + (diff.tv_usec / 1000); } void ms_to_timespec(struct timespec *ts, int ms) { if (ms < 0) ms = 0; ts->tv_sec = ms / 1000; ts->tv_nsec = (ms % 1000) * 1000 * 1000; } void monotime_ts(struct timespec *ts) { struct timeval tv; #if defined(HAVE_CLOCK_GETTIME) && (defined(CLOCK_BOOTTIME) || \ defined(CLOCK_MONOTONIC) || defined(CLOCK_REALTIME)) static int gettime_failed = 0; if (!gettime_failed) { # ifdef CLOCK_BOOTTIME if (clock_gettime(CLOCK_BOOTTIME, ts) == 0) return; # endif /* CLOCK_BOOTTIME */ # ifdef CLOCK_MONOTONIC if (clock_gettime(CLOCK_MONOTONIC, ts) == 0) return; # endif /* CLOCK_MONOTONIC */ # ifdef CLOCK_REALTIME /* Not monotonic, but we're almost out of options here. */ if (clock_gettime(CLOCK_REALTIME, ts) == 0) return; # endif /* CLOCK_REALTIME */ debug3("clock_gettime: %s", strerror(errno)); gettime_failed = 1; } #endif /* HAVE_CLOCK_GETTIME && (BOOTTIME || MONOTONIC || REALTIME) */ gettimeofday(&tv, NULL); ts->tv_sec = tv.tv_sec; ts->tv_nsec = (long)tv.tv_usec * 1000; } void monotime_tv(struct timeval *tv) { struct timespec ts; monotime_ts(&ts); tv->tv_sec = ts.tv_sec; tv->tv_usec = ts.tv_nsec / 1000; } time_t monotime(void) { struct timespec ts; monotime_ts(&ts); return ts.tv_sec; } double monotime_double(void) { struct timespec ts; monotime_ts(&ts); return ts.tv_sec + ((double)ts.tv_nsec / 1000000000); } void bandwidth_limit_init(struct bwlimit *bw, u_int64_t kbps, size_t buflen) { bw->buflen = buflen; bw->rate = kbps; bw->thresh = buflen; bw->lamt = 0; timerclear(&bw->bwstart); timerclear(&bw->bwend); } /* Callback from read/write loop to insert bandwidth-limiting delays */ void bandwidth_limit(struct bwlimit *bw, size_t read_len) { u_int64_t waitlen; struct timespec ts, rm; bw->lamt += read_len; if (!timerisset(&bw->bwstart)) { monotime_tv(&bw->bwstart); return; } if (bw->lamt < bw->thresh) return; monotime_tv(&bw->bwend); timersub(&bw->bwend, &bw->bwstart, &bw->bwend); if (!timerisset(&bw->bwend)) return; bw->lamt *= 8; waitlen = (double)1000000L * bw->lamt / bw->rate; bw->bwstart.tv_sec = waitlen / 1000000L; bw->bwstart.tv_usec = waitlen % 1000000L; if (timercmp(&bw->bwstart, &bw->bwend, >)) { timersub(&bw->bwstart, &bw->bwend, &bw->bwend); /* Adjust the wait time */ if (bw->bwend.tv_sec) { bw->thresh /= 2; if (bw->thresh < bw->buflen / 4) bw->thresh = bw->buflen / 4; } else if (bw->bwend.tv_usec < 10000) { bw->thresh *= 2; if (bw->thresh > bw->buflen * 8) bw->thresh = bw->buflen * 8; } TIMEVAL_TO_TIMESPEC(&bw->bwend, &ts); while (nanosleep(&ts, &rm) == -1) { if (errno != EINTR) break; ts = rm; } } bw->lamt = 0; monotime_tv(&bw->bwstart); } /* Make a template filename for mk[sd]temp() */ void mktemp_proto(char *s, size_t len) { const char *tmpdir; int r; if ((tmpdir = getenv("TMPDIR")) != NULL) { r = snprintf(s, len, "%s/ssh-XXXXXXXXXXXX", tmpdir); if (r > 0 && (size_t)r < len) return; } r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX"); if (r < 0 || (size_t)r >= len) fatal_f("template string too short"); } static const struct { const char *name; int value; } ipqos[] = { { "none", INT_MAX }, /* can't use 0 here; that's CS0 */ { "af11", IPTOS_DSCP_AF11 }, { "af12", IPTOS_DSCP_AF12 }, { "af13", IPTOS_DSCP_AF13 }, { "af21", IPTOS_DSCP_AF21 }, { "af22", IPTOS_DSCP_AF22 }, { "af23", IPTOS_DSCP_AF23 }, { "af31", IPTOS_DSCP_AF31 }, { "af32", IPTOS_DSCP_AF32 }, { "af33", IPTOS_DSCP_AF33 }, { "af41", IPTOS_DSCP_AF41 }, { "af42", IPTOS_DSCP_AF42 }, { "af43", IPTOS_DSCP_AF43 }, { "cs0", IPTOS_DSCP_CS0 }, { "cs1", IPTOS_DSCP_CS1 }, { "cs2", IPTOS_DSCP_CS2 }, { "cs3", IPTOS_DSCP_CS3 }, { "cs4", IPTOS_DSCP_CS4 }, { "cs5", IPTOS_DSCP_CS5 }, { "cs6", IPTOS_DSCP_CS6 }, { "cs7", IPTOS_DSCP_CS7 }, { "ef", IPTOS_DSCP_EF }, { "le", IPTOS_DSCP_LE }, { "lowdelay", IPTOS_LOWDELAY }, { "throughput", IPTOS_THROUGHPUT }, { "reliability", IPTOS_RELIABILITY }, { NULL, -1 } }; int parse_ipqos(const char *cp) { u_int i; char *ep; long val; if (cp == NULL) return -1; for (i = 0; ipqos[i].name != NULL; i++) { if (strcasecmp(cp, ipqos[i].name) == 0) return ipqos[i].value; } /* Try parsing as an integer */ val = strtol(cp, &ep, 0); if (*cp == '\0' || *ep != '\0' || val < 0 || val > 255) return -1; return val; } const char * iptos2str(int iptos) { int i; static char iptos_str[sizeof "0xff"]; for (i = 0; ipqos[i].name != NULL; i++) { if (ipqos[i].value == iptos) return ipqos[i].name; } snprintf(iptos_str, sizeof iptos_str, "0x%02x", iptos); return iptos_str; } void lowercase(char *s) { for (; *s; s++) *s = tolower((u_char)*s); } int unix_listener(const char *path, int backlog, int unlink_first) { struct sockaddr_un sunaddr; int saved_errno, sock; memset(&sunaddr, 0, sizeof(sunaddr)); sunaddr.sun_family = AF_UNIX; if (strlcpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path)) >= sizeof(sunaddr.sun_path)) { error_f("path \"%s\" too long for Unix domain socket", path); errno = ENAMETOOLONG; return -1; } sock = socket(PF_UNIX, SOCK_STREAM, 0); if (sock == -1) { saved_errno = errno; error_f("socket: %.100s", strerror(errno)); errno = saved_errno; return -1; } if (unlink_first == 1) { if (unlink(path) != 0 && errno != ENOENT) error("unlink(%s): %.100s", path, strerror(errno)); } if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1) { saved_errno = errno; error_f("cannot bind to path %s: %s", path, strerror(errno)); close(sock); errno = saved_errno; return -1; } if (listen(sock, backlog) == -1) { saved_errno = errno; error_f("cannot listen on path %s: %s", path, strerror(errno)); close(sock); unlink(path); errno = saved_errno; return -1; } return sock; } void sock_set_v6only(int s) { #if defined(IPV6_V6ONLY) && !defined(__OpenBSD__) int on = 1; debug3("%s: set socket %d IPV6_V6ONLY", __func__, s); if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) error("setsockopt IPV6_V6ONLY: %s", strerror(errno)); #endif } /* * Compares two strings that maybe be NULL. Returns non-zero if strings * are both NULL or are identical, returns zero otherwise. */ static int strcmp_maybe_null(const char *a, const char *b) { if ((a == NULL && b != NULL) || (a != NULL && b == NULL)) return 0; if (a != NULL && strcmp(a, b) != 0) return 0; return 1; } /* * Compare two forwards, returning non-zero if they are identical or * zero otherwise. */ int forward_equals(const struct Forward *a, const struct Forward *b) { if (strcmp_maybe_null(a->listen_host, b->listen_host) == 0) return 0; if (a->listen_port != b->listen_port) return 0; if (strcmp_maybe_null(a->listen_path, b->listen_path) == 0) return 0; if (strcmp_maybe_null(a->connect_host, b->connect_host) == 0) return 0; if (a->connect_port != b->connect_port) return 0; if (strcmp_maybe_null(a->connect_path, b->connect_path) == 0) return 0; /* allocated_port and handle are not checked */ return 1; } /* returns 1 if process is already daemonized, 0 otherwise */ int daemonized(void) { int fd; if ((fd = open(_PATH_TTY, O_RDONLY | O_NOCTTY)) >= 0) { close(fd); return 0; /* have controlling terminal */ } if (getppid() != 1) return 0; /* parent is not init */ if (getsid(0) != getpid()) return 0; /* not session leader */ debug3("already daemonized"); return 1; } /* * Splits 's' into an argument vector. Handles quoted string and basic * escape characters (\\, \", \'). Caller must free the argument vector * and its members. */ int argv_split(const char *s, int *argcp, char ***argvp, int terminate_on_comment) { int r = SSH_ERR_INTERNAL_ERROR; int argc = 0, quote, i, j; char *arg, **argv = xcalloc(1, sizeof(*argv)); *argvp = NULL; *argcp = 0; for (i = 0; s[i] != '\0'; i++) { /* Skip leading whitespace */ if (s[i] == ' ' || s[i] == '\t') continue; if (terminate_on_comment && s[i] == '#') break; /* Start of a token */ quote = 0; argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); argv[argc] = NULL; /* Copy the token in, removing escapes */ for (j = 0; s[i] != '\0'; i++) { if (s[i] == '\\') { if (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\' || (quote == 0 && s[i + 1] == ' ')) { i++; /* Skip '\' */ arg[j++] = s[i]; } else { /* Unrecognised escape */ arg[j++] = s[i]; } } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) break; /* done */ else if (quote == 0 && (s[i] == '\"' || s[i] == '\'')) quote = s[i]; /* quote start */ else if (quote != 0 && s[i] == quote) quote = 0; /* quote end */ else arg[j++] = s[i]; } if (s[i] == '\0') { if (quote != 0) { /* Ran out of string looking for close quote */ r = SSH_ERR_INVALID_FORMAT; goto out; } break; } } /* Success */ *argcp = argc; *argvp = argv; argc = 0; argv = NULL; r = 0; out: if (argc != 0 && argv != NULL) { for (i = 0; i < argc; i++) free(argv[i]); free(argv); } return r; } /* * Reassemble an argument vector into a string, quoting and escaping as * necessary. Caller must free returned string. */ char * argv_assemble(int argc, char **argv) { int i, j, ws, r; char c, *ret; struct sshbuf *buf, *arg; if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); for (i = 0; i < argc; i++) { ws = 0; sshbuf_reset(arg); for (j = 0; argv[i][j] != '\0'; j++) { r = 0; c = argv[i][j]; switch (c) { case ' ': case '\t': ws = 1; r = sshbuf_put_u8(arg, c); break; case '\\': case '\'': case '"': if ((r = sshbuf_put_u8(arg, '\\')) != 0) break; /* FALLTHROUGH */ default: r = sshbuf_put_u8(arg, c); break; } if (r != 0) fatal_fr(r, "sshbuf_put_u8"); } if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) || (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) || (r = sshbuf_putb(buf, arg)) != 0 || (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0)) fatal_fr(r, "assemble"); } if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) fatal_f("malloc failed"); memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); ret[sshbuf_len(buf)] = '\0'; sshbuf_free(buf); sshbuf_free(arg); return ret; } char * argv_next(int *argcp, char ***argvp) { char *ret = (*argvp)[0]; if (*argcp > 0 && ret != NULL) { (*argcp)--; (*argvp)++; } return ret; } void argv_consume(int *argcp) { *argcp = 0; } void argv_free(char **av, int ac) { int i; if (av == NULL) return; for (i = 0; i < ac; i++) free(av[i]); free(av); } /* Returns 0 if pid exited cleanly, non-zero otherwise */ int exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet) { int status; while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { error("%s waitpid: %s", tag, strerror(errno)); return -1; } } if (WIFSIGNALED(status)) { error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); return -1; } else if (WEXITSTATUS(status) != 0) { do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, "%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); return -1; } return 0; } /* * Check a given path for security. This is defined as all components * of the path to the file must be owned by either the owner of * of the file or root and no directories must be group or world writable. * * XXX Should any specific check be done for sym links ? * * Takes a file name, its stat information (preferably from fstat() to * avoid races), the uid of the expected owner, their home directory and an * error buffer plus max size as arguments. * * Returns 0 on success and -1 on failure */ int safe_path(const char *name, struct stat *stp, const char *pw_dir, uid_t uid, char *err, size_t errlen) { char buf[PATH_MAX], homedir[PATH_MAX]; char *cp; int comparehome = 0; struct stat st; if (realpath(name, buf) == NULL) { snprintf(err, errlen, "realpath %s failed: %s", name, strerror(errno)); return -1; } if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) comparehome = 1; if (!S_ISREG(stp->st_mode)) { snprintf(err, errlen, "%s is not a regular file", buf); return -1; } if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || (stp->st_mode & 022) != 0) { snprintf(err, errlen, "bad ownership or modes for file %s", buf); return -1; } /* for each component of the canonical path, walking upwards */ for (;;) { if ((cp = dirname(buf)) == NULL) { snprintf(err, errlen, "dirname() failed"); return -1; } strlcpy(buf, cp, sizeof(buf)); if (stat(buf, &st) == -1 || (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || (st.st_mode & 022) != 0) { snprintf(err, errlen, "bad ownership or modes for directory %s", buf); return -1; } /* If are past the homedir then we can stop */ if (comparehome && strcmp(homedir, buf) == 0) break; /* * dirname should always complete with a "/" path, * but we can be paranoid and check for "." too */ if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) break; } return 0; } /* * Version of safe_path() that accepts an open file descriptor to * avoid races. * * Returns 0 on success and -1 on failure */ int safe_path_fd(int fd, const char *file, struct passwd *pw, char *err, size_t errlen) { struct stat st; /* check the open file to avoid races */ if (fstat(fd, &st) == -1) { snprintf(err, errlen, "cannot stat file %s: %s", file, strerror(errno)); return -1; } return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); } /* * Sets the value of the given variable in the environment. If the variable * already exists, its value is overridden. */ void child_set_env(char ***envp, u_int *envsizep, const char *name, const char *value) { char **env; u_int envsize; u_int i, namelen; if (strchr(name, '=') != NULL) { error("Invalid environment variable \"%.100s\"", name); return; } /* * If we're passed an uninitialized list, allocate a single null * entry before continuing. */ if ((*envp == NULL) != (*envsizep == 0)) fatal_f("environment size mismatch"); if (*envp == NULL && *envsizep == 0) { *envp = xmalloc(sizeof(char *)); *envp[0] = NULL; *envsizep = 1; } /* * Find the slot where the value should be stored. If the variable * already exists, we reuse the slot; otherwise we append a new slot * at the end of the array, expanding if necessary. */ env = *envp; namelen = strlen(name); for (i = 0; env[i]; i++) if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') break; if (env[i]) { /* Reuse the slot. */ free(env[i]); } else { /* New variable. Expand if necessary. */ envsize = *envsizep; if (i >= envsize - 1) { if (envsize >= 1000) fatal("child_set_env: too many env vars"); envsize += 50; env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); *envsizep = envsize; } /* Need to set the NULL pointer at end of array beyond the new slot. */ env[i + 1] = NULL; } /* Allocate space and format the variable in the appropriate slot. */ /* XXX xasprintf */ env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); } /* * Check and optionally lowercase a domain name, also removes trailing '.' * Returns 1 on success and 0 on failure, storing an error message in errstr. */ int valid_domain(char *name, int makelower, const char **errstr) { size_t i, l = strlen(name); u_char c, last = '\0'; static char errbuf[256]; if (l == 0) { strlcpy(errbuf, "empty domain name", sizeof(errbuf)); goto bad; } if (!isalpha((u_char)name[0]) && !isdigit((u_char)name[0])) { snprintf(errbuf, sizeof(errbuf), "domain name \"%.100s\" " "starts with invalid character", name); goto bad; } for (i = 0; i < l; i++) { c = tolower((u_char)name[i]); if (makelower) name[i] = (char)c; if (last == '.' && c == '.') { snprintf(errbuf, sizeof(errbuf), "domain name " "\"%.100s\" contains consecutive separators", name); goto bad; } if (c != '.' && c != '-' && !isalnum(c) && c != '_') /* technically invalid, but common */ { snprintf(errbuf, sizeof(errbuf), "domain name " "\"%.100s\" contains invalid characters", name); goto bad; } last = c; } if (name[l - 1] == '.') name[l - 1] = '\0'; if (errstr != NULL) *errstr = NULL; return 1; bad: if (errstr != NULL) *errstr = errbuf; return 0; } /* * Verify that a environment variable name (not including initial '$') is * valid; consisting of one or more alphanumeric or underscore characters only. * Returns 1 on valid, 0 otherwise. */ int valid_env_name(const char *name) { const char *cp; if (name[0] == '\0') return 0; for (cp = name; *cp != '\0'; cp++) { if (!isalnum((u_char)*cp) && *cp != '_') return 0; } return 1; } const char * atoi_err(const char *nptr, int *val) { const char *errstr = NULL; long long num; if (nptr == NULL || *nptr == '\0') return "missing"; num = strtonum(nptr, 0, INT_MAX, &errstr); if (errstr == NULL) *val = (int)num; return errstr; } int parse_absolute_time(const char *s, uint64_t *tp) { struct tm tm; time_t tt; char buf[32], *fmt; const char *cp; size_t l; int is_utc = 0; *tp = 0; l = strlen(s); if (l > 1 && strcasecmp(s + l - 1, "Z") == 0) { is_utc = 1; l--; } else if (l > 3 && strcasecmp(s + l - 3, "UTC") == 0) { is_utc = 1; l -= 3; } /* * POSIX strptime says "The application shall ensure that there * is white-space or other non-alphanumeric characters between * any two conversion specifications" so arrange things this way. */ switch (l) { case 8: /* YYYYMMDD */ fmt = "%Y-%m-%d"; snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2s", s, s + 4, s + 6); break; case 12: /* YYYYMMDDHHMM */ fmt = "%Y-%m-%dT%H:%M"; snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s", s, s + 4, s + 6, s + 8, s + 10); break; case 14: /* YYYYMMDDHHMMSS */ fmt = "%Y-%m-%dT%H:%M:%S"; snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s:%.2s", s, s + 4, s + 6, s + 8, s + 10, s + 12); break; default: return SSH_ERR_INVALID_FORMAT; } memset(&tm, 0, sizeof(tm)); if ((cp = strptime(buf, fmt, &tm)) == NULL || *cp != '\0') return SSH_ERR_INVALID_FORMAT; if (is_utc) { if ((tt = timegm(&tm)) < 0) return SSH_ERR_INVALID_FORMAT; } else { if ((tt = mktime(&tm)) < 0) return SSH_ERR_INVALID_FORMAT; } /* success */ *tp = (uint64_t)tt; return 0; } void format_absolute_time(uint64_t t, char *buf, size_t len) { time_t tt = t > SSH_TIME_T_MAX ? SSH_TIME_T_MAX : t; struct tm tm; localtime_r(&tt, &tm); strftime(buf, len, "%Y-%m-%dT%H:%M:%S", &tm); } /* check if path is absolute */ int path_absolute(const char *path) { return (*path == '/') ? 1 : 0; } void skip_space(char **cpp) { char *cp; for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) ; *cpp = cp; } /* authorized_key-style options parsing helpers */ /* * Match flag 'opt' in *optsp, and if allow_negate is set then also match * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0 * if negated option matches. * If the option or negated option matches, then *optsp is updated to * point to the first character after the option. */ int opt_flag(const char *opt, int allow_negate, const char **optsp) { size_t opt_len = strlen(opt); const char *opts = *optsp; int negate = 0; if (allow_negate && strncasecmp(opts, "no-", 3) == 0) { opts += 3; negate = 1; } if (strncasecmp(opts, opt, opt_len) == 0) { *optsp = opts + opt_len; return negate ? 0 : 1; } return -1; } char * opt_dequote(const char **sp, const char **errstrp) { const char *s = *sp; char *ret; size_t i; *errstrp = NULL; if (*s != '"') { *errstrp = "missing start quote"; return NULL; } s++; if ((ret = malloc(strlen((s)) + 1)) == NULL) { *errstrp = "memory allocation failed"; return NULL; } for (i = 0; *s != '\0' && *s != '"';) { if (s[0] == '\\' && s[1] == '"') s++; ret[i++] = *s++; } if (*s == '\0') { *errstrp = "missing end quote"; free(ret); return NULL; } ret[i] = '\0'; s++; *sp = s; return ret; } int opt_match(const char **opts, const char *term) { if (strncasecmp((*opts), term, strlen(term)) == 0 && (*opts)[strlen(term)] == '=') { *opts += strlen(term) + 1; return 1; } return 0; } void opt_array_append2(const char *file, const int line, const char *directive, char ***array, int **iarray, u_int *lp, const char *s, int i) { if (*lp >= INT_MAX) fatal("%s line %d: Too many %s entries", file, line, directive); if (iarray != NULL) { *iarray = xrecallocarray(*iarray, *lp, *lp + 1, sizeof(**iarray)); (*iarray)[*lp] = i; } *array = xrecallocarray(*array, *lp, *lp + 1, sizeof(**array)); (*array)[*lp] = xstrdup(s); (*lp)++; } void opt_array_append(const char *file, const int line, const char *directive, char ***array, u_int *lp, const char *s) { opt_array_append2(file, line, directive, array, NULL, lp, s, 0); } sshsig_t ssh_signal(int signum, sshsig_t handler) { struct sigaction sa, osa; /* mask all other signals while in handler */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; sigfillset(&sa.sa_mask); #if defined(SA_RESTART) && !defined(NO_SA_RESTART) if (signum != SIGALRM) sa.sa_flags = SA_RESTART; #endif if (sigaction(signum, &sa, &osa) == -1) { debug3("sigaction(%s): %s", strsignal(signum), strerror(errno)); return SIG_ERR; } return osa.sa_handler; } int stdfd_devnull(int do_stdin, int do_stdout, int do_stderr) { int devnull, ret = 0; if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { error_f("open %s: %s", _PATH_DEVNULL, strerror(errno)); return -1; } if ((do_stdin && dup2(devnull, STDIN_FILENO) == -1) || (do_stdout && dup2(devnull, STDOUT_FILENO) == -1) || (do_stderr && dup2(devnull, STDERR_FILENO) == -1)) { error_f("dup2: %s", strerror(errno)); ret = -1; } if (devnull > STDERR_FILENO) close(devnull); return ret; } /* * Runs command in a subprocess with a minimal environment. * Returns pid on success, 0 on failure. * The child stdout and stderr maybe captured, left attached or sent to * /dev/null depending on the contents of flags. * "tag" is prepended to log messages. * NB. "command" is only used for logging; the actual command executed is * av[0]. */ pid_t subprocess(const char *tag, const char *command, int ac, char **av, FILE **child, u_int flags, struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs) { FILE *f = NULL; struct stat st; int fd, devnull, p[2], i; pid_t pid; char *cp, errmsg[512]; u_int nenv = 0; char **env = NULL; /* If dropping privs, then must specify user and restore function */ if (drop_privs != NULL && (pw == NULL || restore_privs == NULL)) { error("%s: inconsistent arguments", tag); /* XXX fatal? */ return 0; } if (pw == NULL && (pw = getpwuid(getuid())) == NULL) { error("%s: no user for current uid", tag); return 0; } if (child != NULL) *child = NULL; debug3_f("%s command \"%s\" running as %s (flags 0x%x)", tag, command, pw->pw_name, flags); /* Check consistency */ if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) { error_f("inconsistent flags"); return 0; } if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) { error_f("inconsistent flags/output"); return 0; } /* * If executing an explicit binary, then verify the it exists * and appears safe-ish to execute */ if (!path_absolute(av[0])) { error("%s path is not absolute", tag); return 0; } if (drop_privs != NULL) drop_privs(pw); if (stat(av[0], &st) == -1) { error("Could not stat %s \"%s\": %s", tag, av[0], strerror(errno)); goto restore_return; } if ((flags & SSH_SUBPROCESS_UNSAFE_PATH) == 0 && safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) { error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); goto restore_return; } /* Prepare to keep the child's stdout if requested */ if (pipe(p) == -1) { error("%s: pipe: %s", tag, strerror(errno)); restore_return: if (restore_privs != NULL) restore_privs(); return 0; } if (restore_privs != NULL) restore_privs(); switch ((pid = fork())) { case -1: /* error */ error("%s: fork: %s", tag, strerror(errno)); close(p[0]); close(p[1]); return 0; case 0: /* child */ /* Prepare a minimal environment for the child. */ if ((flags & SSH_SUBPROCESS_PRESERVE_ENV) == 0) { nenv = 5; env = xcalloc(sizeof(*env), nenv); child_set_env(&env, &nenv, "PATH", _PATH_STDPATH); child_set_env(&env, &nenv, "USER", pw->pw_name); child_set_env(&env, &nenv, "LOGNAME", pw->pw_name); child_set_env(&env, &nenv, "HOME", pw->pw_dir); if ((cp = getenv("LANG")) != NULL) child_set_env(&env, &nenv, "LANG", cp); } for (i = 1; i < NSIG; i++) ssh_signal(i, SIG_DFL); if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { error("%s: open %s: %s", tag, _PATH_DEVNULL, strerror(errno)); _exit(1); } if (dup2(devnull, STDIN_FILENO) == -1) { error("%s: dup2: %s", tag, strerror(errno)); _exit(1); } /* Set up stdout as requested; leave stderr in place for now. */ fd = -1; if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) fd = p[1]; else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0) fd = devnull; if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) { error("%s: dup2: %s", tag, strerror(errno)); _exit(1); } closefrom(STDERR_FILENO + 1); if (geteuid() == 0 && initgroups(pw->pw_name, pw->pw_gid) == -1) { error("%s: initgroups(%s, %u): %s", tag, pw->pw_name, (u_int)pw->pw_gid, strerror(errno)); _exit(1); } if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) { error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, strerror(errno)); _exit(1); } if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) { error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, strerror(errno)); _exit(1); } /* stdin is pointed to /dev/null at this point */ if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && dup2(STDIN_FILENO, STDERR_FILENO) == -1) { error("%s: dup2: %s", tag, strerror(errno)); _exit(1); } if (env != NULL) execve(av[0], av, env); else execv(av[0], av); error("%s %s \"%s\": %s", tag, env == NULL ? "execv" : "execve", command, strerror(errno)); _exit(127); default: /* parent */ break; } close(p[1]); if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) close(p[0]); else if ((f = fdopen(p[0], "r")) == NULL) { error("%s: fdopen: %s", tag, strerror(errno)); close(p[0]); /* Don't leave zombie child */ kill(pid, SIGTERM); while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) ; return 0; } /* Success */ debug3_f("%s pid %ld", tag, (long)pid); if (child != NULL) *child = f; return pid; } const char * lookup_env_in_list(const char *env, char * const *envs, size_t nenvs) { size_t i, envlen; envlen = strlen(env); for (i = 0; i < nenvs; i++) { if (strncmp(envs[i], env, envlen) == 0 && envs[i][envlen] == '=') { return envs[i] + envlen + 1; } } return NULL; } const char * lookup_setenv_in_list(const char *env, char * const *envs, size_t nenvs) { char *name, *cp; const char *ret; name = xstrdup(env); if ((cp = strchr(name, '=')) == NULL) { free(name); return NULL; /* not env=val */ } *cp = '\0'; ret = lookup_env_in_list(name, envs, nenvs); free(name); return ret; } /* * Helpers for managing poll(2)/ppoll(2) timeouts * Will remember the earliest deadline and return it for use in poll/ppoll. */ /* Initialise a poll/ppoll timeout with an indefinite deadline */ void ptimeout_init(struct timespec *pt) { /* * Deliberately invalid for ppoll(2). * Will be converted to NULL in ptimeout_get_tspec() later. */ pt->tv_sec = -1; pt->tv_nsec = 0; } /* Specify a poll/ppoll deadline of at most 'sec' seconds */ void ptimeout_deadline_sec(struct timespec *pt, long sec) { if (pt->tv_sec == -1 || pt->tv_sec >= sec) { pt->tv_sec = sec; pt->tv_nsec = 0; } } /* Specify a poll/ppoll deadline of at most 'p' (timespec) */ static void ptimeout_deadline_tsp(struct timespec *pt, struct timespec *p) { if (pt->tv_sec == -1 || timespeccmp(pt, p, >=)) *pt = *p; } /* Specify a poll/ppoll deadline of at most 'ms' milliseconds */ void ptimeout_deadline_ms(struct timespec *pt, long ms) { struct timespec p; p.tv_sec = ms / 1000; p.tv_nsec = (ms % 1000) * 1000000; ptimeout_deadline_tsp(pt, &p); } -/* Specify a poll/ppoll deadline at wall clock monotime 'when' */ +/* Specify a poll/ppoll deadline at wall clock monotime 'when' (timespec) */ void -ptimeout_deadline_monotime(struct timespec *pt, time_t when) +ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when) { struct timespec now, t; - t.tv_sec = when; - t.tv_nsec = 0; monotime_ts(&now); - if (timespeccmp(&now, &t, >=)) - ptimeout_deadline_sec(pt, 0); - else { - timespecsub(&t, &now, &t); + if (timespeccmp(&now, when, >=)) { + /* 'when' is now or in the past. Timeout ASAP */ + pt->tv_sec = 0; + pt->tv_nsec = 0; + } else { + timespecsub(when, &now, &t); ptimeout_deadline_tsp(pt, &t); } } +/* Specify a poll/ppoll deadline at wall clock monotime 'when' */ +void +ptimeout_deadline_monotime(struct timespec *pt, time_t when) +{ + struct timespec t; + + t.tv_sec = when; + t.tv_nsec = 0; + ptimeout_deadline_monotime_tsp(pt, &t); +} + /* Get a poll(2) timeout value in milliseconds */ int ptimeout_get_ms(struct timespec *pt) { if (pt->tv_sec == -1) return -1; if (pt->tv_sec >= (INT_MAX - (pt->tv_nsec / 1000000)) / 1000) return INT_MAX; return (pt->tv_sec * 1000) + (pt->tv_nsec / 1000000); } /* Get a ppoll(2) timeout value as a timespec pointer */ struct timespec * ptimeout_get_tsp(struct timespec *pt) { return pt->tv_sec == -1 ? NULL : pt; } /* Returns non-zero if a timeout has been set (i.e. is not indefinite) */ int ptimeout_isset(struct timespec *pt) { return pt->tv_sec != -1; } /* * Returns zero if the library at 'path' contains symbol 's', nonzero * otherwise. */ int lib_contains_symbol(const char *path, const char *s) { #ifdef HAVE_NLIST_H struct nlist nl[2]; int ret = -1, r; memset(nl, 0, sizeof(nl)); nl[0].n_name = xstrdup(s); nl[1].n_name = NULL; if ((r = nlist(path, nl)) == -1) { error_f("nlist failed for %s", path); goto out; } if (r != 0 || nl[0].n_value == 0 || nl[0].n_type == 0) { error_f("library %s does not contain symbol %s", path, s); goto out; } /* success */ ret = 0; out: free(nl[0].n_name); return ret; #else /* HAVE_NLIST_H */ int fd, ret = -1; struct stat st; void *m = NULL; size_t sz = 0; memset(&st, 0, sizeof(st)); if ((fd = open(path, O_RDONLY)) < 0) { error_f("open %s: %s", path, strerror(errno)); return -1; } if (fstat(fd, &st) != 0) { error_f("fstat %s: %s", path, strerror(errno)); goto out; } if (!S_ISREG(st.st_mode)) { error_f("%s is not a regular file", path); goto out; } if (st.st_size < 0 || (size_t)st.st_size < strlen(s) || st.st_size >= INT_MAX/2) { error_f("%s bad size %lld", path, (long long)st.st_size); goto out; } sz = (size_t)st.st_size; if ((m = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED || m == NULL) { error_f("mmap %s: %s", path, strerror(errno)); goto out; } if (memmem(m, sz, s, strlen(s)) == NULL) { error_f("%s does not contain expected string %s", path, s); goto out; } /* success */ ret = 0; out: if (m != NULL && m != MAP_FAILED) munmap(m, sz); close(fd); return ret; #endif /* HAVE_NLIST_H */ } diff --git a/crypto/openssh/misc.h b/crypto/openssh/misc.h index f9bdc6eb51bd..4f941597e9e3 100644 --- a/crypto/openssh/misc.h +++ b/crypto/openssh/misc.h @@ -1,248 +1,249 @@ -/* $OpenBSD: misc.h,v 1.104 2023/08/18 01:37:41 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.105 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef _MISC_H #define _MISC_H #include #include #include #include #include /* Data structure for representing a forwarding request. */ struct Forward { char *listen_host; /* Host (address) to listen on. */ int listen_port; /* Port to forward. */ char *listen_path; /* Path to bind domain socket. */ char *connect_host; /* Host to connect. */ int connect_port; /* Port to connect on connect_host. */ char *connect_path; /* Path to connect domain socket. */ int allocated_port; /* Dynamically allocated listen port */ int handle; /* Handle for dynamic listen ports */ }; int forward_equals(const struct Forward *, const struct Forward *); int daemonized(void); /* Common server and client forwarding options. */ struct ForwardOptions { int gateway_ports; /* Allow remote connects to forwarded ports. */ mode_t streamlocal_bind_mask; /* umask for streamlocal binds */ int streamlocal_bind_unlink; /* unlink socket before bind */ }; /* misc.c */ char *chop(char *); void rtrim(char *); void skip_space(char **); char *strdelim(char **); char *strdelimw(char **); int set_nonblock(int); int unset_nonblock(int); void set_nodelay(int); int set_reuseaddr(int); char *get_rdomain(int); int set_rdomain(int, const char *); int get_sock_af(int); void set_sock_tos(int, int); int waitrfd(int, int *, volatile sig_atomic_t *); int timeout_connect(int, const struct sockaddr *, socklen_t, int *); int a2port(const char *); int a2tun(const char *, int *); char *put_host_port(const char *, u_short); char *hpdelim2(char **, char *); char *hpdelim(char **); char *cleanhostname(char *); char *colon(char *); int parse_user_host_path(const char *, char **, char **, char **); int parse_user_host_port(const char *, char **, char **, int *); int parse_uri(const char *, const char *, char **, char **, int *, char **); int convtime(const char *); const char *fmt_timeframe(time_t t); int tilde_expand(const char *, uid_t, char **); char *tilde_expand_filename(const char *, uid_t); char *dollar_expand(int *, const char *string, ...); char *percent_expand(const char *, ...) __attribute__((__sentinel__)); char *percent_dollar_expand(const char *, ...) __attribute__((__sentinel__)); char *tohex(const void *, size_t); void xextendf(char **s, const char *sep, const char *fmt, ...) __attribute__((__format__ (printf, 3, 4))) __attribute__((__nonnull__ (3))); void sanitise_stdfd(void); void ms_subtract_diff(struct timeval *, int *); void ms_to_timespec(struct timespec *, int); void monotime_ts(struct timespec *); void monotime_tv(struct timeval *); time_t monotime(void); double monotime_double(void); void lowercase(char *s); int unix_listener(const char *, int, int); int valid_domain(char *, int, const char **); int valid_env_name(const char *); const char *atoi_err(const char *, int *); int parse_absolute_time(const char *, uint64_t *); void format_absolute_time(uint64_t, char *, size_t); int path_absolute(const char *); int stdfd_devnull(int, int, int); int lib_contains_symbol(const char *, const char *); void sock_set_v6only(int); struct passwd *pwcopy(struct passwd *); const char *ssh_gai_strerror(int); typedef void privdrop_fn(struct passwd *); typedef void privrestore_fn(void); #define SSH_SUBPROCESS_STDOUT_DISCARD (1) /* Discard stdout */ #define SSH_SUBPROCESS_STDOUT_CAPTURE (1<<1) /* Redirect stdout */ #define SSH_SUBPROCESS_STDERR_DISCARD (1<<2) /* Discard stderr */ #define SSH_SUBPROCESS_UNSAFE_PATH (1<<3) /* Don't check for safe cmd */ #define SSH_SUBPROCESS_PRESERVE_ENV (1<<4) /* Keep parent environment */ pid_t subprocess(const char *, const char *, int, char **, FILE **, u_int, struct passwd *, privdrop_fn *, privrestore_fn *); typedef struct arglist arglist; struct arglist { char **list; u_int num; u_int nalloc; }; void addargs(arglist *, char *, ...) __attribute__((format(printf, 2, 3))); void replacearg(arglist *, u_int, char *, ...) __attribute__((format(printf, 3, 4))); void freeargs(arglist *); int tun_open(int, int, char **); /* Common definitions for ssh tunnel device forwarding */ #define SSH_TUNMODE_NO 0x00 #define SSH_TUNMODE_POINTOPOINT 0x01 #define SSH_TUNMODE_ETHERNET 0x02 #define SSH_TUNMODE_DEFAULT SSH_TUNMODE_POINTOPOINT #define SSH_TUNMODE_YES (SSH_TUNMODE_POINTOPOINT|SSH_TUNMODE_ETHERNET) #define SSH_TUNID_ANY 0x7fffffff #define SSH_TUNID_ERR (SSH_TUNID_ANY - 1) #define SSH_TUNID_MAX (SSH_TUNID_ANY - 2) /* Fake port to indicate that host field is really a path. */ #define PORT_STREAMLOCAL -2 /* Functions to extract or store big-endian words of various sizes */ u_int64_t get_u64(const void *) __attribute__((__bounded__( __minbytes__, 1, 8))); u_int32_t get_u32(const void *) __attribute__((__bounded__( __minbytes__, 1, 4))); u_int16_t get_u16(const void *) __attribute__((__bounded__( __minbytes__, 1, 2))); void put_u64(void *, u_int64_t) __attribute__((__bounded__( __minbytes__, 1, 8))); void put_u32(void *, u_int32_t) __attribute__((__bounded__( __minbytes__, 1, 4))); void put_u16(void *, u_int16_t) __attribute__((__bounded__( __minbytes__, 1, 2))); /* Little-endian store/load, used by umac.c */ u_int32_t get_u32_le(const void *) __attribute__((__bounded__(__minbytes__, 1, 4))); void put_u32_le(void *, u_int32_t) __attribute__((__bounded__(__minbytes__, 1, 4))); struct bwlimit { size_t buflen; u_int64_t rate; /* desired rate in kbit/s */ u_int64_t thresh; /* threshold after which we'll check timers */ u_int64_t lamt; /* amount written in last timer interval */ struct timeval bwstart, bwend; }; void bandwidth_limit_init(struct bwlimit *, u_int64_t, size_t); void bandwidth_limit(struct bwlimit *, size_t); int parse_ipqos(const char *); const char *iptos2str(int); void mktemp_proto(char *, size_t); void child_set_env(char ***envp, u_int *envsizep, const char *name, const char *value); const char *lookup_env_in_list(const char *env, char * const *envs, size_t nenvs); const char *lookup_setenv_in_list(const char *env, char * const *envs, size_t nenvs); int argv_split(const char *, int *, char ***, int); char *argv_assemble(int, char **argv); char *argv_next(int *, char ***); void argv_consume(int *); void argv_free(char **, int); int exited_cleanly(pid_t, const char *, const char *, int); struct stat; int safe_path(const char *, struct stat *, const char *, uid_t, char *, size_t); int safe_path_fd(int, const char *, struct passwd *, char *err, size_t errlen); /* authorized_key-style options parsing helpers */ int opt_flag(const char *opt, int allow_negate, const char **optsp); char *opt_dequote(const char **sp, const char **errstrp); int opt_match(const char **opts, const char *term); /* readconf/servconf option lists */ void opt_array_append(const char *file, const int line, const char *directive, char ***array, u_int *lp, const char *s); void opt_array_append2(const char *file, const int line, const char *directive, char ***array, int **iarray, u_int *lp, const char *s, int i); struct timespec; void ptimeout_init(struct timespec *pt); void ptimeout_deadline_sec(struct timespec *pt, long sec); void ptimeout_deadline_ms(struct timespec *pt, long ms); +void ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when); void ptimeout_deadline_monotime(struct timespec *pt, time_t when); int ptimeout_get_ms(struct timespec *pt); struct timespec *ptimeout_get_tsp(struct timespec *pt); int ptimeout_isset(struct timespec *pt); /* readpass.c */ #define RP_ECHO 0x0001 #define RP_ALLOW_STDIN 0x0002 #define RP_ALLOW_EOF 0x0004 #define RP_USE_ASKPASS 0x0008 struct notifier_ctx; char *read_passphrase(const char *, int); int ask_permission(const char *, ...) __attribute__((format(printf, 1, 2))); struct notifier_ctx *notify_start(int, const char *, ...) __attribute__((format(printf, 2, 3))); void notify_complete(struct notifier_ctx *, const char *, ...) __attribute__((format(printf, 2, 3))); #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) #define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y)) typedef void (*sshsig_t)(int); sshsig_t ssh_signal(int, sshsig_t); /* On OpenBSD time_t is int64_t which is long long. */ /* #define SSH_TIME_T_MAX LLONG_MAX */ #endif /* _MISC_H */ diff --git a/crypto/openssh/monitor.c b/crypto/openssh/monitor.c index 1489c78d81ec..b3ed515ed0ba 100644 --- a/crypto/openssh/monitor.c +++ b/crypto/openssh/monitor.c @@ -1,1951 +1,1956 @@ -/* $OpenBSD: monitor.c,v 1.236 2023/05/10 10:04:20 dtucker Exp $ */ +/* $OpenBSD: monitor.c,v 1.237 2023/08/16 16:14:11 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #ifdef HAVE_STDINT_H # include #endif #include #include #include #include #include #ifdef HAVE_POLL_H #include #else # ifdef HAVE_SYS_POLL_H # include # endif #endif #ifdef WITH_OPENSSL #include #endif #include "openbsd-compat/sys-tree.h" #include "openbsd-compat/sys-queue.h" #include "openbsd-compat/openssl-compat.h" #include "atomicio.h" #include "xmalloc.h" #include "ssh.h" #include "sshkey.h" #include "sshbuf.h" #include "hostfile.h" #include "auth.h" #include "cipher.h" #include "kex.h" #include "dh.h" #include "auth-pam.h" #include "packet.h" #include "auth-options.h" #include "sshpty.h" #include "channels.h" #include "session.h" #include "sshlogin.h" #include "canohost.h" #include "log.h" #include "misc.h" #include "servconf.h" #include "monitor.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "monitor_fdpass.h" #include "compat.h" #include "ssh2.h" #include "authfd.h" #include "match.h" #include "ssherr.h" #include "sk-api.h" #ifdef GSSAPI static Gssctxt *gsscontext = NULL; #endif /* Imports */ extern ServerOptions options; extern u_int utmp_len; extern struct sshbuf *loginmsg; extern struct sshauthopt *auth_opts; /* XXX move to permanent ssh->authctxt? */ /* State exported from the child */ static struct sshbuf *child_state; /* Functions on the monitor that answer unprivileged requests */ int mm_answer_moduli(struct ssh *, int, struct sshbuf *); int mm_answer_sign(struct ssh *, int, struct sshbuf *); int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *); int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *); int mm_answer_authserv(struct ssh *, int, struct sshbuf *); int mm_answer_authpassword(struct ssh *, int, struct sshbuf *); int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *); int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *); int mm_answer_keyallowed(struct ssh *, int, struct sshbuf *); int mm_answer_keyverify(struct ssh *, int, struct sshbuf *); int mm_answer_pty(struct ssh *, int, struct sshbuf *); int mm_answer_pty_cleanup(struct ssh *, int, struct sshbuf *); int mm_answer_term(struct ssh *, int, struct sshbuf *); int mm_answer_sesskey(struct ssh *, int, struct sshbuf *); int mm_answer_sessid(struct ssh *, int, struct sshbuf *); #ifdef USE_PAM int mm_answer_pam_start(struct ssh *, int, struct sshbuf *); int mm_answer_pam_account(struct ssh *, int, struct sshbuf *); int mm_answer_pam_init_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_pam_query(struct ssh *, int, struct sshbuf *); int mm_answer_pam_respond(struct ssh *, int, struct sshbuf *); int mm_answer_pam_free_ctx(struct ssh *, int, struct sshbuf *); #endif #ifdef GSSAPI int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); #endif #ifdef SSH_AUDIT_EVENTS int mm_answer_audit_event(struct ssh *, int, struct sshbuf *); int mm_answer_audit_command(struct ssh *, int, struct sshbuf *); #endif static Authctxt *authctxt; /* local state for key verify */ static u_char *key_blob = NULL; static size_t key_bloblen = 0; static u_int key_blobtype = MM_NOKEY; static struct sshauthopt *key_opts = NULL; static char *hostbased_cuser = NULL; static char *hostbased_chost = NULL; static char *auth_method = "unknown"; static char *auth_submethod = NULL; static u_int session_id2_len = 0; static u_char *session_id2 = NULL; static pid_t monitor_child_pid; struct mon_table { enum monitor_reqtype type; int flags; int (*f)(struct ssh *, int, struct sshbuf *); }; #define MON_ISAUTH 0x0004 /* Required for Authentication */ #define MON_AUTHDECIDE 0x0008 /* Decides Authentication */ #define MON_ONCE 0x0010 /* Disable after calling */ #define MON_ALOG 0x0020 /* Log auth attempt without authenticating */ #define MON_AUTH (MON_ISAUTH|MON_AUTHDECIDE) #define MON_PERMIT 0x1000 /* Request is permitted */ static int monitor_read(struct ssh *, struct monitor *, struct mon_table *, struct mon_table **); static int monitor_read_log(struct monitor *); struct mon_table mon_dispatch_proto20[] = { #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, MON_ONCE, mm_answer_moduli}, #endif {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign}, {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow}, {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv}, {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner}, {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword}, #ifdef USE_PAM {MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start}, {MONITOR_REQ_PAM_ACCOUNT, 0, mm_answer_pam_account}, {MONITOR_REQ_PAM_INIT_CTX, MON_ONCE, mm_answer_pam_init_ctx}, {MONITOR_REQ_PAM_QUERY, 0, mm_answer_pam_query}, {MONITOR_REQ_PAM_RESPOND, MON_ONCE, mm_answer_pam_respond}, {MONITOR_REQ_PAM_FREE_CTX, MON_ONCE|MON_AUTHDECIDE, mm_answer_pam_free_ctx}, #endif #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, #endif #ifdef BSD_AUTH {MONITOR_REQ_BSDAUTHQUERY, MON_ISAUTH, mm_answer_bsdauthquery}, {MONITOR_REQ_BSDAUTHRESPOND, MON_AUTH, mm_answer_bsdauthrespond}, #endif {MONITOR_REQ_KEYALLOWED, MON_ISAUTH, mm_answer_keyallowed}, {MONITOR_REQ_KEYVERIFY, MON_AUTH, mm_answer_keyverify}, #ifdef GSSAPI {MONITOR_REQ_GSSSETUP, MON_ISAUTH, mm_answer_gss_setup_ctx}, {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_postauth20[] = { #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, #endif {MONITOR_REQ_SIGN, 0, mm_answer_sign}, {MONITOR_REQ_PTY, 0, mm_answer_pty}, {MONITOR_REQ_PTYCLEANUP, 0, mm_answer_pty_cleanup}, {MONITOR_REQ_TERM, 0, mm_answer_term}, #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_COMMAND, MON_PERMIT, mm_answer_audit_command}, #endif {0, 0, NULL} }; struct mon_table *mon_dispatch; /* Specifies if a certain message is allowed at the moment */ static void monitor_permit(struct mon_table *ent, enum monitor_reqtype type, int permit) { while (ent->f != NULL) { if (ent->type == type) { ent->flags &= ~MON_PERMIT; ent->flags |= permit ? MON_PERMIT : 0; return; } ent++; } } static void monitor_permit_authentications(int permit) { struct mon_table *ent = mon_dispatch; while (ent->f != NULL) { if (ent->flags & MON_AUTH) { ent->flags &= ~MON_PERMIT; ent->flags |= permit ? MON_PERMIT : 0; } ent++; } } void monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) { struct mon_table *ent; int authenticated = 0, partial = 0; debug3("preauth child monitor started"); if (pmonitor->m_recvfd >= 0) close(pmonitor->m_recvfd); if (pmonitor->m_log_sendfd >= 0) close(pmonitor->m_log_sendfd); pmonitor->m_log_sendfd = pmonitor->m_recvfd = -1; authctxt = (Authctxt *)ssh->authctxt; memset(authctxt, 0, sizeof(*authctxt)); ssh->authctxt = authctxt; authctxt->loginmsg = loginmsg; mon_dispatch = mon_dispatch_proto20; /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); /* The first few requests do not require asynchronous access */ while (!authenticated) { partial = 0; auth_method = "unknown"; auth_submethod = NULL; auth2_authctxt_reset_info(authctxt); authenticated = (monitor_read(ssh, pmonitor, mon_dispatch, &ent) == 1); /* Special handling for multiple required authentications */ if (options.num_auth_methods != 0) { if (authenticated && !auth2_update_methods_lists(authctxt, auth_method, auth_submethod)) { debug3_f("method %s: partial", auth_method); authenticated = 0; partial = 1; } } if (authenticated) { if (!(ent->flags & MON_AUTHDECIDE)) fatal_f("unexpected authentication from %d", ent->type); if (authctxt->pw->pw_uid == 0 && !auth_root_allowed(ssh, auth_method)) authenticated = 0; #ifdef USE_PAM /* PAM needs to perform account checks after auth */ if (options.use_pam && authenticated) { struct sshbuf *m; if ((m = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_PAM_ACCOUNT, m); authenticated = mm_answer_pam_account( ssh, pmonitor->m_sendfd, m); sshbuf_free(m); } #endif } if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { auth_log(ssh, authenticated, partial, auth_method, auth_submethod); if (!partial && !authenticated) authctxt->failures++; if (authenticated || partial) { auth2_update_session_info(authctxt, auth_method, auth_submethod); } } + if (authctxt->failures > options.max_authtries) { + /* Shouldn't happen */ + fatal_f("privsep child made too many authentication " + "attempts"); + } } if (!authctxt->valid) fatal_f("authenticated invalid user"); if (strcmp(auth_method, "unknown") == 0) fatal_f("authentication method name unknown"); debug_f("user %s authenticated by privileged process", authctxt->user); ssh->authctxt = NULL; ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user); mm_get_keystate(ssh, pmonitor); /* Drain any buffered messages from the child */ while (pmonitor->m_log_recvfd != -1 && monitor_read_log(pmonitor) == 0) ; if (pmonitor->m_recvfd >= 0) close(pmonitor->m_recvfd); if (pmonitor->m_log_sendfd >= 0) close(pmonitor->m_log_sendfd); pmonitor->m_sendfd = pmonitor->m_log_recvfd = -1; } static void monitor_set_child_handler(pid_t pid) { monitor_child_pid = pid; } static void monitor_child_handler(int sig) { kill(monitor_child_pid, sig); } void monitor_child_postauth(struct ssh *ssh, struct monitor *pmonitor) { close(pmonitor->m_recvfd); pmonitor->m_recvfd = -1; monitor_set_child_handler(pmonitor->m_pid); ssh_signal(SIGHUP, &monitor_child_handler); ssh_signal(SIGTERM, &monitor_child_handler); ssh_signal(SIGINT, &monitor_child_handler); #ifdef SIGXFSZ ssh_signal(SIGXFSZ, SIG_IGN); #endif mon_dispatch = mon_dispatch_postauth20; /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); if (auth_opts->permit_pty_flag) { monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); monitor_permit(mon_dispatch, MONITOR_REQ_PTYCLEANUP, 1); } for (;;) monitor_read(ssh, pmonitor, mon_dispatch, NULL); } static int monitor_read_log(struct monitor *pmonitor) { struct sshbuf *logmsg; u_int len, level, forced; char *msg; u_char *p; int r; if ((logmsg = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); /* Read length */ if ((r = sshbuf_reserve(logmsg, 4, &p)) != 0) fatal_fr(r, "reserve len"); if (atomicio(read, pmonitor->m_log_recvfd, p, 4) != 4) { if (errno == EPIPE) { sshbuf_free(logmsg); debug_f("child log fd closed"); close(pmonitor->m_log_recvfd); pmonitor->m_log_recvfd = -1; return -1; } fatal_f("log fd read: %s", strerror(errno)); } if ((r = sshbuf_get_u32(logmsg, &len)) != 0) fatal_fr(r, "parse len"); if (len <= 4 || len > 8192) fatal_f("invalid log message length %u", len); /* Read severity, message */ sshbuf_reset(logmsg); if ((r = sshbuf_reserve(logmsg, len, &p)) != 0) fatal_fr(r, "reserve msg"); if (atomicio(read, pmonitor->m_log_recvfd, p, len) != len) fatal_f("log fd read: %s", strerror(errno)); if ((r = sshbuf_get_u32(logmsg, &level)) != 0 || (r = sshbuf_get_u32(logmsg, &forced)) != 0 || (r = sshbuf_get_cstring(logmsg, &msg, NULL)) != 0) fatal_fr(r, "parse"); /* Log it */ if (log_level_name(level) == NULL) fatal_f("invalid log level %u (corrupted message?)", level); sshlogdirect(level, forced, "%s [preauth]", msg); sshbuf_free(logmsg); free(msg); return 0; } static int monitor_read(struct ssh *ssh, struct monitor *pmonitor, struct mon_table *ent, struct mon_table **pent) { struct sshbuf *m; int r, ret; u_char type; struct pollfd pfd[2]; for (;;) { memset(&pfd, 0, sizeof(pfd)); pfd[0].fd = pmonitor->m_sendfd; pfd[0].events = POLLIN; pfd[1].fd = pmonitor->m_log_recvfd; pfd[1].events = pfd[1].fd == -1 ? 0 : POLLIN; if (poll(pfd, pfd[1].fd == -1 ? 1 : 2, -1) == -1) { if (errno == EINTR || errno == EAGAIN) continue; fatal_f("poll: %s", strerror(errno)); } if (pfd[1].revents) { /* * Drain all log messages before processing next * monitor request. */ monitor_read_log(pmonitor); continue; } if (pfd[0].revents) break; /* Continues below */ } if ((m = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); mm_request_receive(pmonitor->m_sendfd, m); if ((r = sshbuf_get_u8(m, &type)) != 0) fatal_fr(r, "parse type"); debug3_f("checking request %d", type); while (ent->f != NULL) { if (ent->type == type) break; ent++; } if (ent->f != NULL) { if (!(ent->flags & MON_PERMIT)) fatal_f("unpermitted request %d", type); ret = (*ent->f)(ssh, pmonitor->m_sendfd, m); sshbuf_free(m); /* The child may use this request only once, disable it */ if (ent->flags & MON_ONCE) { debug2_f("%d used once, disabling now", type); ent->flags &= ~MON_PERMIT; } if (pent != NULL) *pent = ent; return ret; } fatal_f("unsupported request: %d", type); /* NOTREACHED */ return (-1); } /* allowed key state */ static int monitor_allowed_key(const u_char *blob, u_int bloblen) { /* make sure key is allowed */ if (key_blob == NULL || key_bloblen != bloblen || timingsafe_bcmp(key_blob, blob, key_bloblen)) return (0); return (1); } static void monitor_reset_key_state(void) { /* reset state */ free(key_blob); free(hostbased_cuser); free(hostbased_chost); sshauthopt_free(key_opts); key_blob = NULL; key_bloblen = 0; key_blobtype = MM_NOKEY; key_opts = NULL; hostbased_cuser = NULL; hostbased_chost = NULL; } #ifdef WITH_OPENSSL int mm_answer_moduli(struct ssh *ssh, int sock, struct sshbuf *m) { DH *dh; const BIGNUM *dh_p, *dh_g; int r; u_int min, want, max; if ((r = sshbuf_get_u32(m, &min)) != 0 || (r = sshbuf_get_u32(m, &want)) != 0 || (r = sshbuf_get_u32(m, &max)) != 0) fatal_fr(r, "parse"); debug3_f("got parameters: %d %d %d", min, want, max); /* We need to check here, too, in case the child got corrupted */ if (max < min || want < min || max < want) fatal_f("bad parameters: %d %d %d", min, want, max); sshbuf_reset(m); dh = choose_dh(min, want, max); if (dh == NULL) { if ((r = sshbuf_put_u8(m, 0)) != 0) fatal_fr(r, "assemble empty"); return (0); } else { /* Send first bignum */ DH_get0_pqg(dh, &dh_p, NULL, &dh_g); if ((r = sshbuf_put_u8(m, 1)) != 0 || (r = sshbuf_put_bignum2(m, dh_p)) != 0 || (r = sshbuf_put_bignum2(m, dh_g)) != 0) fatal_fr(r, "assemble"); DH_free(dh); } mm_request_send(sock, MONITOR_ANS_MODULI, m); return (0); } #endif int mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m) { extern int auth_sock; /* XXX move to state struct? */ struct sshkey *key; struct sshbuf *sigbuf = NULL; u_char *p = NULL, *signature = NULL; char *alg = NULL; size_t datlen, siglen, alglen; int r, is_proof = 0; u_int keyid, compat; const char proof_req[] = "hostkeys-prove-00@openssh.com"; debug3_f("entering"); if ((r = sshbuf_get_u32(m, &keyid)) != 0 || (r = sshbuf_get_string(m, &p, &datlen)) != 0 || (r = sshbuf_get_cstring(m, &alg, &alglen)) != 0 || (r = sshbuf_get_u32(m, &compat)) != 0) fatal_fr(r, "parse"); if (keyid > INT_MAX) fatal_f("invalid key ID"); /* * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes), * SHA384 (48 bytes) and SHA512 (64 bytes). * * Otherwise, verify the signature request is for a hostkey * proof. * * XXX perform similar check for KEX signature requests too? * it's not trivial, since what is signed is the hash, rather * than the full kex structure... */ if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) { /* * Construct expected hostkey proof and compare it to what * the client sent us. */ if (session_id2_len == 0) /* hostkeys is never first */ fatal_f("bad data length: %zu", datlen); if ((key = get_hostkey_public_by_index(keyid, ssh)) == NULL) fatal_f("no hostkey for index %d", keyid); if ((sigbuf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); if ((r = sshbuf_put_cstring(sigbuf, proof_req)) != 0 || (r = sshbuf_put_string(sigbuf, session_id2, session_id2_len)) != 0 || (r = sshkey_puts(key, sigbuf)) != 0) fatal_fr(r, "assemble private key proof"); if (datlen != sshbuf_len(sigbuf) || memcmp(p, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf)) != 0) fatal_f("bad data length: %zu, hostkey proof len %zu", datlen, sshbuf_len(sigbuf)); sshbuf_free(sigbuf); is_proof = 1; } /* save session id, it will be passed on the first call */ if (session_id2_len == 0) { session_id2_len = datlen; session_id2 = xmalloc(session_id2_len); memcpy(session_id2, p, session_id2_len); } if ((key = get_hostkey_by_index(keyid)) != NULL) { if ((r = sshkey_sign(key, &signature, &siglen, p, datlen, alg, options.sk_provider, NULL, compat)) != 0) fatal_fr(r, "sign"); } else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL && auth_sock > 0) { if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen, p, datlen, alg, compat)) != 0) fatal_fr(r, "agent sign"); } else fatal_f("no hostkey from index %d", keyid); debug3_f("%s %s signature len=%zu", alg, is_proof ? "hostkey proof" : "KEX", siglen); sshbuf_reset(m); if ((r = sshbuf_put_string(m, signature, siglen)) != 0) fatal_fr(r, "assemble"); free(alg); free(p); free(signature); mm_request_send(sock, MONITOR_ANS_SIGN, m); /* Turn on permissions for getpwnam */ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); return (0); } #define PUTPW(b, id) \ do { \ if ((r = sshbuf_put_string(b, \ &pwent->id, sizeof(pwent->id))) != 0) \ fatal_fr(r, "assemble %s", #id); \ } while (0) /* Retrieves the password entry and also checks if the user is permitted */ int mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m) { struct passwd *pwent; int r, allowed = 0; u_int i; debug3_f("entering"); if (authctxt->attempt++ != 0) fatal_f("multiple attempts for getpwnam"); if ((r = sshbuf_get_cstring(m, &authctxt->user, NULL)) != 0) fatal_fr(r, "parse"); pwent = getpwnamallow(ssh, authctxt->user); setproctitle("%s [priv]", pwent ? authctxt->user : "unknown"); sshbuf_reset(m); if (pwent == NULL) { if ((r = sshbuf_put_u8(m, 0)) != 0) fatal_fr(r, "assemble fakepw"); authctxt->pw = fakepw(); goto out; } allowed = 1; authctxt->pw = pwent; authctxt->valid = 1; /* XXX send fake class/dir/shell, etc. */ if ((r = sshbuf_put_u8(m, 1)) != 0) fatal_fr(r, "assemble ok"); PUTPW(m, pw_uid); PUTPW(m, pw_gid); #ifdef HAVE_STRUCT_PASSWD_PW_CHANGE PUTPW(m, pw_change); #endif #ifdef HAVE_STRUCT_PASSWD_PW_EXPIRE PUTPW(m, pw_expire); #endif if ((r = sshbuf_put_cstring(m, pwent->pw_name)) != 0 || (r = sshbuf_put_cstring(m, "*")) != 0 || #ifdef HAVE_STRUCT_PASSWD_PW_GECOS (r = sshbuf_put_cstring(m, pwent->pw_gecos)) != 0 || #endif #ifdef HAVE_STRUCT_PASSWD_PW_CLASS (r = sshbuf_put_cstring(m, pwent->pw_class)) != 0 || #endif (r = sshbuf_put_cstring(m, pwent->pw_dir)) != 0 || (r = sshbuf_put_cstring(m, pwent->pw_shell)) != 0) fatal_fr(r, "assemble pw"); out: ssh_packet_set_log_preamble(ssh, "%suser %s", authctxt->valid ? "authenticating" : "invalid ", authctxt->user); if ((r = sshbuf_put_string(m, &options, sizeof(options))) != 0) fatal_fr(r, "assemble options"); #define M_CP_STROPT(x) do { \ if (options.x != NULL && \ (r = sshbuf_put_cstring(m, options.x)) != 0) \ fatal_fr(r, "assemble %s", #x); \ } while (0) #define M_CP_STRARRAYOPT(x, nx) do { \ for (i = 0; i < options.nx; i++) { \ if ((r = sshbuf_put_cstring(m, options.x[i])) != 0) \ fatal_fr(r, "assemble %s", #x); \ } \ } while (0) /* See comment in servconf.h */ COPY_MATCH_STRING_OPTS(); #undef M_CP_STROPT #undef M_CP_STRARRAYOPT /* Create valid auth method lists */ if (auth2_setup_methods_lists(authctxt) != 0) { /* * The monitor will continue long enough to let the child * run to its packet_disconnect(), but it must not allow any * authentication to succeed. */ debug_f("no valid authentication method lists"); } debug3_f("sending MONITOR_ANS_PWNAM: %d", allowed); mm_request_send(sock, MONITOR_ANS_PWNAM, m); /* Allow service/style information on the auth context */ monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1); monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1); #ifdef USE_PAM if (options.use_pam) monitor_permit(mon_dispatch, MONITOR_REQ_PAM_START, 1); #endif return (0); } int mm_answer_auth2_read_banner(struct ssh *ssh, int sock, struct sshbuf *m) { char *banner; int r; sshbuf_reset(m); banner = auth2_read_banner(); if ((r = sshbuf_put_cstring(m, banner != NULL ? banner : "")) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_AUTH2_READ_BANNER, m); free(banner); return (0); } int mm_answer_authserv(struct ssh *ssh, int sock, struct sshbuf *m) { int r; monitor_permit_authentications(1); if ((r = sshbuf_get_cstring(m, &authctxt->service, NULL)) != 0 || (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0) fatal_fr(r, "parse"); debug3_f("service=%s, style=%s", authctxt->service, authctxt->style); if (strlen(authctxt->style) == 0) { free(authctxt->style); authctxt->style = NULL; } return (0); } /* * Check that the key type appears in the supplied pattern list, ignoring * mismatches in the signature algorithm. (Signature algorithm checks are * performed in the unprivileged authentication code). * Returns 1 on success, 0 otherwise. */ static int key_base_type_match(const char *method, const struct sshkey *key, const char *list) { char *s, *l, *ol = xstrdup(list); int found = 0; l = ol; for ((s = strsep(&l, ",")); s && *s != '\0'; (s = strsep(&l, ","))) { if (sshkey_type_from_name(s) == key->type) { found = 1; break; } } if (!found) { error("%s key type %s is not in permitted list %s", method, sshkey_ssh_name(key), list); } free(ol); return found; } int mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m) { static int call_count; char *passwd; int r, authenticated; size_t plen; if (!options.password_authentication) fatal_f("password authentication not enabled"); if ((r = sshbuf_get_cstring(m, &passwd, &plen)) != 0) fatal_fr(r, "parse"); /* Only authenticate if the context is valid */ authenticated = options.password_authentication && auth_password(ssh, passwd); freezero(passwd, plen); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authenticated)) != 0) fatal_fr(r, "assemble"); #ifdef USE_PAM if ((r = sshbuf_put_u32(m, sshpam_get_maxtries_reached())) != 0) fatal_fr(r, "assemble PAM"); #endif debug3("%s: sending result %d", __func__, authenticated); debug3_f("sending result %d", authenticated); mm_request_send(sock, MONITOR_ANS_AUTHPASSWORD, m); call_count++; if (plen == 0 && call_count == 1) auth_method = "none"; else auth_method = "password"; /* Causes monitor loop to terminate if authenticated */ return (authenticated); } #ifdef BSD_AUTH int mm_answer_bsdauthquery(struct ssh *ssh, int sock, struct sshbuf *m) { char *name, *infotxt; u_int numprompts, *echo_on, success; char **prompts; int r; if (!options.kbd_interactive_authentication) fatal_f("kbd-int authentication not enabled"); success = bsdauth_query(authctxt, &name, &infotxt, &numprompts, &prompts, &echo_on) < 0 ? 0 : 1; sshbuf_reset(m); if ((r = sshbuf_put_u32(m, success)) != 0) fatal_fr(r, "assemble"); if (success) { if ((r = sshbuf_put_cstring(m, prompts[0])) != 0) fatal_fr(r, "assemble prompt"); } debug3_f("sending challenge success: %u", success); mm_request_send(sock, MONITOR_ANS_BSDAUTHQUERY, m); if (success) { free(name); free(infotxt); free(prompts); free(echo_on); } return (0); } int mm_answer_bsdauthrespond(struct ssh *ssh, int sock, struct sshbuf *m) { char *response; int r, authok; if (!options.kbd_interactive_authentication) fatal_f("kbd-int authentication not enabled"); if (authctxt->as == NULL) fatal_f("no bsd auth session"); if ((r = sshbuf_get_cstring(m, &response, NULL)) != 0) fatal_fr(r, "parse"); authok = options.kbd_interactive_authentication && auth_userresponse(authctxt->as, response, 0); authctxt->as = NULL; debug3_f("<%s> = <%d>", response, authok); free(response); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authok)) != 0) fatal_fr(r, "assemble"); debug3_f("sending authenticated: %d", authok); mm_request_send(sock, MONITOR_ANS_BSDAUTHRESPOND, m); auth_method = "keyboard-interactive"; auth_submethod = "bsdauth"; return (authok != 0); } #endif #ifdef USE_PAM int mm_answer_pam_start(struct ssh *ssh, int sock, struct sshbuf *m) { if (!options.use_pam) fatal("UsePAM not set, but ended up in %s anyway", __func__); start_pam(ssh); monitor_permit(mon_dispatch, MONITOR_REQ_PAM_ACCOUNT, 1); if (options.kbd_interactive_authentication) monitor_permit(mon_dispatch, MONITOR_REQ_PAM_INIT_CTX, 1); return (0); } int mm_answer_pam_account(struct ssh *ssh, int sock, struct sshbuf *m) { u_int ret; int r; if (!options.use_pam) fatal("%s: PAM not enabled", __func__); ret = do_pam_account(); if ((r = sshbuf_put_u32(m, ret)) != 0 || (r = sshbuf_put_stringb(m, loginmsg)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(sock, MONITOR_ANS_PAM_ACCOUNT, m); return (ret); } static void *sshpam_ctxt, *sshpam_authok; extern KbdintDevice sshpam_device; int mm_answer_pam_init_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { u_int ok = 0; int r; debug3("%s", __func__); if (!options.kbd_interactive_authentication) fatal("%s: kbd-int authentication not enabled", __func__); if (sshpam_ctxt != NULL) fatal("%s: already called", __func__); sshpam_ctxt = (sshpam_device.init_ctx)(authctxt); sshpam_authok = NULL; sshbuf_reset(m); if (sshpam_ctxt != NULL) { monitor_permit(mon_dispatch, MONITOR_REQ_PAM_FREE_CTX, 1); monitor_permit(mon_dispatch, MONITOR_REQ_PAM_QUERY, 1); ok = 1; } if ((r = sshbuf_put_u32(m, ok)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(sock, MONITOR_ANS_PAM_INIT_CTX, m); return (0); } int mm_answer_pam_query(struct ssh *ssh, int sock, struct sshbuf *m) { char *name = NULL, *info = NULL, **prompts = NULL; u_int i, num = 0, *echo_on = 0; int r, ret; debug3("%s", __func__); sshpam_authok = NULL; if (sshpam_ctxt == NULL) fatal("%s: no context", __func__); ret = (sshpam_device.query)(sshpam_ctxt, &name, &info, &num, &prompts, &echo_on); if (ret == 0 && num == 0) sshpam_authok = sshpam_ctxt; if (num > 1 || name == NULL || info == NULL) fatal("sshpam_device.query failed"); monitor_permit(mon_dispatch, MONITOR_REQ_PAM_RESPOND, 1); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, ret)) != 0 || (r = sshbuf_put_cstring(m, name)) != 0 || (r = sshbuf_put_cstring(m, info)) != 0 || (r = sshbuf_put_u32(m, sshpam_get_maxtries_reached())) != 0 || (r = sshbuf_put_u32(m, num)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(name); free(info); for (i = 0; i < num; ++i) { if ((r = sshbuf_put_cstring(m, prompts[i])) != 0 || (r = sshbuf_put_u32(m, echo_on[i])) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(prompts[i]); } free(prompts); free(echo_on); auth_method = "keyboard-interactive"; auth_submethod = "pam"; mm_request_send(sock, MONITOR_ANS_PAM_QUERY, m); return (0); } int mm_answer_pam_respond(struct ssh *ssh, int sock, struct sshbuf *m) { char **resp; u_int i, num; int r, ret; debug3("%s", __func__); if (sshpam_ctxt == NULL) fatal("%s: no context", __func__); sshpam_authok = NULL; if ((r = sshbuf_get_u32(m, &num)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (num > PAM_MAX_NUM_MSG) { fatal_f("Too many PAM messages, got %u, expected <= %u", num, (unsigned)PAM_MAX_NUM_MSG); } if (num > 0) { resp = xcalloc(num, sizeof(char *)); for (i = 0; i < num; ++i) { if ((r = sshbuf_get_cstring(m, &(resp[i]), NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } ret = (sshpam_device.respond)(sshpam_ctxt, num, resp); for (i = 0; i < num; ++i) free(resp[i]); free(resp); } else { ret = (sshpam_device.respond)(sshpam_ctxt, num, NULL); } sshbuf_reset(m); if ((r = sshbuf_put_u32(m, ret)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(sock, MONITOR_ANS_PAM_RESPOND, m); auth_method = "keyboard-interactive"; auth_submethod = "pam"; if (ret == 0) sshpam_authok = sshpam_ctxt; return (0); } int mm_answer_pam_free_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { int r = sshpam_authok != NULL && sshpam_authok == sshpam_ctxt; debug3("%s", __func__); if (sshpam_ctxt == NULL) fatal("%s: no context", __func__); (sshpam_device.free_ctx)(sshpam_ctxt); sshpam_ctxt = sshpam_authok = NULL; sshbuf_reset(m); mm_request_send(sock, MONITOR_ANS_PAM_FREE_CTX, m); /* Allow another attempt */ monitor_permit(mon_dispatch, MONITOR_REQ_PAM_INIT_CTX, 1); auth_method = "keyboard-interactive"; auth_submethod = "pam"; return r; } #endif int mm_answer_keyallowed(struct ssh *ssh, int sock, struct sshbuf *m) { struct sshkey *key = NULL; char *cuser, *chost; u_int pubkey_auth_attempt; u_int type = 0; int r, allowed = 0; struct sshauthopt *opts = NULL; debug3_f("entering"); if ((r = sshbuf_get_u32(m, &type)) != 0 || (r = sshbuf_get_cstring(m, &cuser, NULL)) != 0 || (r = sshbuf_get_cstring(m, &chost, NULL)) != 0 || (r = sshkey_froms(m, &key)) != 0 || (r = sshbuf_get_u32(m, &pubkey_auth_attempt)) != 0) fatal_fr(r, "parse"); if (key != NULL && authctxt->valid) { switch (type) { case MM_USERKEY: auth_method = "publickey"; if (!options.pubkey_authentication) break; if (auth2_key_already_used(authctxt, key)) break; if (!key_base_type_match(auth_method, key, options.pubkey_accepted_algos)) break; allowed = user_key_allowed(ssh, authctxt->pw, key, pubkey_auth_attempt, &opts); break; case MM_HOSTKEY: auth_method = "hostbased"; if (!options.hostbased_authentication) break; if (auth2_key_already_used(authctxt, key)) break; if (!key_base_type_match(auth_method, key, options.hostbased_accepted_algos)) break; allowed = hostbased_key_allowed(ssh, authctxt->pw, cuser, chost, key); auth2_record_info(authctxt, "client user \"%.100s\", client host \"%.100s\"", cuser, chost); break; default: fatal_f("unknown key type %u", type); break; } } debug3_f("%s authentication%s: %s key is %s", auth_method, pubkey_auth_attempt ? "" : " test", (key == NULL || !authctxt->valid) ? "invalid" : sshkey_type(key), allowed ? "allowed" : "not allowed"); auth2_record_key(authctxt, 0, key); /* clear temporarily storage (used by verify) */ monitor_reset_key_state(); if (allowed) { /* Save temporarily for comparison in verify */ if ((r = sshkey_to_blob(key, &key_blob, &key_bloblen)) != 0) fatal_fr(r, "sshkey_to_blob"); key_blobtype = type; key_opts = opts; hostbased_cuser = cuser; hostbased_chost = chost; } else { /* Log failed attempt */ auth_log(ssh, 0, 0, auth_method, NULL); free(cuser); free(chost); } sshkey_free(key); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, allowed)) != 0) fatal_fr(r, "assemble"); if (opts != NULL && (r = sshauthopt_serialise(opts, m, 1)) != 0) fatal_fr(r, "sshauthopt_serialise"); mm_request_send(sock, MONITOR_ANS_KEYALLOWED, m); if (!allowed) sshauthopt_free(opts); return (0); } static int monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen) { struct sshbuf *b; struct sshkey *hostkey = NULL; const u_char *p; char *userstyle, *cp; size_t len; u_char type; int hostbound = 0, r, fail = 0; if ((b = sshbuf_from(data, datalen)) == NULL) fatal_f("sshbuf_from"); if (ssh->compat & SSH_OLD_SESSIONID) { p = sshbuf_ptr(b); len = sshbuf_len(b); if ((session_id2 == NULL) || (len < session_id2_len) || (timingsafe_bcmp(p, session_id2, session_id2_len) != 0)) fail++; if ((r = sshbuf_consume(b, session_id2_len)) != 0) fatal_fr(r, "consume"); } else { if ((r = sshbuf_get_string_direct(b, &p, &len)) != 0) fatal_fr(r, "parse sessionid"); if ((session_id2 == NULL) || (len != session_id2_len) || (timingsafe_bcmp(p, session_id2, session_id2_len) != 0)) fail++; } if ((r = sshbuf_get_u8(b, &type)) != 0) fatal_fr(r, "parse type"); if (type != SSH2_MSG_USERAUTH_REQUEST) fail++; if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse userstyle"); xasprintf(&userstyle, "%s%s%s", authctxt->user, authctxt->style ? ":" : "", authctxt->style ? authctxt->style : ""); if (strcmp(userstyle, cp) != 0) { logit("wrong user name passed to monitor: " "expected %s != %.100s", userstyle, cp); fail++; } free(userstyle); free(cp); if ((r = sshbuf_skip_string(b)) != 0 || /* service */ (r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse method"); if (strcmp("publickey", cp) != 0) { if (strcmp("publickey-hostbound-v00@openssh.com", cp) == 0) hostbound = 1; else fail++; } free(cp); if ((r = sshbuf_get_u8(b, &type)) != 0) fatal_fr(r, "parse pktype"); if (type == 0) fail++; if ((r = sshbuf_skip_string(b)) != 0 || /* pkalg */ (r = sshbuf_skip_string(b)) != 0 || /* pkblob */ (hostbound && (r = sshkey_froms(b, &hostkey)) != 0)) fatal_fr(r, "parse pk"); if (sshbuf_len(b) != 0) fail++; sshbuf_free(b); if (hostkey != NULL) { /* * Ensure this is actually one of our hostkeys; unfortunately * can't check ssh->kex->initial_hostkey directly at this point * as packet state has not yet been exported to monitor. */ if (get_hostkey_index(hostkey, 1, ssh) == -1) fatal_f("hostbound hostkey does not match"); sshkey_free(hostkey); } return (fail == 0); } static int monitor_valid_hostbasedblob(const u_char *data, u_int datalen, const char *cuser, const char *chost) { struct sshbuf *b; const u_char *p; char *cp, *userstyle; size_t len; int r, fail = 0; u_char type; if ((b = sshbuf_from(data, datalen)) == NULL) fatal_f("sshbuf_new"); if ((r = sshbuf_get_string_direct(b, &p, &len)) != 0) fatal_fr(r, "parse sessionid"); if ((session_id2 == NULL) || (len != session_id2_len) || (timingsafe_bcmp(p, session_id2, session_id2_len) != 0)) fail++; if ((r = sshbuf_get_u8(b, &type)) != 0) fatal_fr(r, "parse type"); if (type != SSH2_MSG_USERAUTH_REQUEST) fail++; if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse userstyle"); xasprintf(&userstyle, "%s%s%s", authctxt->user, authctxt->style ? ":" : "", authctxt->style ? authctxt->style : ""); if (strcmp(userstyle, cp) != 0) { logit("wrong user name passed to monitor: " "expected %s != %.100s", userstyle, cp); fail++; } free(userstyle); free(cp); if ((r = sshbuf_skip_string(b)) != 0 || /* service */ (r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse method"); if (strcmp(cp, "hostbased") != 0) fail++; free(cp); if ((r = sshbuf_skip_string(b)) != 0 || /* pkalg */ (r = sshbuf_skip_string(b)) != 0) /* pkblob */ fatal_fr(r, "parse pk"); /* verify client host, strip trailing dot if necessary */ if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse host"); if (((len = strlen(cp)) > 0) && cp[len - 1] == '.') cp[len - 1] = '\0'; if (strcmp(cp, chost) != 0) fail++; free(cp); /* verify client user */ if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0) fatal_fr(r, "parse ruser"); if (strcmp(cp, cuser) != 0) fail++; free(cp); if (sshbuf_len(b) != 0) fail++; sshbuf_free(b); return (fail == 0); } int mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m) { struct sshkey *key; const u_char *signature, *data, *blob; char *sigalg = NULL, *fp = NULL; size_t signaturelen, datalen, bloblen; int r, ret, req_presence = 0, req_verify = 0, valid_data = 0; int encoded_ret; struct sshkey_sig_details *sig_details = NULL; if ((r = sshbuf_get_string_direct(m, &blob, &bloblen)) != 0 || (r = sshbuf_get_string_direct(m, &signature, &signaturelen)) != 0 || (r = sshbuf_get_string_direct(m, &data, &datalen)) != 0 || (r = sshbuf_get_cstring(m, &sigalg, NULL)) != 0) fatal_fr(r, "parse"); if (hostbased_cuser == NULL || hostbased_chost == NULL || !monitor_allowed_key(blob, bloblen)) fatal_f("bad key, not previously allowed"); /* Empty signature algorithm means NULL. */ if (*sigalg == '\0') { free(sigalg); sigalg = NULL; } /* XXX use sshkey_froms here; need to change key_blob, etc. */ if ((r = sshkey_from_blob(blob, bloblen, &key)) != 0) fatal_fr(r, "parse key"); switch (key_blobtype) { case MM_USERKEY: valid_data = monitor_valid_userblob(ssh, data, datalen); auth_method = "publickey"; break; case MM_HOSTKEY: valid_data = monitor_valid_hostbasedblob(data, datalen, hostbased_cuser, hostbased_chost); auth_method = "hostbased"; break; default: valid_data = 0; break; } if (!valid_data) fatal_f("bad %s signature data blob", key_blobtype == MM_USERKEY ? "userkey" : (key_blobtype == MM_HOSTKEY ? "hostkey" : "unknown")); if ((fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal_f("sshkey_fingerprint failed"); ret = sshkey_verify(key, signature, signaturelen, data, datalen, sigalg, ssh->compat, &sig_details); debug3_f("%s %s signature using %s %s%s%s", auth_method, sshkey_type(key), sigalg == NULL ? "default" : sigalg, (ret == 0) ? "verified" : "unverified", (ret != 0) ? ": " : "", (ret != 0) ? ssh_err(ret) : ""); if (ret == 0 && key_blobtype == MM_USERKEY && sig_details != NULL) { req_presence = (options.pubkey_auth_options & PUBKEYAUTH_TOUCH_REQUIRED) || !key_opts->no_require_user_presence; if (req_presence && (sig_details->sk_flags & SSH_SK_USER_PRESENCE_REQD) == 0) { error("public key %s %s signature for %s%s from %.128s " "port %d rejected: user presence " "(authenticator touch) requirement not met ", sshkey_type(key), fp, authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); ret = SSH_ERR_SIGNATURE_INVALID; } req_verify = (options.pubkey_auth_options & PUBKEYAUTH_VERIFY_REQUIRED) || key_opts->require_verify; if (req_verify && (sig_details->sk_flags & SSH_SK_USER_VERIFICATION_REQD) == 0) { error("public key %s %s signature for %s%s from %.128s " "port %d rejected: user verification requirement " "not met ", sshkey_type(key), fp, authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); ret = SSH_ERR_SIGNATURE_INVALID; } } auth2_record_key(authctxt, ret == 0, key); if (key_blobtype == MM_USERKEY) auth_activate_options(ssh, key_opts); monitor_reset_key_state(); sshbuf_reset(m); /* encode ret != 0 as positive integer, since we're sending u32 */ encoded_ret = (ret != 0); if ((r = sshbuf_put_u32(m, encoded_ret)) != 0 || (r = sshbuf_put_u8(m, sig_details != NULL)) != 0) fatal_fr(r, "assemble"); if (sig_details != NULL) { if ((r = sshbuf_put_u32(m, sig_details->sk_counter)) != 0 || (r = sshbuf_put_u8(m, sig_details->sk_flags)) != 0) fatal_fr(r, "assemble sk"); } sshkey_sig_details_free(sig_details); mm_request_send(sock, MONITOR_ANS_KEYVERIFY, m); free(sigalg); free(fp); sshkey_free(key); return ret == 0; } static void mm_record_login(struct ssh *ssh, Session *s, struct passwd *pw) { socklen_t fromlen; struct sockaddr_storage from; /* * Get IP address of client. If the connection is not a socket, let * the address be 0.0.0.0. */ memset(&from, 0, sizeof(from)); fromlen = sizeof(from); if (ssh_packet_connection_is_on_socket(ssh)) { if (getpeername(ssh_packet_get_connection_in(ssh), (struct sockaddr *)&from, &fromlen) == -1) { debug("getpeername: %.100s", strerror(errno)); cleanup_exit(255); } } /* Record that there was a login on that tty from the remote host. */ record_login(s->pid, s->tty, pw->pw_name, pw->pw_uid, session_get_remote_name_or_ip(ssh, utmp_len, options.use_dns), (struct sockaddr *)&from, fromlen); } static void mm_session_close(Session *s) { debug3_f("session %d pid %ld", s->self, (long)s->pid); if (s->ttyfd != -1) { debug3_f("tty %s ptyfd %d", s->tty, s->ptyfd); session_pty_cleanup2(s); } session_unused(s->self); } int mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m) { extern struct monitor *pmonitor; Session *s; int r, res, fd0; debug3_f("entering"); sshbuf_reset(m); s = session_new(); if (s == NULL) goto error; s->authctxt = authctxt; s->pw = authctxt->pw; s->pid = pmonitor->m_pid; res = pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)); if (res == 0) goto error; pty_setowner(authctxt->pw, s->tty); if ((r = sshbuf_put_u32(m, 1)) != 0 || (r = sshbuf_put_cstring(m, s->tty)) != 0) fatal_fr(r, "assemble"); /* We need to trick ttyslot */ if (dup2(s->ttyfd, 0) == -1) fatal_f("dup2"); mm_record_login(ssh, s, authctxt->pw); /* Now we can close the file descriptor again */ close(0); /* send messages generated by record_login */ if ((r = sshbuf_put_stringb(m, loginmsg)) != 0) fatal_fr(r, "assemble loginmsg"); sshbuf_reset(loginmsg); mm_request_send(sock, MONITOR_ANS_PTY, m); if (mm_send_fd(sock, s->ptyfd) == -1 || mm_send_fd(sock, s->ttyfd) == -1) fatal_f("send fds failed"); /* make sure nothing uses fd 0 */ if ((fd0 = open(_PATH_DEVNULL, O_RDONLY)) == -1) fatal_f("open(/dev/null): %s", strerror(errno)); if (fd0 != 0) error_f("fd0 %d != 0", fd0); /* slave side of pty is not needed */ close(s->ttyfd); s->ttyfd = s->ptyfd; /* no need to dup() because nobody closes ptyfd */ s->ptymaster = s->ptyfd; debug3_f("tty %s ptyfd %d", s->tty, s->ttyfd); return (0); error: if (s != NULL) mm_session_close(s); if ((r = sshbuf_put_u32(m, 0)) != 0) fatal_fr(r, "assemble 0"); mm_request_send(sock, MONITOR_ANS_PTY, m); return (0); } int mm_answer_pty_cleanup(struct ssh *ssh, int sock, struct sshbuf *m) { Session *s; char *tty; int r; debug3_f("entering"); if ((r = sshbuf_get_cstring(m, &tty, NULL)) != 0) fatal_fr(r, "parse tty"); if ((s = session_by_tty(tty)) != NULL) mm_session_close(s); sshbuf_reset(m); free(tty); return (0); } int mm_answer_term(struct ssh *ssh, int sock, struct sshbuf *req) { extern struct monitor *pmonitor; int res, status; debug3_f("tearing down sessions"); /* The child is terminating */ session_destroy_all(ssh, &mm_session_close); #ifdef USE_PAM if (options.use_pam) sshpam_cleanup(); #endif while (waitpid(pmonitor->m_pid, &status, 0) == -1) if (errno != EINTR) exit(1); res = WIFEXITED(status) ? WEXITSTATUS(status) : 1; /* Terminate process */ exit(res); } #ifdef SSH_AUDIT_EVENTS /* Report that an audit event occurred */ int mm_answer_audit_event(struct ssh *ssh, int socket, struct sshbuf *m) { u_int n; ssh_audit_event_t event; int r; debug3("%s entering", __func__); if ((r = sshbuf_get_u32(m, &n)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); event = (ssh_audit_event_t)n; switch (event) { case SSH_AUTH_FAIL_PUBKEY: case SSH_AUTH_FAIL_HOSTBASED: case SSH_AUTH_FAIL_GSSAPI: case SSH_LOGIN_EXCEED_MAXTRIES: case SSH_LOGIN_ROOT_DENIED: case SSH_CONNECTION_CLOSE: case SSH_INVALID_USER: audit_event(ssh, event); break; default: fatal("Audit event type %d not permitted", event); } return (0); } int mm_answer_audit_command(struct ssh *ssh, int socket, struct sshbuf *m) { char *cmd; int r; debug3("%s entering", __func__); if ((r = sshbuf_get_cstring(m, &cmd, NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* sanity check command, if so how? */ audit_run_command(cmd); free(cmd); return (0); } #endif /* SSH_AUDIT_EVENTS */ void monitor_clear_keystate(struct ssh *ssh, struct monitor *pmonitor) { ssh_clear_newkeys(ssh, MODE_IN); ssh_clear_newkeys(ssh, MODE_OUT); sshbuf_free(child_state); child_state = NULL; } void monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) { struct kex *kex; int r; debug3_f("packet_set_state"); if ((r = ssh_packet_set_state(ssh, child_state)) != 0) fatal_fr(r, "packet_set_state"); sshbuf_free(child_state); child_state = NULL; if ((kex = ssh->kex) == NULL) fatal_f("internal error: ssh->kex == NULL"); if (session_id2_len != sshbuf_len(ssh->kex->session_id)) { fatal_f("incorrect session id length %zu (expected %u)", sshbuf_len(ssh->kex->session_id), session_id2_len); } if (memcmp(sshbuf_ptr(ssh->kex->session_id), session_id2, session_id2_len) != 0) fatal_f("session ID mismatch"); /* XXX set callbacks */ #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; # ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif #endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kex_gen_server; kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; kex->sign = sshd_hostkey_sign; } /* This function requires careful sanity checking */ void mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor) { debug3_f("Waiting for new keys"); if ((child_state = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT, child_state); debug3_f("GOT new keys"); } /* XXX */ #define FD_CLOSEONEXEC(x) do { \ if (fcntl(x, F_SETFD, FD_CLOEXEC) == -1) \ fatal("fcntl(%d, F_SETFD)", x); \ } while (0) static void monitor_openfds(struct monitor *mon, int do_logfds) { int pair[2]; #ifdef SO_ZEROIZE int on = 1; #endif if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) fatal_f("socketpair: %s", strerror(errno)); #ifdef SO_ZEROIZE if (setsockopt(pair[0], SOL_SOCKET, SO_ZEROIZE, &on, sizeof(on)) == -1) error("setsockopt SO_ZEROIZE(0): %.100s", strerror(errno)); if (setsockopt(pair[1], SOL_SOCKET, SO_ZEROIZE, &on, sizeof(on)) == -1) error("setsockopt SO_ZEROIZE(1): %.100s", strerror(errno)); #endif FD_CLOSEONEXEC(pair[0]); FD_CLOSEONEXEC(pair[1]); mon->m_recvfd = pair[0]; mon->m_sendfd = pair[1]; if (do_logfds) { if (pipe(pair) == -1) fatal_f("pipe: %s", strerror(errno)); FD_CLOSEONEXEC(pair[0]); FD_CLOSEONEXEC(pair[1]); mon->m_log_recvfd = pair[0]; mon->m_log_sendfd = pair[1]; } else mon->m_log_recvfd = mon->m_log_sendfd = -1; } #define MM_MEMSIZE 65536 struct monitor * monitor_init(void) { struct monitor *mon; mon = xcalloc(1, sizeof(*mon)); monitor_openfds(mon, 1); return mon; } void monitor_reinit(struct monitor *mon) { monitor_openfds(mon, 0); } #ifdef GSSAPI int mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { gss_OID_desc goid; OM_uint32 major; size_t len; u_char *p; int r; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); if ((r = sshbuf_get_string(m, &p, &len)) != 0) fatal_fr(r, "parse"); goid.elements = p; goid.length = len; major = ssh_gssapi_server_ctx(&gsscontext, &goid); free(goid.elements); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, major)) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_GSSSETUP, m); /* Now we have a context, enable the step */ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 1); return (0); } int mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m) { gss_buffer_desc in; gss_buffer_desc out = GSS_C_EMPTY_BUFFER; OM_uint32 major, minor; OM_uint32 flags = 0; /* GSI needs this */ int r; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) fatal_fr(r, "ssh_gssapi_get_buffer_desc"); major = ssh_gssapi_accept_ctx(gsscontext, &in, &out, &flags); free(in.value); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, major)) != 0 || (r = sshbuf_put_string(m, out.value, out.length)) != 0 || (r = sshbuf_put_u32(m, flags)) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_GSSSTEP, m); gss_release_buffer(&minor, &out); if (major == GSS_S_COMPLETE) { monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); } return (0); } int mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m) { gss_buffer_desc gssbuf, mic; OM_uint32 ret; int r; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) fatal_fr(r, "ssh_gssapi_get_buffer_desc"); ret = ssh_gssapi_checkmic(gsscontext, &gssbuf, &mic); free(gssbuf.value); free(mic.value); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, ret)) != 0) fatal_fr(r, "assemble"); mm_request_send(sock, MONITOR_ANS_GSSCHECKMIC, m); if (!GSS_ERROR(ret)) monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); return (0); } int mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) { int r, authenticated; const char *displayname; if (!options.gss_authentication) fatal_f("GSSAPI authentication not enabled"); authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authenticated)) != 0) fatal_fr(r, "assemble"); debug3_f("sending result %d", authenticated); mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); auth_method = "gssapi-with-mic"; if ((displayname = ssh_gssapi_displayname()) != NULL) auth2_record_info(authctxt, "%s", displayname); /* Monitor loop will terminate if authenticated */ return (authenticated); } #endif /* GSSAPI */ diff --git a/crypto/openssh/openbsd-compat/bsd-closefrom.c b/crypto/openssh/openbsd-compat/bsd-closefrom.c index 704da531fef9..49a4f35ff9c3 100644 --- a/crypto/openssh/openbsd-compat/bsd-closefrom.c +++ b/crypto/openssh/openbsd-compat/bsd-closefrom.c @@ -1,159 +1,158 @@ /* * Copyright (c) 2004-2005 Todd C. Miller * * 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 "includes.h" #if !defined(HAVE_CLOSEFROM) || defined(BROKEN_CLOSEFROM) #include #include #include #ifdef HAVE_FCNTL_H # include #endif #include #include #include #include -#include #ifdef HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif # ifdef HAVE_NDIR_H # include # endif #endif #if defined(HAVE_LIBPROC_H) # include #endif #ifndef OPEN_MAX # define OPEN_MAX 256 #endif #if 0 __unused static const char rcsid[] = "$Sudo: closefrom.c,v 1.11 2006/08/17 15:26:54 millert Exp $"; #endif /* lint */ #ifndef HAVE_FCNTL_CLOSEM /* * Close all file descriptors greater than or equal to lowfd. */ static void closefrom_fallback(int lowfd) { long fd, maxfd; /* * Fall back on sysconf() or getdtablesize(). We avoid checking * resource limits since it is possible to open a file descriptor * and then drop the rlimit such that it is below the open fd. */ #ifdef HAVE_SYSCONF maxfd = sysconf(_SC_OPEN_MAX); #else maxfd = getdtablesize(); #endif /* HAVE_SYSCONF */ if (maxfd < 0) maxfd = OPEN_MAX; for (fd = lowfd; fd < maxfd; fd++) (void) close((int) fd); } #endif /* HAVE_FCNTL_CLOSEM */ #ifdef HAVE_FCNTL_CLOSEM void closefrom(int lowfd) { (void) fcntl(lowfd, F_CLOSEM, 0); } #elif defined(HAVE_LIBPROC_H) && defined(HAVE_PROC_PIDINFO) void closefrom(int lowfd) { int i, r, sz; pid_t pid = getpid(); struct proc_fdinfo *fdinfo_buf = NULL; sz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); if (sz == 0) return; /* no fds, really? */ else if (sz == -1) goto fallback; if ((fdinfo_buf = malloc(sz)) == NULL) goto fallback; r = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdinfo_buf, sz); if (r < 0 || r > sz) goto fallback; for (i = 0; i < r / (int)PROC_PIDLISTFD_SIZE; i++) { if (fdinfo_buf[i].proc_fd >= lowfd) close(fdinfo_buf[i].proc_fd); } free(fdinfo_buf); return; fallback: free(fdinfo_buf); closefrom_fallback(lowfd); return; } #elif defined(HAVE_DIRFD) && defined(HAVE_PROC_PID) void closefrom(int lowfd) { long fd; char fdpath[PATH_MAX], *endp; struct dirent *dent; DIR *dirp; int len; #ifdef HAVE_CLOSE_RANGE if (close_range(lowfd, INT_MAX, 0) == 0) return; #endif /* Check for a /proc/$$/fd directory. */ len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid()); if (len > 0 && (size_t)len < sizeof(fdpath) && (dirp = opendir(fdpath))) { while ((dent = readdir(dirp)) != NULL) { fd = strtol(dent->d_name, &endp, 10); if (dent->d_name != endp && *endp == '\0' && fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp)) (void) close((int) fd); } (void) closedir(dirp); return; } /* /proc/$$/fd strategy failed, fall back to brute force closure */ closefrom_fallback(lowfd); } #else void closefrom(int lowfd) { closefrom_fallback(lowfd); } #endif /* !HAVE_FCNTL_CLOSEM */ #endif /* HAVE_CLOSEFROM */ diff --git a/crypto/openssh/packet.c b/crypto/openssh/packet.c index 421acc1af3c4..d69b903c844c 100644 --- a/crypto/openssh/packet.c +++ b/crypto/openssh/packet.c @@ -1,2721 +1,2752 @@ -/* $OpenBSD: packet.c,v 1.310 2023/04/06 03:21:31 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.312 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This file contains code implementing the packet protocol and communication * with the other side. This same code is used both on client and server side. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * SSH2 packet format added by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include "openbsd-compat/sys-queue.h" #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #include /* * Explicitly include OpenSSL before zlib as some versions of OpenSSL have * "free_func" in their headers, which zlib typedefs. */ #ifdef WITH_OPENSSL # include # include # ifdef OPENSSL_HAS_ECC # include # endif #endif #ifdef WITH_ZLIB #include #endif #include "xmalloc.h" #include "compat.h" #include "ssh2.h" #include "cipher.h" #include "sshkey.h" #include "kex.h" #include "digest.h" #include "mac.h" #include "log.h" #include "canohost.h" #include "misc.h" #include "channels.h" #include "ssh.h" #include "packet.h" #include "ssherr.h" #include "sshbuf.h" #include "blacklist_client.h" #ifdef PACKET_DEBUG #define DBG(x) x #else #define DBG(x) #endif #define PACKET_MAX_SIZE (256 * 1024) struct packet_state { u_int32_t seqnr; u_int32_t packets; u_int64_t blocks; u_int64_t bytes; }; struct packet { TAILQ_ENTRY(packet) next; u_char type; struct sshbuf *payload; }; struct session_state { /* * This variable contains the file descriptors used for * communicating with the other side. connection_in is used for * reading; connection_out for writing. These can be the same * descriptor, in which case it is assumed to be a socket. */ int connection_in; int connection_out; /* Protocol flags for the remote side. */ u_int remote_protocol_flags; /* Encryption context for receiving data. Only used for decryption. */ struct sshcipher_ctx *receive_context; /* Encryption context for sending data. Only used for encryption. */ struct sshcipher_ctx *send_context; /* Buffer for raw input data from the socket. */ struct sshbuf *input; /* Buffer for raw output data going to the socket. */ struct sshbuf *output; /* Buffer for the partial outgoing packet being constructed. */ struct sshbuf *outgoing_packet; /* Buffer for the incoming packet currently being processed. */ struct sshbuf *incoming_packet; /* Scratch buffer for packet compression/decompression. */ struct sshbuf *compression_buffer; #ifdef WITH_ZLIB /* Incoming/outgoing compression dictionaries */ z_stream compression_in_stream; z_stream compression_out_stream; #endif int compression_in_started; int compression_out_started; int compression_in_failures; int compression_out_failures; /* default maximum packet size */ u_int max_packet_size; /* Flag indicating whether this module has been initialized. */ int initialized; /* Set to true if the connection is interactive. */ int interactive_mode; /* Set to true if we are the server side. */ int server_side; /* Set to true if we are authenticated. */ int after_authentication; int keep_alive_timeouts; /* The maximum time that we will wait to send or receive a packet */ int packet_timeout_ms; /* Session key information for Encryption and MAC */ struct newkeys *newkeys[MODE_MAX]; struct packet_state p_read, p_send; /* Volume-based rekeying */ u_int64_t max_blocks_in, max_blocks_out, rekey_limit; /* Time-based rekeying */ u_int32_t rekey_interval; /* how often in seconds */ time_t rekey_time; /* time of last rekeying */ /* roundup current message to extra_pad bytes */ u_char extra_pad; /* XXX discard incoming data after MAC error */ u_int packet_discard; size_t packet_discard_mac_already; struct sshmac *packet_discard_mac; /* Used in packet_read_poll2() */ u_int packlen; /* Used in packet_send2 */ int rekeying; /* Used in ssh_packet_send_mux() */ int mux; /* Used in packet_set_interactive */ int set_interactive_called; /* Used in packet_set_maxsize */ int set_maxsize_called; /* One-off warning about weak ciphers */ int cipher_warning_done; /* Hook for fuzzing inbound packets */ ssh_packet_hook_fn *hook_in; void *hook_in_ctx; TAILQ_HEAD(, packet) outgoing; }; struct ssh * ssh_alloc_session_state(void) { struct ssh *ssh = NULL; struct session_state *state = NULL; if ((ssh = calloc(1, sizeof(*ssh))) == NULL || (state = calloc(1, sizeof(*state))) == NULL || (ssh->kex = kex_new()) == NULL || (state->input = sshbuf_new()) == NULL || (state->output = sshbuf_new()) == NULL || (state->outgoing_packet = sshbuf_new()) == NULL || (state->incoming_packet = sshbuf_new()) == NULL) goto fail; TAILQ_INIT(&state->outgoing); TAILQ_INIT(&ssh->private_keys); TAILQ_INIT(&ssh->public_keys); state->connection_in = -1; state->connection_out = -1; state->max_packet_size = 32768; state->packet_timeout_ms = -1; state->p_send.packets = state->p_read.packets = 0; state->initialized = 1; /* * ssh_packet_send2() needs to queue packets until * we've done the initial key exchange. */ state->rekeying = 1; ssh->state = state; return ssh; fail: if (ssh) { kex_free(ssh->kex); free(ssh); } if (state) { sshbuf_free(state->input); sshbuf_free(state->output); sshbuf_free(state->incoming_packet); sshbuf_free(state->outgoing_packet); free(state); } return NULL; } void ssh_packet_set_input_hook(struct ssh *ssh, ssh_packet_hook_fn *hook, void *ctx) { ssh->state->hook_in = hook; ssh->state->hook_in_ctx = ctx; } /* Returns nonzero if rekeying is in progress */ int ssh_packet_is_rekeying(struct ssh *ssh) { return ssh->state->rekeying || (ssh->kex != NULL && ssh->kex->done == 0); } /* * Sets the descriptors used for communication. */ struct ssh * ssh_packet_set_connection(struct ssh *ssh, int fd_in, int fd_out) { struct session_state *state; const struct sshcipher *none = cipher_by_name("none"); int r; if (none == NULL) { error_f("cannot load cipher 'none'"); return NULL; } if (ssh == NULL) ssh = ssh_alloc_session_state(); if (ssh == NULL) { error_f("could not allocate state"); return NULL; } state = ssh->state; state->connection_in = fd_in; state->connection_out = fd_out; if ((r = cipher_init(&state->send_context, none, (const u_char *)"", 0, NULL, 0, CIPHER_ENCRYPT)) != 0 || (r = cipher_init(&state->receive_context, none, (const u_char *)"", 0, NULL, 0, CIPHER_DECRYPT)) != 0) { error_fr(r, "cipher_init failed"); free(ssh); /* XXX need ssh_free_session_state? */ return NULL; } state->newkeys[MODE_IN] = state->newkeys[MODE_OUT] = NULL; /* * Cache the IP address of the remote connection for use in error * messages that might be generated after the connection has closed. */ (void)ssh_remote_ipaddr(ssh); return ssh; } void ssh_packet_set_timeout(struct ssh *ssh, int timeout, int count) { struct session_state *state = ssh->state; if (timeout <= 0 || count <= 0) { state->packet_timeout_ms = -1; return; } if ((INT_MAX / 1000) / count < timeout) state->packet_timeout_ms = INT_MAX; else state->packet_timeout_ms = timeout * count * 1000; } void ssh_packet_set_mux(struct ssh *ssh) { ssh->state->mux = 1; ssh->state->rekeying = 0; kex_free(ssh->kex); ssh->kex = NULL; } int ssh_packet_get_mux(struct ssh *ssh) { return ssh->state->mux; } int ssh_packet_set_log_preamble(struct ssh *ssh, const char *fmt, ...) { va_list args; int r; free(ssh->log_preamble); if (fmt == NULL) ssh->log_preamble = NULL; else { va_start(args, fmt); r = vasprintf(&ssh->log_preamble, fmt, args); va_end(args); if (r < 0 || ssh->log_preamble == NULL) return SSH_ERR_ALLOC_FAIL; } return 0; } int ssh_packet_stop_discard(struct ssh *ssh) { struct session_state *state = ssh->state; int r; if (state->packet_discard_mac) { char buf[1024]; size_t dlen = PACKET_MAX_SIZE; if (dlen > state->packet_discard_mac_already) dlen -= state->packet_discard_mac_already; memset(buf, 'a', sizeof(buf)); while (sshbuf_len(state->incoming_packet) < dlen) if ((r = sshbuf_put(state->incoming_packet, buf, sizeof(buf))) != 0) return r; (void) mac_compute(state->packet_discard_mac, state->p_read.seqnr, sshbuf_ptr(state->incoming_packet), dlen, NULL, 0); } logit("Finished discarding for %.200s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); return SSH_ERR_MAC_INVALID; } static int ssh_packet_start_discard(struct ssh *ssh, struct sshenc *enc, struct sshmac *mac, size_t mac_already, u_int discard) { struct session_state *state = ssh->state; int r; if (enc == NULL || !cipher_is_cbc(enc->cipher) || (mac && mac->etm)) { if ((r = sshpkt_disconnect(ssh, "Packet corrupt")) != 0) return r; return SSH_ERR_MAC_INVALID; } /* * Record number of bytes over which the mac has already * been computed in order to minimize timing attacks. */ if (mac && mac->enabled) { state->packet_discard_mac = mac; state->packet_discard_mac_already = mac_already; } if (sshbuf_len(state->input) >= discard) return ssh_packet_stop_discard(ssh); state->packet_discard = discard - sshbuf_len(state->input); return 0; } /* Returns 1 if remote host is connected via socket, 0 if not. */ int ssh_packet_connection_is_on_socket(struct ssh *ssh) { struct session_state *state; struct sockaddr_storage from, to; socklen_t fromlen, tolen; if (ssh == NULL || ssh->state == NULL) return 0; state = ssh->state; if (state->connection_in == -1 || state->connection_out == -1) return 0; /* filedescriptors in and out are the same, so it's a socket */ if (state->connection_in == state->connection_out) return 1; fromlen = sizeof(from); memset(&from, 0, sizeof(from)); if (getpeername(state->connection_in, (struct sockaddr *)&from, &fromlen) == -1) return 0; tolen = sizeof(to); memset(&to, 0, sizeof(to)); if (getpeername(state->connection_out, (struct sockaddr *)&to, &tolen) == -1) return 0; if (fromlen != tolen || memcmp(&from, &to, fromlen) != 0) return 0; if (from.ss_family != AF_INET && from.ss_family != AF_INET6) return 0; return 1; } void ssh_packet_get_bytes(struct ssh *ssh, u_int64_t *ibytes, u_int64_t *obytes) { if (ibytes) *ibytes = ssh->state->p_read.bytes; if (obytes) *obytes = ssh->state->p_send.bytes; } int ssh_packet_connection_af(struct ssh *ssh) { return get_sock_af(ssh->state->connection_out); } /* Sets the connection into non-blocking mode. */ void ssh_packet_set_nonblocking(struct ssh *ssh) { /* Set the socket into non-blocking mode. */ set_nonblock(ssh->state->connection_in); if (ssh->state->connection_out != ssh->state->connection_in) set_nonblock(ssh->state->connection_out); } /* Returns the socket used for reading. */ int ssh_packet_get_connection_in(struct ssh *ssh) { return ssh->state->connection_in; } /* Returns the descriptor used for writing. */ int ssh_packet_get_connection_out(struct ssh *ssh) { return ssh->state->connection_out; } /* * Returns the IP-address of the remote host as a string. The returned * string must not be freed. */ const char * ssh_remote_ipaddr(struct ssh *ssh) { int sock; /* Check whether we have cached the ipaddr. */ if (ssh->remote_ipaddr == NULL) { if (ssh_packet_connection_is_on_socket(ssh)) { sock = ssh->state->connection_in; ssh->remote_ipaddr = get_peer_ipaddr(sock); ssh->remote_port = get_peer_port(sock); ssh->local_ipaddr = get_local_ipaddr(sock); ssh->local_port = get_local_port(sock); } else { ssh->remote_ipaddr = xstrdup("UNKNOWN"); ssh->remote_port = 65535; ssh->local_ipaddr = xstrdup("UNKNOWN"); ssh->local_port = 65535; } } return ssh->remote_ipaddr; } /* Returns the port number of the remote host. */ int ssh_remote_port(struct ssh *ssh) { (void)ssh_remote_ipaddr(ssh); /* Will lookup and cache. */ return ssh->remote_port; } /* * Returns the IP-address of the local host as a string. The returned * string must not be freed. */ const char * ssh_local_ipaddr(struct ssh *ssh) { (void)ssh_remote_ipaddr(ssh); /* Will lookup and cache. */ return ssh->local_ipaddr; } /* Returns the port number of the local host. */ int ssh_local_port(struct ssh *ssh) { (void)ssh_remote_ipaddr(ssh); /* Will lookup and cache. */ return ssh->local_port; } /* Returns the routing domain of the input socket, or NULL if unavailable */ const char * ssh_packet_rdomain_in(struct ssh *ssh) { if (ssh->rdomain_in != NULL) return ssh->rdomain_in; if (!ssh_packet_connection_is_on_socket(ssh)) return NULL; ssh->rdomain_in = get_rdomain(ssh->state->connection_in); return ssh->rdomain_in; } /* Closes the connection and clears and frees internal data structures. */ static void ssh_packet_close_internal(struct ssh *ssh, int do_close) { struct session_state *state = ssh->state; u_int mode; if (!state->initialized) return; state->initialized = 0; if (do_close) { if (state->connection_in == state->connection_out) { close(state->connection_out); } else { close(state->connection_in); close(state->connection_out); } } sshbuf_free(state->input); sshbuf_free(state->output); sshbuf_free(state->outgoing_packet); sshbuf_free(state->incoming_packet); for (mode = 0; mode < MODE_MAX; mode++) { kex_free_newkeys(state->newkeys[mode]); /* current keys */ state->newkeys[mode] = NULL; ssh_clear_newkeys(ssh, mode); /* next keys */ } #ifdef WITH_ZLIB /* compression state is in shared mem, so we can only release it once */ if (do_close && state->compression_buffer) { sshbuf_free(state->compression_buffer); if (state->compression_out_started) { z_streamp stream = &state->compression_out_stream; debug("compress outgoing: " "raw data %llu, compressed %llu, factor %.2f", (unsigned long long)stream->total_in, (unsigned long long)stream->total_out, stream->total_in == 0 ? 0.0 : (double) stream->total_out / stream->total_in); if (state->compression_out_failures == 0) deflateEnd(stream); } if (state->compression_in_started) { z_streamp stream = &state->compression_in_stream; debug("compress incoming: " "raw data %llu, compressed %llu, factor %.2f", (unsigned long long)stream->total_out, (unsigned long long)stream->total_in, stream->total_out == 0 ? 0.0 : (double) stream->total_in / stream->total_out); if (state->compression_in_failures == 0) inflateEnd(stream); } } #endif /* WITH_ZLIB */ cipher_free(state->send_context); cipher_free(state->receive_context); state->send_context = state->receive_context = NULL; if (do_close) { free(ssh->local_ipaddr); ssh->local_ipaddr = NULL; free(ssh->remote_ipaddr); ssh->remote_ipaddr = NULL; free(ssh->state); ssh->state = NULL; kex_free(ssh->kex); ssh->kex = NULL; } } void ssh_packet_close(struct ssh *ssh) { ssh_packet_close_internal(ssh, 1); } void ssh_packet_clear_keys(struct ssh *ssh) { ssh_packet_close_internal(ssh, 0); } /* Sets remote side protocol flags. */ void ssh_packet_set_protocol_flags(struct ssh *ssh, u_int protocol_flags) { ssh->state->remote_protocol_flags = protocol_flags; } /* Returns the remote protocol flags set earlier by the above function. */ u_int ssh_packet_get_protocol_flags(struct ssh *ssh) { return ssh->state->remote_protocol_flags; } /* * Starts packet compression from the next packet on in both directions. * Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. */ static int ssh_packet_init_compression(struct ssh *ssh) { if (!ssh->state->compression_buffer && ((ssh->state->compression_buffer = sshbuf_new()) == NULL)) return SSH_ERR_ALLOC_FAIL; return 0; } #ifdef WITH_ZLIB static int start_compression_out(struct ssh *ssh, int level) { if (level < 1 || level > 9) return SSH_ERR_INVALID_ARGUMENT; debug("Enabling compression at level %d.", level); if (ssh->state->compression_out_started == 1) deflateEnd(&ssh->state->compression_out_stream); switch (deflateInit(&ssh->state->compression_out_stream, level)) { case Z_OK: ssh->state->compression_out_started = 1; break; case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; default: return SSH_ERR_INTERNAL_ERROR; } return 0; } static int start_compression_in(struct ssh *ssh) { if (ssh->state->compression_in_started == 1) inflateEnd(&ssh->state->compression_in_stream); switch (inflateInit(&ssh->state->compression_in_stream)) { case Z_OK: ssh->state->compression_in_started = 1; break; case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; default: return SSH_ERR_INTERNAL_ERROR; } return 0; } /* XXX remove need for separate compression buffer */ static int compress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { u_char buf[4096]; int r, status; if (ssh->state->compression_out_started != 1) return SSH_ERR_INTERNAL_ERROR; /* This case is not handled below. */ if (sshbuf_len(in) == 0) return 0; /* Input is the contents of the input buffer. */ if ((ssh->state->compression_out_stream.next_in = sshbuf_mutable_ptr(in)) == NULL) return SSH_ERR_INTERNAL_ERROR; ssh->state->compression_out_stream.avail_in = sshbuf_len(in); /* Loop compressing until deflate() returns with avail_out != 0. */ do { /* Set up fixed-size output buffer. */ ssh->state->compression_out_stream.next_out = buf; ssh->state->compression_out_stream.avail_out = sizeof(buf); /* Compress as much data into the buffer as possible. */ status = deflate(&ssh->state->compression_out_stream, Z_PARTIAL_FLUSH); switch (status) { case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; case Z_OK: /* Append compressed data to output_buffer. */ if ((r = sshbuf_put(out, buf, sizeof(buf) - ssh->state->compression_out_stream.avail_out)) != 0) return r; break; case Z_STREAM_ERROR: default: ssh->state->compression_out_failures++; return SSH_ERR_INVALID_FORMAT; } } while (ssh->state->compression_out_stream.avail_out == 0); return 0; } static int uncompress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { u_char buf[4096]; int r, status; if (ssh->state->compression_in_started != 1) return SSH_ERR_INTERNAL_ERROR; if ((ssh->state->compression_in_stream.next_in = sshbuf_mutable_ptr(in)) == NULL) return SSH_ERR_INTERNAL_ERROR; ssh->state->compression_in_stream.avail_in = sshbuf_len(in); for (;;) { /* Set up fixed-size output buffer. */ ssh->state->compression_in_stream.next_out = buf; ssh->state->compression_in_stream.avail_out = sizeof(buf); status = inflate(&ssh->state->compression_in_stream, Z_SYNC_FLUSH); switch (status) { case Z_OK: if ((r = sshbuf_put(out, buf, sizeof(buf) - ssh->state->compression_in_stream.avail_out)) != 0) return r; break; case Z_BUF_ERROR: /* * Comments in zlib.h say that we should keep calling * inflate() until we get an error. This appears to * be the error that we get. */ return 0; case Z_DATA_ERROR: return SSH_ERR_INVALID_FORMAT; case Z_MEM_ERROR: return SSH_ERR_ALLOC_FAIL; case Z_STREAM_ERROR: default: ssh->state->compression_in_failures++; return SSH_ERR_INTERNAL_ERROR; } } /* NOTREACHED */ } #else /* WITH_ZLIB */ static int start_compression_out(struct ssh *ssh, int level) { return SSH_ERR_INTERNAL_ERROR; } static int start_compression_in(struct ssh *ssh) { return SSH_ERR_INTERNAL_ERROR; } static int compress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { return SSH_ERR_INTERNAL_ERROR; } static int uncompress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) { return SSH_ERR_INTERNAL_ERROR; } #endif /* WITH_ZLIB */ void ssh_clear_newkeys(struct ssh *ssh, int mode) { if (ssh->kex && ssh->kex->newkeys[mode]) { kex_free_newkeys(ssh->kex->newkeys[mode]); ssh->kex->newkeys[mode] = NULL; } } int ssh_set_newkeys(struct ssh *ssh, int mode) { struct session_state *state = ssh->state; struct sshenc *enc; struct sshmac *mac; struct sshcomp *comp; struct sshcipher_ctx **ccp; struct packet_state *ps; u_int64_t *max_blocks; const char *wmsg; int r, crypt_type; const char *dir = mode == MODE_OUT ? "out" : "in"; debug2_f("mode %d", mode); if (mode == MODE_OUT) { ccp = &state->send_context; crypt_type = CIPHER_ENCRYPT; ps = &state->p_send; max_blocks = &state->max_blocks_out; } else { ccp = &state->receive_context; crypt_type = CIPHER_DECRYPT; ps = &state->p_read; max_blocks = &state->max_blocks_in; } if (state->newkeys[mode] != NULL) { debug_f("rekeying %s, input %llu bytes %llu blocks, " "output %llu bytes %llu blocks", dir, (unsigned long long)state->p_read.bytes, (unsigned long long)state->p_read.blocks, (unsigned long long)state->p_send.bytes, (unsigned long long)state->p_send.blocks); kex_free_newkeys(state->newkeys[mode]); state->newkeys[mode] = NULL; } /* note that both bytes and the seqnr are not reset */ ps->packets = ps->blocks = 0; /* move newkeys from kex to state */ if ((state->newkeys[mode] = ssh->kex->newkeys[mode]) == NULL) return SSH_ERR_INTERNAL_ERROR; ssh->kex->newkeys[mode] = NULL; enc = &state->newkeys[mode]->enc; mac = &state->newkeys[mode]->mac; comp = &state->newkeys[mode]->comp; if (cipher_authlen(enc->cipher) == 0) { if ((r = mac_init(mac)) != 0) return r; } mac->enabled = 1; DBG(debug_f("cipher_init: %s", dir)); cipher_free(*ccp); *ccp = NULL; if ((r = cipher_init(ccp, enc->cipher, enc->key, enc->key_len, enc->iv, enc->iv_len, crypt_type)) != 0) return r; if (!state->cipher_warning_done && (wmsg = cipher_warning_message(*ccp)) != NULL) { error("Warning: %s", wmsg); state->cipher_warning_done = 1; } /* Deleting the keys does not gain extra security */ /* explicit_bzero(enc->iv, enc->block_size); explicit_bzero(enc->key, enc->key_len); explicit_bzero(mac->key, mac->key_len); */ if ((comp->type == COMP_ZLIB || (comp->type == COMP_DELAYED && state->after_authentication)) && comp->enabled == 0) { if ((r = ssh_packet_init_compression(ssh)) < 0) return r; if (mode == MODE_OUT) { if ((r = start_compression_out(ssh, 6)) != 0) return r; } else { if ((r = start_compression_in(ssh)) != 0) return r; } comp->enabled = 1; } /* * The 2^(blocksize*2) limit is too expensive for 3DES, * so enforce a 1GB limit for small blocksizes. * See RFC4344 section 3.2. */ if (enc->block_size >= 16) *max_blocks = (u_int64_t)1 << (enc->block_size*2); else *max_blocks = ((u_int64_t)1 << 30) / enc->block_size; if (state->rekey_limit) *max_blocks = MINIMUM(*max_blocks, state->rekey_limit / enc->block_size); debug("rekey %s after %llu blocks", dir, (unsigned long long)*max_blocks); return 0; } #define MAX_PACKETS (1U<<31) static int ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) { struct session_state *state = ssh->state; u_int32_t out_blocks; /* XXX client can't cope with rekeying pre-auth */ if (!state->after_authentication) return 0; /* Haven't keyed yet or KEX in progress. */ if (ssh_packet_is_rekeying(ssh)) return 0; /* Peer can't rekey */ if (ssh->compat & SSH_BUG_NOREKEY) return 0; /* * Permit one packet in or out per rekey - this allows us to * make progress when rekey limits are very small. */ if (state->p_send.packets == 0 && state->p_read.packets == 0) return 0; /* Time-based rekeying */ if (state->rekey_interval != 0 && (int64_t)state->rekey_time + state->rekey_interval <= monotime()) return 1; /* * Always rekey when MAX_PACKETS sent in either direction * As per RFC4344 section 3.1 we do this after 2^31 packets. */ if (state->p_send.packets > MAX_PACKETS || state->p_read.packets > MAX_PACKETS) return 1; /* Rekey after (cipher-specific) maximum blocks */ out_blocks = ROUNDUP(outbound_packet_len, state->newkeys[MODE_OUT]->enc.block_size); return (state->max_blocks_out && (state->p_send.blocks + out_blocks > state->max_blocks_out)) || (state->max_blocks_in && (state->p_read.blocks > state->max_blocks_in)); } int ssh_packet_check_rekey(struct ssh *ssh) { if (!ssh_packet_need_rekeying(ssh, 0)) return 0; debug3_f("rekex triggered"); return kex_start_rekex(ssh); } /* * Delayed compression for SSH2 is enabled after authentication: * This happens on the server side after a SSH2_MSG_USERAUTH_SUCCESS is sent, * and on the client side after a SSH2_MSG_USERAUTH_SUCCESS is received. */ static int ssh_packet_enable_delayed_compress(struct ssh *ssh) { struct session_state *state = ssh->state; struct sshcomp *comp = NULL; int r, mode; /* * Remember that we are past the authentication step, so rekeying * with COMP_DELAYED will turn on compression immediately. */ state->after_authentication = 1; for (mode = 0; mode < MODE_MAX; mode++) { /* protocol error: USERAUTH_SUCCESS received before NEWKEYS */ if (state->newkeys[mode] == NULL) continue; comp = &state->newkeys[mode]->comp; if (comp && !comp->enabled && comp->type == COMP_DELAYED) { if ((r = ssh_packet_init_compression(ssh)) != 0) return r; if (mode == MODE_OUT) { if ((r = start_compression_out(ssh, 6)) != 0) return r; } else { if ((r = start_compression_in(ssh)) != 0) return r; } comp->enabled = 1; } } return 0; } /* Used to mute debug logging for noisy packet types */ int ssh_packet_log_type(u_char type) { switch (type) { + case SSH2_MSG_PING: + case SSH2_MSG_PONG: case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_WINDOW_ADJUST: return 0; default: return 1; } } /* * Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) */ int ssh_packet_send2_wrapped(struct ssh *ssh) { struct session_state *state = ssh->state; u_char type, *cp, macbuf[SSH_DIGEST_MAX_LENGTH]; u_char tmp, padlen, pad = 0; u_int authlen = 0, aadlen = 0; u_int len; struct sshenc *enc = NULL; struct sshmac *mac = NULL; struct sshcomp *comp = NULL; int r, block_size; if (state->newkeys[MODE_OUT] != NULL) { enc = &state->newkeys[MODE_OUT]->enc; mac = &state->newkeys[MODE_OUT]->mac; comp = &state->newkeys[MODE_OUT]->comp; /* disable mac for authenticated encryption */ if ((authlen = cipher_authlen(enc->cipher)) != 0) mac = NULL; } block_size = enc ? enc->block_size : 8; aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; type = (sshbuf_ptr(state->outgoing_packet))[5]; if (ssh_packet_log_type(type)) debug3("send packet: type %u", type); #ifdef PACKET_DEBUG fprintf(stderr, "plain: "); sshbuf_dump(state->outgoing_packet, stderr); #endif if (comp && comp->enabled) { len = sshbuf_len(state->outgoing_packet); /* skip header, compress only payload */ if ((r = sshbuf_consume(state->outgoing_packet, 5)) != 0) goto out; sshbuf_reset(state->compression_buffer); if ((r = compress_buffer(ssh, state->outgoing_packet, state->compression_buffer)) != 0) goto out; sshbuf_reset(state->outgoing_packet); if ((r = sshbuf_put(state->outgoing_packet, "\0\0\0\0\0", 5)) != 0 || (r = sshbuf_putb(state->outgoing_packet, state->compression_buffer)) != 0) goto out; DBG(debug("compression: raw %d compressed %zd", len, sshbuf_len(state->outgoing_packet))); } /* sizeof (packet_len + pad_len + payload) */ len = sshbuf_len(state->outgoing_packet); /* * calc size of padding, alloc space, get random data, * minimum padding is 4 bytes */ len -= aadlen; /* packet length is not encrypted for EtM modes */ padlen = block_size - (len % block_size); if (padlen < 4) padlen += block_size; if (state->extra_pad) { tmp = state->extra_pad; state->extra_pad = ROUNDUP(state->extra_pad, block_size); /* check if roundup overflowed */ if (state->extra_pad < tmp) return SSH_ERR_INVALID_ARGUMENT; tmp = (len + padlen) % state->extra_pad; /* Check whether pad calculation below will underflow */ if (tmp > state->extra_pad) return SSH_ERR_INVALID_ARGUMENT; pad = state->extra_pad - tmp; DBG(debug3_f("adding %d (len %d padlen %d extra_pad %d)", pad, len, padlen, state->extra_pad)); tmp = padlen; padlen += pad; /* Check whether padlen calculation overflowed */ if (padlen < tmp) return SSH_ERR_INVALID_ARGUMENT; /* overflow */ state->extra_pad = 0; } if ((r = sshbuf_reserve(state->outgoing_packet, padlen, &cp)) != 0) goto out; if (enc && !cipher_ctx_is_plaintext(state->send_context)) { /* random padding */ arc4random_buf(cp, padlen); } else { /* clear padding */ explicit_bzero(cp, padlen); } /* sizeof (packet_len + pad_len + payload + padding) */ len = sshbuf_len(state->outgoing_packet); cp = sshbuf_mutable_ptr(state->outgoing_packet); if (cp == NULL) { r = SSH_ERR_INTERNAL_ERROR; goto out; } /* packet_length includes payload, padding and padding length field */ POKE_U32(cp, len - 4); cp[4] = padlen; DBG(debug("send: len %d (includes padlen %d, aadlen %d)", len, padlen, aadlen)); /* compute MAC over seqnr and packet(length fields, payload, padding) */ if (mac && mac->enabled && !mac->etm) { if ((r = mac_compute(mac, state->p_send.seqnr, sshbuf_ptr(state->outgoing_packet), len, macbuf, sizeof(macbuf))) != 0) goto out; DBG(debug("done calc MAC out #%d", state->p_send.seqnr)); } /* encrypt packet and append to output buffer. */ if ((r = sshbuf_reserve(state->output, sshbuf_len(state->outgoing_packet) + authlen, &cp)) != 0) goto out; if ((r = cipher_crypt(state->send_context, state->p_send.seqnr, cp, sshbuf_ptr(state->outgoing_packet), len - aadlen, aadlen, authlen)) != 0) goto out; /* append unencrypted MAC */ if (mac && mac->enabled) { if (mac->etm) { /* EtM: compute mac over aadlen + cipher text */ if ((r = mac_compute(mac, state->p_send.seqnr, cp, len, macbuf, sizeof(macbuf))) != 0) goto out; DBG(debug("done calc MAC(EtM) out #%d", state->p_send.seqnr)); } if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) goto out; } #ifdef PACKET_DEBUG fprintf(stderr, "encrypted: "); sshbuf_dump(state->output, stderr); #endif /* increment sequence number for outgoing packets */ if (++state->p_send.seqnr == 0) logit("outgoing seqnr wraps around"); if (++state->p_send.packets == 0) if (!(ssh->compat & SSH_BUG_NOREKEY)) return SSH_ERR_NEED_REKEY; state->p_send.blocks += len / block_size; state->p_send.bytes += len; sshbuf_reset(state->outgoing_packet); if (type == SSH2_MSG_NEWKEYS) r = ssh_set_newkeys(ssh, MODE_OUT); else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side) r = ssh_packet_enable_delayed_compress(ssh); else r = 0; out: return r; } /* returns non-zero if the specified packet type is usec by KEX */ static int ssh_packet_type_is_kex(u_char type) { return type >= SSH2_MSG_TRANSPORT_MIN && type <= SSH2_MSG_TRANSPORT_MAX && type != SSH2_MSG_SERVICE_REQUEST && type != SSH2_MSG_SERVICE_ACCEPT && type != SSH2_MSG_EXT_INFO; } int ssh_packet_send2(struct ssh *ssh) { struct session_state *state = ssh->state; struct packet *p; u_char type; int r, need_rekey; if (sshbuf_len(state->outgoing_packet) < 6) return SSH_ERR_INTERNAL_ERROR; type = sshbuf_ptr(state->outgoing_packet)[5]; need_rekey = !ssh_packet_type_is_kex(type) && ssh_packet_need_rekeying(ssh, sshbuf_len(state->outgoing_packet)); /* * During rekeying we can only send key exchange messages. * Queue everything else. */ if ((need_rekey || state->rekeying) && !ssh_packet_type_is_kex(type)) { if (need_rekey) debug3_f("rekex triggered"); debug("enqueue packet: %u", type); p = calloc(1, sizeof(*p)); if (p == NULL) return SSH_ERR_ALLOC_FAIL; p->type = type; p->payload = state->outgoing_packet; TAILQ_INSERT_TAIL(&state->outgoing, p, next); state->outgoing_packet = sshbuf_new(); if (state->outgoing_packet == NULL) return SSH_ERR_ALLOC_FAIL; if (need_rekey) { /* * This packet triggered a rekey, so send the * KEXINIT now. * NB. reenters this function via kex_start_rekex(). */ return kex_start_rekex(ssh); } return 0; } /* rekeying starts with sending KEXINIT */ if (type == SSH2_MSG_KEXINIT) state->rekeying = 1; if ((r = ssh_packet_send2_wrapped(ssh)) != 0) return r; /* after a NEWKEYS message we can send the complete queue */ if (type == SSH2_MSG_NEWKEYS) { state->rekeying = 0; state->rekey_time = monotime(); while ((p = TAILQ_FIRST(&state->outgoing))) { type = p->type; /* * If this packet triggers a rekex, then skip the * remaining packets in the queue for now. * NB. re-enters this function via kex_start_rekex. */ if (ssh_packet_need_rekeying(ssh, sshbuf_len(p->payload))) { debug3_f("queued packet triggered rekex"); return kex_start_rekex(ssh); } debug("dequeue packet: %u", type); sshbuf_free(state->outgoing_packet); state->outgoing_packet = p->payload; TAILQ_REMOVE(&state->outgoing, p, next); memset(p, 0, sizeof(*p)); free(p); if ((r = ssh_packet_send2_wrapped(ssh)) != 0) return r; } } return 0; } /* * Waits until a packet has been received, and returns its type. Note that * no other data is processed until this returns, so this function should not * be used during the interactive session. */ int ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; int len, r, ms_remain = 0; struct pollfd pfd; char buf[8192]; struct timeval start; struct timespec timespec, *timespecp = NULL; DBG(debug("packet_read()")); /* * Since we are blocking, ensure that all written packets have * been sent. */ if ((r = ssh_packet_write_wait(ssh)) != 0) goto out; /* Stay in the loop until we have received a complete packet. */ for (;;) { /* Try to read a packet from the buffer. */ r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p); if (r != 0) break; /* If we got a packet, return it. */ if (*typep != SSH_MSG_NONE) break; /* * Otherwise, wait for some data to arrive, add it to the * buffer, and try again. */ pfd.fd = state->connection_in; pfd.events = POLLIN; if (state->packet_timeout_ms > 0) { ms_remain = state->packet_timeout_ms; timespecp = ×pec; } /* Wait for some data to arrive. */ for (;;) { if (state->packet_timeout_ms > 0) { ms_to_timespec(×pec, ms_remain); monotime_tv(&start); } if ((r = ppoll(&pfd, 1, timespecp, NULL)) >= 0) break; if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) { r = SSH_ERR_SYSTEM_ERROR; goto out; } if (state->packet_timeout_ms <= 0) continue; ms_subtract_diff(&start, &ms_remain); if (ms_remain <= 0) { r = 0; break; } } if (r == 0) { r = SSH_ERR_CONN_TIMEOUT; goto out; } /* Read data from the socket. */ len = read(state->connection_in, buf, sizeof(buf)); if (len == 0) { r = SSH_ERR_CONN_CLOSED; goto out; } if (len == -1) { r = SSH_ERR_SYSTEM_ERROR; goto out; } /* Append it to the buffer. */ if ((r = ssh_packet_process_incoming(ssh, buf, len)) != 0) goto out; } out: return r; } int ssh_packet_read(struct ssh *ssh) { u_char type; int r; if ((r = ssh_packet_read_seqnr(ssh, &type, NULL)) != 0) fatal_fr(r, "read"); return type; } /* * Waits until a packet has been received, verifies that its type matches * that given, and gives a fatal error and exits if there is a mismatch. */ int ssh_packet_read_expect(struct ssh *ssh, u_int expected_type) { int r; u_char type; if ((r = ssh_packet_read_seqnr(ssh, &type, NULL)) != 0) return r; if (type != expected_type) { if ((r = sshpkt_disconnect(ssh, "Protocol error: expected packet type %d, got %d", expected_type, type)) != 0) return r; return SSH_ERR_PROTOCOL_ERROR; } return 0; } static int ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; const u_char *cp; size_t need; int r; if (ssh->kex) return SSH_ERR_INTERNAL_ERROR; *typep = SSH_MSG_NONE; cp = sshbuf_ptr(state->input); if (state->packlen == 0) { if (sshbuf_len(state->input) < 4 + 1) return 0; /* packet is incomplete */ state->packlen = PEEK_U32(cp); if (state->packlen < 4 + 1 || state->packlen > PACKET_MAX_SIZE) return SSH_ERR_MESSAGE_INCOMPLETE; } need = state->packlen + 4; if (sshbuf_len(state->input) < need) return 0; /* packet is incomplete */ sshbuf_reset(state->incoming_packet); if ((r = sshbuf_put(state->incoming_packet, cp + 4, state->packlen)) != 0 || (r = sshbuf_consume(state->input, need)) != 0 || (r = sshbuf_get_u8(state->incoming_packet, NULL)) != 0 || (r = sshbuf_get_u8(state->incoming_packet, typep)) != 0) return r; if (ssh_packet_log_type(*typep)) debug3_f("type %u", *typep); /* sshbuf_dump(state->incoming_packet, stderr); */ /* reset for next packet */ state->packlen = 0; return r; } int ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; u_int padlen, need; u_char *cp; u_int maclen, aadlen = 0, authlen = 0, block_size; struct sshenc *enc = NULL; struct sshmac *mac = NULL; struct sshcomp *comp = NULL; int r; if (state->mux) return ssh_packet_read_poll2_mux(ssh, typep, seqnr_p); *typep = SSH_MSG_NONE; if (state->packet_discard) return 0; if (state->newkeys[MODE_IN] != NULL) { enc = &state->newkeys[MODE_IN]->enc; mac = &state->newkeys[MODE_IN]->mac; comp = &state->newkeys[MODE_IN]->comp; /* disable mac for authenticated encryption */ if ((authlen = cipher_authlen(enc->cipher)) != 0) mac = NULL; } maclen = mac && mac->enabled ? mac->mac_len : 0; block_size = enc ? enc->block_size : 8; aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; if (aadlen && state->packlen == 0) { if (cipher_get_length(state->receive_context, &state->packlen, state->p_read.seqnr, sshbuf_ptr(state->input), sshbuf_len(state->input)) != 0) return 0; if (state->packlen < 1 + 4 || state->packlen > PACKET_MAX_SIZE) { #ifdef PACKET_DEBUG sshbuf_dump(state->input, stderr); #endif logit("Bad packet length %u.", state->packlen); if ((r = sshpkt_disconnect(ssh, "Packet corrupt")) != 0) return r; return SSH_ERR_CONN_CORRUPT; } sshbuf_reset(state->incoming_packet); } else if (state->packlen == 0) { /* * check if input size is less than the cipher block size, * decrypt first block and extract length of incoming packet */ if (sshbuf_len(state->input) < block_size) return 0; sshbuf_reset(state->incoming_packet); if ((r = sshbuf_reserve(state->incoming_packet, block_size, &cp)) != 0) goto out; if ((r = cipher_crypt(state->receive_context, state->p_send.seqnr, cp, sshbuf_ptr(state->input), block_size, 0, 0)) != 0) goto out; state->packlen = PEEK_U32(sshbuf_ptr(state->incoming_packet)); if (state->packlen < 1 + 4 || state->packlen > PACKET_MAX_SIZE) { #ifdef PACKET_DEBUG fprintf(stderr, "input: \n"); sshbuf_dump(state->input, stderr); fprintf(stderr, "incoming_packet: \n"); sshbuf_dump(state->incoming_packet, stderr); #endif logit("Bad packet length %u.", state->packlen); return ssh_packet_start_discard(ssh, enc, mac, 0, PACKET_MAX_SIZE); } if ((r = sshbuf_consume(state->input, block_size)) != 0) goto out; } DBG(debug("input: packet len %u", state->packlen+4)); if (aadlen) { /* only the payload is encrypted */ need = state->packlen; } else { /* * the payload size and the payload are encrypted, but we * have a partial packet of block_size bytes */ need = 4 + state->packlen - block_size; } DBG(debug("partial packet: block %d, need %d, maclen %d, authlen %d," " aadlen %d", block_size, need, maclen, authlen, aadlen)); if (need % block_size != 0) { logit("padding error: need %d block %d mod %d", need, block_size, need % block_size); return ssh_packet_start_discard(ssh, enc, mac, 0, PACKET_MAX_SIZE - block_size); } /* * check if the entire packet has been received and * decrypt into incoming_packet: * 'aadlen' bytes are unencrypted, but authenticated. * 'need' bytes are encrypted, followed by either * 'authlen' bytes of authentication tag or * 'maclen' bytes of message authentication code. */ if (sshbuf_len(state->input) < aadlen + need + authlen + maclen) return 0; /* packet is incomplete */ #ifdef PACKET_DEBUG fprintf(stderr, "read_poll enc/full: "); sshbuf_dump(state->input, stderr); #endif /* EtM: check mac over encrypted input */ if (mac && mac->enabled && mac->etm) { if ((r = mac_check(mac, state->p_read.seqnr, sshbuf_ptr(state->input), aadlen + need, sshbuf_ptr(state->input) + aadlen + need + authlen, maclen)) != 0) { if (r == SSH_ERR_MAC_INVALID) logit("Corrupted MAC on input."); goto out; } } if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, &cp)) != 0) goto out; if ((r = cipher_crypt(state->receive_context, state->p_read.seqnr, cp, sshbuf_ptr(state->input), need, aadlen, authlen)) != 0) goto out; if ((r = sshbuf_consume(state->input, aadlen + need + authlen)) != 0) goto out; if (mac && mac->enabled) { /* Not EtM: check MAC over cleartext */ if (!mac->etm && (r = mac_check(mac, state->p_read.seqnr, sshbuf_ptr(state->incoming_packet), sshbuf_len(state->incoming_packet), sshbuf_ptr(state->input), maclen)) != 0) { if (r != SSH_ERR_MAC_INVALID) goto out; logit("Corrupted MAC on input."); if (need + block_size > PACKET_MAX_SIZE) return SSH_ERR_INTERNAL_ERROR; return ssh_packet_start_discard(ssh, enc, mac, sshbuf_len(state->incoming_packet), PACKET_MAX_SIZE - need - block_size); } /* Remove MAC from input buffer */ DBG(debug("MAC #%d ok", state->p_read.seqnr)); if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0) goto out; } if (seqnr_p != NULL) *seqnr_p = state->p_read.seqnr; if (++state->p_read.seqnr == 0) logit("incoming seqnr wraps around"); if (++state->p_read.packets == 0) if (!(ssh->compat & SSH_BUG_NOREKEY)) return SSH_ERR_NEED_REKEY; state->p_read.blocks += (state->packlen + 4) / block_size; state->p_read.bytes += state->packlen + 4; /* get padlen */ padlen = sshbuf_ptr(state->incoming_packet)[4]; DBG(debug("input: padlen %d", padlen)); if (padlen < 4) { if ((r = sshpkt_disconnect(ssh, "Corrupted padlen %d on input.", padlen)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) return r; return SSH_ERR_CONN_CORRUPT; } /* skip packet size + padlen, discard padding */ if ((r = sshbuf_consume(state->incoming_packet, 4 + 1)) != 0 || ((r = sshbuf_consume_end(state->incoming_packet, padlen)) != 0)) goto out; DBG(debug("input: len before de-compress %zd", sshbuf_len(state->incoming_packet))); if (comp && comp->enabled) { sshbuf_reset(state->compression_buffer); if ((r = uncompress_buffer(ssh, state->incoming_packet, state->compression_buffer)) != 0) goto out; sshbuf_reset(state->incoming_packet); if ((r = sshbuf_putb(state->incoming_packet, state->compression_buffer)) != 0) goto out; DBG(debug("input: len after de-compress %zd", sshbuf_len(state->incoming_packet))); } /* * get packet type, implies consume. * return length of payload (without type field) */ if ((r = sshbuf_get_u8(state->incoming_packet, typep)) != 0) goto out; if (ssh_packet_log_type(*typep)) debug3("receive packet: type %u", *typep); - if (*typep < SSH2_MSG_MIN || *typep >= SSH2_MSG_LOCAL_MIN) { + if (*typep < SSH2_MSG_MIN) { if ((r = sshpkt_disconnect(ssh, "Invalid ssh2 packet type: %d", *typep)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) return r; return SSH_ERR_PROTOCOL_ERROR; } if (state->hook_in != NULL && (r = state->hook_in(ssh, state->incoming_packet, typep, state->hook_in_ctx)) != 0) return r; if (*typep == SSH2_MSG_USERAUTH_SUCCESS && !state->server_side) r = ssh_packet_enable_delayed_compress(ssh); else r = 0; #ifdef PACKET_DEBUG fprintf(stderr, "read/plain[%d]:\r\n", *typep); sshbuf_dump(state->incoming_packet, stderr); #endif /* reset for next packet */ state->packlen = 0; if ((r = ssh_packet_check_rekey(ssh)) != 0) return r; out: return r; } int ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) { struct session_state *state = ssh->state; u_int reason, seqnr; int r; u_char *msg; + const u_char *d; + size_t len; for (;;) { msg = NULL; r = ssh_packet_read_poll2(ssh, typep, seqnr_p); if (r != 0) return r; if (*typep) { state->keep_alive_timeouts = 0; DBG(debug("received packet type %d", *typep)); } switch (*typep) { case SSH2_MSG_IGNORE: debug3("Received SSH2_MSG_IGNORE"); break; case SSH2_MSG_DEBUG: if ((r = sshpkt_get_u8(ssh, NULL)) != 0 || (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || (r = sshpkt_get_string(ssh, NULL, NULL)) != 0) { free(msg); return r; } debug("Remote: %.900s", msg); free(msg); break; case SSH2_MSG_DISCONNECT: if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || (r = sshpkt_get_string(ssh, &msg, NULL)) != 0) return r; /* Ignore normal client exit notifications */ do_log2(ssh->state->server_side && reason == SSH2_DISCONNECT_BY_APPLICATION ? SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, "Received disconnect from %s port %d:" "%u: %.400s", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), reason, msg); free(msg); return SSH_ERR_DISCONNECTED; case SSH2_MSG_UNIMPLEMENTED: if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0) return r; debug("Received SSH2_MSG_UNIMPLEMENTED for %u", seqnr); break; + case SSH2_MSG_PING: + if ((r = sshpkt_get_string_direct(ssh, &d, &len)) != 0) + return r; + DBG(debug("Received SSH2_MSG_PING len %zu", len)); + if ((r = sshpkt_start(ssh, SSH2_MSG_PONG)) != 0 || + (r = sshpkt_put_string(ssh, d, len)) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; + break; + case SSH2_MSG_PONG: + if ((r = sshpkt_get_string_direct(ssh, + NULL, &len)) != 0) + return r; + DBG(debug("Received SSH2_MSG_PONG len %zu", len)); + break; default: return 0; } } } /* * Buffers the supplied input data. This is intended to be used together * with packet_read_poll(). */ int ssh_packet_process_incoming(struct ssh *ssh, const char *buf, u_int len) { struct session_state *state = ssh->state; int r; if (state->packet_discard) { state->keep_alive_timeouts = 0; /* ?? */ if (len >= state->packet_discard) { if ((r = ssh_packet_stop_discard(ssh)) != 0) return r; } state->packet_discard -= len; return 0; } if ((r = sshbuf_put(state->input, buf, len)) != 0) return r; return 0; } /* Reads and buffers data from the specified fd */ int ssh_packet_process_read(struct ssh *ssh, int fd) { struct session_state *state = ssh->state; int r; size_t rlen; if ((r = sshbuf_read(fd, state->input, PACKET_MAX_SIZE, &rlen)) != 0) return r; if (state->packet_discard) { if ((r = sshbuf_consume_end(state->input, rlen)) != 0) return r; state->keep_alive_timeouts = 0; /* ?? */ if (rlen >= state->packet_discard) { if ((r = ssh_packet_stop_discard(ssh)) != 0) return r; } state->packet_discard -= rlen; return 0; } return 0; } int ssh_packet_remaining(struct ssh *ssh) { return sshbuf_len(ssh->state->incoming_packet); } /* * Sends a diagnostic message from the server to the client. This message * can be sent at any time (but not while constructing another message). The * message is printed immediately, but only if the client is being executed * in verbose mode. These messages are primarily intended to ease debugging * authentication problems. The length of the formatted message must not * exceed 1024 bytes. This will automatically call ssh_packet_write_wait. */ void ssh_packet_send_debug(struct ssh *ssh, const char *fmt,...) { char buf[1024]; va_list args; int r; if ((ssh->compat & SSH_BUG_DEBUG)) return; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); debug3("sending debug message: %s", buf); if ((r = sshpkt_start(ssh, SSH2_MSG_DEBUG)) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || /* always display */ (r = sshpkt_put_cstring(ssh, buf)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send DEBUG"); } void sshpkt_fmt_connection_id(struct ssh *ssh, char *s, size_t l) { snprintf(s, l, "%.200s%s%s port %d", ssh->log_preamble ? ssh->log_preamble : "", ssh->log_preamble ? " " : "", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); } /* * Pretty-print connection-terminating errors and exit. */ static void sshpkt_vfatal(struct ssh *ssh, int r, const char *fmt, va_list ap) { char *tag = NULL, remote_id[512]; int oerrno = errno; sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); switch (r) { case SSH_ERR_CONN_CLOSED: ssh_packet_clear_keys(ssh); logdie("Connection closed by %s", remote_id); case SSH_ERR_CONN_TIMEOUT: ssh_packet_clear_keys(ssh); logdie("Connection %s %s timed out", ssh->state->server_side ? "from" : "to", remote_id); case SSH_ERR_DISCONNECTED: ssh_packet_clear_keys(ssh); logdie("Disconnected from %s", remote_id); case SSH_ERR_SYSTEM_ERROR: if (errno == ECONNRESET) { ssh_packet_clear_keys(ssh); logdie("Connection reset by %s", remote_id); } /* FALLTHROUGH */ case SSH_ERR_NO_CIPHER_ALG_MATCH: case SSH_ERR_NO_MAC_ALG_MATCH: case SSH_ERR_NO_COMPRESS_ALG_MATCH: case SSH_ERR_NO_KEX_ALG_MATCH: case SSH_ERR_NO_HOSTKEY_ALG_MATCH: if (ssh->kex && ssh->kex->failed_choice) { BLACKLIST_NOTIFY(ssh, BLACKLIST_AUTH_FAIL, "ssh"); ssh_packet_clear_keys(ssh); errno = oerrno; logdie("Unable to negotiate with %s: %s. " "Their offer: %s", remote_id, ssh_err(r), ssh->kex->failed_choice); } /* FALLTHROUGH */ default: if (vasprintf(&tag, fmt, ap) == -1) { ssh_packet_clear_keys(ssh); logdie_f("could not allocate failure message"); } ssh_packet_clear_keys(ssh); errno = oerrno; logdie_r(r, "%s%sConnection %s %s", tag != NULL ? tag : "", tag != NULL ? ": " : "", ssh->state->server_side ? "from" : "to", remote_id); } } void sshpkt_fatal(struct ssh *ssh, int r, const char *fmt, ...) { va_list ap; va_start(ap, fmt); sshpkt_vfatal(ssh, r, fmt, ap); /* NOTREACHED */ va_end(ap); logdie_f("should have exited"); } /* * Logs the error plus constructs and sends a disconnect packet, closes the * connection, and exits. This function never returns. The error message * should not contain a newline. The length of the formatted message must * not exceed 1024 bytes. */ void ssh_packet_disconnect(struct ssh *ssh, const char *fmt,...) { char buf[1024], remote_id[512]; va_list args; static int disconnecting = 0; int r; if (disconnecting) /* Guard against recursive invocations. */ fatal("packet_disconnect called recursively."); disconnecting = 1; /* * Format the message. Note that the caller must make sure the * message is of limited size. */ sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); /* Display the error locally */ logit("Disconnecting %s: %.100s", remote_id, buf); /* * Send the disconnect message to the other side, and wait * for it to get sent. */ if ((r = sshpkt_disconnect(ssh, "%s", buf)) != 0) sshpkt_fatal(ssh, r, "%s", __func__); if ((r = ssh_packet_write_wait(ssh)) != 0) sshpkt_fatal(ssh, r, "%s", __func__); /* Close the connection. */ ssh_packet_close(ssh); cleanup_exit(255); } /* * Checks if there is any buffered output, and tries to write some of * the output. */ int ssh_packet_write_poll(struct ssh *ssh) { struct session_state *state = ssh->state; int len = sshbuf_len(state->output); int r; if (len > 0) { len = write(state->connection_out, sshbuf_ptr(state->output), len); if (len == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return 0; return SSH_ERR_SYSTEM_ERROR; } if (len == 0) return SSH_ERR_CONN_CLOSED; if ((r = sshbuf_consume(state->output, len)) != 0) return r; } return 0; } /* * Calls packet_write_poll repeatedly until all pending output data has been * written. */ int ssh_packet_write_wait(struct ssh *ssh) { int ret, r, ms_remain = 0; struct timeval start; struct timespec timespec, *timespecp = NULL; struct session_state *state = ssh->state; struct pollfd pfd; if ((r = ssh_packet_write_poll(ssh)) != 0) return r; while (ssh_packet_have_data_to_write(ssh)) { pfd.fd = state->connection_out; pfd.events = POLLOUT; if (state->packet_timeout_ms > 0) { ms_remain = state->packet_timeout_ms; timespecp = ×pec; } for (;;) { if (state->packet_timeout_ms > 0) { ms_to_timespec(×pec, ms_remain); monotime_tv(&start); } if ((ret = ppoll(&pfd, 1, timespecp, NULL)) >= 0) break; if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) break; if (state->packet_timeout_ms <= 0) continue; ms_subtract_diff(&start, &ms_remain); if (ms_remain <= 0) { ret = 0; break; } } if (ret == 0) return SSH_ERR_CONN_TIMEOUT; if ((r = ssh_packet_write_poll(ssh)) != 0) return r; } return 0; } /* Returns true if there is buffered data to write to the connection. */ int ssh_packet_have_data_to_write(struct ssh *ssh) { return sshbuf_len(ssh->state->output) != 0; } /* Returns true if there is not too much data to write to the connection. */ int ssh_packet_not_very_much_data_to_write(struct ssh *ssh) { if (ssh->state->interactive_mode) return sshbuf_len(ssh->state->output) < 16384; else return sshbuf_len(ssh->state->output) < 128 * 1024; } +/* + * returns true when there are at most a few keystrokes of data to write + * and the connection is in interactive mode. + */ + +int +ssh_packet_interactive_data_to_write(struct ssh *ssh) +{ + return ssh->state->interactive_mode && + sshbuf_len(ssh->state->output) < 256; +} + void ssh_packet_set_tos(struct ssh *ssh, int tos) { if (!ssh_packet_connection_is_on_socket(ssh) || tos == INT_MAX) return; set_sock_tos(ssh->state->connection_in, tos); } /* Informs that the current session is interactive. Sets IP flags for that. */ void ssh_packet_set_interactive(struct ssh *ssh, int interactive, int qos_interactive, int qos_bulk) { struct session_state *state = ssh->state; if (state->set_interactive_called) return; state->set_interactive_called = 1; /* Record that we are in interactive mode. */ state->interactive_mode = interactive; /* Only set socket options if using a socket. */ if (!ssh_packet_connection_is_on_socket(ssh)) return; set_nodelay(state->connection_in); ssh_packet_set_tos(ssh, interactive ? qos_interactive : qos_bulk); } /* Returns true if the current connection is interactive. */ int ssh_packet_is_interactive(struct ssh *ssh) { return ssh->state->interactive_mode; } int ssh_packet_set_maxsize(struct ssh *ssh, u_int s) { struct session_state *state = ssh->state; if (state->set_maxsize_called) { logit_f("called twice: old %d new %d", state->max_packet_size, s); return -1; } if (s < 4 * 1024 || s > 1024 * 1024) { logit_f("bad size %d", s); return -1; } state->set_maxsize_called = 1; debug_f("setting to %d", s); state->max_packet_size = s; return s; } int ssh_packet_inc_alive_timeouts(struct ssh *ssh) { return ++ssh->state->keep_alive_timeouts; } void ssh_packet_set_alive_timeouts(struct ssh *ssh, int ka) { ssh->state->keep_alive_timeouts = ka; } u_int ssh_packet_get_maxsize(struct ssh *ssh) { return ssh->state->max_packet_size; } void ssh_packet_set_rekey_limits(struct ssh *ssh, u_int64_t bytes, u_int32_t seconds) { debug3("rekey after %llu bytes, %u seconds", (unsigned long long)bytes, (unsigned int)seconds); ssh->state->rekey_limit = bytes; ssh->state->rekey_interval = seconds; } time_t ssh_packet_get_rekey_timeout(struct ssh *ssh) { time_t seconds; seconds = ssh->state->rekey_time + ssh->state->rekey_interval - monotime(); return (seconds <= 0 ? 1 : seconds); } void ssh_packet_set_server(struct ssh *ssh) { ssh->state->server_side = 1; ssh->kex->server = 1; /* XXX unify? */ } void ssh_packet_set_authenticated(struct ssh *ssh) { ssh->state->after_authentication = 1; } void * ssh_packet_get_input(struct ssh *ssh) { return (void *)ssh->state->input; } void * ssh_packet_get_output(struct ssh *ssh) { return (void *)ssh->state->output; } /* Reset after_authentication and reset compression in post-auth privsep */ static int ssh_packet_set_postauth(struct ssh *ssh) { int r; debug_f("called"); /* This was set in net child, but is not visible in user child */ ssh->state->after_authentication = 1; ssh->state->rekeying = 0; if ((r = ssh_packet_enable_delayed_compress(ssh)) != 0) return r; return 0; } /* Packet state (de-)serialization for privsep */ /* turn kex into a blob for packet state serialization */ static int kex_to_blob(struct sshbuf *m, struct kex *kex) { int r; if ((r = sshbuf_put_u32(m, kex->we_need)) != 0 || (r = sshbuf_put_cstring(m, kex->hostkey_alg)) != 0 || (r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 || (r = sshbuf_put_u32(m, kex->hostkey_nid)) != 0 || (r = sshbuf_put_u32(m, kex->kex_type)) != 0 || (r = sshbuf_put_stringb(m, kex->my)) != 0 || (r = sshbuf_put_stringb(m, kex->peer)) != 0 || (r = sshbuf_put_stringb(m, kex->client_version)) != 0 || (r = sshbuf_put_stringb(m, kex->server_version)) != 0 || (r = sshbuf_put_stringb(m, kex->session_id)) != 0 || (r = sshbuf_put_u32(m, kex->flags)) != 0) return r; return 0; } /* turn key exchange results into a blob for packet state serialization */ static int newkeys_to_blob(struct sshbuf *m, struct ssh *ssh, int mode) { struct sshbuf *b; struct sshcipher_ctx *cc; struct sshcomp *comp; struct sshenc *enc; struct sshmac *mac; struct newkeys *newkey; int r; if ((newkey = ssh->state->newkeys[mode]) == NULL) return SSH_ERR_INTERNAL_ERROR; enc = &newkey->enc; mac = &newkey->mac; comp = &newkey->comp; cc = (mode == MODE_OUT) ? ssh->state->send_context : ssh->state->receive_context; if ((r = cipher_get_keyiv(cc, enc->iv, enc->iv_len)) != 0) return r; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_put_cstring(b, enc->name)) != 0 || (r = sshbuf_put_u32(b, enc->enabled)) != 0 || (r = sshbuf_put_u32(b, enc->block_size)) != 0 || (r = sshbuf_put_string(b, enc->key, enc->key_len)) != 0 || (r = sshbuf_put_string(b, enc->iv, enc->iv_len)) != 0) goto out; if (cipher_authlen(enc->cipher) == 0) { if ((r = sshbuf_put_cstring(b, mac->name)) != 0 || (r = sshbuf_put_u32(b, mac->enabled)) != 0 || (r = sshbuf_put_string(b, mac->key, mac->key_len)) != 0) goto out; } if ((r = sshbuf_put_u32(b, comp->type)) != 0 || (r = sshbuf_put_cstring(b, comp->name)) != 0) goto out; r = sshbuf_put_stringb(m, b); out: sshbuf_free(b); return r; } /* serialize packet state into a blob */ int ssh_packet_get_state(struct ssh *ssh, struct sshbuf *m) { struct session_state *state = ssh->state; int r; if ((r = kex_to_blob(m, ssh->kex)) != 0 || (r = newkeys_to_blob(m, ssh, MODE_OUT)) != 0 || (r = newkeys_to_blob(m, ssh, MODE_IN)) != 0 || (r = sshbuf_put_u64(m, state->rekey_limit)) != 0 || (r = sshbuf_put_u32(m, state->rekey_interval)) != 0 || (r = sshbuf_put_u32(m, state->p_send.seqnr)) != 0 || (r = sshbuf_put_u64(m, state->p_send.blocks)) != 0 || (r = sshbuf_put_u32(m, state->p_send.packets)) != 0 || (r = sshbuf_put_u64(m, state->p_send.bytes)) != 0 || (r = sshbuf_put_u32(m, state->p_read.seqnr)) != 0 || (r = sshbuf_put_u64(m, state->p_read.blocks)) != 0 || (r = sshbuf_put_u32(m, state->p_read.packets)) != 0 || (r = sshbuf_put_u64(m, state->p_read.bytes)) != 0 || (r = sshbuf_put_stringb(m, state->input)) != 0 || (r = sshbuf_put_stringb(m, state->output)) != 0) return r; return 0; } /* restore key exchange results from blob for packet state de-serialization */ static int newkeys_from_blob(struct sshbuf *m, struct ssh *ssh, int mode) { struct sshbuf *b = NULL; struct sshcomp *comp; struct sshenc *enc; struct sshmac *mac; struct newkeys *newkey = NULL; size_t keylen, ivlen, maclen; int r; if ((newkey = calloc(1, sizeof(*newkey))) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_froms(m, &b)) != 0) goto out; #ifdef DEBUG_PK sshbuf_dump(b, stderr); #endif enc = &newkey->enc; mac = &newkey->mac; comp = &newkey->comp; if ((r = sshbuf_get_cstring(b, &enc->name, NULL)) != 0 || (r = sshbuf_get_u32(b, (u_int *)&enc->enabled)) != 0 || (r = sshbuf_get_u32(b, &enc->block_size)) != 0 || (r = sshbuf_get_string(b, &enc->key, &keylen)) != 0 || (r = sshbuf_get_string(b, &enc->iv, &ivlen)) != 0) goto out; if ((enc->cipher = cipher_by_name(enc->name)) == NULL) { r = SSH_ERR_INVALID_FORMAT; goto out; } if (cipher_authlen(enc->cipher) == 0) { if ((r = sshbuf_get_cstring(b, &mac->name, NULL)) != 0) goto out; if ((r = mac_setup(mac, mac->name)) != 0) goto out; if ((r = sshbuf_get_u32(b, (u_int *)&mac->enabled)) != 0 || (r = sshbuf_get_string(b, &mac->key, &maclen)) != 0) goto out; if (maclen > mac->key_len) { r = SSH_ERR_INVALID_FORMAT; goto out; } mac->key_len = maclen; } if ((r = sshbuf_get_u32(b, &comp->type)) != 0 || (r = sshbuf_get_cstring(b, &comp->name, NULL)) != 0) goto out; if (sshbuf_len(b) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } enc->key_len = keylen; enc->iv_len = ivlen; ssh->kex->newkeys[mode] = newkey; newkey = NULL; r = 0; out: free(newkey); sshbuf_free(b); return r; } /* restore kex from blob for packet state de-serialization */ static int kex_from_blob(struct sshbuf *m, struct kex **kexp) { struct kex *kex; int r; if ((kex = kex_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_u32(m, &kex->we_need)) != 0 || (r = sshbuf_get_cstring(m, &kex->hostkey_alg, NULL)) != 0 || (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 || (r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_nid)) != 0 || (r = sshbuf_get_u32(m, &kex->kex_type)) != 0 || (r = sshbuf_get_stringb(m, kex->my)) != 0 || (r = sshbuf_get_stringb(m, kex->peer)) != 0 || (r = sshbuf_get_stringb(m, kex->client_version)) != 0 || (r = sshbuf_get_stringb(m, kex->server_version)) != 0 || (r = sshbuf_get_stringb(m, kex->session_id)) != 0 || (r = sshbuf_get_u32(m, &kex->flags)) != 0) goto out; kex->server = 1; kex->done = 1; r = 0; out: if (r != 0 || kexp == NULL) { kex_free(kex); if (kexp != NULL) *kexp = NULL; } else { kex_free(*kexp); *kexp = kex; } return r; } /* * Restore packet state from content of blob 'm' (de-serialization). * Note that 'm' will be partially consumed on parsing or any other errors. */ int ssh_packet_set_state(struct ssh *ssh, struct sshbuf *m) { struct session_state *state = ssh->state; const u_char *input, *output; size_t ilen, olen; int r; if ((r = kex_from_blob(m, &ssh->kex)) != 0 || (r = newkeys_from_blob(m, ssh, MODE_OUT)) != 0 || (r = newkeys_from_blob(m, ssh, MODE_IN)) != 0 || (r = sshbuf_get_u64(m, &state->rekey_limit)) != 0 || (r = sshbuf_get_u32(m, &state->rekey_interval)) != 0 || (r = sshbuf_get_u32(m, &state->p_send.seqnr)) != 0 || (r = sshbuf_get_u64(m, &state->p_send.blocks)) != 0 || (r = sshbuf_get_u32(m, &state->p_send.packets)) != 0 || (r = sshbuf_get_u64(m, &state->p_send.bytes)) != 0 || (r = sshbuf_get_u32(m, &state->p_read.seqnr)) != 0 || (r = sshbuf_get_u64(m, &state->p_read.blocks)) != 0 || (r = sshbuf_get_u32(m, &state->p_read.packets)) != 0 || (r = sshbuf_get_u64(m, &state->p_read.bytes)) != 0) return r; /* * We set the time here so that in post-auth privsep child we * count from the completion of the authentication. */ state->rekey_time = monotime(); /* XXX ssh_set_newkeys overrides p_read.packets? XXX */ if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0 || (r = ssh_set_newkeys(ssh, MODE_OUT)) != 0) return r; if ((r = ssh_packet_set_postauth(ssh)) != 0) return r; sshbuf_reset(state->input); sshbuf_reset(state->output); if ((r = sshbuf_get_string_direct(m, &input, &ilen)) != 0 || (r = sshbuf_get_string_direct(m, &output, &olen)) != 0 || (r = sshbuf_put(state->input, input, ilen)) != 0 || (r = sshbuf_put(state->output, output, olen)) != 0) return r; if (sshbuf_len(m)) return SSH_ERR_INVALID_FORMAT; debug3_f("done"); return 0; } /* NEW API */ /* put data to the outgoing packet */ int sshpkt_put(struct ssh *ssh, const void *v, size_t len) { return sshbuf_put(ssh->state->outgoing_packet, v, len); } int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b) { return sshbuf_putb(ssh->state->outgoing_packet, b); } int sshpkt_put_u8(struct ssh *ssh, u_char val) { return sshbuf_put_u8(ssh->state->outgoing_packet, val); } int sshpkt_put_u32(struct ssh *ssh, u_int32_t val) { return sshbuf_put_u32(ssh->state->outgoing_packet, val); } int sshpkt_put_u64(struct ssh *ssh, u_int64_t val) { return sshbuf_put_u64(ssh->state->outgoing_packet, val); } int sshpkt_put_string(struct ssh *ssh, const void *v, size_t len) { return sshbuf_put_string(ssh->state->outgoing_packet, v, len); } int sshpkt_put_cstring(struct ssh *ssh, const void *v) { return sshbuf_put_cstring(ssh->state->outgoing_packet, v); } int sshpkt_put_stringb(struct ssh *ssh, const struct sshbuf *v) { return sshbuf_put_stringb(ssh->state->outgoing_packet, v); } int sshpkt_getb_froms(struct ssh *ssh, struct sshbuf **valp) { return sshbuf_froms(ssh->state->incoming_packet, valp); } #ifdef WITH_OPENSSL #ifdef OPENSSL_HAS_ECC int sshpkt_put_ec(struct ssh *ssh, const EC_POINT *v, const EC_GROUP *g) { return sshbuf_put_ec(ssh->state->outgoing_packet, v, g); } #endif /* OPENSSL_HAS_ECC */ int sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v) { return sshbuf_put_bignum2(ssh->state->outgoing_packet, v); } #endif /* WITH_OPENSSL */ /* fetch data from the incoming packet */ int sshpkt_get(struct ssh *ssh, void *valp, size_t len) { return sshbuf_get(ssh->state->incoming_packet, valp, len); } int sshpkt_get_u8(struct ssh *ssh, u_char *valp) { return sshbuf_get_u8(ssh->state->incoming_packet, valp); } int sshpkt_get_u32(struct ssh *ssh, u_int32_t *valp) { return sshbuf_get_u32(ssh->state->incoming_packet, valp); } int sshpkt_get_u64(struct ssh *ssh, u_int64_t *valp) { return sshbuf_get_u64(ssh->state->incoming_packet, valp); } int sshpkt_get_string(struct ssh *ssh, u_char **valp, size_t *lenp) { return sshbuf_get_string(ssh->state->incoming_packet, valp, lenp); } int sshpkt_get_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp) { return sshbuf_get_string_direct(ssh->state->incoming_packet, valp, lenp); } int sshpkt_peek_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp) { return sshbuf_peek_string_direct(ssh->state->incoming_packet, valp, lenp); } int sshpkt_get_cstring(struct ssh *ssh, char **valp, size_t *lenp) { return sshbuf_get_cstring(ssh->state->incoming_packet, valp, lenp); } #ifdef WITH_OPENSSL #ifdef OPENSSL_HAS_ECC int sshpkt_get_ec(struct ssh *ssh, EC_POINT *v, const EC_GROUP *g) { return sshbuf_get_ec(ssh->state->incoming_packet, v, g); } #endif /* OPENSSL_HAS_ECC */ int sshpkt_get_bignum2(struct ssh *ssh, BIGNUM **valp) { return sshbuf_get_bignum2(ssh->state->incoming_packet, valp); } #endif /* WITH_OPENSSL */ int sshpkt_get_end(struct ssh *ssh) { if (sshbuf_len(ssh->state->incoming_packet) > 0) return SSH_ERR_UNEXPECTED_TRAILING_DATA; return 0; } const u_char * sshpkt_ptr(struct ssh *ssh, size_t *lenp) { if (lenp != NULL) *lenp = sshbuf_len(ssh->state->incoming_packet); return sshbuf_ptr(ssh->state->incoming_packet); } /* start a new packet */ int sshpkt_start(struct ssh *ssh, u_char type) { u_char buf[6]; /* u32 packet length, u8 pad len, u8 type */ DBG(debug("packet_start[%d]", type)); memset(buf, 0, sizeof(buf)); buf[sizeof(buf) - 1] = type; sshbuf_reset(ssh->state->outgoing_packet); return sshbuf_put(ssh->state->outgoing_packet, buf, sizeof(buf)); } static int ssh_packet_send_mux(struct ssh *ssh) { struct session_state *state = ssh->state; u_char type, *cp; size_t len; int r; if (ssh->kex) return SSH_ERR_INTERNAL_ERROR; len = sshbuf_len(state->outgoing_packet); if (len < 6) return SSH_ERR_INTERNAL_ERROR; cp = sshbuf_mutable_ptr(state->outgoing_packet); type = cp[5]; if (ssh_packet_log_type(type)) debug3_f("type %u", type); /* drop everything, but the connection protocol */ if (type >= SSH2_MSG_CONNECTION_MIN && type <= SSH2_MSG_CONNECTION_MAX) { POKE_U32(cp, len - 4); if ((r = sshbuf_putb(state->output, state->outgoing_packet)) != 0) return r; /* sshbuf_dump(state->output, stderr); */ } sshbuf_reset(state->outgoing_packet); return 0; } /* * 9.2. Ignored Data Message * * byte SSH_MSG_IGNORE * string data * * All implementations MUST understand (and ignore) this message at any * time (after receiving the protocol version). No implementation is * required to send them. This message can be used as an additional * protection measure against advanced traffic analysis techniques. */ int sshpkt_msg_ignore(struct ssh *ssh, u_int nbytes) { u_int32_t rnd = 0; int r; u_int i; if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || (r = sshpkt_put_u32(ssh, nbytes)) != 0) return r; for (i = 0; i < nbytes; i++) { if (i % 4 == 0) rnd = arc4random(); if ((r = sshpkt_put_u8(ssh, (u_char)rnd & 0xff)) != 0) return r; rnd >>= 8; } return 0; } /* send it */ int sshpkt_send(struct ssh *ssh) { if (ssh->state && ssh->state->mux) return ssh_packet_send_mux(ssh); return ssh_packet_send2(ssh); } int sshpkt_disconnect(struct ssh *ssh, const char *fmt,...) { char buf[1024]; va_list args; int r; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || (r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 || (r = sshpkt_put_cstring(ssh, buf)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0) return r; return 0; } /* roundup current message to pad bytes */ int sshpkt_add_padding(struct ssh *ssh, u_char pad) { ssh->state->extra_pad = pad; return 0; } diff --git a/crypto/openssh/packet.h b/crypto/openssh/packet.h index 176488b1e5d2..11925a27d438 100644 --- a/crypto/openssh/packet.h +++ b/crypto/openssh/packet.h @@ -1,223 +1,224 @@ -/* $OpenBSD: packet.h,v 1.94 2022/01/22 00:49:34 djm Exp $ */ +/* $OpenBSD: packet.h,v 1.95 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Interface for the packet protocol functions. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef PACKET_H #define PACKET_H #include #ifdef WITH_OPENSSL # include # ifdef OPENSSL_HAS_ECC # include # else /* OPENSSL_HAS_ECC */ # define EC_KEY void # define EC_GROUP void # define EC_POINT void # endif /* OPENSSL_HAS_ECC */ #else /* WITH_OPENSSL */ # define BIGNUM void # define EC_KEY void # define EC_GROUP void # define EC_POINT void #endif /* WITH_OPENSSL */ #include #include "openbsd-compat/sys-queue.h" struct kex; struct sshkey; struct sshbuf; struct session_state; /* private session data */ #include "dispatch.h" /* typedef, DISPATCH_MAX */ struct key_entry { TAILQ_ENTRY(key_entry) next; struct sshkey *key; }; struct ssh { /* Session state */ struct session_state *state; /* Key exchange */ struct kex *kex; /* cached local and remote ip addresses and ports */ char *remote_ipaddr; int remote_port; char *local_ipaddr; int local_port; char *rdomain_in; /* Optional preamble for log messages (e.g. username) */ char *log_preamble; /* Dispatcher table */ dispatch_fn *dispatch[DISPATCH_MAX]; /* number of packets to ignore in the dispatcher */ int dispatch_skip_packets; /* datafellows */ int compat; /* Lists for private and public keys */ TAILQ_HEAD(, key_entry) private_keys; TAILQ_HEAD(, key_entry) public_keys; /* Client/Server authentication context */ void *authctxt; /* Channels context */ struct ssh_channels *chanctxt; /* APP data */ void *app_data; }; typedef int (ssh_packet_hook_fn)(struct ssh *, struct sshbuf *, u_char *, void *); struct ssh *ssh_alloc_session_state(void); struct ssh *ssh_packet_set_connection(struct ssh *, int, int); void ssh_packet_set_timeout(struct ssh *, int, int); int ssh_packet_stop_discard(struct ssh *); int ssh_packet_connection_af(struct ssh *); void ssh_packet_set_nonblocking(struct ssh *); int ssh_packet_get_connection_in(struct ssh *); int ssh_packet_get_connection_out(struct ssh *); void ssh_packet_close(struct ssh *); void ssh_packet_set_input_hook(struct ssh *, ssh_packet_hook_fn *, void *); void ssh_packet_clear_keys(struct ssh *); void ssh_clear_newkeys(struct ssh *, int); int ssh_packet_is_rekeying(struct ssh *); int ssh_packet_check_rekey(struct ssh *); void ssh_packet_set_protocol_flags(struct ssh *, u_int); u_int ssh_packet_get_protocol_flags(struct ssh *); void ssh_packet_set_tos(struct ssh *, int); void ssh_packet_set_interactive(struct ssh *, int, int, int); int ssh_packet_is_interactive(struct ssh *); void ssh_packet_set_server(struct ssh *); void ssh_packet_set_authenticated(struct ssh *); void ssh_packet_set_mux(struct ssh *); int ssh_packet_get_mux(struct ssh *); int ssh_packet_set_log_preamble(struct ssh *, const char *, ...) __attribute__((format(printf, 2, 3))); int ssh_packet_log_type(u_char); int ssh_packet_send2_wrapped(struct ssh *); int ssh_packet_send2(struct ssh *); int ssh_packet_read(struct ssh *); int ssh_packet_read_expect(struct ssh *, u_int type); int ssh_packet_read_poll(struct ssh *); int ssh_packet_read_poll2(struct ssh *, u_char *, u_int32_t *seqnr_p); int ssh_packet_process_incoming(struct ssh *, const char *buf, u_int len); int ssh_packet_process_read(struct ssh *, int); int ssh_packet_read_seqnr(struct ssh *, u_char *, u_int32_t *seqnr_p); int ssh_packet_read_poll_seqnr(struct ssh *, u_char *, u_int32_t *seqnr_p); const void *ssh_packet_get_string_ptr(struct ssh *, u_int *length_ptr); void ssh_packet_disconnect(struct ssh *, const char *fmt, ...) __attribute__((format(printf, 2, 3))) __attribute__((noreturn)); void ssh_packet_send_debug(struct ssh *, const char *fmt, ...) __attribute__((format(printf, 2, 3))); int ssh_set_newkeys(struct ssh *, int mode); void ssh_packet_get_bytes(struct ssh *, u_int64_t *, u_int64_t *); int ssh_packet_write_poll(struct ssh *); int ssh_packet_write_wait(struct ssh *); int ssh_packet_have_data_to_write(struct ssh *); int ssh_packet_not_very_much_data_to_write(struct ssh *); +int ssh_packet_interactive_data_to_write(struct ssh *); int ssh_packet_connection_is_on_socket(struct ssh *); int ssh_packet_remaining(struct ssh *); void ssh_tty_make_modes(struct ssh *, int, struct termios *); void ssh_tty_parse_modes(struct ssh *, int); void ssh_packet_set_alive_timeouts(struct ssh *, int); int ssh_packet_inc_alive_timeouts(struct ssh *); int ssh_packet_set_maxsize(struct ssh *, u_int); u_int ssh_packet_get_maxsize(struct ssh *); int ssh_packet_get_state(struct ssh *, struct sshbuf *); int ssh_packet_set_state(struct ssh *, struct sshbuf *); const char *ssh_remote_ipaddr(struct ssh *); int ssh_remote_port(struct ssh *); const char *ssh_local_ipaddr(struct ssh *); int ssh_local_port(struct ssh *); const char *ssh_packet_rdomain_in(struct ssh *); void ssh_packet_set_rekey_limits(struct ssh *, u_int64_t, u_int32_t); time_t ssh_packet_get_rekey_timeout(struct ssh *); void *ssh_packet_get_input(struct ssh *); void *ssh_packet_get_output(struct ssh *); /* new API */ int sshpkt_start(struct ssh *ssh, u_char type); int sshpkt_send(struct ssh *ssh); int sshpkt_disconnect(struct ssh *, const char *fmt, ...) __attribute__((format(printf, 2, 3))); int sshpkt_add_padding(struct ssh *, u_char); void sshpkt_fatal(struct ssh *ssh, int r, const char *fmt, ...) __attribute__((format(printf, 3, 4))) __attribute__((noreturn)); int sshpkt_msg_ignore(struct ssh *, u_int); int sshpkt_put(struct ssh *ssh, const void *v, size_t len); int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); int sshpkt_put_u8(struct ssh *ssh, u_char val); int sshpkt_put_u32(struct ssh *ssh, u_int32_t val); int sshpkt_put_u64(struct ssh *ssh, u_int64_t val); int sshpkt_put_string(struct ssh *ssh, const void *v, size_t len); int sshpkt_put_cstring(struct ssh *ssh, const void *v); int sshpkt_put_stringb(struct ssh *ssh, const struct sshbuf *v); int sshpkt_put_ec(struct ssh *ssh, const EC_POINT *v, const EC_GROUP *g); int sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v); int sshpkt_get(struct ssh *ssh, void *valp, size_t len); int sshpkt_get_u8(struct ssh *ssh, u_char *valp); int sshpkt_get_u32(struct ssh *ssh, u_int32_t *valp); int sshpkt_get_u64(struct ssh *ssh, u_int64_t *valp); int sshpkt_get_string(struct ssh *ssh, u_char **valp, size_t *lenp); int sshpkt_get_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp); int sshpkt_peek_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp); int sshpkt_get_cstring(struct ssh *ssh, char **valp, size_t *lenp); int sshpkt_getb_froms(struct ssh *ssh, struct sshbuf **valp); int sshpkt_get_ec(struct ssh *ssh, EC_POINT *v, const EC_GROUP *g); int sshpkt_get_bignum2(struct ssh *ssh, BIGNUM **valp); int sshpkt_get_end(struct ssh *ssh); void sshpkt_fmt_connection_id(struct ssh *ssh, char *s, size_t l); const u_char *sshpkt_ptr(struct ssh *, size_t *lenp); #if !defined(WITH_OPENSSL) # undef BIGNUM # undef EC_KEY # undef EC_GROUP # undef EC_POINT #elif !defined(OPENSSL_HAS_ECC) # undef EC_KEY # undef EC_GROUP # undef EC_POINT #endif #endif /* PACKET_H */ diff --git a/crypto/openssh/readconf.c b/crypto/openssh/readconf.c index 6b891b868e01..2ff8f68b6fb3 100644 --- a/crypto/openssh/readconf.c +++ b/crypto/openssh/readconf.c @@ -1,3580 +1,3640 @@ -/* $OpenBSD: readconf.c,v 1.380 2023/07/17 06:16:33 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.381 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for reading the configuration files. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_IFADDRS_H # include #endif #include #include #ifdef HAVE_PATHS_H # include #endif #include #include #include #include #include #include #ifdef USE_SYSTEM_GLOB # include #else # include "openbsd-compat/glob.h" #endif #ifdef HAVE_UTIL_H #include #endif #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) # include #endif #include "xmalloc.h" #include "ssh.h" #include "ssherr.h" #include "cipher.h" #include "pathnames.h" #include "log.h" #include "sshkey.h" #include "misc.h" #include "readconf.h" #include "match.h" #include "kex.h" #include "mac.h" #include "uidswap.h" #include "myproposal.h" #include "digest.h" /* Format of the configuration file: # Configuration data is parsed as follows: # 1. command line options # 2. user-specific file # 3. system-wide file # Any configuration value is only changed the first time it is set. # Thus, host-specific definitions should be at the beginning of the # configuration file, and defaults at the end. # Host-specific declarations. These may override anything above. A single # host may match multiple declarations; these are processed in the order # that they are given in. Host *.ngs.fi ngs.fi User foo Host fake.com Hostname another.host.name.real.org User blaah Port 34289 ForwardX11 no ForwardAgent no Host books.com RemoteForward 9999 shadows.cs.hut.fi:9999 Ciphers 3des-cbc Host fascist.blob.com Port 23123 User tylonen PasswordAuthentication no Host puukko.hut.fi User t35124p ProxyCommand ssh-proxy %h %p Host *.fr PublicKeyAuthentication no Host *.su Ciphers aes128-ctr PasswordAuthentication no Host vpn.fake.com Tunnel yes TunnelDevice 3 # Defaults for various options Host * ForwardAgent no ForwardX11 no PasswordAuthentication yes StrictHostKeyChecking yes TcpKeepAlive no IdentityFile ~/.ssh/identity Port 22 EscapeChar ~ */ static int read_config_file_depth(const char *filename, struct passwd *pw, const char *host, const char *original_host, Options *options, int flags, int *activep, int *want_final_pass, int depth); static int process_config_line_depth(Options *options, struct passwd *pw, const char *host, const char *original_host, char *line, const char *filename, int linenum, int *activep, int flags, int *want_final_pass, int depth); /* Keyword tokens. */ typedef enum { oBadOption, oHost, oMatch, oInclude, oTag, oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, oGatewayPorts, oExitOnForwardFailure, oPasswordAuthentication, oXAuthLocation, oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, oPermitRemoteOpen, oCertificateFile, oAddKeysToAgent, oIdentityAgent, oUser, oEscapeChar, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oTCPKeepAlive, oNumberOfPasswordPrompts, oLogFacility, oLogLevel, oLogVerbose, oCiphers, oMacs, oPubkeyAuthentication, oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, oHostKeyAlgorithms, oBindAddress, oBindInterface, oPKCS11Provider, oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oAddressFamily, oGssAuthentication, oGssDelegateCreds, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oRemoteCommand, oVisualHostKey, oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull, oForkAfterAuthentication, oIgnoreUnknown, oProxyUseFdpass, oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms, oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump, oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize, - oEnableEscapeCommandline, + oEnableEscapeCommandline, oObscureKeystrokeTiming, oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ static struct { const char *name; OpCodes opcode; } keywords[] = { /* Deprecated options */ { "protocol", oIgnore }, /* NB. silently ignored */ { "cipher", oDeprecated }, { "fallbacktorsh", oDeprecated }, { "globalknownhostsfile2", oDeprecated }, { "rhostsauthentication", oDeprecated }, { "userknownhostsfile2", oDeprecated }, { "useroaming", oDeprecated }, { "usersh", oDeprecated }, { "useprivilegedport", oDeprecated }, /* Unsupported options */ { "afstokenpassing", oUnsupported }, { "kerberosauthentication", oUnsupported }, { "kerberostgtpassing", oUnsupported }, { "rsaauthentication", oUnsupported }, { "rhostsrsaauthentication", oUnsupported }, { "compressionlevel", oUnsupported }, /* Sometimes-unsupported options */ #if defined(GSSAPI) { "gssapiauthentication", oGssAuthentication }, { "gssapidelegatecredentials", oGssDelegateCreds }, # else { "gssapiauthentication", oUnsupported }, { "gssapidelegatecredentials", oUnsupported }, #endif #ifdef ENABLE_PKCS11 { "pkcs11provider", oPKCS11Provider }, { "smartcarddevice", oPKCS11Provider }, # else { "smartcarddevice", oUnsupported }, { "pkcs11provider", oUnsupported }, #endif { "forwardagent", oForwardAgent }, { "forwardx11", oForwardX11 }, { "forwardx11trusted", oForwardX11Trusted }, { "forwardx11timeout", oForwardX11Timeout }, { "exitonforwardfailure", oExitOnForwardFailure }, { "xauthlocation", oXAuthLocation }, { "gatewayports", oGatewayPorts }, { "passwordauthentication", oPasswordAuthentication }, { "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, { "kbdinteractivedevices", oKbdInteractiveDevices }, { "challengeresponseauthentication", oKbdInteractiveAuthentication }, /* alias */ { "skeyauthentication", oKbdInteractiveAuthentication }, /* alias */ { "tisauthentication", oKbdInteractiveAuthentication }, /* alias */ { "pubkeyauthentication", oPubkeyAuthentication }, { "dsaauthentication", oPubkeyAuthentication }, /* alias */ { "hostbasedauthentication", oHostbasedAuthentication }, { "identityfile", oIdentityFile }, { "identityfile2", oIdentityFile }, /* obsolete */ { "identitiesonly", oIdentitiesOnly }, { "certificatefile", oCertificateFile }, { "addkeystoagent", oAddKeysToAgent }, { "identityagent", oIdentityAgent }, { "hostname", oHostname }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, { "port", oPort }, { "ciphers", oCiphers }, { "macs", oMacs }, { "remoteforward", oRemoteForward }, { "localforward", oLocalForward }, { "permitremoteopen", oPermitRemoteOpen }, { "user", oUser }, { "host", oHost }, { "match", oMatch }, { "tag", oTag }, { "escapechar", oEscapeChar }, { "globalknownhostsfile", oGlobalKnownHostsFile }, { "userknownhostsfile", oUserKnownHostsFile }, { "connectionattempts", oConnectionAttempts }, { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, { "stricthostkeychecking", oStrictHostKeyChecking }, { "compression", oCompression }, { "tcpkeepalive", oTCPKeepAlive }, { "keepalive", oTCPKeepAlive }, /* obsolete */ { "numberofpasswordprompts", oNumberOfPasswordPrompts }, { "syslogfacility", oLogFacility }, { "loglevel", oLogLevel }, { "logverbose", oLogVerbose }, { "dynamicforward", oDynamicForward }, { "preferredauthentications", oPreferredAuthentications }, { "hostkeyalgorithms", oHostKeyAlgorithms }, { "casignaturealgorithms", oCASignatureAlgorithms }, { "bindaddress", oBindAddress }, { "bindinterface", oBindInterface }, { "clearallforwardings", oClearAllForwardings }, { "enablesshkeysign", oEnableSSHKeysign }, { "verifyhostkeydns", oVerifyHostKeyDNS }, { "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost }, { "rekeylimit", oRekeyLimit }, { "connecttimeout", oConnectTimeout }, { "addressfamily", oAddressFamily }, { "serveraliveinterval", oServerAliveInterval }, { "serveralivecountmax", oServerAliveCountMax }, { "sendenv", oSendEnv }, { "setenv", oSetEnv }, { "controlpath", oControlPath }, { "controlmaster", oControlMaster }, { "controlpersist", oControlPersist }, { "hashknownhosts", oHashKnownHosts }, { "include", oInclude }, { "tunnel", oTunnel }, { "tunneldevice", oTunnelDevice }, { "localcommand", oLocalCommand }, { "permitlocalcommand", oPermitLocalCommand }, { "remotecommand", oRemoteCommand }, { "visualhostkey", oVisualHostKey }, { "kexalgorithms", oKexAlgorithms }, { "ipqos", oIPQoS }, { "requesttty", oRequestTTY }, { "sessiontype", oSessionType }, { "stdinnull", oStdinNull }, { "forkafterauthentication", oForkAfterAuthentication }, { "proxyusefdpass", oProxyUseFdpass }, { "canonicaldomains", oCanonicalDomains }, { "canonicalizefallbacklocal", oCanonicalizeFallbackLocal }, { "canonicalizehostname", oCanonicalizeHostname }, { "canonicalizemaxdots", oCanonicalizeMaxDots }, { "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs }, { "streamlocalbindmask", oStreamLocalBindMask }, { "streamlocalbindunlink", oStreamLocalBindUnlink }, { "revokedhostkeys", oRevokedHostKeys }, { "fingerprinthash", oFingerprintHash }, { "updatehostkeys", oUpdateHostkeys }, { "hostbasedacceptedalgorithms", oHostbasedAcceptedAlgorithms }, { "hostbasedkeytypes", oHostbasedAcceptedAlgorithms }, /* obsolete */ { "pubkeyacceptedalgorithms", oPubkeyAcceptedAlgorithms }, { "pubkeyacceptedkeytypes", oPubkeyAcceptedAlgorithms }, /* obsolete */ { "ignoreunknown", oIgnoreUnknown }, { "proxyjump", oProxyJump }, { "securitykeyprovider", oSecurityKeyProvider }, { "knownhostscommand", oKnownHostsCommand }, { "requiredrsasize", oRequiredRSASize }, { "enableescapecommandline", oEnableEscapeCommandline }, + { "obscurekeystroketiming", oObscureKeystrokeTiming }, /* Client VersionAddendum - retired in bffe60ead024 */ { "versionaddendum", oDeprecated }, { NULL, oBadOption } }; static const char *lookup_opcode_name(OpCodes code); const char * kex_default_pk_alg(void) { static char *pkalgs; if (pkalgs == NULL) { char *all_key; all_key = sshkey_alg_list(0, 0, 1, ','); pkalgs = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key); free(all_key); } return pkalgs; } char * ssh_connection_hash(const char *thishost, const char *host, const char *portstr, const char *user) { struct ssh_digest_ctx *md; u_char conn_hash[SSH_DIGEST_MAX_LENGTH]; if ((md = ssh_digest_start(SSH_DIGEST_SHA1)) == NULL || ssh_digest_update(md, thishost, strlen(thishost)) < 0 || ssh_digest_update(md, host, strlen(host)) < 0 || ssh_digest_update(md, portstr, strlen(portstr)) < 0 || ssh_digest_update(md, user, strlen(user)) < 0 || ssh_digest_final(md, conn_hash, sizeof(conn_hash)) < 0) fatal_f("mux digest failed"); ssh_digest_free(md); return tohex(conn_hash, ssh_digest_bytes(SSH_DIGEST_SHA1)); } /* * Adds a local TCP/IP port forward to options. Never returns if there is an * error. */ void add_local_forward(Options *options, const struct Forward *newfwd) { struct Forward *fwd; int i; /* Don't add duplicates */ for (i = 0; i < options->num_local_forwards; i++) { if (forward_equals(newfwd, options->local_forwards + i)) return; } options->local_forwards = xreallocarray(options->local_forwards, options->num_local_forwards + 1, sizeof(*options->local_forwards)); fwd = &options->local_forwards[options->num_local_forwards++]; fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; fwd->listen_path = newfwd->listen_path; fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; fwd->connect_path = newfwd->connect_path; } /* * Adds a remote TCP/IP port forward to options. Never returns if there is * an error. */ void add_remote_forward(Options *options, const struct Forward *newfwd) { struct Forward *fwd; int i; /* Don't add duplicates */ for (i = 0; i < options->num_remote_forwards; i++) { if (forward_equals(newfwd, options->remote_forwards + i)) return; } options->remote_forwards = xreallocarray(options->remote_forwards, options->num_remote_forwards + 1, sizeof(*options->remote_forwards)); fwd = &options->remote_forwards[options->num_remote_forwards++]; fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; fwd->listen_path = newfwd->listen_path; fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; fwd->connect_path = newfwd->connect_path; fwd->handle = newfwd->handle; fwd->allocated_port = 0; } static void clear_forwardings(Options *options) { int i; for (i = 0; i < options->num_local_forwards; i++) { free(options->local_forwards[i].listen_host); free(options->local_forwards[i].listen_path); free(options->local_forwards[i].connect_host); free(options->local_forwards[i].connect_path); } if (options->num_local_forwards > 0) { free(options->local_forwards); options->local_forwards = NULL; } options->num_local_forwards = 0; for (i = 0; i < options->num_remote_forwards; i++) { free(options->remote_forwards[i].listen_host); free(options->remote_forwards[i].listen_path); free(options->remote_forwards[i].connect_host); free(options->remote_forwards[i].connect_path); } if (options->num_remote_forwards > 0) { free(options->remote_forwards); options->remote_forwards = NULL; } options->num_remote_forwards = 0; options->tun_open = SSH_TUNMODE_NO; } void add_certificate_file(Options *options, const char *path, int userprovided) { int i; if (options->num_certificate_files >= SSH_MAX_CERTIFICATE_FILES) fatal("Too many certificate files specified (max %d)", SSH_MAX_CERTIFICATE_FILES); /* Avoid registering duplicates */ for (i = 0; i < options->num_certificate_files; i++) { if (options->certificate_file_userprovided[i] == userprovided && strcmp(options->certificate_files[i], path) == 0) { debug2_f("ignoring duplicate key %s", path); return; } } options->certificate_file_userprovided[options->num_certificate_files] = userprovided; options->certificate_files[options->num_certificate_files++] = xstrdup(path); } void add_identity_file(Options *options, const char *dir, const char *filename, int userprovided) { char *path; int i; if (options->num_identity_files >= SSH_MAX_IDENTITY_FILES) fatal("Too many identity files specified (max %d)", SSH_MAX_IDENTITY_FILES); if (dir == NULL) /* no dir, filename is absolute */ path = xstrdup(filename); else if (xasprintf(&path, "%s%s", dir, filename) >= PATH_MAX) fatal("Identity file path %s too long", path); /* Avoid registering duplicates */ for (i = 0; i < options->num_identity_files; i++) { if (options->identity_file_userprovided[i] == userprovided && strcmp(options->identity_files[i], path) == 0) { debug2_f("ignoring duplicate key %s", path); free(path); return; } } options->identity_file_userprovided[options->num_identity_files] = userprovided; options->identity_files[options->num_identity_files++] = path; } int default_ssh_port(void) { static int port; struct servent *sp; if (port == 0) { sp = getservbyname(SSH_SERVICE_NAME, "tcp"); port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; } return port; } /* * Execute a command in a shell. * Return its exit status or -1 on abnormal exit. */ static int execute_in_shell(const char *cmd) { char *shell; pid_t pid; int status; if ((shell = getenv("SHELL")) == NULL) shell = _PATH_BSHELL; if (access(shell, X_OK) == -1) { fatal("Shell \"%s\" is not executable: %s", shell, strerror(errno)); } debug("Executing command: '%.500s'", cmd); /* Fork and execute the command. */ if ((pid = fork()) == 0) { char *argv[4]; if (stdfd_devnull(1, 1, 0) == -1) fatal_f("stdfd_devnull failed"); closefrom(STDERR_FILENO + 1); argv[0] = shell; argv[1] = "-c"; argv[2] = xstrdup(cmd); argv[3] = NULL; execv(argv[0], argv); error("Unable to execute '%.100s': %s", cmd, strerror(errno)); /* Die with signal to make this error apparent to parent. */ ssh_signal(SIGTERM, SIG_DFL); kill(getpid(), SIGTERM); _exit(1); } /* Parent. */ if (pid == -1) fatal_f("fork: %.100s", strerror(errno)); while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR && errno != EAGAIN) fatal_f("waitpid: %s", strerror(errno)); } if (!WIFEXITED(status)) { error("command '%.100s' exited abnormally", cmd); return -1; } debug3("command returned status %d", WEXITSTATUS(status)); return WEXITSTATUS(status); } /* * Check whether a local network interface address appears in CIDR pattern- * list 'addrlist'. Returns 1 if matched or 0 otherwise. */ static int check_match_ifaddrs(const char *addrlist) { #ifdef HAVE_IFADDRS_H struct ifaddrs *ifa, *ifaddrs = NULL; int r, found = 0; char addr[NI_MAXHOST]; socklen_t salen; if (getifaddrs(&ifaddrs) != 0) { error("match localnetwork: getifaddrs failed: %s", strerror(errno)); return 0; } for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL || (ifa->ifa_flags & IFF_UP) == 0) continue; switch (ifa->ifa_addr->sa_family) { case AF_INET: salen = sizeof(struct sockaddr_in); break; case AF_INET6: salen = sizeof(struct sockaddr_in6); break; #ifdef AF_LINK case AF_LINK: /* ignore */ continue; #endif /* AF_LINK */ default: debug2_f("interface %s: unsupported address family %d", ifa->ifa_name, ifa->ifa_addr->sa_family); continue; } if ((r = getnameinfo(ifa->ifa_addr, salen, addr, sizeof(addr), NULL, 0, NI_NUMERICHOST)) != 0) { debug2_f("interface %s getnameinfo failed: %s", ifa->ifa_name, gai_strerror(r)); continue; } debug3_f("interface %s addr %s", ifa->ifa_name, addr); if (addr_match_cidr_list(addr, addrlist) == 1) { debug3_f("matched interface %s: address %s in %s", ifa->ifa_name, addr, addrlist); found = 1; break; } } freeifaddrs(ifaddrs); return found; #else /* HAVE_IFADDRS_H */ error("match localnetwork: not supported on this platform"); return 0; #endif /* HAVE_IFADDRS_H */ } /* * Parse and execute a Match directive. */ static int match_cfg_line(Options *options, char **condition, struct passwd *pw, const char *host_arg, const char *original_host, int final_pass, int *want_final_pass, const char *filename, int linenum) { char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria; const char *ruser; int r, port, this_result, result = 1, attributes = 0, negate; char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; char uidstr[32]; /* * Configuration is likely to be incomplete at this point so we * must be prepared to use default values. */ port = options->port <= 0 ? default_ssh_port() : options->port; ruser = options->user == NULL ? pw->pw_name : options->user; if (final_pass) { host = xstrdup(options->hostname); } else if (options->hostname != NULL) { /* NB. Please keep in sync with ssh.c:main() */ host = percent_expand(options->hostname, "h", host_arg, (char *)NULL); } else { host = xstrdup(host_arg); } debug2("checking match for '%s' host %s originally %s", cp, host, original_host); while ((oattrib = attrib = strdelim(&cp)) && *attrib != '\0') { /* Terminate on comment */ if (*attrib == '#') { cp = NULL; /* mark all arguments consumed */ break; } arg = criteria = NULL; this_result = 1; if ((negate = (attrib[0] == '!'))) attrib++; /* Criterion "all" has no argument and must appear alone */ if (strcasecmp(attrib, "all") == 0) { if (attributes > 1 || ((arg = strdelim(&cp)) != NULL && *arg != '\0' && *arg != '#')) { error("%.200s line %d: '%s' cannot be combined " "with other Match attributes", filename, linenum, oattrib); result = -1; goto out; } if (arg != NULL && *arg == '#') cp = NULL; /* mark all arguments consumed */ if (result) result = negate ? 0 : 1; goto out; } attributes++; /* criteria "final" and "canonical" have no argument */ if (strcasecmp(attrib, "canonical") == 0 || strcasecmp(attrib, "final") == 0) { /* * If the config requests "Match final" then remember * this so we can perform a second pass later. */ if (strcasecmp(attrib, "final") == 0 && want_final_pass != NULL) *want_final_pass = 1; r = !!final_pass; /* force bitmask member to boolean */ if (r == (negate ? 1 : 0)) this_result = result = 0; debug3("%.200s line %d: %smatched '%s'", filename, linenum, this_result ? "" : "not ", oattrib); continue; } /* All other criteria require an argument */ if ((arg = strdelim(&cp)) == NULL || *arg == '\0' || *arg == '#') { error("Missing Match criteria for %s", attrib); result = -1; goto out; } if (strcasecmp(attrib, "host") == 0) { criteria = xstrdup(host); r = match_hostname(host, arg) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "originalhost") == 0) { criteria = xstrdup(original_host); r = match_hostname(original_host, arg) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "user") == 0) { criteria = xstrdup(ruser); r = match_pattern_list(ruser, arg, 0) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "localuser") == 0) { criteria = xstrdup(pw->pw_name); r = match_pattern_list(pw->pw_name, arg, 0) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "localnetwork") == 0) { if (addr_match_cidr_list(NULL, arg) == -1) { /* Error already printed */ result = -1; goto out; } r = check_match_ifaddrs(arg) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "tagged") == 0) { criteria = xstrdup(options->tag == NULL ? "" : options->tag); r = match_pattern_list(criteria, arg, 0) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "exec") == 0) { char *conn_hash_hex, *keyalias; if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); strlcpy(shorthost, thishost, sizeof(shorthost)); shorthost[strcspn(thishost, ".")] = '\0'; snprintf(portstr, sizeof(portstr), "%d", port); snprintf(uidstr, sizeof(uidstr), "%llu", (unsigned long long)pw->pw_uid); conn_hash_hex = ssh_connection_hash(thishost, host, portstr, ruser); keyalias = options->host_key_alias ? options->host_key_alias : host; cmd = percent_expand(arg, "C", conn_hash_hex, "L", shorthost, "d", pw->pw_dir, "h", host, "k", keyalias, "l", thishost, "n", original_host, "p", portstr, "r", ruser, "u", pw->pw_name, "i", uidstr, (char *)NULL); free(conn_hash_hex); if (result != 1) { /* skip execution if prior predicate failed */ debug3("%.200s line %d: skipped exec " "\"%.100s\"", filename, linenum, cmd); free(cmd); continue; } r = execute_in_shell(cmd); if (r == -1) { fatal("%.200s line %d: match exec " "'%.100s' error", filename, linenum, cmd); } criteria = xstrdup(cmd); free(cmd); /* Force exit status to boolean */ r = r == 0; if (r == (negate ? 1 : 0)) this_result = result = 0; } else { error("Unsupported Match attribute %s", attrib); result = -1; goto out; } debug3("%.200s line %d: %smatched '%s%s%.100s%s' ", filename, linenum, this_result ? "": "not ", oattrib, criteria == NULL ? "" : " \"", criteria == NULL ? "" : criteria, criteria == NULL ? "" : "\""); free(criteria); } if (attributes == 0) { error("One or more attributes required for Match"); result = -1; goto out; } out: if (result != -1) debug2("match %sfound", result ? "" : "not "); *condition = cp; free(host); return result; } /* Remove environment variable by pattern */ static void rm_env(Options *options, const char *arg, const char *filename, int linenum) { u_int i, j, onum_send_env = options->num_send_env; /* Remove an environment variable */ for (i = 0; i < options->num_send_env; ) { if (!match_pattern(options->send_env[i], arg + 1)) { i++; continue; } debug3("%s line %d: removing environment %s", filename, linenum, options->send_env[i]); free(options->send_env[i]); options->send_env[i] = NULL; for (j = i; j < options->num_send_env - 1; j++) { options->send_env[j] = options->send_env[j + 1]; options->send_env[j + 1] = NULL; } options->num_send_env--; /* NB. don't increment i */ } if (onum_send_env != options->num_send_env) { options->send_env = xrecallocarray(options->send_env, onum_send_env, options->num_send_env, sizeof(*options->send_env)); } } /* * Returns the number of the token pointed to by cp or oBadOption. */ static OpCodes parse_token(const char *cp, const char *filename, int linenum, const char *ignored_unknown) { int i; for (i = 0; keywords[i].name; i++) if (strcmp(cp, keywords[i].name) == 0) return keywords[i].opcode; if (ignored_unknown != NULL && match_pattern_list(cp, ignored_unknown, 1) == 1) return oIgnoredUnknownOption; error("%s: line %d: Bad configuration option: %s", filename, linenum, cp); return oBadOption; } /* Multistate option parsing */ struct multistate { char *key; int value; }; static const struct multistate multistate_flag[] = { { "true", 1 }, { "false", 0 }, { "yes", 1 }, { "no", 0 }, { NULL, -1 } }; static const struct multistate multistate_yesnoask[] = { { "true", 1 }, { "false", 0 }, { "yes", 1 }, { "no", 0 }, { "ask", 2 }, { NULL, -1 } }; static const struct multistate multistate_strict_hostkey[] = { { "true", SSH_STRICT_HOSTKEY_YES }, { "false", SSH_STRICT_HOSTKEY_OFF }, { "yes", SSH_STRICT_HOSTKEY_YES }, { "no", SSH_STRICT_HOSTKEY_OFF }, { "ask", SSH_STRICT_HOSTKEY_ASK }, { "off", SSH_STRICT_HOSTKEY_OFF }, { "accept-new", SSH_STRICT_HOSTKEY_NEW }, { NULL, -1 } }; static const struct multistate multistate_yesnoaskconfirm[] = { { "true", 1 }, { "false", 0 }, { "yes", 1 }, { "no", 0 }, { "ask", 2 }, { "confirm", 3 }, { NULL, -1 } }; static const struct multistate multistate_addressfamily[] = { { "inet", AF_INET }, { "inet6", AF_INET6 }, { "any", AF_UNSPEC }, { NULL, -1 } }; static const struct multistate multistate_controlmaster[] = { { "true", SSHCTL_MASTER_YES }, { "yes", SSHCTL_MASTER_YES }, { "false", SSHCTL_MASTER_NO }, { "no", SSHCTL_MASTER_NO }, { "auto", SSHCTL_MASTER_AUTO }, { "ask", SSHCTL_MASTER_ASK }, { "autoask", SSHCTL_MASTER_AUTO_ASK }, { NULL, -1 } }; static const struct multistate multistate_tunnel[] = { { "ethernet", SSH_TUNMODE_ETHERNET }, { "point-to-point", SSH_TUNMODE_POINTOPOINT }, { "true", SSH_TUNMODE_DEFAULT }, { "yes", SSH_TUNMODE_DEFAULT }, { "false", SSH_TUNMODE_NO }, { "no", SSH_TUNMODE_NO }, { NULL, -1 } }; static const struct multistate multistate_requesttty[] = { { "true", REQUEST_TTY_YES }, { "yes", REQUEST_TTY_YES }, { "false", REQUEST_TTY_NO }, { "no", REQUEST_TTY_NO }, { "force", REQUEST_TTY_FORCE }, { "auto", REQUEST_TTY_AUTO }, { NULL, -1 } }; static const struct multistate multistate_sessiontype[] = { { "none", SESSION_TYPE_NONE }, { "subsystem", SESSION_TYPE_SUBSYSTEM }, { "default", SESSION_TYPE_DEFAULT }, { NULL, -1 } }; static const struct multistate multistate_canonicalizehostname[] = { { "true", SSH_CANONICALISE_YES }, { "false", SSH_CANONICALISE_NO }, { "yes", SSH_CANONICALISE_YES }, { "no", SSH_CANONICALISE_NO }, { "always", SSH_CANONICALISE_ALWAYS }, { NULL, -1 } }; static const struct multistate multistate_pubkey_auth[] = { { "true", SSH_PUBKEY_AUTH_ALL }, { "false", SSH_PUBKEY_AUTH_NO }, { "yes", SSH_PUBKEY_AUTH_ALL }, { "no", SSH_PUBKEY_AUTH_NO }, { "unbound", SSH_PUBKEY_AUTH_UNBOUND }, { "host-bound", SSH_PUBKEY_AUTH_HBOUND }, { NULL, -1 } }; static const struct multistate multistate_compression[] = { #ifdef WITH_ZLIB { "yes", COMP_ZLIB }, #endif { "no", COMP_NONE }, { NULL, -1 } }; static int parse_multistate_value(const char *arg, const char *filename, int linenum, const struct multistate *multistate_ptr) { int i; if (!arg || *arg == '\0') { error("%s line %d: missing argument.", filename, linenum); return -1; } for (i = 0; multistate_ptr[i].key != NULL; i++) { if (strcasecmp(arg, multistate_ptr[i].key) == 0) return multistate_ptr[i].value; } return -1; } /* * Processes a single option line as used in the configuration files. This * only sets those values that have not already been set. */ int process_config_line(Options *options, struct passwd *pw, const char *host, const char *original_host, char *line, const char *filename, int linenum, int *activep, int flags) { return process_config_line_depth(options, pw, host, original_host, line, filename, linenum, activep, flags, NULL, 0); } #define WHITESPACE " \t\r\n" static int process_config_line_depth(Options *options, struct passwd *pw, const char *host, const char *original_host, char *line, const char *filename, int linenum, int *activep, int flags, int *want_final_pass, int depth) { char *str, **charptr, *endofnumber, *keyword, *arg, *arg2, *p; char **cpptr, ***cppptr, fwdarg[256]; u_int i, *uintptr, uvalue, max_entries = 0; int r, oactive, negated, opcode, *intptr, value, value2, cmdline = 0; int remotefwd, dynamicfwd, ca_only = 0; LogLevel *log_level_ptr; SyslogFacility *log_facility_ptr; long long val64; size_t len; struct Forward fwd; const struct multistate *multistate_ptr; struct allowed_cname *cname; glob_t gl; const char *errstr; char **oav = NULL, **av; int oac = 0, ac; int ret = -1; if (activep == NULL) { /* We are processing a command line directive */ cmdline = 1; activep = &cmdline; } /* Strip trailing whitespace. Allow \f (form feed) at EOL only */ if ((len = strlen(line)) == 0) return 0; for (len--; len > 0; len--) { if (strchr(WHITESPACE "\f", line[len]) == NULL) break; line[len] = '\0'; } str = line; /* Get the keyword. (Each line is supposed to begin with a keyword). */ if ((keyword = strdelim(&str)) == NULL) return 0; /* Ignore leading whitespace. */ if (*keyword == '\0') keyword = strdelim(&str); if (keyword == NULL || !*keyword || *keyword == '\n' || *keyword == '#') return 0; /* Match lowercase keyword */ lowercase(keyword); /* Prepare to parse remainder of line */ if (str != NULL) str += strspn(str, WHITESPACE); if (str == NULL || *str == '\0') { error("%s line %d: no argument after keyword \"%s\"", filename, linenum, keyword); return -1; } opcode = parse_token(keyword, filename, linenum, options->ignored_unknown); if (argv_split(str, &oac, &oav, 1) != 0) { error("%s line %d: invalid quotes", filename, linenum); return -1; } ac = oac; av = oav; switch (opcode) { case oBadOption: /* don't panic, but count bad options */ goto out; case oIgnore: argv_consume(&ac); break; case oIgnoredUnknownOption: debug("%s line %d: Ignored unknown option \"%s\"", filename, linenum, keyword); argv_consume(&ac); break; case oConnectTimeout: intptr = &options->connection_timeout; parse_time: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%s line %d: missing time value.", filename, linenum); goto out; } if (strcmp(arg, "none") == 0) value = -1; else if ((value = convtime(arg)) == -1) { error("%s line %d: invalid time value.", filename, linenum); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oForwardAgent: intptr = &options->forward_agent; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%s line %d: missing argument.", filename, linenum); goto out; } value = -1; multistate_ptr = multistate_flag; for (i = 0; multistate_ptr[i].key != NULL; i++) { if (strcasecmp(arg, multistate_ptr[i].key) == 0) { value = multistate_ptr[i].value; break; } } if (value != -1) { if (*activep && *intptr == -1) *intptr = value; break; } /* ForwardAgent wasn't 'yes' or 'no', assume a path */ if (*activep && *intptr == -1) *intptr = 1; charptr = &options->forward_agent_sock_path; goto parse_agent_path; case oForwardX11: intptr = &options->forward_x11; parse_flag: multistate_ptr = multistate_flag; parse_multistate: arg = argv_next(&ac, &av); if ((value = parse_multistate_value(arg, filename, linenum, multistate_ptr)) == -1) { error("%s line %d: unsupported option \"%s\".", filename, linenum, arg); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oForwardX11Trusted: intptr = &options->forward_x11_trusted; goto parse_flag; case oForwardX11Timeout: intptr = &options->forward_x11_timeout; goto parse_time; case oGatewayPorts: intptr = &options->fwd_opts.gateway_ports; goto parse_flag; case oExitOnForwardFailure: intptr = &options->exit_on_forward_failure; goto parse_flag; case oPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; case oKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; case oKbdInteractiveDevices: charptr = &options->kbd_interactive_devices; goto parse_string; case oPubkeyAuthentication: multistate_ptr = multistate_pubkey_auth; intptr = &options->pubkey_authentication; goto parse_multistate; case oHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; case oGssAuthentication: intptr = &options->gss_authentication; goto parse_flag; case oGssDelegateCreds: intptr = &options->gss_deleg_creds; goto parse_flag; case oBatchMode: intptr = &options->batch_mode; goto parse_flag; case oCheckHostIP: intptr = &options->check_host_ip; goto parse_flag; case oVerifyHostKeyDNS: intptr = &options->verify_host_key_dns; multistate_ptr = multistate_yesnoask; goto parse_multistate; case oStrictHostKeyChecking: intptr = &options->strict_host_key_checking; multistate_ptr = multistate_strict_hostkey; goto parse_multistate; case oCompression: intptr = &options->compression; multistate_ptr = multistate_compression; goto parse_multistate; case oTCPKeepAlive: intptr = &options->tcp_keep_alive; goto parse_flag; case oNoHostAuthenticationForLocalhost: intptr = &options->no_host_authentication_for_localhost; goto parse_flag; case oNumberOfPasswordPrompts: intptr = &options->number_of_password_prompts; goto parse_int; case oRekeyLimit: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (strcmp(arg, "default") == 0) { val64 = 0; } else { if (scan_scaled(arg, &val64) == -1) { error("%.200s line %d: Bad number '%s': %s", filename, linenum, arg, strerror(errno)); goto out; } if (val64 != 0 && val64 < 16) { error("%.200s line %d: RekeyLimit too small", filename, linenum); goto out; } } if (*activep && options->rekey_limit == -1) options->rekey_limit = val64; if (ac != 0) { /* optional rekey interval present */ if (strcmp(av[0], "none") == 0) { (void)argv_next(&ac, &av); /* discard */ break; } intptr = &options->rekey_interval; goto parse_time; } break; case oIdentityFile: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*activep) { intptr = &options->num_identity_files; if (*intptr >= SSH_MAX_IDENTITY_FILES) { error("%.200s line %d: Too many identity files " "specified (max %d).", filename, linenum, SSH_MAX_IDENTITY_FILES); goto out; } add_identity_file(options, NULL, arg, flags & SSHCONF_USERCONF); } break; case oCertificateFile: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*activep) { intptr = &options->num_certificate_files; if (*intptr >= SSH_MAX_CERTIFICATE_FILES) { error("%.200s line %d: Too many certificate " "files specified (max %d).", filename, linenum, SSH_MAX_CERTIFICATE_FILES); goto out; } add_certificate_file(options, arg, flags & SSHCONF_USERCONF); } break; case oXAuthLocation: charptr=&options->xauth_location; goto parse_string; case oUser: charptr = &options->user; parse_string: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oGlobalKnownHostsFile: cpptr = (char **)&options->system_hostfiles; uintptr = &options->num_system_hostfiles; max_entries = SSH_MAX_HOSTS_FILES; parse_char_array: i = 0; value = *uintptr == 0; /* was array empty when we started? */ while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (*activep && value) { if ((*uintptr) >= max_entries) { error("%s line %d: too many %s " "entries.", filename, linenum, keyword); goto out; } cpptr[(*uintptr)++] = xstrdup(arg); } } break; case oUserKnownHostsFile: cpptr = (char **)&options->user_hostfiles; uintptr = &options->num_user_hostfiles; max_entries = SSH_MAX_HOSTS_FILES; goto parse_char_array; case oHostname: charptr = &options->hostname; goto parse_string; case oTag: charptr = &options->tag; goto parse_string; case oHostKeyAlias: charptr = &options->host_key_alias; goto parse_string; case oPreferredAuthentications: charptr = &options->preferred_authentications; goto parse_string; case oBindAddress: charptr = &options->bind_address; goto parse_string; case oBindInterface: charptr = &options->bind_interface; goto parse_string; case oPKCS11Provider: charptr = &options->pkcs11_provider; goto parse_string; case oSecurityKeyProvider: charptr = &options->sk_provider; goto parse_string; case oKnownHostsCommand: charptr = &options->known_hosts_command; goto parse_command; case oProxyCommand: charptr = &options->proxy_command; /* Ignore ProxyCommand if ProxyJump already specified */ if (options->jump_host != NULL) charptr = &options->jump_host; /* Skip below */ parse_command: if (str == NULL) { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } len = strspn(str, WHITESPACE "="); if (*activep && *charptr == NULL) *charptr = xstrdup(str + len); argv_consume(&ac); break; case oProxyJump: if (str == NULL) { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } len = strspn(str, WHITESPACE "="); /* XXX use argv? */ if (parse_jump(str + len, options, *activep) == -1) { error("%.200s line %d: Invalid ProxyJump \"%s\"", filename, linenum, str + len); goto out; } argv_consume(&ac); break; case oPort: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } value = a2port(arg); if (value <= 0) { error("%.200s line %d: Bad port '%s'.", filename, linenum, arg); goto out; } if (*activep && options->port == -1) options->port = value; break; case oConnectionAttempts: intptr = &options->connection_attempts; parse_int: arg = argv_next(&ac, &av); if ((errstr = atoi_err(arg, &value)) != NULL) { error("%s line %d: integer value %s.", filename, linenum, errstr); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oCiphers: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !ciphers_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)){ error("%.200s line %d: Bad SSH2 cipher spec '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && options->ciphers == NULL) options->ciphers = xstrdup(arg); break; case oMacs: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !mac_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) { error("%.200s line %d: Bad SSH2 MAC spec '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && options->macs == NULL) options->macs = xstrdup(arg); break; case oKexAlgorithms: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !kex_names_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) { error("%.200s line %d: Bad SSH2 KexAlgorithms '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && options->kex_algorithms == NULL) options->kex_algorithms = xstrdup(arg); break; case oHostKeyAlgorithms: charptr = &options->hostkeyalgorithms; ca_only = 0; parse_pubkey_algos: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !sshkey_names_valid2(*arg == '+' || *arg == '^' ? arg + 1 : arg, 1, ca_only)) { error("%s line %d: Bad key types '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oCASignatureAlgorithms: charptr = &options->ca_sign_algorithms; ca_only = 1; goto parse_pubkey_algos; case oLogLevel: log_level_ptr = &options->log_level; arg = argv_next(&ac, &av); value = log_level_number(arg); if (value == SYSLOG_LEVEL_NOT_SET) { error("%.200s line %d: unsupported log level '%s'", filename, linenum, arg ? arg : ""); goto out; } if (*activep && *log_level_ptr == SYSLOG_LEVEL_NOT_SET) *log_level_ptr = (LogLevel) value; break; case oLogFacility: log_facility_ptr = &options->log_facility; arg = argv_next(&ac, &av); value = log_facility_number(arg); if (value == SYSLOG_FACILITY_NOT_SET) { error("%.200s line %d: unsupported log facility '%s'", filename, linenum, arg ? arg : ""); goto out; } if (*log_facility_ptr == -1) *log_facility_ptr = (SyslogFacility) value; break; case oLogVerbose: cppptr = &options->log_verbose; uintptr = &options->num_log_verbose; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (*activep && *uintptr == 0) { *cppptr = xrecallocarray(*cppptr, *uintptr, *uintptr + 1, sizeof(**cppptr)); (*cppptr)[(*uintptr)++] = xstrdup(arg); } } break; case oLocalForward: case oRemoteForward: case oDynamicForward: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } remotefwd = (opcode == oRemoteForward); dynamicfwd = (opcode == oDynamicForward); if (!dynamicfwd) { arg2 = argv_next(&ac, &av); if (arg2 == NULL || *arg2 == '\0') { if (remotefwd) dynamicfwd = 1; else { error("%.200s line %d: Missing target " "argument.", filename, linenum); goto out; } } else { /* construct a string for parse_forward */ snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); } } if (dynamicfwd) strlcpy(fwdarg, arg, sizeof(fwdarg)); if (parse_forward(&fwd, fwdarg, dynamicfwd, remotefwd) == 0) { error("%.200s line %d: Bad forwarding specification.", filename, linenum); goto out; } if (*activep) { if (remotefwd) { add_remote_forward(options, &fwd); } else { add_local_forward(options, &fwd); } } break; case oPermitRemoteOpen: uintptr = &options->num_permitted_remote_opens; cppptr = &options->permitted_remote_opens; uvalue = *uintptr; /* modified later */ i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { arg2 = xstrdup(arg); /* Allow any/none only in first position */ if (strcasecmp(arg, "none") == 0 || strcasecmp(arg, "any") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"%s\" " "argument must appear alone.", filename, linenum, keyword, arg); free(arg2); goto out; } } else { p = hpdelim(&arg); if (p == NULL) { fatal("%s line %d: missing host in %s", filename, linenum, lookup_opcode_name(opcode)); } p = cleanhostname(p); /* * don't want to use permitopen_port to avoid * dependency on channels.[ch] here. */ if (arg == NULL || (strcmp(arg, "*") != 0 && a2port(arg) <= 0)) { fatal("%s line %d: bad port number " "in %s", filename, linenum, lookup_opcode_name(opcode)); } } if (*activep && uvalue == 0) { opt_array_append(filename, linenum, lookup_opcode_name(opcode), cppptr, uintptr, arg2); } free(arg2); i++; } if (i == 0) fatal("%s line %d: missing %s specification", filename, linenum, lookup_opcode_name(opcode)); break; case oClearAllForwardings: intptr = &options->clear_forwardings; goto parse_flag; case oHost: if (cmdline) { error("Host directive not supported as a command-line " "option"); goto out; } *activep = 0; arg2 = NULL; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } if ((flags & SSHCONF_NEVERMATCH) != 0) { argv_consume(&ac); break; } negated = *arg == '!'; if (negated) arg++; if (match_pattern(host, arg)) { if (negated) { debug("%.200s line %d: Skipping Host " "block because of negated match " "for %.100s", filename, linenum, arg); *activep = 0; argv_consume(&ac); break; } if (!*activep) arg2 = arg; /* logged below */ *activep = 1; } } if (*activep) debug("%.200s line %d: Applying options for %.100s", filename, linenum, arg2); break; case oMatch: if (cmdline) { error("Host directive not supported as a command-line " "option"); goto out; } value = match_cfg_line(options, &str, pw, host, original_host, flags & SSHCONF_FINAL, want_final_pass, filename, linenum); if (value < 0) { error("%.200s line %d: Bad Match condition", filename, linenum); goto out; } *activep = (flags & SSHCONF_NEVERMATCH) ? 0 : value; /* * If match_cfg_line() didn't consume all its arguments then * arrange for the extra arguments check below to fail. */ if (str == NULL || *str == '\0') argv_consume(&ac); break; case oEscapeChar: intptr = &options->escape_char; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (strcmp(arg, "none") == 0) value = SSH_ESCAPECHAR_NONE; else if (arg[1] == '\0') value = (u_char) arg[0]; else if (arg[0] == '^' && arg[2] == 0 && (u_char) arg[1] >= 64 && (u_char) arg[1] < 128) value = (u_char) arg[1] & 31; else { error("%.200s line %d: Bad escape character.", filename, linenum); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oAddressFamily: intptr = &options->address_family; multistate_ptr = multistate_addressfamily; goto parse_multistate; case oEnableSSHKeysign: intptr = &options->enable_ssh_keysign; goto parse_flag; case oIdentitiesOnly: intptr = &options->identities_only; goto parse_flag; case oServerAliveInterval: intptr = &options->server_alive_interval; goto parse_time; case oServerAliveCountMax: intptr = &options->server_alive_count_max; goto parse_int; case oSendEnv: while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0' || strchr(arg, '=') != NULL) { error("%s line %d: Invalid environment name.", filename, linenum); goto out; } if (!*activep) continue; if (*arg == '-') { /* Removing an env var */ rm_env(options, arg, filename, linenum); continue; } opt_array_append(filename, linenum, lookup_opcode_name(opcode), &options->send_env, &options->num_send_env, arg); } break; case oSetEnv: value = options->num_setenv; while ((arg = argv_next(&ac, &av)) != NULL) { if (strchr(arg, '=') == NULL) { error("%s line %d: Invalid SetEnv.", filename, linenum); goto out; } if (!*activep || value != 0) continue; if (lookup_setenv_in_list(arg, options->setenv, options->num_setenv) != NULL) { debug2("%s line %d: ignoring duplicate env " "name \"%.64s\"", filename, linenum, arg); continue; } opt_array_append(filename, linenum, lookup_opcode_name(opcode), &options->setenv, &options->num_setenv, arg); } break; case oControlPath: charptr = &options->control_path; goto parse_string; case oControlMaster: intptr = &options->control_master; multistate_ptr = multistate_controlmaster; goto parse_multistate; case oControlPersist: /* no/false/yes/true, or a time spec */ intptr = &options->control_persist; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing ControlPersist" " argument.", filename, linenum); goto out; } value = 0; value2 = 0; /* timeout */ if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if ((value2 = convtime(arg)) >= 0) value = 1; else { error("%.200s line %d: Bad ControlPersist argument.", filename, linenum); goto out; } if (*activep && *intptr == -1) { *intptr = value; options->control_persist_timeout = value2; } break; case oHashKnownHosts: intptr = &options->hash_known_hosts; goto parse_flag; case oTunnel: intptr = &options->tun_open; multistate_ptr = multistate_tunnel; goto parse_multistate; case oTunnelDevice: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } value = a2tun(arg, &value2); if (value == SSH_TUNID_ERR) { error("%.200s line %d: Bad tun device.", filename, linenum); goto out; } if (*activep && options->tun_local == -1) { options->tun_local = value; options->tun_remote = value2; } break; case oLocalCommand: charptr = &options->local_command; goto parse_command; case oPermitLocalCommand: intptr = &options->permit_local_command; goto parse_flag; case oRemoteCommand: charptr = &options->remote_command; goto parse_command; case oVisualHostKey: intptr = &options->visual_host_key; goto parse_flag; case oInclude: if (cmdline) { error("Include directive not supported as a " "command-line option"); goto out; } value = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* * Ensure all paths are anchored. User configuration * files may begin with '~/' but system configurations * must not. If the path is relative, then treat it * as living in ~/.ssh for user configurations or * /etc/ssh for system ones. */ if (*arg == '~' && (flags & SSHCONF_USERCONF) == 0) { error("%.200s line %d: bad include path %s.", filename, linenum, arg); goto out; } if (!path_absolute(arg) && *arg != '~') { xasprintf(&arg2, "%s/%s", (flags & SSHCONF_USERCONF) ? "~/" _PATH_SSH_USER_DIR : SSHDIR, arg); } else arg2 = xstrdup(arg); memset(&gl, 0, sizeof(gl)); r = glob(arg2, GLOB_TILDE, NULL, &gl); if (r == GLOB_NOMATCH) { debug("%.200s line %d: include %s matched no " "files",filename, linenum, arg2); free(arg2); continue; } else if (r != 0) { error("%.200s line %d: glob failed for %s.", filename, linenum, arg2); goto out; } free(arg2); oactive = *activep; for (i = 0; i < gl.gl_pathc; i++) { debug3("%.200s line %d: Including file %s " "depth %d%s", filename, linenum, gl.gl_pathv[i], depth, oactive ? "" : " (parse only)"); r = read_config_file_depth(gl.gl_pathv[i], pw, host, original_host, options, flags | SSHCONF_CHECKPERM | (oactive ? 0 : SSHCONF_NEVERMATCH), activep, want_final_pass, depth + 1); if (r != 1 && errno != ENOENT) { error("Can't open user config file " "%.100s: %.100s", gl.gl_pathv[i], strerror(errno)); globfree(&gl); goto out; } /* * don't let Match in includes clobber the * containing file's Match state. */ *activep = oactive; if (r != 1) value = -1; } globfree(&gl); } if (value != 0) ret = value; break; case oIPQoS: arg = argv_next(&ac, &av); if ((value = parse_ipqos(arg)) == -1) { error("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); goto out; } arg = argv_next(&ac, &av); if (arg == NULL) value2 = value; else if ((value2 = parse_ipqos(arg)) == -1) { error("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); goto out; } if (*activep && options->ip_qos_interactive == -1) { options->ip_qos_interactive = value; options->ip_qos_bulk = value2; } break; case oRequestTTY: intptr = &options->request_tty; multistate_ptr = multistate_requesttty; goto parse_multistate; case oSessionType: intptr = &options->session_type; multistate_ptr = multistate_sessiontype; goto parse_multistate; case oStdinNull: intptr = &options->stdin_null; goto parse_flag; case oForkAfterAuthentication: intptr = &options->fork_after_authentication; goto parse_flag; case oIgnoreUnknown: charptr = &options->ignored_unknown; goto parse_string; case oProxyUseFdpass: intptr = &options->proxy_use_fdpass; goto parse_flag; case oCanonicalDomains: value = options->num_canonical_domains != 0; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (!valid_domain(arg, 1, &errstr)) { error("%s line %d: %s", filename, linenum, errstr); goto out; } if (!*activep || value) continue; if (options->num_canonical_domains >= MAX_CANON_DOMAINS) { error("%s line %d: too many hostname suffixes.", filename, linenum); goto out; } options->canonical_domains[ options->num_canonical_domains++] = xstrdup(arg); } break; case oCanonicalizePermittedCNAMEs: value = options->num_permitted_cnames != 0; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { /* * Either 'none' (only in first position), '*' for * everything or 'list:list' */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } arg2 = ""; } else if (strcmp(arg, "*") == 0) { arg2 = arg; } else { lowercase(arg); if ((arg2 = strchr(arg, ':')) == NULL || arg2[1] == '\0') { error("%s line %d: " "Invalid permitted CNAME \"%s\"", filename, linenum, arg); goto out; } *arg2 = '\0'; arg2++; } i++; if (!*activep || value) continue; if (options->num_permitted_cnames >= MAX_CANON_DOMAINS) { error("%s line %d: too many permitted CNAMEs.", filename, linenum); goto out; } cname = options->permitted_cnames + options->num_permitted_cnames++; cname->source_list = xstrdup(arg); cname->target_list = xstrdup(arg2); } break; case oCanonicalizeHostname: intptr = &options->canonicalize_hostname; multistate_ptr = multistate_canonicalizehostname; goto parse_multistate; case oCanonicalizeMaxDots: intptr = &options->canonicalize_max_dots; goto parse_int; case oCanonicalizeFallbackLocal: intptr = &options->canonicalize_fallback_local; goto parse_flag; case oStreamLocalBindMask: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing StreamLocalBindMask " "argument.", filename, linenum); goto out; } /* Parse mode in octal format */ value = strtol(arg, &endofnumber, 8); if (arg == endofnumber || value < 0 || value > 0777) { error("%.200s line %d: Bad mask.", filename, linenum); goto out; } options->fwd_opts.streamlocal_bind_mask = (mode_t)value; break; case oStreamLocalBindUnlink: intptr = &options->fwd_opts.streamlocal_bind_unlink; goto parse_flag; case oRevokedHostKeys: charptr = &options->revoked_host_keys; goto parse_string; case oFingerprintHash: intptr = &options->fingerprint_hash; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if ((value = ssh_digest_alg_by_name(arg)) == -1) { error("%.200s line %d: Invalid hash algorithm \"%s\".", filename, linenum, arg); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oUpdateHostkeys: intptr = &options->update_hostkeys; multistate_ptr = multistate_yesnoask; goto parse_multistate; case oHostbasedAcceptedAlgorithms: charptr = &options->hostbased_accepted_algos; ca_only = 0; goto parse_pubkey_algos; case oPubkeyAcceptedAlgorithms: charptr = &options->pubkey_accepted_algos; ca_only = 0; goto parse_pubkey_algos; case oAddKeysToAgent: arg = argv_next(&ac, &av); arg2 = argv_next(&ac, &av); value = parse_multistate_value(arg, filename, linenum, multistate_yesnoaskconfirm); value2 = 0; /* unlimited lifespan by default */ if (value == 3 && arg2 != NULL) { /* allow "AddKeysToAgent confirm 5m" */ if ((value2 = convtime(arg2)) == -1) { error("%s line %d: invalid time value.", filename, linenum); goto out; } } else if (value == -1 && arg2 == NULL) { if ((value2 = convtime(arg)) == -1) { error("%s line %d: unsupported option", filename, linenum); goto out; } value = 1; /* yes */ } else if (value == -1 || arg2 != NULL) { error("%s line %d: unsupported option", filename, linenum); goto out; } if (*activep && options->add_keys_to_agent == -1) { options->add_keys_to_agent = value; options->add_keys_to_agent_lifespan = value2; } break; case oIdentityAgent: charptr = &options->identity_agent; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } parse_agent_path: /* Extra validation if the string represents an env var. */ if ((arg2 = dollar_expand(&r, arg)) == NULL || r) { error("%.200s line %d: Invalid environment expansion " "%s.", filename, linenum, arg); goto out; } free(arg2); /* check for legacy environment format */ if (arg[0] == '$' && arg[1] != '{' && !valid_env_name(arg + 1)) { error("%.200s line %d: Invalid environment name %s.", filename, linenum, arg); goto out; } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oEnableEscapeCommandline: intptr = &options->enable_escape_commandline; goto parse_flag; case oRequiredRSASize: intptr = &options->required_rsa_size; goto parse_int; + case oObscureKeystrokeTiming: + value = -1; + while ((arg = argv_next(&ac, &av)) != NULL) { + if (value != -1) { + error("%s line %d: invalid arguments", + filename, linenum); + goto out; + } + if (strcmp(arg, "yes") == 0 || + strcmp(arg, "true") == 0) + value = SSH_KEYSTROKE_DEFAULT_INTERVAL_MS; + else if (strcmp(arg, "no") == 0 || + strcmp(arg, "false") == 0) + value = 0; + else if (strncmp(arg, "interval:", 9) == 0) { + if ((errstr = atoi_err(arg + 9, + &value)) != NULL) { + error("%s line %d: integer value %s.", + filename, linenum, errstr); + goto out; + } + if (value <= 0 || value > 1000) { + error("%s line %d: value out of range.", + filename, linenum); + goto out; + } + } else { + error("%s line %d: unsupported argument \"%s\"", + filename, linenum, arg); + goto out; + } + } + if (value == -1) { + error("%s line %d: missing argument", + filename, linenum); + goto out; + } + intptr = &options->obscure_keystroke_timing_interval; + if (*activep && *intptr == -1) + *intptr = value; + break; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); argv_consume(&ac); break; case oUnsupported: error("%s line %d: Unsupported option \"%s\"", filename, linenum, keyword); argv_consume(&ac); break; default: error("%s line %d: Unimplemented opcode %d", filename, linenum, opcode); goto out; } /* Check that there is no garbage at end of line. */ if (ac > 0) { error("%.200s line %d: keyword %s extra arguments " "at end of line", filename, linenum, keyword); goto out; } /* success */ ret = 0; out: argv_free(oav, oac); return ret; } /* * Reads the config file and modifies the options accordingly. Options * should already be initialized before this call. This never returns if * there is an error. If the file does not exist, this returns 0. */ int read_config_file(const char *filename, struct passwd *pw, const char *host, const char *original_host, Options *options, int flags, int *want_final_pass) { int active = 1; return read_config_file_depth(filename, pw, host, original_host, options, flags, &active, want_final_pass, 0); } #define READCONF_MAX_DEPTH 16 static int read_config_file_depth(const char *filename, struct passwd *pw, const char *host, const char *original_host, Options *options, int flags, int *activep, int *want_final_pass, int depth) { FILE *f; char *line = NULL; size_t linesize = 0; int linenum; int bad_options = 0; if (depth < 0 || depth > READCONF_MAX_DEPTH) fatal("Too many recursive configuration includes"); if ((f = fopen(filename, "r")) == NULL) return 0; if (flags & SSHCONF_CHECKPERM) { struct stat sb; if (fstat(fileno(f), &sb) == -1) fatal("fstat %s: %s", filename, strerror(errno)); if (((sb.st_uid != 0 && sb.st_uid != getuid()) || (sb.st_mode & 022) != 0)) fatal("Bad owner or permissions on %s", filename); } debug("Reading configuration data %.200s", filename); /* * Mark that we are now processing the options. This flag is turned * on/off by Host specifications. */ linenum = 0; while (getline(&line, &linesize, f) != -1) { /* Update line number counter. */ linenum++; /* * Trim out comments and strip whitespace. * NB - preserve newlines, they are needed to reproduce * line numbers later for error messages. */ if (process_config_line_depth(options, pw, host, original_host, line, filename, linenum, activep, flags, want_final_pass, depth) != 0) bad_options++; } free(line); fclose(f); if (bad_options > 0) fatal("%s: terminating, %d bad configuration options", filename, bad_options); return 1; } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ int option_clear_or_none(const char *o) { return o == NULL || strcasecmp(o, "none") == 0; } /* * Returns 1 if CanonicalizePermittedCNAMEs have been specified, 0 otherwise. * Allowed to be called on non-final configuration. */ int config_has_permitted_cnames(Options *options) { if (options->num_permitted_cnames == 1 && strcasecmp(options->permitted_cnames[0].source_list, "none") == 0 && strcmp(options->permitted_cnames[0].target_list, "") == 0) return 0; return options->num_permitted_cnames > 0; } /* * Initializes options to special values that indicate that they have not yet * been set. Read_config_file will only set options with this value. Options * are processed in the following order: command line, user config file, * system config file. Last, fill_default_options is called. */ void initialize_options(Options * options) { memset(options, 'X', sizeof(*options)); options->host_arg = NULL; options->forward_agent = -1; options->forward_agent_sock_path = NULL; options->forward_x11 = -1; options->forward_x11_trusted = -1; options->forward_x11_timeout = -1; options->stdio_forward_host = NULL; options->stdio_forward_port = 0; options->clear_forwardings = -1; options->exit_on_forward_failure = -1; options->xauth_location = NULL; options->fwd_opts.gateway_ports = -1; options->fwd_opts.streamlocal_bind_mask = (mode_t)-1; options->fwd_opts.streamlocal_bind_unlink = -1; options->pubkey_authentication = -1; options->gss_authentication = -1; options->gss_deleg_creds = -1; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; options->hostbased_authentication = -1; options->batch_mode = -1; options->check_host_ip = -1; options->strict_host_key_checking = -1; options->compression = -1; options->tcp_keep_alive = -1; options->port = -1; options->address_family = -1; options->connection_attempts = -1; options->connection_timeout = -1; options->number_of_password_prompts = -1; options->ciphers = NULL; options->macs = NULL; options->kex_algorithms = NULL; options->hostkeyalgorithms = NULL; options->ca_sign_algorithms = NULL; options->num_identity_files = 0; memset(options->identity_keys, 0, sizeof(options->identity_keys)); options->num_certificate_files = 0; memset(options->certificates, 0, sizeof(options->certificates)); options->hostname = NULL; options->host_key_alias = NULL; options->proxy_command = NULL; options->jump_user = NULL; options->jump_host = NULL; options->jump_port = -1; options->jump_extra = NULL; options->user = NULL; options->escape_char = -1; options->num_system_hostfiles = 0; options->num_user_hostfiles = 0; options->local_forwards = NULL; options->num_local_forwards = 0; options->remote_forwards = NULL; options->num_remote_forwards = 0; options->permitted_remote_opens = NULL; options->num_permitted_remote_opens = 0; options->log_facility = SYSLOG_FACILITY_NOT_SET; options->log_level = SYSLOG_LEVEL_NOT_SET; options->num_log_verbose = 0; options->log_verbose = NULL; options->preferred_authentications = NULL; options->bind_address = NULL; options->bind_interface = NULL; options->pkcs11_provider = NULL; options->sk_provider = NULL; options->enable_ssh_keysign = - 1; options->no_host_authentication_for_localhost = - 1; options->identities_only = - 1; options->rekey_limit = - 1; options->rekey_interval = -1; options->verify_host_key_dns = -1; options->server_alive_interval = -1; options->server_alive_count_max = -1; options->send_env = NULL; options->num_send_env = 0; options->setenv = NULL; options->num_setenv = 0; options->control_path = NULL; options->control_master = -1; options->control_persist = -1; options->control_persist_timeout = 0; options->hash_known_hosts = -1; options->tun_open = -1; options->tun_local = -1; options->tun_remote = -1; options->local_command = NULL; options->permit_local_command = -1; options->remote_command = NULL; options->add_keys_to_agent = -1; options->add_keys_to_agent_lifespan = -1; options->identity_agent = NULL; options->visual_host_key = -1; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; options->request_tty = -1; options->session_type = -1; options->stdin_null = -1; options->fork_after_authentication = -1; options->proxy_use_fdpass = -1; options->ignored_unknown = NULL; options->num_canonical_domains = 0; options->num_permitted_cnames = 0; options->canonicalize_max_dots = -1; options->canonicalize_fallback_local = -1; options->canonicalize_hostname = -1; options->revoked_host_keys = NULL; options->fingerprint_hash = -1; options->update_hostkeys = -1; options->hostbased_accepted_algos = NULL; options->pubkey_accepted_algos = NULL; options->known_hosts_command = NULL; options->required_rsa_size = -1; options->enable_escape_commandline = -1; + options->obscure_keystroke_timing_interval = -1; options->tag = NULL; } /* * A petite version of fill_default_options() that just fills the options * needed for hostname canonicalization to proceed. */ void fill_default_options_for_canonicalization(Options *options) { if (options->canonicalize_max_dots == -1) options->canonicalize_max_dots = 1; if (options->canonicalize_fallback_local == -1) options->canonicalize_fallback_local = 1; if (options->canonicalize_hostname == -1) options->canonicalize_hostname = SSH_CANONICALISE_NO; } /* * Called after processing other sources of option data, this fills those * options for which no value has been specified with their default values. */ int fill_default_options(Options * options) { char *all_cipher, *all_mac, *all_kex, *all_key, *all_sig; char *def_cipher, *def_mac, *def_kex, *def_key, *def_sig; int ret = 0, r; if (options->forward_agent == -1) options->forward_agent = 0; if (options->forward_x11 == -1) options->forward_x11 = 0; if (options->forward_x11_trusted == -1) options->forward_x11_trusted = 0; if (options->forward_x11_timeout == -1) options->forward_x11_timeout = 1200; /* * stdio forwarding (-W) changes the default for these but we defer * setting the values so they can be overridden. */ if (options->exit_on_forward_failure == -1) options->exit_on_forward_failure = options->stdio_forward_host != NULL ? 1 : 0; if (options->clear_forwardings == -1) options->clear_forwardings = options->stdio_forward_host != NULL ? 1 : 0; if (options->clear_forwardings == 1) clear_forwardings(options); if (options->xauth_location == NULL) options->xauth_location = xstrdup(_PATH_XAUTH); if (options->fwd_opts.gateway_ports == -1) options->fwd_opts.gateway_ports = 0; if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1) options->fwd_opts.streamlocal_bind_mask = 0177; if (options->fwd_opts.streamlocal_bind_unlink == -1) options->fwd_opts.streamlocal_bind_unlink = 0; if (options->pubkey_authentication == -1) options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL; if (options->gss_authentication == -1) options->gss_authentication = 0; if (options->gss_deleg_creds == -1) options->gss_deleg_creds = 0; if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 1; if (options->hostbased_authentication == -1) options->hostbased_authentication = 0; if (options->batch_mode == -1) options->batch_mode = 0; if (options->check_host_ip == -1) options->check_host_ip = 0; if (options->strict_host_key_checking == -1) options->strict_host_key_checking = SSH_STRICT_HOSTKEY_ASK; if (options->compression == -1) options->compression = 0; if (options->tcp_keep_alive == -1) options->tcp_keep_alive = 1; if (options->port == -1) options->port = 0; /* Filled in ssh_connect. */ if (options->address_family == -1) options->address_family = AF_UNSPEC; if (options->connection_attempts == -1) options->connection_attempts = 1; if (options->number_of_password_prompts == -1) options->number_of_password_prompts = 3; /* options->hostkeyalgorithms, default set in myproposals.h */ if (options->add_keys_to_agent == -1) { options->add_keys_to_agent = 0; options->add_keys_to_agent_lifespan = 0; } if (options->num_identity_files == 0) { add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_RSA, 0); #ifdef OPENSSL_HAS_ECC add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ECDSA, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ECDSA_SK, 0); #endif add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ED25519, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ED25519_SK, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_XMSS, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_DSA, 0); } if (options->escape_char == -1) options->escape_char = '~'; if (options->num_system_hostfiles == 0) { options->system_hostfiles[options->num_system_hostfiles++] = xstrdup(_PATH_SSH_SYSTEM_HOSTFILE); options->system_hostfiles[options->num_system_hostfiles++] = xstrdup(_PATH_SSH_SYSTEM_HOSTFILE2); } if (options->update_hostkeys == -1) { if (options->verify_host_key_dns <= 0 && (options->num_user_hostfiles == 0 || (options->num_user_hostfiles == 1 && strcmp(options-> user_hostfiles[0], _PATH_SSH_USER_HOSTFILE) == 0))) options->update_hostkeys = SSH_UPDATE_HOSTKEYS_YES; else options->update_hostkeys = SSH_UPDATE_HOSTKEYS_NO; } if (options->num_user_hostfiles == 0) { options->user_hostfiles[options->num_user_hostfiles++] = xstrdup(_PATH_SSH_USER_HOSTFILE); options->user_hostfiles[options->num_user_hostfiles++] = xstrdup(_PATH_SSH_USER_HOSTFILE2); } if (options->log_level == SYSLOG_LEVEL_NOT_SET) options->log_level = SYSLOG_LEVEL_INFO; if (options->log_facility == SYSLOG_FACILITY_NOT_SET) options->log_facility = SYSLOG_FACILITY_USER; if (options->no_host_authentication_for_localhost == - 1) options->no_host_authentication_for_localhost = 0; if (options->identities_only == -1) options->identities_only = 0; if (options->enable_ssh_keysign == -1) options->enable_ssh_keysign = 0; if (options->rekey_limit == -1) options->rekey_limit = 0; if (options->rekey_interval == -1) options->rekey_interval = 0; if (options->verify_host_key_dns == -1) options->verify_host_key_dns = 0; if (options->server_alive_interval == -1) options->server_alive_interval = 0; if (options->server_alive_count_max == -1) options->server_alive_count_max = 3; if (options->control_master == -1) options->control_master = 0; if (options->control_persist == -1) { options->control_persist = 0; options->control_persist_timeout = 0; } if (options->hash_known_hosts == -1) options->hash_known_hosts = 0; if (options->tun_open == -1) options->tun_open = SSH_TUNMODE_NO; if (options->tun_local == -1) options->tun_local = SSH_TUNID_ANY; if (options->tun_remote == -1) options->tun_remote = SSH_TUNID_ANY; if (options->permit_local_command == -1) options->permit_local_command = 0; if (options->visual_host_key == -1) options->visual_host_key = 0; if (options->ip_qos_interactive == -1) options->ip_qos_interactive = IPTOS_DSCP_AF21; if (options->ip_qos_bulk == -1) options->ip_qos_bulk = IPTOS_DSCP_CS1; if (options->request_tty == -1) options->request_tty = REQUEST_TTY_AUTO; if (options->session_type == -1) options->session_type = SESSION_TYPE_DEFAULT; if (options->stdin_null == -1) options->stdin_null = 0; if (options->fork_after_authentication == -1) options->fork_after_authentication = 0; if (options->proxy_use_fdpass == -1) options->proxy_use_fdpass = 0; if (options->canonicalize_max_dots == -1) options->canonicalize_max_dots = 1; if (options->canonicalize_fallback_local == -1) options->canonicalize_fallback_local = 1; if (options->canonicalize_hostname == -1) options->canonicalize_hostname = SSH_CANONICALISE_NO; if (options->fingerprint_hash == -1) options->fingerprint_hash = SSH_FP_HASH_DEFAULT; #ifdef ENABLE_SK_INTERNAL if (options->sk_provider == NULL) options->sk_provider = xstrdup("internal"); #else if (options->sk_provider == NULL) options->sk_provider = xstrdup("$SSH_SK_PROVIDER"); #endif if (options->required_rsa_size == -1) options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; if (options->enable_escape_commandline == -1) options->enable_escape_commandline = 0; + if (options->obscure_keystroke_timing_interval == -1) { + options->obscure_keystroke_timing_interval = + SSH_KEYSTROKE_DEFAULT_INTERVAL_MS; + } /* Expand KEX name lists */ all_cipher = cipher_alg_list(',', 0); all_mac = mac_alg_list(','); all_kex = kex_alg_list(','); all_key = sshkey_alg_list(0, 0, 1, ','); all_sig = sshkey_alg_list(0, 1, 1, ','); /* remove unsupported algos from default lists */ def_cipher = match_filter_allowlist(KEX_CLIENT_ENCRYPT, all_cipher); def_mac = match_filter_allowlist(KEX_CLIENT_MAC, all_mac); def_kex = match_filter_allowlist(KEX_CLIENT_KEX, all_kex); def_key = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key); def_sig = match_filter_allowlist(SSH_ALLOWED_CA_SIGALGS, all_sig); #define ASSEMBLE(what, defaults, all) \ do { \ if ((r = kex_assemble_names(&options->what, \ defaults, all)) != 0) { \ error_fr(r, "%s", #what); \ goto fail; \ } \ } while (0) ASSEMBLE(ciphers, def_cipher, all_cipher); ASSEMBLE(macs, def_mac, all_mac); ASSEMBLE(kex_algorithms, def_kex, all_kex); ASSEMBLE(hostbased_accepted_algos, def_key, all_key); ASSEMBLE(pubkey_accepted_algos, def_key, all_key); ASSEMBLE(ca_sign_algorithms, def_sig, all_sig); #undef ASSEMBLE #define CLEAR_ON_NONE(v) \ do { \ if (option_clear_or_none(v)) { \ free(v); \ v = NULL; \ } \ } while(0) CLEAR_ON_NONE(options->local_command); CLEAR_ON_NONE(options->remote_command); CLEAR_ON_NONE(options->proxy_command); CLEAR_ON_NONE(options->control_path); CLEAR_ON_NONE(options->revoked_host_keys); CLEAR_ON_NONE(options->pkcs11_provider); CLEAR_ON_NONE(options->sk_provider); CLEAR_ON_NONE(options->known_hosts_command); if (options->jump_host != NULL && strcmp(options->jump_host, "none") == 0 && options->jump_port == 0 && options->jump_user == NULL) { free(options->jump_host); options->jump_host = NULL; } if (options->num_permitted_cnames == 1 && !config_has_permitted_cnames(options)) { /* clean up CanonicalizePermittedCNAMEs=none */ free(options->permitted_cnames[0].source_list); free(options->permitted_cnames[0].target_list); memset(options->permitted_cnames, '\0', sizeof(*options->permitted_cnames)); options->num_permitted_cnames = 0; } /* options->identity_agent distinguishes NULL from 'none' */ /* options->user will be set in the main program if appropriate */ /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ /* options->preferred_authentications will be set in ssh */ /* success */ ret = 0; fail: free(all_cipher); free(all_mac); free(all_kex); free(all_key); free(all_sig); free(def_cipher); free(def_mac); free(def_kex); free(def_key); free(def_sig); return ret; } void free_options(Options *o) { int i; if (o == NULL) return; #define FREE_ARRAY(type, n, a) \ do { \ type _i; \ for (_i = 0; _i < (n); _i++) \ free((a)[_i]); \ } while (0) free(o->forward_agent_sock_path); free(o->xauth_location); FREE_ARRAY(u_int, o->num_log_verbose, o->log_verbose); free(o->log_verbose); free(o->ciphers); free(o->macs); free(o->hostkeyalgorithms); free(o->kex_algorithms); free(o->ca_sign_algorithms); free(o->hostname); free(o->host_key_alias); free(o->proxy_command); free(o->user); FREE_ARRAY(u_int, o->num_system_hostfiles, o->system_hostfiles); FREE_ARRAY(u_int, o->num_user_hostfiles, o->user_hostfiles); free(o->preferred_authentications); free(o->bind_address); free(o->bind_interface); free(o->pkcs11_provider); free(o->sk_provider); for (i = 0; i < o->num_identity_files; i++) { free(o->identity_files[i]); sshkey_free(o->identity_keys[i]); } for (i = 0; i < o->num_certificate_files; i++) { free(o->certificate_files[i]); sshkey_free(o->certificates[i]); } free(o->identity_agent); for (i = 0; i < o->num_local_forwards; i++) { free(o->local_forwards[i].listen_host); free(o->local_forwards[i].listen_path); free(o->local_forwards[i].connect_host); free(o->local_forwards[i].connect_path); } free(o->local_forwards); for (i = 0; i < o->num_remote_forwards; i++) { free(o->remote_forwards[i].listen_host); free(o->remote_forwards[i].listen_path); free(o->remote_forwards[i].connect_host); free(o->remote_forwards[i].connect_path); } free(o->remote_forwards); free(o->stdio_forward_host); FREE_ARRAY(u_int, o->num_send_env, o->send_env); free(o->send_env); FREE_ARRAY(u_int, o->num_setenv, o->setenv); free(o->setenv); free(o->control_path); free(o->local_command); free(o->remote_command); FREE_ARRAY(int, o->num_canonical_domains, o->canonical_domains); for (i = 0; i < o->num_permitted_cnames; i++) { free(o->permitted_cnames[i].source_list); free(o->permitted_cnames[i].target_list); } free(o->revoked_host_keys); free(o->hostbased_accepted_algos); free(o->pubkey_accepted_algos); free(o->jump_user); free(o->jump_host); free(o->jump_extra); free(o->ignored_unknown); explicit_bzero(o, sizeof(*o)); #undef FREE_ARRAY } struct fwdarg { char *arg; int ispath; }; /* * parse_fwd_field * parses the next field in a port forwarding specification. * sets fwd to the parsed field and advances p past the colon * or sets it to NULL at end of string. * returns 0 on success, else non-zero. */ static int parse_fwd_field(char **p, struct fwdarg *fwd) { char *ep, *cp = *p; int ispath = 0; if (*cp == '\0') { *p = NULL; return -1; /* end of string */ } /* * A field escaped with square brackets is used literally. * XXX - allow ']' to be escaped via backslash? */ if (*cp == '[') { /* find matching ']' */ for (ep = cp + 1; *ep != ']' && *ep != '\0'; ep++) { if (*ep == '/') ispath = 1; } /* no matching ']' or not at end of field. */ if (ep[0] != ']' || (ep[1] != ':' && ep[1] != '\0')) return -1; /* NUL terminate the field and advance p past the colon */ *ep++ = '\0'; if (*ep != '\0') *ep++ = '\0'; fwd->arg = cp + 1; fwd->ispath = ispath; *p = ep; return 0; } for (cp = *p; *cp != '\0'; cp++) { switch (*cp) { case '\\': memmove(cp, cp + 1, strlen(cp + 1) + 1); if (*cp == '\0') return -1; break; case '/': ispath = 1; break; case ':': *cp++ = '\0'; goto done; } } done: fwd->arg = *p; fwd->ispath = ispath; *p = cp; return 0; } /* * parse_forward * parses a string containing a port forwarding specification of the form: * dynamicfwd == 0 * [listenhost:]listenport|listenpath:connecthost:connectport|connectpath * listenpath:connectpath * dynamicfwd == 1 * [listenhost:]listenport * returns number of arguments parsed or zero on error */ int parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd) { struct fwdarg fwdargs[4]; char *p, *cp; int i, err; memset(fwd, 0, sizeof(*fwd)); memset(fwdargs, 0, sizeof(fwdargs)); /* * We expand environment variables before checking if we think they're * paths so that if ${VAR} expands to a fully qualified path it is * treated as a path. */ cp = p = dollar_expand(&err, fwdspec); if (p == NULL || err) return 0; /* skip leading spaces */ while (isspace((u_char)*cp)) cp++; for (i = 0; i < 4; ++i) { if (parse_fwd_field(&cp, &fwdargs[i]) != 0) break; } /* Check for trailing garbage */ if (cp != NULL && *cp != '\0') { i = 0; /* failure */ } switch (i) { case 1: if (fwdargs[0].ispath) { fwd->listen_path = xstrdup(fwdargs[0].arg); fwd->listen_port = PORT_STREAMLOCAL; } else { fwd->listen_host = NULL; fwd->listen_port = a2port(fwdargs[0].arg); } fwd->connect_host = xstrdup("socks"); break; case 2: if (fwdargs[0].ispath && fwdargs[1].ispath) { fwd->listen_path = xstrdup(fwdargs[0].arg); fwd->listen_port = PORT_STREAMLOCAL; fwd->connect_path = xstrdup(fwdargs[1].arg); fwd->connect_port = PORT_STREAMLOCAL; } else if (fwdargs[1].ispath) { fwd->listen_host = NULL; fwd->listen_port = a2port(fwdargs[0].arg); fwd->connect_path = xstrdup(fwdargs[1].arg); fwd->connect_port = PORT_STREAMLOCAL; } else { fwd->listen_host = xstrdup(fwdargs[0].arg); fwd->listen_port = a2port(fwdargs[1].arg); fwd->connect_host = xstrdup("socks"); } break; case 3: if (fwdargs[0].ispath) { fwd->listen_path = xstrdup(fwdargs[0].arg); fwd->listen_port = PORT_STREAMLOCAL; fwd->connect_host = xstrdup(fwdargs[1].arg); fwd->connect_port = a2port(fwdargs[2].arg); } else if (fwdargs[2].ispath) { fwd->listen_host = xstrdup(fwdargs[0].arg); fwd->listen_port = a2port(fwdargs[1].arg); fwd->connect_path = xstrdup(fwdargs[2].arg); fwd->connect_port = PORT_STREAMLOCAL; } else { fwd->listen_host = NULL; fwd->listen_port = a2port(fwdargs[0].arg); fwd->connect_host = xstrdup(fwdargs[1].arg); fwd->connect_port = a2port(fwdargs[2].arg); } break; case 4: fwd->listen_host = xstrdup(fwdargs[0].arg); fwd->listen_port = a2port(fwdargs[1].arg); fwd->connect_host = xstrdup(fwdargs[2].arg); fwd->connect_port = a2port(fwdargs[3].arg); break; default: i = 0; /* failure */ } free(p); if (dynamicfwd) { if (!(i == 1 || i == 2)) goto fail_free; } else { if (!(i == 3 || i == 4)) { if (fwd->connect_path == NULL && fwd->listen_path == NULL) goto fail_free; } if (fwd->connect_port <= 0 && fwd->connect_path == NULL) goto fail_free; } if ((fwd->listen_port < 0 && fwd->listen_path == NULL) || (!remotefwd && fwd->listen_port == 0)) goto fail_free; if (fwd->connect_host != NULL && strlen(fwd->connect_host) >= NI_MAXHOST) goto fail_free; /* * XXX - if connecting to a remote socket, max sun len may not * match this host */ if (fwd->connect_path != NULL && strlen(fwd->connect_path) >= PATH_MAX_SUN) goto fail_free; if (fwd->listen_host != NULL && strlen(fwd->listen_host) >= NI_MAXHOST) goto fail_free; if (fwd->listen_path != NULL && strlen(fwd->listen_path) >= PATH_MAX_SUN) goto fail_free; return (i); fail_free: free(fwd->connect_host); fwd->connect_host = NULL; free(fwd->connect_path); fwd->connect_path = NULL; free(fwd->listen_host); fwd->listen_host = NULL; free(fwd->listen_path); fwd->listen_path = NULL; return (0); } int parse_jump(const char *s, Options *o, int active) { char *orig, *sdup, *cp; char *host = NULL, *user = NULL; int r, ret = -1, port = -1, first; active &= o->proxy_command == NULL && o->jump_host == NULL; orig = sdup = xstrdup(s); /* Remove comment and trailing whitespace */ if ((cp = strchr(orig, '#')) != NULL) *cp = '\0'; rtrim(orig); first = active; do { if (strcasecmp(s, "none") == 0) break; if ((cp = strrchr(sdup, ',')) == NULL) cp = sdup; /* last */ else *cp++ = '\0'; if (first) { /* First argument and configuration is active */ r = parse_ssh_uri(cp, &user, &host, &port); if (r == -1 || (r == 1 && parse_user_host_port(cp, &user, &host, &port) != 0)) goto out; } else { /* Subsequent argument or inactive configuration */ r = parse_ssh_uri(cp, NULL, NULL, NULL); if (r == -1 || (r == 1 && parse_user_host_port(cp, NULL, NULL, NULL) != 0)) goto out; } first = 0; /* only check syntax for subsequent hosts */ } while (cp != sdup); /* success */ if (active) { if (strcasecmp(s, "none") == 0) { o->jump_host = xstrdup("none"); o->jump_port = 0; } else { o->jump_user = user; o->jump_host = host; o->jump_port = port; o->proxy_command = xstrdup("none"); user = host = NULL; if ((cp = strrchr(s, ',')) != NULL && cp != s) { o->jump_extra = xstrdup(s); o->jump_extra[cp - s] = '\0'; } } } ret = 0; out: free(orig); free(user); free(host); return ret; } int parse_ssh_uri(const char *uri, char **userp, char **hostp, int *portp) { char *user = NULL, *host = NULL, *path = NULL; int r, port; r = parse_uri("ssh", uri, &user, &host, &port, &path); if (r == 0 && path != NULL) r = -1; /* path not allowed */ if (r == 0) { if (userp != NULL) { *userp = user; user = NULL; } if (hostp != NULL) { *hostp = host; host = NULL; } if (portp != NULL) *portp = port; } free(user); free(host); free(path); return r; } /* XXX the following is a near-vebatim copy from servconf.c; refactor */ static const char * fmt_multistate_int(int val, const struct multistate *m) { u_int i; for (i = 0; m[i].key != NULL; i++) { if (m[i].value == val) return m[i].key; } return "UNKNOWN"; } static const char * fmt_intarg(OpCodes code, int val) { if (val == -1) return "unset"; switch (code) { case oAddressFamily: return fmt_multistate_int(val, multistate_addressfamily); case oVerifyHostKeyDNS: case oUpdateHostkeys: return fmt_multistate_int(val, multistate_yesnoask); case oStrictHostKeyChecking: return fmt_multistate_int(val, multistate_strict_hostkey); case oControlMaster: return fmt_multistate_int(val, multistate_controlmaster); case oTunnel: return fmt_multistate_int(val, multistate_tunnel); case oRequestTTY: return fmt_multistate_int(val, multistate_requesttty); case oSessionType: return fmt_multistate_int(val, multistate_sessiontype); case oCanonicalizeHostname: return fmt_multistate_int(val, multistate_canonicalizehostname); case oAddKeysToAgent: return fmt_multistate_int(val, multistate_yesnoaskconfirm); case oPubkeyAuthentication: return fmt_multistate_int(val, multistate_pubkey_auth); case oFingerprintHash: return ssh_digest_alg_name(val); default: switch (val) { case 0: return "no"; case 1: return "yes"; default: return "UNKNOWN"; } } } static const char * lookup_opcode_name(OpCodes code) { u_int i; for (i = 0; keywords[i].name != NULL; i++) if (keywords[i].opcode == code) return(keywords[i].name); return "UNKNOWN"; } static void dump_cfg_int(OpCodes code, int val) { + if (code == oObscureKeystrokeTiming) { + if (val == 0) { + printf("%s no\n", lookup_opcode_name(code)); + return; + } else if (val == SSH_KEYSTROKE_DEFAULT_INTERVAL_MS) { + printf("%s yes\n", lookup_opcode_name(code)); + return; + } + /* FALLTHROUGH */ + } printf("%s %d\n", lookup_opcode_name(code), val); } static void dump_cfg_fmtint(OpCodes code, int val) { printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val)); } static void dump_cfg_string(OpCodes code, const char *val) { if (val == NULL) return; printf("%s %s\n", lookup_opcode_name(code), val); } static void dump_cfg_strarray(OpCodes code, u_int count, char **vals) { u_int i; for (i = 0; i < count; i++) printf("%s %s\n", lookup_opcode_name(code), vals[i]); } static void dump_cfg_strarray_oneline(OpCodes code, u_int count, char **vals) { u_int i; printf("%s", lookup_opcode_name(code)); if (count == 0) printf(" none"); for (i = 0; i < count; i++) printf(" %s", vals[i]); printf("\n"); } static void dump_cfg_forwards(OpCodes code, u_int count, const struct Forward *fwds) { const struct Forward *fwd; u_int i; /* oDynamicForward */ for (i = 0; i < count; i++) { fwd = &fwds[i]; if (code == oDynamicForward && fwd->connect_host != NULL && strcmp(fwd->connect_host, "socks") != 0) continue; if (code == oLocalForward && fwd->connect_host != NULL && strcmp(fwd->connect_host, "socks") == 0) continue; printf("%s", lookup_opcode_name(code)); if (fwd->listen_port == PORT_STREAMLOCAL) printf(" %s", fwd->listen_path); else if (fwd->listen_host == NULL) printf(" %d", fwd->listen_port); else { printf(" [%s]:%d", fwd->listen_host, fwd->listen_port); } if (code != oDynamicForward) { if (fwd->connect_port == PORT_STREAMLOCAL) printf(" %s", fwd->connect_path); else if (fwd->connect_host == NULL) printf(" %d", fwd->connect_port); else { printf(" [%s]:%d", fwd->connect_host, fwd->connect_port); } } printf("\n"); } } void dump_client_config(Options *o, const char *host) { int i, r; char buf[8], *all_key; /* * Expand HostKeyAlgorithms name lists. This isn't handled in * fill_default_options() like the other algorithm lists because * the host key algorithms are by default dynamically chosen based * on the host's keys found in known_hosts. */ all_key = sshkey_alg_list(0, 0, 1, ','); if ((r = kex_assemble_names(&o->hostkeyalgorithms, kex_default_pk_alg(), all_key)) != 0) fatal_fr(r, "expand HostKeyAlgorithms"); free(all_key); /* Most interesting options first: user, host, port */ dump_cfg_string(oHost, o->host_arg); dump_cfg_string(oUser, o->user); dump_cfg_string(oHostname, host); dump_cfg_int(oPort, o->port); /* Flag options */ dump_cfg_fmtint(oAddressFamily, o->address_family); dump_cfg_fmtint(oBatchMode, o->batch_mode); dump_cfg_fmtint(oCanonicalizeFallbackLocal, o->canonicalize_fallback_local); dump_cfg_fmtint(oCanonicalizeHostname, o->canonicalize_hostname); dump_cfg_fmtint(oCheckHostIP, o->check_host_ip); dump_cfg_fmtint(oCompression, o->compression); dump_cfg_fmtint(oControlMaster, o->control_master); dump_cfg_fmtint(oEnableSSHKeysign, o->enable_ssh_keysign); dump_cfg_fmtint(oClearAllForwardings, o->clear_forwardings); dump_cfg_fmtint(oExitOnForwardFailure, o->exit_on_forward_failure); dump_cfg_fmtint(oFingerprintHash, o->fingerprint_hash); dump_cfg_fmtint(oForwardX11, o->forward_x11); dump_cfg_fmtint(oForwardX11Trusted, o->forward_x11_trusted); dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); #ifdef GSSAPI dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); #endif /* GSSAPI */ dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); dump_cfg_fmtint(oIdentitiesOnly, o->identities_only); dump_cfg_fmtint(oKbdInteractiveAuthentication, o->kbd_interactive_authentication); dump_cfg_fmtint(oNoHostAuthenticationForLocalhost, o->no_host_authentication_for_localhost); dump_cfg_fmtint(oPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(oPermitLocalCommand, o->permit_local_command); dump_cfg_fmtint(oProxyUseFdpass, o->proxy_use_fdpass); dump_cfg_fmtint(oPubkeyAuthentication, o->pubkey_authentication); dump_cfg_fmtint(oRequestTTY, o->request_tty); dump_cfg_fmtint(oSessionType, o->session_type); dump_cfg_fmtint(oStdinNull, o->stdin_null); dump_cfg_fmtint(oForkAfterAuthentication, o->fork_after_authentication); dump_cfg_fmtint(oStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); dump_cfg_fmtint(oStrictHostKeyChecking, o->strict_host_key_checking); dump_cfg_fmtint(oTCPKeepAlive, o->tcp_keep_alive); dump_cfg_fmtint(oTunnel, o->tun_open); dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns); dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys); dump_cfg_fmtint(oEnableEscapeCommandline, o->enable_escape_commandline); /* Integer options */ dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots); dump_cfg_int(oConnectionAttempts, o->connection_attempts); dump_cfg_int(oForwardX11Timeout, o->forward_x11_timeout); dump_cfg_int(oNumberOfPasswordPrompts, o->number_of_password_prompts); dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max); dump_cfg_int(oServerAliveInterval, o->server_alive_interval); dump_cfg_int(oRequiredRSASize, o->required_rsa_size); + dump_cfg_int(oObscureKeystrokeTiming, + o->obscure_keystroke_timing_interval); /* String options */ dump_cfg_string(oBindAddress, o->bind_address); dump_cfg_string(oBindInterface, o->bind_interface); dump_cfg_string(oCiphers, o->ciphers); dump_cfg_string(oControlPath, o->control_path); dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms); dump_cfg_string(oHostKeyAlias, o->host_key_alias); dump_cfg_string(oHostbasedAcceptedAlgorithms, o->hostbased_accepted_algos); dump_cfg_string(oIdentityAgent, o->identity_agent); dump_cfg_string(oIgnoreUnknown, o->ignored_unknown); dump_cfg_string(oKbdInteractiveDevices, o->kbd_interactive_devices); dump_cfg_string(oKexAlgorithms, o->kex_algorithms); dump_cfg_string(oCASignatureAlgorithms, o->ca_sign_algorithms); dump_cfg_string(oLocalCommand, o->local_command); dump_cfg_string(oRemoteCommand, o->remote_command); dump_cfg_string(oLogLevel, log_level_name(o->log_level)); dump_cfg_string(oMacs, o->macs); #ifdef ENABLE_PKCS11 dump_cfg_string(oPKCS11Provider, o->pkcs11_provider); #endif dump_cfg_string(oSecurityKeyProvider, o->sk_provider); dump_cfg_string(oPreferredAuthentications, o->preferred_authentications); dump_cfg_string(oPubkeyAcceptedAlgorithms, o->pubkey_accepted_algos); dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys); dump_cfg_string(oXAuthLocation, o->xauth_location); dump_cfg_string(oKnownHostsCommand, o->known_hosts_command); dump_cfg_string(oTag, o->tag); /* Forwards */ dump_cfg_forwards(oDynamicForward, o->num_local_forwards, o->local_forwards); dump_cfg_forwards(oLocalForward, o->num_local_forwards, o->local_forwards); dump_cfg_forwards(oRemoteForward, o->num_remote_forwards, o->remote_forwards); /* String array options */ dump_cfg_strarray(oIdentityFile, o->num_identity_files, o->identity_files); dump_cfg_strarray_oneline(oCanonicalDomains, o->num_canonical_domains, o->canonical_domains); dump_cfg_strarray(oCertificateFile, o->num_certificate_files, o->certificate_files); dump_cfg_strarray_oneline(oGlobalKnownHostsFile, o->num_system_hostfiles, o->system_hostfiles); dump_cfg_strarray_oneline(oUserKnownHostsFile, o->num_user_hostfiles, o->user_hostfiles); dump_cfg_strarray(oSendEnv, o->num_send_env, o->send_env); dump_cfg_strarray(oSetEnv, o->num_setenv, o->setenv); dump_cfg_strarray_oneline(oLogVerbose, o->num_log_verbose, o->log_verbose); /* Special cases */ /* PermitRemoteOpen */ if (o->num_permitted_remote_opens == 0) printf("%s any\n", lookup_opcode_name(oPermitRemoteOpen)); else dump_cfg_strarray_oneline(oPermitRemoteOpen, o->num_permitted_remote_opens, o->permitted_remote_opens); /* AddKeysToAgent */ if (o->add_keys_to_agent_lifespan <= 0) dump_cfg_fmtint(oAddKeysToAgent, o->add_keys_to_agent); else { printf("addkeystoagent%s %d\n", o->add_keys_to_agent == 3 ? " confirm" : "", o->add_keys_to_agent_lifespan); } /* oForwardAgent */ if (o->forward_agent_sock_path == NULL) dump_cfg_fmtint(oForwardAgent, o->forward_agent); else dump_cfg_string(oForwardAgent, o->forward_agent_sock_path); /* oConnectTimeout */ if (o->connection_timeout == -1) printf("connecttimeout none\n"); else dump_cfg_int(oConnectTimeout, o->connection_timeout); /* oTunnelDevice */ printf("tunneldevice"); if (o->tun_local == SSH_TUNID_ANY) printf(" any"); else printf(" %d", o->tun_local); if (o->tun_remote == SSH_TUNID_ANY) printf(":any"); else printf(":%d", o->tun_remote); printf("\n"); /* oCanonicalizePermittedCNAMEs */ printf("canonicalizePermittedcnames"); if (o->num_permitted_cnames == 0) printf(" none"); for (i = 0; i < o->num_permitted_cnames; i++) { printf(" %s:%s", o->permitted_cnames[i].source_list, o->permitted_cnames[i].target_list); } printf("\n"); /* oControlPersist */ if (o->control_persist == 0 || o->control_persist_timeout == 0) dump_cfg_fmtint(oControlPersist, o->control_persist); else dump_cfg_int(oControlPersist, o->control_persist_timeout); /* oEscapeChar */ if (o->escape_char == SSH_ESCAPECHAR_NONE) printf("escapechar none\n"); else { vis(buf, o->escape_char, VIS_WHITE, 0); printf("escapechar %s\n", buf); } /* oIPQoS */ printf("ipqos %s ", iptos2str(o->ip_qos_interactive)); printf("%s\n", iptos2str(o->ip_qos_bulk)); /* oRekeyLimit */ printf("rekeylimit %llu %d\n", (unsigned long long)o->rekey_limit, o->rekey_interval); /* oStreamLocalBindMask */ printf("streamlocalbindmask 0%o\n", o->fwd_opts.streamlocal_bind_mask); /* oLogFacility */ printf("syslogfacility %s\n", log_facility_name(o->log_facility)); /* oProxyCommand / oProxyJump */ if (o->jump_host == NULL) dump_cfg_string(oProxyCommand, o->proxy_command); else { /* Check for numeric addresses */ i = strchr(o->jump_host, ':') != NULL || strspn(o->jump_host, "1234567890.") == strlen(o->jump_host); snprintf(buf, sizeof(buf), "%d", o->jump_port); printf("proxyjump %s%s%s%s%s%s%s%s%s\n", /* optional additional jump spec */ o->jump_extra == NULL ? "" : o->jump_extra, o->jump_extra == NULL ? "" : ",", /* optional user */ o->jump_user == NULL ? "" : o->jump_user, o->jump_user == NULL ? "" : "@", /* opening [ if hostname is numeric */ i ? "[" : "", /* mandatory hostname */ o->jump_host, /* closing ] if hostname is numeric */ i ? "]" : "", /* optional port number */ o->jump_port <= 0 ? "" : ":", o->jump_port <= 0 ? "" : buf); } } diff --git a/crypto/openssh/readconf.h b/crypto/openssh/readconf.h index dfe5bab0a3ca..ce261bd63642 100644 --- a/crypto/openssh/readconf.h +++ b/crypto/openssh/readconf.h @@ -1,249 +1,255 @@ -/* $OpenBSD: readconf.h,v 1.151 2023/07/17 04:08:31 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.152 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for reading the configuration file. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef READCONF_H #define READCONF_H /* Data structure for representing option data. */ #define SSH_MAX_HOSTS_FILES 32 #define MAX_CANON_DOMAINS 32 #define PATH_MAX_SUN (sizeof((struct sockaddr_un *)0)->sun_path) struct allowed_cname { char *source_list; char *target_list; }; typedef struct { char *host_arg; /* Host arg as specified on command line. */ int forward_agent; /* Forward authentication agent. */ char *forward_agent_sock_path; /* Optional path of the agent. */ int forward_x11; /* Forward X11 display. */ int forward_x11_timeout; /* Expiration for Cookies */ int forward_x11_trusted; /* Trust Forward X11 display. */ int exit_on_forward_failure; /* Exit if bind(2) fails for -L/-R */ char *xauth_location; /* Location for xauth program */ struct ForwardOptions fwd_opts; /* forwarding options */ int pubkey_authentication; /* Try ssh2 pubkey authentication. */ int hostbased_authentication; /* ssh2's rhosts_rsa */ int gss_authentication; /* Try GSS authentication */ int gss_deleg_creds; /* Delegate GSS credentials */ int password_authentication; /* Try password * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */ int batch_mode; /* Batch mode: do not ask for passwords. */ int check_host_ip; /* Also keep track of keys for IP address */ int strict_host_key_checking; /* Strict host key checking. */ int compression; /* Compress packets in both directions. */ int tcp_keep_alive; /* Set SO_KEEPALIVE. */ int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ SyslogFacility log_facility; /* Facility for system logging. */ LogLevel log_level; /* Level for logging. */ u_int num_log_verbose; /* Verbose log overrides */ char **log_verbose; int port; /* Port to connect. */ int address_family; int connection_attempts; /* Max attempts (seconds) before * giving up */ int connection_timeout; /* Max time (seconds) before * aborting connection attempt */ int number_of_password_prompts; /* Max number of password * prompts. */ char *ciphers; /* SSH2 ciphers in order of preference. */ char *macs; /* SSH2 macs in order of preference. */ char *hostkeyalgorithms; /* SSH2 server key types in order of preference. */ char *kex_algorithms; /* SSH2 kex methods in order of preference. */ char *ca_sign_algorithms; /* Allowed CA signature algorithms */ char *hostname; /* Real host to connect. */ char *tag; /* Configuration tag name. */ char *host_key_alias; /* hostname alias for .ssh/known_hosts */ char *proxy_command; /* Proxy command for connecting the host. */ char *user; /* User to log in as. */ int escape_char; /* Escape character; -2 = none */ u_int num_system_hostfiles; /* Paths for /etc/ssh/ssh_known_hosts */ char *system_hostfiles[SSH_MAX_HOSTS_FILES]; u_int num_user_hostfiles; /* Path for $HOME/.ssh/known_hosts */ char *user_hostfiles[SSH_MAX_HOSTS_FILES]; char *preferred_authentications; char *bind_address; /* local socket address for connection to sshd */ char *bind_interface; /* local interface for bind address */ char *pkcs11_provider; /* PKCS#11 provider */ char *sk_provider; /* Security key provider */ int verify_host_key_dns; /* Verify host key using DNS */ int num_identity_files; /* Number of files for RSA/DSA identities. */ char *identity_files[SSH_MAX_IDENTITY_FILES]; int identity_file_userprovided[SSH_MAX_IDENTITY_FILES]; struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES]; int num_certificate_files; /* Number of extra certificates for ssh. */ char *certificate_files[SSH_MAX_CERTIFICATE_FILES]; int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES]; struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; int add_keys_to_agent; int add_keys_to_agent_lifespan; char *identity_agent; /* Optional path to ssh-agent socket */ /* Local TCP/IP forward requests. */ int num_local_forwards; struct Forward *local_forwards; /* Remote TCP/IP forward requests. */ int num_remote_forwards; struct Forward *remote_forwards; int clear_forwardings; /* Restrict remote dynamic forwarding */ char **permitted_remote_opens; u_int num_permitted_remote_opens; /* stdio forwarding (-W) host and port */ char *stdio_forward_host; int stdio_forward_port; int enable_ssh_keysign; int64_t rekey_limit; int rekey_interval; int no_host_authentication_for_localhost; int identities_only; int server_alive_interval; int server_alive_count_max; u_int num_send_env; char **send_env; u_int num_setenv; char **setenv; char *control_path; int control_master; int control_persist; /* ControlPersist flag */ int control_persist_timeout; /* ControlPersist timeout (seconds) */ int hash_known_hosts; int tun_open; /* tun(4) */ int tun_local; /* force tun device (optional) */ int tun_remote; /* force tun device (optional) */ char *local_command; int permit_local_command; char *remote_command; int visual_host_key; int request_tty; int session_type; int stdin_null; int fork_after_authentication; int proxy_use_fdpass; int num_canonical_domains; char *canonical_domains[MAX_CANON_DOMAINS]; int canonicalize_hostname; int canonicalize_max_dots; int canonicalize_fallback_local; int num_permitted_cnames; struct allowed_cname permitted_cnames[MAX_CANON_DOMAINS]; char *revoked_host_keys; int fingerprint_hash; int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */ char *hostbased_accepted_algos; char *pubkey_accepted_algos; char *jump_user; char *jump_host; int jump_port; char *jump_extra; char *known_hosts_command; int required_rsa_size; /* minimum size of RSA keys */ int enable_escape_commandline; /* ~C commandline */ + int obscure_keystroke_timing_interval; char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ } Options; #define SSH_PUBKEY_AUTH_NO 0x00 #define SSH_PUBKEY_AUTH_UNBOUND 0x01 #define SSH_PUBKEY_AUTH_HBOUND 0x02 #define SSH_PUBKEY_AUTH_ALL 0x03 #define SSH_CANONICALISE_NO 0 #define SSH_CANONICALISE_YES 1 #define SSH_CANONICALISE_ALWAYS 2 #define SSHCTL_MASTER_NO 0 #define SSHCTL_MASTER_YES 1 #define SSHCTL_MASTER_AUTO 2 #define SSHCTL_MASTER_ASK 3 #define SSHCTL_MASTER_AUTO_ASK 4 #define REQUEST_TTY_AUTO 0 #define REQUEST_TTY_NO 1 #define REQUEST_TTY_YES 2 #define REQUEST_TTY_FORCE 3 #define SESSION_TYPE_NONE 0 #define SESSION_TYPE_SUBSYSTEM 1 #define SESSION_TYPE_DEFAULT 2 #define SSHCONF_CHECKPERM 1 /* check permissions on config file */ #define SSHCONF_USERCONF 2 /* user provided config file not system */ #define SSHCONF_FINAL 4 /* Final pass over config, after canon. */ #define SSHCONF_NEVERMATCH 8 /* Match/Host never matches; internal only */ #define SSH_UPDATE_HOSTKEYS_NO 0 #define SSH_UPDATE_HOSTKEYS_YES 1 #define SSH_UPDATE_HOSTKEYS_ASK 2 #define SSH_STRICT_HOSTKEY_OFF 0 #define SSH_STRICT_HOSTKEY_NEW 1 #define SSH_STRICT_HOSTKEY_YES 2 #define SSH_STRICT_HOSTKEY_ASK 3 +/* ObscureKeystrokes parameters */ +#define SSH_KEYSTROKE_DEFAULT_INTERVAL_MS 20 +#define SSH_KEYSTROKE_CHAFF_MIN_MS 1024 +#define SSH_KEYSTROKE_CHAFF_RNG_MS 2048 + const char *kex_default_pk_alg(void); char *ssh_connection_hash(const char *thishost, const char *host, const char *portstr, const char *user); void initialize_options(Options *); int fill_default_options(Options *); void fill_default_options_for_canonicalization(Options *); void free_options(Options *o); int process_config_line(Options *, struct passwd *, const char *, const char *, char *, const char *, int, int *, int); int read_config_file(const char *, struct passwd *, const char *, const char *, Options *, int, int *); int parse_forward(struct Forward *, const char *, int, int); int parse_jump(const char *, Options *, int); int parse_ssh_uri(const char *, char **, char **, int *); int default_ssh_port(void); int option_clear_or_none(const char *); int config_has_permitted_cnames(Options *); void dump_client_config(Options *o, const char *host); void add_local_forward(Options *, const struct Forward *); void add_remote_forward(Options *, const struct Forward *); void add_identity_file(Options *, const char *, const char *, int); void add_certificate_file(Options *, const char *, int); #endif /* READCONF_H */ diff --git a/crypto/openssh/regress/Makefile b/crypto/openssh/regress/Makefile index 5caf9b8e41dd..c21b0215a8cd 100644 --- a/crypto/openssh/regress/Makefile +++ b/crypto/openssh/regress/Makefile @@ -1,286 +1,287 @@ -# $OpenBSD: Makefile,v 1.125 2023/05/17 05:52:01 djm Exp $ +# $OpenBSD: Makefile,v 1.126 2023/09/06 23:36:09 djm Exp $ tests: prep file-tests t-exec unit REGRESS_TARGETS= t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 # File based tests file-tests: $(REGRESS_TARGETS) # Interop tests are not run by default interop interop-tests: t-exec-interop prep: test "x${USE_VALGRIND}" = "x" || mkdir -p $(OBJ)/valgrind-out clean: for F in $(CLEANFILES); do rm -f $(OBJ)$$F; done rm -rf $(OBJ).putty distclean: clean LTESTS= connect \ proxy-connect \ sshfp-connect \ connect-privsep \ connect-uri \ proto-version \ proto-mismatch \ exit-status \ exit-status-signal \ envpass \ transfer \ banner \ rekey \ dhgex \ stderr-data \ stderr-after-eof \ broken-pipe \ try-ciphers \ yes-head \ login-timeout \ agent \ agent-getpeereid \ agent-timeout \ agent-ptrace \ agent-subprocess \ keyscan \ keygen-change \ keygen-comment \ keygen-convert \ keygen-knownhosts \ keygen-moduli \ keygen-sshfp \ key-options \ scp \ scp3 \ scp-uri \ sftp \ sftp-chroot \ sftp-cmds \ sftp-badcmds \ sftp-batch \ sftp-glob \ sftp-perm \ sftp-uri \ reconfigure \ dynamic-forward \ forwarding \ multiplex \ reexec \ brokenkeys \ sshcfgparse \ cfgparse \ cfgmatch \ cfgmatchlisten \ percent \ addrmatch \ localcommand \ forcecommand \ portnum \ keytype \ kextype \ cert-hostkey \ cert-userkey \ host-expand \ keys-command \ forward-control \ integrity \ krl \ multipubkey \ limit-keytype \ hostkey-agent \ hostkey-rotate \ principals-command \ cert-file \ cfginclude \ servcfginclude \ allow-deny-users \ authinfo \ sshsig \ knownhosts \ knownhosts-command \ agent-restrict \ hostbased \ channel-timeout \ - connection-timeout + connection-timeout \ + match-subsystem INTEROP_TESTS= putty-transfer putty-ciphers putty-kex conch-ciphers #INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp EXTRA_TESTS= agent-pkcs11 #EXTRA_TESTS+= cipher-speed USERNAME= ${LOGNAME} CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \ authorized_keys_${USERNAME}.* \ authorized_principals_${USERNAME} \ banner.in banner.out cert_host_key* cert_user_key* \ copy.1 copy.2 data ed25519-agent ed25519-agent* \ ed25519-agent.pub ed25519 ed25519.pub empty.in \ expect failed-regress.log failed-ssh.log failed-sshd.log \ hkr.* host.ecdsa-sha2-nistp256 host.ecdsa-sha2-nistp384 \ host.ecdsa-sha2-nistp521 host.ssh-dss host.ssh-ed25519 \ host.ssh-rsa host_ca_key* host_krl_* host_revoked_* key.* \ key.dsa-* key.ecdsa-* key.ed25519-512 \ key.ed25519-512.pub key.rsa-* keys-command-args kh.* askpass \ known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \ modpipe netcat no_identity_config \ pidfile putty.rsa2 ready regress.log remote_pid \ revoked-* rsa rsa-agent rsa-agent.pub rsa.pub rsa_ssh2_cr.prv \ rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \ scp-ssh-wrapper.scp setuid-allowed sftp-server.log \ sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \ ssh-agent.log ssh-add.log slow-sftp-server.sh \ ssh-rsa_oldfmt knownhosts_command \ ssh_config ssh_config.* ssh_proxy ssh_proxy_bak \ ssh_proxy_* sshd.log sshd_config sshd_config.* \ sshd_config.* sshd_proxy sshd_proxy.* sshd_proxy_bak \ sshd_proxy_orig t10.out t10.out.pub t12.out t12.out.pub \ t2.out t3.out t6.out1 t6.out2 t7.out t7.out.pub \ t8.out t8.out.pub t9.out t9.out.pub \ timestamp testdata user_*key* user_ca* user_key* # Enable all malloc(3) randomisations and checks TEST_ENV= "MALLOC_OPTIONS=CFGJRSUX" TEST_SSH_SSHKEYGEN?=ssh-keygen CPPFLAGS=-I.. t1: ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/rsa_ssh2.prv | diff - ${.CURDIR}/rsa_openssh.prv tr '\n' '\r' <${.CURDIR}/rsa_ssh2.prv > ${.OBJDIR}/rsa_ssh2_cr.prv ${TEST_SSH_SSHKEYGEN} -if ${.OBJDIR}/rsa_ssh2_cr.prv | diff - ${.CURDIR}/rsa_openssh.prv awk '{print $$0 "\r"}' ${.CURDIR}/rsa_ssh2.prv > ${.OBJDIR}/rsa_ssh2_crnl.prv ${TEST_SSH_SSHKEYGEN} -if ${.OBJDIR}/rsa_ssh2_crnl.prv | diff - ${.CURDIR}/rsa_openssh.prv t2: cat ${.CURDIR}/rsa_openssh.prv > $(OBJ)/t2.out chmod 600 $(OBJ)/t2.out ${TEST_SSH_SSHKEYGEN} -yf $(OBJ)/t2.out | diff - ${.CURDIR}/rsa_openssh.pub t3: ${TEST_SSH_SSHKEYGEN} -ef ${.CURDIR}/rsa_openssh.pub >$(OBJ)/t3.out ${TEST_SSH_SSHKEYGEN} -if $(OBJ)/t3.out | diff - ${.CURDIR}/rsa_openssh.pub t4: ${TEST_SSH_SSHKEYGEN} -E md5 -lf ${.CURDIR}/rsa_openssh.pub |\ awk '{print $$2}' | diff - ${.CURDIR}/t4.ok t5: ${TEST_SSH_SSHKEYGEN} -Bf ${.CURDIR}/rsa_openssh.pub |\ awk '{print $$2}' | diff - ${.CURDIR}/t5.ok t6: ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/dsa_ssh2.prv > $(OBJ)/t6.out1 ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/dsa_ssh2.pub > $(OBJ)/t6.out2 chmod 600 $(OBJ)/t6.out1 ${TEST_SSH_SSHKEYGEN} -yf $(OBJ)/t6.out1 | diff - $(OBJ)/t6.out2 $(OBJ)/t7.out: ${TEST_SSH_SSHKEYGEN} -q -t rsa -N '' -f $@ t7: $(OBJ)/t7.out ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t7.out > /dev/null ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t7.out > /dev/null $(OBJ)/t8.out: ${TEST_SSH_SSHKEYGEN} -q -t dsa -N '' -f $@ t8: $(OBJ)/t8.out ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t8.out > /dev/null ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t8.out > /dev/null $(OBJ)/t9.out: ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \ ${TEST_SSH_SSHKEYGEN} -q -t ecdsa -N '' -f $@ t9: $(OBJ)/t9.out ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t9.out > /dev/null ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \ ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t9.out > /dev/null $(OBJ)/t10.out: ${TEST_SSH_SSHKEYGEN} -q -t ed25519 -N '' -f $@ t10: $(OBJ)/t10.out ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t10.out > /dev/null ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t10.out > /dev/null t11: ${TEST_SSH_SSHKEYGEN} -E sha256 -lf ${.CURDIR}/rsa_openssh.pub |\ awk '{print $$2}' | diff - ${.CURDIR}/t11.ok $(OBJ)/t12.out: ${TEST_SSH_SSHKEYGEN} -q -t ed25519 -N '' -C 'test-comment-1234' -f $@ t12: $(OBJ)/t12.out ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t12.out.pub | grep test-comment-1234 >/dev/null t-exec: ${LTESTS:=.sh} @if [ "x$?" = "x" ]; then exit 0; fi; \ _started=""; test -z "${LTESTS_FROM}" && _started=1 ;\ for TEST in ""$?; do \ if [ -z "$$_started" ] ; then \ if [ "x$$TEST" = "x${LTESTS_FROM}.sh" ]; then \ _started=1; \ else \ continue; \ fi ; \ fi ; \ skip=no; \ for t in ""$${SKIP_LTESTS}; do \ if [ "x$${t}.sh" = "x$${TEST}" ]; then skip=yes; fi; \ done; \ if [ "x$${skip}" = "xno" ]; then \ echo "run test $${TEST}" ... 1>&2; \ (env SUDO="${SUDO}" TEST_ENV=${TEST_ENV} ${TEST_SHELL} ${.CURDIR}/test-exec.sh ${.OBJDIR} ${.CURDIR}/$${TEST}) || exit $$?; \ else \ echo skip test $${TEST} 1>&2; \ fi; \ done t-exec-interop: ${INTEROP_TESTS:=.sh} @if [ "x$?" = "x" ]; then exit 0; fi; \ for TEST in ""$?; do \ echo "run test $${TEST}" ... 1>&2; \ (env SUDO="${SUDO}" TEST_ENV=${TEST_ENV} ${TEST_SHELL} ${.CURDIR}/test-exec.sh ${.OBJDIR} ${.CURDIR}/$${TEST}) || exit $$?; \ done t-extra: ${EXTRA_TESTS:=.sh} @if [ "x$?" = "x" ]; then exit 0; fi; \ for TEST in ""$?; do \ echo "run test $${TEST}" ... 1>&2; \ (env SUDO="${SUDO}" TEST_ENV=${TEST_ENV} ${TEST_SHELL} ${.CURDIR}/test-exec.sh ${.OBJDIR} ${.CURDIR}/$${TEST}) || exit $$?; \ done # Not run by default interop: ${INTEROP_TARGETS} # Unit tests, built by top-level Makefile unit: set -e ; if test -z "${SKIP_UNIT}" ; then \ V="" ; \ test "x${USE_VALGRIND}" = "x" || \ V=${.CURDIR}/valgrind-unit.sh ; \ $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf ; \ $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \ -d ${.CURDIR}/unittests/sshkey/testdata ; \ $$V ${.OBJDIR}/unittests/sshsig/test_sshsig \ -d ${.CURDIR}/unittests/sshsig/testdata ; \ $$V ${.OBJDIR}/unittests/authopt/test_authopt \ -d ${.CURDIR}/unittests/authopt/testdata ; \ $$V ${.OBJDIR}/unittests/bitmap/test_bitmap ; \ $$V ${.OBJDIR}/unittests/conversion/test_conversion ; \ $$V ${.OBJDIR}/unittests/kex/test_kex ; \ $$V ${.OBJDIR}/unittests/hostkeys/test_hostkeys \ -d ${.CURDIR}/unittests/hostkeys/testdata ; \ $$V ${.OBJDIR}/unittests/match/test_match ; \ $$V ${.OBJDIR}/unittests/misc/test_misc ; \ if test "x${TEST_SSH_UTF8}" = "xyes" ; then \ $$V ${.OBJDIR}/unittests/utf8/test_utf8 ; \ fi \ fi diff --git a/crypto/openssh/regress/match-subsystem.sh b/crypto/openssh/regress/match-subsystem.sh new file mode 100644 index 000000000000..0b691d8e8884 --- /dev/null +++ b/crypto/openssh/regress/match-subsystem.sh @@ -0,0 +1,90 @@ +# $OpenBSD: match-subsystem.sh,v 1.1 2023/09/06 23:36:09 djm Exp $ +# Placed in the Public Domain. + +tid="sshd_config match subsystem" + +cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak + +try_subsystem() { + _id=$1 + _subsystem=$2 + _expect=$3 + ${SSHD} -tf $OBJ/sshd_proxy || fatal "$_id: bad config" + ${SSH} -sF $OBJ/ssh_proxy somehost $_subsystem + _exit=$? + trace "$_id subsystem $_subsystem" + if [ $_exit -ne $_expect ] ; then + fail "$_id: subsystem $_subsystem exit $_exit expected $_expect" + fi + return $? +} + +# Simple case: subsystem in main config. +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Subsystem xxx /bin/sh -c "exit 23" +_EOF +try_subsystem "main config" xxx 23 + +# No clobber in main config. +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Subsystem xxx /bin/sh -c "exit 23" +Subsystem xxx /bin/sh -c "exit 24" +_EOF +try_subsystem "main config no clobber" xxx 23 + +# Subsystem in match all block +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Match all +Subsystem xxx /bin/sh -c "exit 21" +_EOF +try_subsystem "match all" xxx 21 + +# No clobber in match all block +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Match all +Subsystem xxx /bin/sh -c "exit 21" +Subsystem xxx /bin/sh -c "exit 24" +_EOF +try_subsystem "match all no clobber" xxx 21 + +# Subsystem in match user block +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Match user * +Subsystem xxx /bin/sh -c "exit 20" +_EOF +try_subsystem "match user" xxx 20 + +# No clobber in match user block +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Match user * +Subsystem xxx /bin/sh -c "exit 20" +Subsystem xxx /bin/sh -c "exit 24" +Match all +Subsystem xxx /bin/sh -c "exit 24" +_EOF +try_subsystem "match user no clobber" xxx 20 + +# Override main with match all +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Subsystem xxx /bin/sh -c "exit 23" +Match all +Subsystem xxx /bin/sh -c "exit 19" +_EOF +try_subsystem "match all override" xxx 19 + +# Override main with match user +cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy +cat >> $OBJ/sshd_proxy << _EOF +Subsystem xxx /bin/sh -c "exit 23" +Match user * +Subsystem xxx /bin/sh -c "exit 18" +_EOF +try_subsystem "match user override" xxx 18 + diff --git a/crypto/openssh/regress/scp.sh b/crypto/openssh/regress/scp.sh index 76c2b2a6bb56..640cf434ff67 100644 --- a/crypto/openssh/regress/scp.sh +++ b/crypto/openssh/regress/scp.sh @@ -1,195 +1,205 @@ -# $OpenBSD: scp.sh,v 1.18 2023/01/13 04:47:34 dtucker Exp $ +# $OpenBSD: scp.sh,v 1.19 2023/09/08 05:50:57 djm Exp $ # Placed in the Public Domain. tid="scp" #set -x COPY2=${OBJ}/copy2 DIR=${COPY}.dd DIR2=${COPY}.dd2 COPY3=${OBJ}/copy.glob[123] DIR3=${COPY}.dd.glob[456] DIFFOPT="-rN" # Figure out if diff does not understand "-N" if ! diff -N ${SRC}/scp.sh ${SRC}/scp.sh 2>/dev/null; then DIFFOPT="-r" fi maybe_add_scp_path_to_sshd SRC=`dirname ${SCRIPT}` cp ${SRC}/scp-ssh-wrapper.sh ${OBJ}/scp-ssh-wrapper.scp chmod 755 ${OBJ}/scp-ssh-wrapper.scp export SCP # used in scp-ssh-wrapper.scp scpclean() { rm -rf ${COPY} ${COPY2} ${DIR} ${DIR2} ${COPY3} ${DIR3} mkdir ${DIR} ${DIR2} ${DIR3} chmod 755 ${DIR} ${DIR2} ${DIR3} } +# Create directory structure for recursive copy tests. +forest() { + scpclean + rm -rf ${DIR2} + cp ${DATA} ${DIR}/copy + ln -s ${DIR}/copy ${DIR}/copy-sym + mkdir ${DIR}/subdir + cp ${DATA} ${DIR}/subdir/copy + ln -s ${DIR}/subdir ${DIR}/subdir-sym +} + for mode in scp sftp ; do tag="$tid: $mode mode" if test $mode = scp ; then scpopts="-O -q -S ${OBJ}/scp-ssh-wrapper.scp" else - scpopts="-s -D ${SFTPSERVER}" + scpopts="-qs -D ${SFTPSERVER}" fi + verbose "$tag: simple copy local file to local file" scpclean $SCP $scpopts ${DATA} ${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: simple copy local file to remote file" scpclean $SCP $scpopts ${DATA} somehost:${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: simple copy remote file to local file" scpclean $SCP $scpopts somehost:${DATA} ${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: copy local file to remote file in place" scpclean cp ${DATA} ${COPY} $SCP $scpopts ${COPY} somehost:${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: copy remote file to local file in place" scpclean cp ${DATA} ${COPY} $SCP $scpopts somehost:${COPY} ${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: copy local file to remote file clobber" scpclean cat ${DATA} ${DATA} > ${COPY} $SCP $scpopts ${DATA} somehost:${COPY} || fail "copy failed" ls -l $DATA $COPY cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: copy remote file to local file clobber" scpclean cat ${DATA} ${DATA} > ${COPY} $SCP $scpopts somehost:${DATA} ${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: simple copy local file to remote dir" scpclean cp ${DATA} ${COPY} $SCP $scpopts ${COPY} somehost:${DIR} || fail "copy failed" cmp ${COPY} ${DIR}/copy || fail "corrupted copy" verbose "$tag: simple copy local file to local dir" scpclean cp ${DATA} ${COPY} $SCP $scpopts ${COPY} ${DIR} || fail "copy failed" cmp ${COPY} ${DIR}/copy || fail "corrupted copy" verbose "$tag: simple copy remote file to local dir" scpclean cp ${DATA} ${COPY} $SCP $scpopts somehost:${COPY} ${DIR} || fail "copy failed" cmp ${COPY} ${DIR}/copy || fail "corrupted copy" verbose "$tag: recursive local dir to remote dir" - scpclean - rm -rf ${DIR2} - cp ${DATA} ${DIR}/copy + forest $SCP $scpopts -r ${DIR} somehost:${DIR2} || fail "copy failed" diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" verbose "$tag: recursive local dir to local dir" - scpclean + forest rm -rf ${DIR2} cp ${DATA} ${DIR}/copy $SCP $scpopts -r ${DIR} ${DIR2} || fail "copy failed" diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" verbose "$tag: recursive remote dir to local dir" - scpclean + forest rm -rf ${DIR2} cp ${DATA} ${DIR}/copy $SCP $scpopts -r somehost:${DIR} ${DIR2} || fail "copy failed" diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" verbose "$tag: unmatched glob file local->remote" scpclean $SCP $scpopts ${DATA} somehost:${COPY3} || fail "copy failed" cmp ${DATA} ${COPY3} || fail "corrupted copy" verbose "$tag: unmatched glob file remote->local" # NB. no clean $SCP $scpopts somehost:${COPY3} ${COPY2} || fail "copy failed" cmp ${DATA} ${COPY2} || fail "corrupted copy" verbose "$tag: unmatched glob dir recursive local->remote" scpclean rm -rf ${DIR3} cp ${DATA} ${DIR}/copy cp ${DATA} ${DIR}/copy.glob[1234] $SCP $scpopts -r ${DIR} somehost:${DIR3} || fail "copy failed" diff ${DIFFOPT} ${DIR} ${DIR3} || fail "corrupted copy" verbose "$tag: unmatched glob dir recursive remote->local" # NB. no clean rm -rf ${DIR2} $SCP $scpopts -r somehost:${DIR3} ${DIR2} || fail "copy failed" diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" verbose "$tag: shell metacharacters" scpclean (cd ${DIR} && \ touch '`touch metachartest`' && \ $SCP $scpopts *metachar* ${DIR2} 2>/dev/null; \ [ ! -f metachartest ] ) || fail "shell metacharacters" if [ ! -z "$SUDO" ]; then verbose "$tag: skipped file after scp -p with failed chown+utimes" scpclean cp -p ${DATA} ${DIR}/copy cp -p ${DATA} ${DIR}/copy2 cp ${DATA} ${DIR2}/copy chmod 660 ${DIR2}/copy $SUDO chown root ${DIR2}/copy $SCP -p $scpopts somehost:${DIR}/\* ${DIR2} >/dev/null 2>&1 $SUDO diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" $SUDO rm ${DIR2}/copy fi for i in 0 1 2 3 4 5 6 7; do verbose "$tag: disallow bad server #$i" SCPTESTMODE=badserver_$i export DIR SCPTESTMODE scpclean $SCP $scpopts somehost:${DATA} ${DIR} >/dev/null 2>/dev/null [ -d {$DIR}/rootpathdir ] && fail "allows dir relative to root dir" [ -d ${DIR}/dotpathdir ] && fail "allows dir creation in non-recursive mode" scpclean $SCP -r $scpopts somehost:${DATA} ${DIR2} >/dev/null 2>/dev/null [ -d ${DIR}/dotpathdir ] && fail "allows dir creation outside of subdir" scpclean $SCP -pr $scpopts somehost:${DATA} ${DIR2} >/dev/null 2>/dev/null [ ! -w ${DIR2} ] && fail "allows target root attribute change" scpclean $SCP $scpopts somehost:${DATA} ${DIR2} >/dev/null 2>/dev/null [ -e ${DIR2}/extrafile ] && fail "allows unauth object creation" rm -f ${DIR2}/extrafile done verbose "$tag: detect non-directory target" scpclean echo a > ${COPY} echo b > ${COPY2} $SCP $scpopts ${DATA} ${COPY} ${COPY2} cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target" done scpclean rm -f ${OBJ}/scp-ssh-wrapper.scp diff --git a/crypto/openssh/regress/scp3.sh b/crypto/openssh/regress/scp3.sh index 383121f4519e..eeb7a9dde475 100644 --- a/crypto/openssh/regress/scp3.sh +++ b/crypto/openssh/regress/scp3.sh @@ -1,62 +1,69 @@ -# $OpenBSD: scp3.sh,v 1.4 2023/01/13 04:47:34 dtucker Exp $ +# $OpenBSD: scp3.sh,v 1.5 2023/09/08 06:10:57 djm Exp $ # Placed in the Public Domain. tid="scp3" -set -x - COPY2=${OBJ}/copy2 DIR=${COPY}.dd DIR2=${COPY}.dd2 maybe_add_scp_path_to_sshd SRC=`dirname ${SCRIPT}` cp ${SRC}/scp-ssh-wrapper.sh ${OBJ}/scp-ssh-wrapper.scp chmod 755 ${OBJ}/scp-ssh-wrapper.scp export SCP # used in scp-ssh-wrapper.scp scpclean() { rm -rf ${COPY} ${COPY2} ${DIR} ${DIR2} mkdir ${DIR} ${DIR2} chmod 755 ${DIR} ${DIR2} } +# Create directory structure for recursive copy tests. +forest() { + scpclean + rm -rf ${DIR2} + cp ${DATA} ${DIR}/copy + ln -s ${DIR}/copy ${DIR}/copy-sym + mkdir ${DIR}/subdir + cp ${DATA} ${DIR}/subdir/copy + ln -s ${DIR}/subdir ${DIR}/subdir-sym +} + for mode in scp sftp ; do scpopts="-F${OBJ}/ssh_proxy -S ${SSH} -q" tag="$tid: $mode mode" if test $mode = scp ; then scpopts="$scpopts -O" else scpopts="-s -D ${SFTPSERVER}" fi verbose "$tag: simple copy remote file to remote file" scpclean $SCP $scpopts -3 hostA:${DATA} hostB:${COPY} || fail "copy failed" cmp ${DATA} ${COPY} || fail "corrupted copy" verbose "$tag: simple copy remote file to remote dir" scpclean cp ${DATA} ${COPY} $SCP $scpopts -3 hostA:${COPY} hostB:${DIR} || fail "copy failed" cmp ${COPY} ${DIR}/copy || fail "corrupted copy" verbose "$tag: recursive remote dir to remote dir" - scpclean - rm -rf ${DIR2} - cp ${DATA} ${DIR}/copy + forest $SCP $scpopts -3r hostA:${DIR} hostB:${DIR2} || fail "copy failed" diff -r ${DIR} ${DIR2} || fail "corrupted copy" diff -r ${DIR2} ${DIR} || fail "corrupted copy" verbose "$tag: detect non-directory target" scpclean echo a > ${COPY} echo b > ${COPY2} $SCP $scpopts -3 hostA:${DATA} hostA:${COPY} hostB:${COPY2} cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target" done scpclean rm -f ${OBJ}/scp-ssh-wrapper.exe diff --git a/crypto/openssh/scp.c b/crypto/openssh/scp.c index 5edb4f07dba6..eaa407cb1de8 100644 --- a/crypto/openssh/scp.c +++ b/crypto/openssh/scp.c @@ -1,2267 +1,2267 @@ -/* $OpenBSD: scp.c,v 1.257 2023/07/14 05:31:44 djm Exp $ */ +/* $OpenBSD: scp.c,v 1.259 2023/09/10 23:12:32 djm Exp $ */ /* * scp - secure remote copy. This is basically patched BSD rcp which * uses ssh to do the data transfer (instead of using rcmd). * * NOTE: This version should NOT be suid root. (This uses ssh to * do the transfer and ssh has the necessary privileges.) * * 1995 Timo Rinne , Tatu Ylonen * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ /* * Copyright (c) 1999 Theo de Raadt. All rights reserved. * Copyright (c) 1999 Aaron Campbell. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Parts from: * * Copyright (c) 1983, 1990, 1992, 1993, 1995 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include "includes.h" #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_POLL_H #include #else # ifdef HAVE_SYS_POLL_H # include # endif #endif #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #ifdef HAVE_FNMATCH_H #include #endif #ifdef USE_SYSTEM_GLOB # include #else # include "openbsd-compat/glob.h" #endif #ifdef HAVE_LIBGEN_H #include #endif #include #ifdef HAVE_UTIL_H # include #endif #include #include #include #include #ifdef HAVE_STDINT_H # include #endif #include #include #include #include #include #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) #include #endif #include "xmalloc.h" #include "ssh.h" #include "atomicio.h" #include "pathnames.h" #include "log.h" #include "misc.h" #include "progressmeter.h" #include "utf8.h" #include "sftp.h" #include "sftp-common.h" #include "sftp-client.h" extern char *__progname; #define COPY_BUFLEN 16384 int do_cmd(char *, char *, char *, int, int, char *, int *, int *, pid_t *); int do_cmd2(char *, char *, int, char *, int, int); /* Struct for addargs */ arglist args; arglist remote_remote_args; /* Bandwidth limit */ long long limit_kbps = 0; struct bwlimit bwlimit; /* Name of current file being transferred. */ char *curfile; /* This is set to non-zero to enable verbose mode. */ int verbose_mode = 0; LogLevel log_level = SYSLOG_LEVEL_INFO; /* This is set to zero if the progressmeter is not desired. */ int showprogress = 1; /* * This is set to non-zero if remote-remote copy should be piped * through this process. */ int throughlocal = 1; /* Non-standard port to use for the ssh connection or -1. */ int sshport = -1; /* This is the program to execute for the secured connection. ("ssh" or -S) */ char *ssh_program = _PATH_SSH_PROGRAM; /* This is used to store the pid of ssh_program */ pid_t do_cmd_pid = -1; pid_t do_cmd_pid2 = -1; /* SFTP copy parameters */ size_t sftp_copy_buflen; size_t sftp_nrequests; /* Needed for sftp */ volatile sig_atomic_t interrupted = 0; -int remote_glob(struct sftp_conn *, const char *, int, +int sftp_glob(struct sftp_conn *, const char *, int, int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ static void killchild(int signo) { if (do_cmd_pid > 1) { kill(do_cmd_pid, signo ? signo : SIGTERM); (void)waitpid(do_cmd_pid, NULL, 0); } if (do_cmd_pid2 > 1) { kill(do_cmd_pid2, signo ? signo : SIGTERM); (void)waitpid(do_cmd_pid2, NULL, 0); } if (signo) _exit(1); exit(1); } static void suspone(int pid, int signo) { int status; if (pid > 1) { kill(pid, signo); while (waitpid(pid, &status, WUNTRACED) == -1 && errno == EINTR) ; } } static void suspchild(int signo) { suspone(do_cmd_pid, signo); suspone(do_cmd_pid2, signo); kill(getpid(), SIGSTOP); } static int do_local_cmd(arglist *a) { u_int i; int status; pid_t pid; if (a->num == 0) fatal("do_local_cmd: no arguments"); if (verbose_mode) { fprintf(stderr, "Executing:"); for (i = 0; i < a->num; i++) fmprintf(stderr, " %s", a->list[i]); fprintf(stderr, "\n"); } if ((pid = fork()) == -1) fatal("do_local_cmd: fork: %s", strerror(errno)); if (pid == 0) { execvp(a->list[0], a->list); perror(a->list[0]); exit(1); } do_cmd_pid = pid; ssh_signal(SIGTERM, killchild); ssh_signal(SIGINT, killchild); ssh_signal(SIGHUP, killchild); while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("do_local_cmd: waitpid: %s", strerror(errno)); do_cmd_pid = -1; if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return (-1); return (0); } /* * This function executes the given command as the specified user on the * given host. This returns < 0 if execution fails, and >= 0 otherwise. This * assigns the input and output file descriptors on success. */ int do_cmd(char *program, char *host, char *remuser, int port, int subsystem, char *cmd, int *fdin, int *fdout, pid_t *pid) { #ifdef USE_PIPES int pin[2], pout[2]; #else int sv[2]; #endif if (verbose_mode) fmprintf(stderr, "Executing: program %s host %s, user %s, command %s\n", program, host, remuser ? remuser : "(unspecified)", cmd); if (port == -1) port = sshport; #ifdef USE_PIPES if (pipe(pin) == -1 || pipe(pout) == -1) fatal("pipe: %s", strerror(errno)); #else /* Create a socket pair for communicating with ssh. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) fatal("socketpair: %s", strerror(errno)); #endif ssh_signal(SIGTSTP, suspchild); ssh_signal(SIGTTIN, suspchild); ssh_signal(SIGTTOU, suspchild); /* Fork a child to execute the command on the remote host using ssh. */ *pid = fork(); switch (*pid) { case -1: fatal("fork: %s", strerror(errno)); case 0: /* Child. */ #ifdef USE_PIPES if (dup2(pin[0], STDIN_FILENO) == -1 || dup2(pout[1], STDOUT_FILENO) == -1) { error("dup2: %s", strerror(errno)); _exit(1); } close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); #else if (dup2(sv[0], STDIN_FILENO) == -1 || dup2(sv[0], STDOUT_FILENO) == -1) { error("dup2: %s", strerror(errno)); _exit(1); } close(sv[0]); close(sv[1]); #endif replacearg(&args, 0, "%s", program); if (port != -1) { addargs(&args, "-p"); addargs(&args, "%d", port); } if (remuser != NULL) { addargs(&args, "-l"); addargs(&args, "%s", remuser); } if (subsystem) addargs(&args, "-s"); addargs(&args, "--"); addargs(&args, "%s", host); addargs(&args, "%s", cmd); execvp(program, args.list); perror(program); _exit(1); default: /* Parent. Close the other side, and return the local side. */ #ifdef USE_PIPES close(pin[0]); close(pout[1]); *fdout = pin[1]; *fdin = pout[0]; #else close(sv[0]); *fdin = sv[1]; *fdout = sv[1]; #endif ssh_signal(SIGTERM, killchild); ssh_signal(SIGINT, killchild); ssh_signal(SIGHUP, killchild); return 0; } } /* * This function executes a command similar to do_cmd(), but expects the * input and output descriptors to be setup by a previous call to do_cmd(). * This way the input and output of two commands can be connected. */ int do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout) { int status; pid_t pid; if (verbose_mode) fmprintf(stderr, "Executing: 2nd program %s host %s, user %s, command %s\n", ssh_program, host, remuser ? remuser : "(unspecified)", cmd); if (port == -1) port = sshport; /* Fork a child to execute the command on the remote host using ssh. */ pid = fork(); if (pid == 0) { if (dup2(fdin, 0) == -1) perror("dup2"); if (dup2(fdout, 1) == -1) perror("dup2"); replacearg(&args, 0, "%s", ssh_program); if (port != -1) { addargs(&args, "-p"); addargs(&args, "%d", port); } if (remuser != NULL) { addargs(&args, "-l"); addargs(&args, "%s", remuser); } addargs(&args, "-oBatchMode=yes"); addargs(&args, "--"); addargs(&args, "%s", host); addargs(&args, "%s", cmd); execvp(ssh_program, args.list); perror(ssh_program); exit(1); } else if (pid == -1) { fatal("fork: %s", strerror(errno)); } while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("do_cmd2: waitpid: %s", strerror(errno)); return 0; } typedef struct { size_t cnt; char *buf; } BUF; BUF *allocbuf(BUF *, int, int); void lostconn(int); int okname(char *); void run_err(const char *,...) __attribute__((__format__ (printf, 1, 2))) __attribute__((__nonnull__ (1))); int note_err(const char *,...) __attribute__((__format__ (printf, 1, 2))); void verifydir(char *); struct passwd *pwd; uid_t userid; int errs, remin, remout, remin2, remout2; int Tflag, pflag, iamremote, iamrecursive, targetshouldbedirectory; #define CMDNEEDS 64 char cmd[CMDNEEDS]; /* must hold "rcp -r -p -d\0" */ enum scp_mode_e { MODE_SCP, MODE_SFTP }; int response(void); void rsource(char *, struct stat *); void sink(int, char *[], const char *); void source(int, char *[]); void tolocal(int, char *[], enum scp_mode_e, char *sftp_direct); void toremote(int, char *[], enum scp_mode_e, char *sftp_direct); void usage(void); void source_sftp(int, char *, char *, struct sftp_conn *); void sink_sftp(int, char *, const char *, struct sftp_conn *); void throughlocal_sftp(struct sftp_conn *, struct sftp_conn *, char *, char *); int main(int argc, char **argv) { int ch, fflag, tflag, status, r, n; char **newargv, *argv0; const char *errstr; extern char *optarg; extern int optind; enum scp_mode_e mode = MODE_SFTP; char *sftp_direct = NULL; long long llv; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); msetlocale(); /* Copy argv, because we modify it */ argv0 = argv[0]; newargv = xcalloc(MAXIMUM(argc + 1, 1), sizeof(*newargv)); for (n = 0; n < argc; n++) newargv[n] = xstrdup(argv[n]); argv = newargv; __progname = ssh_get_progname(argv[0]); log_init(argv0, log_level, SYSLOG_FACILITY_USER, 2); memset(&args, '\0', sizeof(args)); memset(&remote_remote_args, '\0', sizeof(remote_remote_args)); args.list = remote_remote_args.list = NULL; addargs(&args, "%s", ssh_program); addargs(&args, "-x"); addargs(&args, "-oPermitLocalCommand=no"); addargs(&args, "-oClearAllForwardings=yes"); addargs(&args, "-oRemoteCommand=none"); addargs(&args, "-oRequestTTY=no"); fflag = Tflag = tflag = 0; while ((ch = getopt(argc, argv, "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) { switch (ch) { /* User-visible flags. */ case '1': fatal("SSH protocol v.1 is no longer supported"); break; case '2': /* Ignored */ break; case 'A': case '4': case '6': case 'C': addargs(&args, "-%c", ch); addargs(&remote_remote_args, "-%c", ch); break; case 'D': sftp_direct = optarg; break; case '3': throughlocal = 1; break; case 'R': throughlocal = 0; break; case 'o': case 'c': case 'i': case 'F': case 'J': addargs(&remote_remote_args, "-%c", ch); addargs(&remote_remote_args, "%s", optarg); addargs(&args, "-%c", ch); addargs(&args, "%s", optarg); break; case 'O': mode = MODE_SCP; break; case 's': mode = MODE_SFTP; break; case 'P': sshport = a2port(optarg); if (sshport <= 0) fatal("bad port \"%s\"\n", optarg); break; case 'B': addargs(&remote_remote_args, "-oBatchmode=yes"); addargs(&args, "-oBatchmode=yes"); break; case 'l': limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, &errstr); if (errstr != NULL) usage(); limit_kbps *= 1024; /* kbps */ bandwidth_limit_init(&bwlimit, limit_kbps, COPY_BUFLEN); break; case 'p': pflag = 1; break; case 'r': iamrecursive = 1; break; case 'S': ssh_program = xstrdup(optarg); break; case 'v': addargs(&args, "-v"); addargs(&remote_remote_args, "-v"); if (verbose_mode == 0) log_level = SYSLOG_LEVEL_DEBUG1; else if (log_level < SYSLOG_LEVEL_DEBUG3) log_level++; verbose_mode = 1; break; case 'q': addargs(&args, "-q"); addargs(&remote_remote_args, "-q"); showprogress = 0; break; case 'X': /* Please keep in sync with sftp.c -X */ if (strncmp(optarg, "buffer=", 7) == 0) { r = scan_scaled(optarg + 7, &llv); if (r == 0 && (llv <= 0 || llv > 256 * 1024)) { r = -1; errno = EINVAL; } if (r == -1) { fatal("Invalid buffer size \"%s\": %s", optarg + 7, strerror(errno)); } sftp_copy_buflen = (size_t)llv; } else if (strncmp(optarg, "nrequests=", 10) == 0) { llv = strtonum(optarg + 10, 1, 256 * 1024, &errstr); if (errstr != NULL) { fatal("Invalid number of requests " "\"%s\": %s", optarg + 10, errstr); } sftp_nrequests = (size_t)llv; } else { fatal("Invalid -X option"); } break; /* Server options. */ case 'd': targetshouldbedirectory = 1; break; case 'f': /* "from" */ iamremote = 1; fflag = 1; break; case 't': /* "to" */ iamremote = 1; tflag = 1; #ifdef HAVE_CYGWIN setmode(0, O_BINARY); #endif break; case 'T': Tflag = 1; break; default: usage(); } } argc -= optind; argv += optind; log_init(argv0, log_level, SYSLOG_FACILITY_USER, 2); /* Do this last because we want the user to be able to override it */ addargs(&args, "-oForwardAgent=no"); if (iamremote) mode = MODE_SCP; if ((pwd = getpwuid(userid = getuid())) == NULL) fatal("unknown user %u", (u_int) userid); if (!isatty(STDOUT_FILENO)) showprogress = 0; if (pflag) { /* Cannot pledge: -p allows setuid/setgid files... */ } else { if (pledge("stdio rpath wpath cpath fattr tty proc exec", NULL) == -1) { perror("pledge"); exit(1); } } remin = STDIN_FILENO; remout = STDOUT_FILENO; if (fflag) { /* Follow "protocol", send data. */ (void) response(); source(argc, argv); exit(errs != 0); } if (tflag) { /* Receive data. */ sink(argc, argv, NULL); exit(errs != 0); } if (argc < 2) usage(); if (argc > 2) targetshouldbedirectory = 1; remin = remout = -1; do_cmd_pid = -1; /* Command to be executed on remote system using "ssh". */ (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s", verbose_mode ? " -v" : "", iamrecursive ? " -r" : "", pflag ? " -p" : "", targetshouldbedirectory ? " -d" : ""); (void) ssh_signal(SIGPIPE, lostconn); if (colon(argv[argc - 1])) /* Dest is remote host. */ toremote(argc, argv, mode, sftp_direct); else { if (targetshouldbedirectory) verifydir(argv[argc - 1]); tolocal(argc, argv, mode, sftp_direct); /* Dest is local host. */ } /* * Finally check the exit status of the ssh process, if one was forked * and no error has occurred yet */ if (do_cmd_pid != -1 && (mode == MODE_SFTP || errs == 0)) { if (remin != -1) (void) close(remin); if (remout != -1) (void) close(remout); if (waitpid(do_cmd_pid, &status, 0) == -1) errs = 1; else { if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) errs = 1; } } exit(errs != 0); } /* Callback from atomicio6 to update progress meter and limit bandwidth */ static int scpio(void *_cnt, size_t s) { off_t *cnt = (off_t *)_cnt; *cnt += s; refresh_progress_meter(0); if (limit_kbps > 0) bandwidth_limit(&bwlimit, s); return 0; } static int do_times(int fd, int verb, const struct stat *sb) { /* strlen(2^64) == 20; strlen(10^6) == 7 */ char buf[(20 + 7 + 2) * 2 + 2]; (void)snprintf(buf, sizeof(buf), "T%llu 0 %llu 0\n", (unsigned long long) (sb->st_mtime < 0 ? 0 : sb->st_mtime), (unsigned long long) (sb->st_atime < 0 ? 0 : sb->st_atime)); if (verb) { fprintf(stderr, "File mtime %lld atime %lld\n", (long long)sb->st_mtime, (long long)sb->st_atime); fprintf(stderr, "Sending file timestamps: %s", buf); } (void) atomicio(vwrite, fd, buf, strlen(buf)); return (response()); } static int parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp, char **pathp) { int r; r = parse_uri("scp", uri, userp, hostp, portp, pathp); if (r == 0 && *pathp == NULL) *pathp = xstrdup("."); return r; } /* Appends a string to an array; returns 0 on success, -1 on alloc failure */ static int append(char *cp, char ***ap, size_t *np) { char **tmp; if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL) return -1; tmp[(*np)] = cp; (*np)++; *ap = tmp; return 0; } /* * Finds the start and end of the first brace pair in the pattern. * returns 0 on success or -1 for invalid patterns. */ static int find_brace(const char *pattern, int *startp, int *endp) { int i; int in_bracket, brace_level; *startp = *endp = -1; in_bracket = brace_level = 0; for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) { switch (pattern[i]) { case '\\': /* skip next character */ if (pattern[i + 1] != '\0') i++; break; case '[': in_bracket = 1; break; case ']': in_bracket = 0; break; case '{': if (in_bracket) break; if (pattern[i + 1] == '}') { /* Protect a single {}, for find(1), like csh */ i++; /* skip */ break; } if (*startp == -1) *startp = i; brace_level++; break; case '}': if (in_bracket) break; if (*startp < 0) { /* Unbalanced brace */ return -1; } if (--brace_level <= 0) *endp = i; break; } } /* unbalanced brackets/braces */ if (*endp < 0 && (*startp >= 0 || in_bracket)) return -1; return 0; } /* * Assembles and records a successfully-expanded pattern, returns -1 on * alloc failure. */ static int emit_expansion(const char *pattern, int brace_start, int brace_end, int sel_start, int sel_end, char ***patternsp, size_t *npatternsp) { char *cp; size_t pattern_len; int o = 0, tail_len; if ((pattern_len = strlen(pattern)) == 0 || pattern_len >= INT_MAX) return -1; tail_len = strlen(pattern + brace_end + 1); if ((cp = malloc(brace_start + (sel_end - sel_start) + tail_len + 1)) == NULL) return -1; /* Pattern before initial brace */ if (brace_start > 0) { memcpy(cp, pattern, brace_start); o = brace_start; } /* Current braced selection */ if (sel_end - sel_start > 0) { memcpy(cp + o, pattern + sel_start, sel_end - sel_start); o += sel_end - sel_start; } /* Remainder of pattern after closing brace */ if (tail_len > 0) { memcpy(cp + o, pattern + brace_end + 1, tail_len); o += tail_len; } cp[o] = '\0'; if (append(cp, patternsp, npatternsp) != 0) { free(cp); return -1; } return 0; } /* * Expand the first encountered brace in pattern, appending the expanded * patterns it yielded to the *patternsp array. * * Returns 0 on success or -1 on allocation failure. * * Signals whether expansion was performed via *expanded and whether * pattern was invalid via *invalid. */ static int brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp, int *expanded, int *invalid) { int i; int in_bracket, brace_start, brace_end, brace_level; int sel_start, sel_end; *invalid = *expanded = 0; if (find_brace(pattern, &brace_start, &brace_end) != 0) { *invalid = 1; return 0; } else if (brace_start == -1) return 0; in_bracket = brace_level = 0; for (i = sel_start = brace_start + 1; i < brace_end; i++) { switch (pattern[i]) { case '{': if (in_bracket) break; brace_level++; break; case '}': if (in_bracket) break; brace_level--; break; case '[': in_bracket = 1; break; case ']': in_bracket = 0; break; case '\\': if (i < brace_end - 1) i++; /* skip */ break; } if (pattern[i] == ',' || i == brace_end - 1) { if (in_bracket || brace_level > 0) continue; /* End of a selection, emit an expanded pattern */ /* Adjust end index for last selection */ sel_end = (i == brace_end - 1) ? brace_end : i; if (emit_expansion(pattern, brace_start, brace_end, sel_start, sel_end, patternsp, npatternsp) != 0) return -1; /* move on to the next selection */ sel_start = i + 1; continue; } } if (in_bracket || brace_level > 0) { *invalid = 1; return 0; } /* success */ *expanded = 1; return 0; } /* Expand braces from pattern. Returns 0 on success, -1 on failure */ static int brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp) { char *cp, *cp2, **active = NULL, **done = NULL; size_t i, nactive = 0, ndone = 0; int ret = -1, invalid = 0, expanded = 0; *patternsp = NULL; *npatternsp = 0; /* Start the worklist with the original pattern */ if ((cp = strdup(pattern)) == NULL) return -1; if (append(cp, &active, &nactive) != 0) { free(cp); return -1; } while (nactive > 0) { cp = active[nactive - 1]; nactive--; if (brace_expand_one(cp, &active, &nactive, &expanded, &invalid) == -1) { free(cp); goto fail; } if (invalid) fatal_f("invalid brace pattern \"%s\"", cp); if (expanded) { /* * Current entry expanded to new entries on the * active list; discard the progenitor pattern. */ free(cp); continue; } /* * Pattern did not expand; append the finename component to * the completed list */ if ((cp2 = strrchr(cp, '/')) != NULL) *cp2++ = '\0'; else cp2 = cp; if (append(xstrdup(cp2), &done, &ndone) != 0) { free(cp); goto fail; } free(cp); } /* success */ *patternsp = done; *npatternsp = ndone; done = NULL; ndone = 0; ret = 0; fail: for (i = 0; i < nactive; i++) free(active[i]); free(active); for (i = 0; i < ndone; i++) free(done[i]); free(done); return ret; } static struct sftp_conn * do_sftp_connect(char *host, char *user, int port, char *sftp_direct, int *reminp, int *remoutp, int *pidp) { if (sftp_direct == NULL) { if (do_cmd(ssh_program, host, user, port, 1, "sftp", reminp, remoutp, pidp) < 0) return NULL; } else { freeargs(&args); addargs(&args, "sftp-server"); if (do_cmd(sftp_direct, host, NULL, -1, 0, "sftp", reminp, remoutp, pidp) < 0) return NULL; } - return do_init(*reminp, *remoutp, + return sftp_init(*reminp, *remoutp, sftp_copy_buflen, sftp_nrequests, limit_kbps); } void toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct) { char *suser = NULL, *host = NULL, *src = NULL; char *bp, *tuser, *thost, *targ; int sport = -1, tport = -1; struct sftp_conn *conn = NULL, *conn2 = NULL; arglist alist; int i, r, status; struct stat sb; u_int j; memset(&alist, '\0', sizeof(alist)); alist.list = NULL; /* Parse target */ r = parse_scp_uri(argv[argc - 1], &tuser, &thost, &tport, &targ); if (r == -1) { fmprintf(stderr, "%s: invalid uri\n", argv[argc - 1]); ++errs; goto out; } if (r != 0) { if (parse_user_host_path(argv[argc - 1], &tuser, &thost, &targ) == -1) { fmprintf(stderr, "%s: invalid target\n", argv[argc - 1]); ++errs; goto out; } } /* Parse source files */ for (i = 0; i < argc - 1; i++) { free(suser); free(host); free(src); r = parse_scp_uri(argv[i], &suser, &host, &sport, &src); if (r == -1) { fmprintf(stderr, "%s: invalid uri\n", argv[i]); ++errs; continue; } if (r != 0) { parse_user_host_path(argv[i], &suser, &host, &src); } if (suser != NULL && !okname(suser)) { ++errs; continue; } if (host && throughlocal) { /* extended remote to remote */ if (mode == MODE_SFTP) { if (remin == -1) { /* Connect to dest now */ conn = do_sftp_connect(thost, tuser, tport, sftp_direct, &remin, &remout, &do_cmd_pid); if (conn == NULL) { fatal("Unable to open " "destination connection"); } debug3_f("origin in %d out %d pid %ld", remin, remout, (long)do_cmd_pid); } /* * XXX remember suser/host/sport and only * reconnect if they change between arguments. * would save reconnections for cases like * scp -3 hosta:/foo hosta:/bar hostb: */ /* Connect to origin now */ conn2 = do_sftp_connect(host, suser, sport, sftp_direct, &remin2, &remout2, &do_cmd_pid2); if (conn2 == NULL) { fatal("Unable to open " "source connection"); } debug3_f("destination in %d out %d pid %ld", remin2, remout2, (long)do_cmd_pid2); throughlocal_sftp(conn2, conn, src, targ); (void) close(remin2); (void) close(remout2); remin2 = remout2 = -1; if (waitpid(do_cmd_pid2, &status, 0) == -1) ++errs; else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) ++errs; do_cmd_pid2 = -1; continue; } else { xasprintf(&bp, "%s -f %s%s", cmd, *src == '-' ? "-- " : "", src); if (do_cmd(ssh_program, host, suser, sport, 0, bp, &remin, &remout, &do_cmd_pid) < 0) exit(1); free(bp); xasprintf(&bp, "%s -t %s%s", cmd, *targ == '-' ? "-- " : "", targ); if (do_cmd2(thost, tuser, tport, bp, remin, remout) < 0) exit(1); free(bp); (void) close(remin); (void) close(remout); remin = remout = -1; } } else if (host) { /* standard remote to remote */ /* * Second remote user is passed to first remote side * via scp command-line. Ensure it contains no obvious * shell characters. */ if (tuser != NULL && !okname(tuser)) { ++errs; continue; } if (tport != -1 && tport != SSH_DEFAULT_PORT) { /* This would require the remote support URIs */ fatal("target port not supported with two " "remote hosts and the -R option"); } freeargs(&alist); addargs(&alist, "%s", ssh_program); addargs(&alist, "-x"); addargs(&alist, "-oClearAllForwardings=yes"); addargs(&alist, "-n"); for (j = 0; j < remote_remote_args.num; j++) { addargs(&alist, "%s", remote_remote_args.list[j]); } if (sport != -1) { addargs(&alist, "-p"); addargs(&alist, "%d", sport); } if (suser) { addargs(&alist, "-l"); addargs(&alist, "%s", suser); } addargs(&alist, "--"); addargs(&alist, "%s", host); addargs(&alist, "%s", cmd); addargs(&alist, "%s", src); addargs(&alist, "%s%s%s:%s", tuser ? tuser : "", tuser ? "@" : "", thost, targ); if (do_local_cmd(&alist) != 0) errs = 1; } else { /* local to remote */ if (mode == MODE_SFTP) { /* no need to glob: already done by shell */ if (stat(argv[i], &sb) != 0) { fatal("stat local \"%s\": %s", argv[i], strerror(errno)); } if (remin == -1) { /* Connect to remote now */ conn = do_sftp_connect(thost, tuser, tport, sftp_direct, &remin, &remout, &do_cmd_pid); if (conn == NULL) { fatal("Unable to open sftp " "connection"); } } /* The protocol */ source_sftp(1, argv[i], targ, conn); continue; } /* SCP */ if (remin == -1) { xasprintf(&bp, "%s -t %s%s", cmd, *targ == '-' ? "-- " : "", targ); if (do_cmd(ssh_program, thost, tuser, tport, 0, bp, &remin, &remout, &do_cmd_pid) < 0) exit(1); if (response() < 0) exit(1); free(bp); } source(1, argv + i); } } out: if (mode == MODE_SFTP) free(conn); free(tuser); free(thost); free(targ); free(suser); free(host); free(src); } void tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct) { char *bp, *host = NULL, *src = NULL, *suser = NULL; arglist alist; struct sftp_conn *conn = NULL; int i, r, sport = -1; memset(&alist, '\0', sizeof(alist)); alist.list = NULL; for (i = 0; i < argc - 1; i++) { free(suser); free(host); free(src); r = parse_scp_uri(argv[i], &suser, &host, &sport, &src); if (r == -1) { fmprintf(stderr, "%s: invalid uri\n", argv[i]); ++errs; continue; } if (r != 0) parse_user_host_path(argv[i], &suser, &host, &src); if (suser != NULL && !okname(suser)) { ++errs; continue; } if (!host) { /* Local to local. */ freeargs(&alist); addargs(&alist, "%s", _PATH_CP); if (iamrecursive) addargs(&alist, "-r"); if (pflag) addargs(&alist, "-p"); addargs(&alist, "--"); addargs(&alist, "%s", argv[i]); addargs(&alist, "%s", argv[argc-1]); if (do_local_cmd(&alist)) ++errs; continue; } /* Remote to local. */ if (mode == MODE_SFTP) { conn = do_sftp_connect(host, suser, sport, sftp_direct, &remin, &remout, &do_cmd_pid); if (conn == NULL) { error("sftp connection failed"); ++errs; continue; } /* The protocol */ sink_sftp(1, argv[argc - 1], src, conn); free(conn); (void) close(remin); (void) close(remout); remin = remout = -1; continue; } /* SCP */ xasprintf(&bp, "%s -f %s%s", cmd, *src == '-' ? "-- " : "", src); if (do_cmd(ssh_program, host, suser, sport, 0, bp, &remin, &remout, &do_cmd_pid) < 0) { free(bp); ++errs; continue; } free(bp); sink(1, argv + argc - 1, src); (void) close(remin); remin = remout = -1; } free(suser); free(host); free(src); } /* Prepare remote path, handling ~ by assuming cwd is the homedir */ static char * prepare_remote_path(struct sftp_conn *conn, const char *path) { size_t nslash; /* Handle ~ prefixed paths */ if (*path == '\0' || strcmp(path, "~") == 0) return xstrdup("."); if (*path != '~') return xstrdup(path); if (strncmp(path, "~/", 2) == 0) { if ((nslash = strspn(path + 2, "/")) == strlen(path + 2)) return xstrdup("."); return xstrdup(path + 2 + nslash); } - if (can_expand_path(conn)) - return do_expand_path(conn, path); + if (sftp_can_expand_path(conn)) + return sftp_expand_path(conn, path); /* No protocol extension */ error("server expand-path extension is required " "for ~user paths in SFTP mode"); return NULL; } void source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn) { char *target = NULL, *filename = NULL, *abs_dst = NULL; int src_is_dir, target_is_dir; Attrib a; struct stat st; memset(&a, '\0', sizeof(a)); if (stat(src, &st) != 0) fatal("stat local \"%s\": %s", src, strerror(errno)); src_is_dir = S_ISDIR(st.st_mode); if ((filename = basename(src)) == NULL) fatal("basename \"%s\": %s", src, strerror(errno)); /* * No need to glob here - the local shell already took care of * the expansions */ if ((target = prepare_remote_path(conn, targ)) == NULL) cleanup_exit(255); - target_is_dir = remote_is_dir(conn, target); + target_is_dir = sftp_remote_is_dir(conn, target); if (targetshouldbedirectory && !target_is_dir) { debug("target directory \"%s\" does not exist", target); a.flags = SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = st.st_mode | 0700; /* ensure writable */ - if (do_mkdir(conn, target, &a, 1) != 0) + if (sftp_mkdir(conn, target, &a, 1) != 0) cleanup_exit(255); /* error already logged */ target_is_dir = 1; } if (target_is_dir) - abs_dst = path_append(target, filename); + abs_dst = sftp_path_append(target, filename); else { abs_dst = target; target = NULL; } debug3_f("copying local %s to remote %s", src, abs_dst); if (src_is_dir && iamrecursive) { - if (upload_dir(conn, src, abs_dst, pflag, + if (sftp_upload_dir(conn, src, abs_dst, pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) != 0) { error("failed to upload directory %s to %s", src, targ); errs = 1; } - } else if (do_upload(conn, src, abs_dst, pflag, 0, 0, 1) != 0) { + } else if (sftp_upload(conn, src, abs_dst, pflag, 0, 0, 1) != 0) { error("failed to upload file %s to %s", src, targ); errs = 1; } free(abs_dst); free(target); } void source(int argc, char **argv) { struct stat stb; static BUF buffer; BUF *bp; off_t i, statbytes; size_t amt, nr; int fd = -1, haderr, indx; char *last, *name, buf[PATH_MAX + 128], encname[PATH_MAX]; int len; for (indx = 0; indx < argc; ++indx) { name = argv[indx]; statbytes = 0; len = strlen(name); while (len > 1 && name[len-1] == '/') name[--len] = '\0'; if ((fd = open(name, O_RDONLY|O_NONBLOCK)) == -1) goto syserr; if (strchr(name, '\n') != NULL) { strnvis(encname, name, sizeof(encname), VIS_NL); name = encname; } if (fstat(fd, &stb) == -1) { syserr: run_err("%s: %s", name, strerror(errno)); goto next; } if (stb.st_size < 0) { run_err("%s: %s", name, "Negative file size"); goto next; } unset_nonblock(fd); switch (stb.st_mode & S_IFMT) { case S_IFREG: break; case S_IFDIR: if (iamrecursive) { rsource(name, &stb); goto next; } /* FALLTHROUGH */ default: run_err("%s: not a regular file", name); goto next; } if ((last = strrchr(name, '/')) == NULL) last = name; else ++last; curfile = last; if (pflag) { if (do_times(remout, verbose_mode, &stb) < 0) goto next; } #define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) snprintf(buf, sizeof buf, "C%04o %lld %s\n", (u_int) (stb.st_mode & FILEMODEMASK), (long long)stb.st_size, last); if (verbose_mode) fmprintf(stderr, "Sending file modes: %s", buf); (void) atomicio(vwrite, remout, buf, strlen(buf)); if (response() < 0) goto next; if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) { next: if (fd != -1) { (void) close(fd); fd = -1; } continue; } if (showprogress) start_progress_meter(curfile, stb.st_size, &statbytes); set_nonblock(remout); for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { amt = bp->cnt; if (i + (off_t)amt > stb.st_size) amt = stb.st_size - i; if (!haderr) { if ((nr = atomicio(read, fd, bp->buf, amt)) != amt) { haderr = errno; memset(bp->buf + nr, 0, amt - nr); } } /* Keep writing after error to retain sync */ if (haderr) { (void)atomicio(vwrite, remout, bp->buf, amt); memset(bp->buf, 0, amt); continue; } if (atomicio6(vwrite, remout, bp->buf, amt, scpio, &statbytes) != amt) haderr = errno; } unset_nonblock(remout); if (fd != -1) { if (close(fd) == -1 && !haderr) haderr = errno; fd = -1; } if (!haderr) (void) atomicio(vwrite, remout, "", 1); else run_err("%s: %s", name, strerror(haderr)); (void) response(); if (showprogress) stop_progress_meter(); } } void rsource(char *name, struct stat *statp) { DIR *dirp; struct dirent *dp; char *last, *vect[1], path[PATH_MAX]; if (!(dirp = opendir(name))) { run_err("%s: %s", name, strerror(errno)); return; } last = strrchr(name, '/'); if (last == NULL) last = name; else last++; if (pflag) { if (do_times(remout, verbose_mode, statp) < 0) { closedir(dirp); return; } } (void) snprintf(path, sizeof path, "D%04o %d %.1024s\n", (u_int) (statp->st_mode & FILEMODEMASK), 0, last); if (verbose_mode) fmprintf(stderr, "Entering directory: %s", path); (void) atomicio(vwrite, remout, path, strlen(path)); if (response() < 0) { closedir(dirp); return; } while ((dp = readdir(dirp)) != NULL) { if (dp->d_ino == 0) continue; if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) { run_err("%s/%s: name too long", name, dp->d_name); continue; } (void) snprintf(path, sizeof path, "%s/%s", name, dp->d_name); vect[0] = path; source(1, vect); } (void) closedir(dirp); (void) atomicio(vwrite, remout, "E\n", 2); (void) response(); } void sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn) { char *abs_src = NULL; char *abs_dst = NULL; glob_t g; char *filename, *tmp = NULL; int i, r, err = 0, dst_is_dir; struct stat st; memset(&g, 0, sizeof(g)); /* * Here, we need remote glob as SFTP can not depend on remote shell * expansions */ if ((abs_src = prepare_remote_path(conn, src)) == NULL) { err = -1; goto out; } debug3_f("copying remote %s to local %s", abs_src, dst); - if ((r = remote_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK, + if ((r = sftp_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK, NULL, &g)) != 0) { if (r == GLOB_NOSPACE) error("%s: too many glob matches", src); else error("%s: %s", src, strerror(ENOENT)); err = -1; goto out; } /* Did we actually get any matches back from the glob? */ if (g.gl_matchc == 0 && g.gl_pathc == 1 && g.gl_pathv[0] != 0) { /* * If nothing matched but a path returned, then it's probably * a GLOB_NOCHECK result. Check whether the unglobbed path * exists so we can give a nice error message early. */ - if (do_stat(conn, g.gl_pathv[0], 1) == NULL) { + if (sftp_stat(conn, g.gl_pathv[0], 1, NULL) != 0) { error("%s: %s", src, strerror(ENOENT)); err = -1; goto out; } } if ((r = stat(dst, &st)) != 0) debug2_f("stat local \"%s\": %s", dst, strerror(errno)); dst_is_dir = r == 0 && S_ISDIR(st.st_mode); if (g.gl_matchc > 1 && !dst_is_dir) { if (r == 0) { error("Multiple files match pattern, but destination " "\"%s\" is not a directory", dst); err = -1; goto out; } debug2_f("creating destination \"%s\"", dst); if (mkdir(dst, 0777) != 0) { error("local mkdir \"%s\": %s", dst, strerror(errno)); err = -1; goto out; } dst_is_dir = 1; } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); err = -1; goto out; } if (dst_is_dir) - abs_dst = path_append(dst, filename); + abs_dst = sftp_path_append(dst, filename); else abs_dst = xstrdup(dst); debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); - if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) { - if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, - pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) == -1) + if (sftp_globpath_is_dir(g.gl_pathv[i]) && iamrecursive) { + if (sftp_download_dir(conn, g.gl_pathv[i], abs_dst, + NULL, pflag, SFTP_PROGRESS_ONLY, 0, 0, 1, 1) == -1) err = -1; } else { - if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, + if (sftp_download(conn, g.gl_pathv[i], abs_dst, NULL, pflag, 0, 0, 1) == -1) err = -1; } free(abs_dst); abs_dst = NULL; free(tmp); tmp = NULL; } out: free(abs_src); free(tmp); globfree(&g); if (err == -1) errs = 1; } #define TYPE_OVERFLOW(type, val) \ ((sizeof(type) == 4 && (val) > INT32_MAX) || \ (sizeof(type) == 8 && (val) > INT64_MAX) || \ (sizeof(type) != 4 && sizeof(type) != 8)) void sink(int argc, char **argv, const char *src) { static BUF buffer; struct stat stb; BUF *bp; off_t i; size_t j, count; int amt, exists, first, ofd; mode_t mode, omode, mask; off_t size, statbytes; unsigned long long ull; int setimes, targisdir, wrerr; char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; char **patterns = NULL; size_t n, npatterns = 0; struct timeval tv[2]; #define atime tv[0] #define mtime tv[1] #define SCREWUP(str) { why = str; goto screwup; } if (TYPE_OVERFLOW(time_t, 0) || TYPE_OVERFLOW(off_t, 0)) SCREWUP("Unexpected off_t/time_t size"); setimes = targisdir = 0; mask = umask(0); if (!pflag) (void) umask(mask); if (argc != 1) { run_err("ambiguous target"); exit(1); } targ = *argv; if (targetshouldbedirectory) verifydir(targ); (void) atomicio(vwrite, remout, "", 1); if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) targisdir = 1; if (src != NULL && !iamrecursive && !Tflag) { /* * Prepare to try to restrict incoming filenames to match * the requested destination file glob. */ if (brace_expand(src, &patterns, &npatterns) != 0) fatal_f("could not expand pattern"); } for (first = 1;; first = 0) { cp = buf; if (atomicio(read, remin, cp, 1) != 1) goto done; if (*cp++ == '\n') SCREWUP("unexpected "); do { if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch)) SCREWUP("lost connection"); *cp++ = ch; } while (cp < &buf[sizeof(buf) - 1] && ch != '\n'); *cp = 0; if (verbose_mode) fmprintf(stderr, "Sink: %s", buf); if (buf[0] == '\01' || buf[0] == '\02') { if (iamremote == 0) { (void) snmprintf(visbuf, sizeof(visbuf), NULL, "%s", buf + 1); (void) atomicio(vwrite, STDERR_FILENO, visbuf, strlen(visbuf)); } if (buf[0] == '\02') exit(1); ++errs; continue; } if (buf[0] == 'E') { (void) atomicio(vwrite, remout, "", 1); goto done; } if (ch == '\n') *--cp = 0; cp = buf; if (*cp == 'T') { setimes++; cp++; if (!isdigit((unsigned char)*cp)) SCREWUP("mtime.sec not present"); ull = strtoull(cp, &cp, 10); if (!cp || *cp++ != ' ') SCREWUP("mtime.sec not delimited"); if (TYPE_OVERFLOW(time_t, ull)) setimes = 0; /* out of range */ mtime.tv_sec = ull; mtime.tv_usec = strtol(cp, &cp, 10); if (!cp || *cp++ != ' ' || mtime.tv_usec < 0 || mtime.tv_usec > 999999) SCREWUP("mtime.usec not delimited"); if (!isdigit((unsigned char)*cp)) SCREWUP("atime.sec not present"); ull = strtoull(cp, &cp, 10); if (!cp || *cp++ != ' ') SCREWUP("atime.sec not delimited"); if (TYPE_OVERFLOW(time_t, ull)) setimes = 0; /* out of range */ atime.tv_sec = ull; atime.tv_usec = strtol(cp, &cp, 10); if (!cp || *cp++ != '\0' || atime.tv_usec < 0 || atime.tv_usec > 999999) SCREWUP("atime.usec not delimited"); (void) atomicio(vwrite, remout, "", 1); continue; } if (*cp != 'C' && *cp != 'D') { /* * Check for the case "rcp remote:foo\* local:bar". * In this case, the line "No match." can be returned * by the shell before the rcp command on the remote is * executed so the ^Aerror_message convention isn't * followed. */ if (first) { run_err("%s", cp); exit(1); } SCREWUP("expected control record"); } mode = 0; for (++cp; cp < buf + 5; cp++) { if (*cp < '0' || *cp > '7') SCREWUP("bad mode"); mode = (mode << 3) | (*cp - '0'); } if (!pflag) mode &= ~mask; if (*cp++ != ' ') SCREWUP("mode not delimited"); if (!isdigit((unsigned char)*cp)) SCREWUP("size not present"); ull = strtoull(cp, &cp, 10); if (!cp || *cp++ != ' ') SCREWUP("size not delimited"); if (TYPE_OVERFLOW(off_t, ull)) SCREWUP("size out of range"); size = (off_t)ull; if (*cp == '\0' || strchr(cp, '/') != NULL || strcmp(cp, ".") == 0 || strcmp(cp, "..") == 0) { run_err("error: unexpected filename: %s", cp); exit(1); } if (npatterns > 0) { for (n = 0; n < npatterns; n++) { if (strcmp(patterns[n], cp) == 0 || fnmatch(patterns[n], cp, 0) == 0) break; } if (n >= npatterns) SCREWUP("filename does not match request"); } if (targisdir) { static char *namebuf; static size_t cursize; size_t need; need = strlen(targ) + strlen(cp) + 250; if (need > cursize) { free(namebuf); namebuf = xmalloc(need); cursize = need; } (void) snprintf(namebuf, need, "%s%s%s", targ, strcmp(targ, "/") ? "/" : "", cp); np = namebuf; } else np = targ; curfile = cp; exists = stat(np, &stb) == 0; if (buf[0] == 'D') { int mod_flag = pflag; if (!iamrecursive) SCREWUP("received directory without -r"); if (exists) { if (!S_ISDIR(stb.st_mode)) { errno = ENOTDIR; goto bad; } if (pflag) (void) chmod(np, mode); } else { /* Handle copying from a read-only directory */ mod_flag = 1; if (mkdir(np, mode | S_IRWXU) == -1) goto bad; } vect[0] = xstrdup(np); sink(1, vect, src); if (setimes) { setimes = 0; (void) utimes(vect[0], tv); } if (mod_flag) (void) chmod(vect[0], mode); free(vect[0]); continue; } omode = mode; mode |= S_IWUSR; if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) == -1) { bad: run_err("%s: %s", np, strerror(errno)); continue; } (void) atomicio(vwrite, remout, "", 1); if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) { (void) close(ofd); continue; } cp = bp->buf; wrerr = 0; /* * NB. do not use run_err() unless immediately followed by * exit() below as it may send a spurious reply that might * desyncronise us from the peer. Use note_err() instead. */ statbytes = 0; if (showprogress) start_progress_meter(curfile, size, &statbytes); set_nonblock(remin); for (count = i = 0; i < size; i += bp->cnt) { amt = bp->cnt; if (i + amt > size) amt = size - i; count += amt; do { j = atomicio6(read, remin, cp, amt, scpio, &statbytes); if (j == 0) { run_err("%s", j != EPIPE ? strerror(errno) : "dropped connection"); exit(1); } amt -= j; cp += j; } while (amt > 0); if (count == bp->cnt) { /* Keep reading so we stay sync'd up. */ if (!wrerr) { if (atomicio(vwrite, ofd, bp->buf, count) != count) { note_err("%s: %s", np, strerror(errno)); wrerr = 1; } } count = 0; cp = bp->buf; } } unset_nonblock(remin); if (count != 0 && !wrerr && atomicio(vwrite, ofd, bp->buf, count) != count) { note_err("%s: %s", np, strerror(errno)); wrerr = 1; } if (!wrerr && (!exists || S_ISREG(stb.st_mode)) && ftruncate(ofd, size) != 0) note_err("%s: truncate: %s", np, strerror(errno)); if (pflag) { if (exists || omode != mode) #ifdef HAVE_FCHMOD if (fchmod(ofd, omode)) { #else /* HAVE_FCHMOD */ if (chmod(np, omode)) { #endif /* HAVE_FCHMOD */ note_err("%s: set mode: %s", np, strerror(errno)); } } else { if (!exists && omode != mode) #ifdef HAVE_FCHMOD if (fchmod(ofd, omode & ~mask)) { #else /* HAVE_FCHMOD */ if (chmod(np, omode & ~mask)) { #endif /* HAVE_FCHMOD */ note_err("%s: set mode: %s", np, strerror(errno)); } } if (close(ofd) == -1) note_err("%s: close: %s", np, strerror(errno)); (void) response(); if (showprogress) stop_progress_meter(); if (setimes && !wrerr) { setimes = 0; if (utimes(np, tv) == -1) { note_err("%s: set times: %s", np, strerror(errno)); } } /* If no error was noted then signal success for this file */ if (note_err(NULL) == 0) (void) atomicio(vwrite, remout, "", 1); } done: for (n = 0; n < npatterns; n++) free(patterns[n]); free(patterns); return; screwup: for (n = 0; n < npatterns; n++) free(patterns[n]); free(patterns); run_err("protocol error: %s", why); exit(1); } void throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to, char *src, char *targ) { char *target = NULL, *filename = NULL, *abs_dst = NULL; char *abs_src = NULL, *tmp = NULL; glob_t g; int i, r, targetisdir, err = 0; if ((filename = basename(src)) == NULL) fatal("basename %s: %s", src, strerror(errno)); if ((abs_src = prepare_remote_path(from, src)) == NULL || (target = prepare_remote_path(to, targ)) == NULL) cleanup_exit(255); memset(&g, 0, sizeof(g)); - targetisdir = remote_is_dir(to, target); + targetisdir = sftp_remote_is_dir(to, target); if (!targetisdir && targetshouldbedirectory) { error("%s: destination is not a directory", targ); err = -1; goto out; } debug3_f("copying remote %s to remote %s", abs_src, target); - if ((r = remote_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK, + if ((r = sftp_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK, NULL, &g)) != 0) { if (r == GLOB_NOSPACE) error("%s: too many glob matches", src); else error("%s: %s", src, strerror(ENOENT)); err = -1; goto out; } /* Did we actually get any matches back from the glob? */ if (g.gl_matchc == 0 && g.gl_pathc == 1 && g.gl_pathv[0] != 0) { /* * If nothing matched but a path returned, then it's probably * a GLOB_NOCHECK result. Check whether the unglobbed path * exists so we can give a nice error message early. */ - if (do_stat(from, g.gl_pathv[0], 1) == NULL) { + if (sftp_stat(from, g.gl_pathv[0], 1, NULL) != 0) { error("%s: %s", src, strerror(ENOENT)); err = -1; goto out; } } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); err = -1; goto out; } if (targetisdir) - abs_dst = path_append(target, filename); + abs_dst = sftp_path_append(target, filename); else abs_dst = xstrdup(target); debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); - if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) { - if (crossload_dir(from, to, g.gl_pathv[i], abs_dst, + if (sftp_globpath_is_dir(g.gl_pathv[i]) && iamrecursive) { + if (sftp_crossload_dir(from, to, g.gl_pathv[i], abs_dst, NULL, pflag, SFTP_PROGRESS_ONLY, 1) == -1) err = -1; } else { - if (do_crossload(from, to, g.gl_pathv[i], abs_dst, NULL, - pflag) == -1) + if (sftp_crossload(from, to, g.gl_pathv[i], abs_dst, + NULL, pflag) == -1) err = -1; } free(abs_dst); abs_dst = NULL; free(tmp); tmp = NULL; } out: free(abs_src); free(abs_dst); free(target); free(tmp); globfree(&g); if (err == -1) errs = 1; } int response(void) { char ch, *cp, resp, rbuf[2048], visbuf[2048]; if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp)) lostconn(0); cp = rbuf; switch (resp) { case 0: /* ok */ return (0); default: *cp++ = resp; /* FALLTHROUGH */ case 1: /* error, followed by error msg */ case 2: /* fatal error, "" */ do { if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch)) lostconn(0); *cp++ = ch; } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n'); if (!iamremote) { cp[-1] = '\0'; (void) snmprintf(visbuf, sizeof(visbuf), NULL, "%s\n", rbuf); (void) atomicio(vwrite, STDERR_FILENO, visbuf, strlen(visbuf)); } ++errs; if (resp == 1) return (-1); exit(1); } /* NOTREACHED */ } void usage(void) { (void) fprintf(stderr, "usage: scp [-346ABCOpqRrsTv] [-c cipher] [-D sftp_server_path] [-F ssh_config]\n" " [-i identity_file] [-J destination] [-l limit] [-o ssh_option]\n" " [-P port] [-S program] [-X sftp_option] source ... target\n"); exit(1); } void run_err(const char *fmt,...) { static FILE *fp; va_list ap; ++errs; if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) { (void) fprintf(fp, "%c", 0x01); (void) fprintf(fp, "scp: "); va_start(ap, fmt); (void) vfprintf(fp, fmt, ap); va_end(ap); (void) fprintf(fp, "\n"); (void) fflush(fp); } if (!iamremote) { va_start(ap, fmt); vfmprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } } /* * Notes a sink error for sending at the end of a file transfer. Returns 0 if * no error has been noted or -1 otherwise. Use note_err(NULL) to flush * any active error at the end of the transfer. */ int note_err(const char *fmt, ...) { static char *emsg; va_list ap; /* Replay any previously-noted error */ if (fmt == NULL) { if (emsg == NULL) return 0; run_err("%s", emsg); free(emsg); emsg = NULL; return -1; } errs++; /* Prefer first-noted error */ if (emsg != NULL) return -1; va_start(ap, fmt); vasnmprintf(&emsg, INT_MAX, NULL, fmt, ap); va_end(ap); return -1; } void verifydir(char *cp) { struct stat stb; if (!stat(cp, &stb)) { if (S_ISDIR(stb.st_mode)) return; errno = ENOTDIR; } run_err("%s: %s", cp, strerror(errno)); killchild(0); } int okname(char *cp0) { int c; char *cp; cp = cp0; do { c = (int)*cp; if (c & 0200) goto bad; if (!isalpha(c) && !isdigit((unsigned char)c)) { switch (c) { case '\'': case '"': case '`': case ' ': case '#': goto bad; default: break; } } } while (*++cp); return (1); bad: fmprintf(stderr, "%s: invalid user name\n", cp0); return (0); } BUF * allocbuf(BUF *bp, int fd, int blksize) { size_t size; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE struct stat stb; if (fstat(fd, &stb) == -1) { run_err("fstat: %s", strerror(errno)); return (0); } size = ROUNDUP(stb.st_blksize, blksize); if (size == 0) size = blksize; #else /* HAVE_STRUCT_STAT_ST_BLKSIZE */ size = blksize; #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ if (bp->cnt >= size) return (bp); bp->buf = xrecallocarray(bp->buf, bp->cnt, size, 1); bp->cnt = size; return (bp); } void lostconn(int signo) { if (!iamremote) (void)write(STDERR_FILENO, "lost connection\n", 16); if (signo) _exit(1); else exit(1); } void cleanup_exit(int i) { if (remin > 0) close(remin); if (remout > 0) close(remout); if (remin2 > 0) close(remin2); if (remout2 > 0) close(remout2); if (do_cmd_pid > 0) (void)waitpid(do_cmd_pid, NULL, 0); if (do_cmd_pid2 > 0) (void)waitpid(do_cmd_pid2, NULL, 0); exit(i); } diff --git a/crypto/openssh/servconf.c b/crypto/openssh/servconf.c index e7c8da5a6b06..c114f95c6677 100644 --- a/crypto/openssh/servconf.c +++ b/crypto/openssh/servconf.c @@ -1,3195 +1,3254 @@ -/* $OpenBSD: servconf.c,v 1.396 2023/07/17 05:26:38 djm Exp $ */ +/* $OpenBSD: servconf.c,v 1.402 2023/09/08 06:34:24 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" #include #include #include #ifdef __OpenBSD__ #include #endif #include #include #include #ifdef HAVE_NET_ROUTE_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UTIL_H #include #endif #ifdef USE_SYSTEM_GLOB # include #else # include "openbsd-compat/glob.h" #endif #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "log.h" #include "sshbuf.h" #include "misc.h" #include "servconf.h" #include "pathnames.h" #include "cipher.h" #include "sshkey.h" #include "kex.h" #include "mac.h" #include "match.h" #include "channels.h" #include "groupaccess.h" #include "canohost.h" #include "packet.h" #include "ssherr.h" #include "hostfile.h" #include "auth.h" #include "myproposal.h" #include "digest.h" #include "version.h" static void add_listen_addr(ServerOptions *, const char *, const char *, int); static void add_one_listen_addr(ServerOptions *, const char *, const char *, int); static void parse_server_config_depth(ServerOptions *options, const char *filename, struct sshbuf *conf, struct include_list *includes, struct connection_info *connectinfo, int flags, int *activep, int depth); /* Use of privilege separation or not */ extern int use_privsep; extern struct sshbuf *cfg; /* Initializes the server options to their default values. */ void initialize_server_options(ServerOptions *options) { memset(options, 0, sizeof(*options)); /* Portable-specific options */ options->use_pam = -1; /* Standard Options */ options->num_ports = 0; options->ports_from_cmdline = 0; options->queued_listen_addrs = NULL; options->num_queued_listens = 0; options->listen_addrs = NULL; options->num_listen_addrs = 0; options->address_family = -1; options->routing_domain = NULL; options->num_host_key_files = 0; options->num_host_cert_files = 0; options->host_key_agent = NULL; options->pid_file = NULL; options->login_grace_time = -1; options->permit_root_login = PERMIT_NOT_SET; options->ignore_rhosts = -1; options->ignore_user_known_hosts = -1; options->print_motd = -1; options->print_lastlog = -1; options->x11_forwarding = -1; options->x11_display_offset = -1; options->x11_use_localhost = -1; options->permit_tty = -1; options->permit_user_rc = -1; options->xauth_location = NULL; options->strict_modes = -1; options->tcp_keep_alive = -1; options->log_facility = SYSLOG_FACILITY_NOT_SET; options->log_level = SYSLOG_LEVEL_NOT_SET; options->num_log_verbose = 0; options->log_verbose = NULL; options->hostbased_authentication = -1; options->hostbased_uses_name_from_packet_only = -1; options->hostbased_accepted_algos = NULL; options->hostkeyalgorithms = NULL; options->pubkey_authentication = -1; options->pubkey_auth_options = -1; options->pubkey_accepted_algos = NULL; options->kerberos_authentication = -1; options->kerberos_or_local_passwd = -1; options->kerberos_ticket_cleanup = -1; options->kerberos_get_afs_token = -1; options->gss_authentication=-1; options->gss_cleanup_creds = -1; options->gss_strict_acceptor = -1; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->permit_empty_passwd = -1; options->permit_user_env = -1; options->permit_user_env_allowlist = NULL; options->compression = -1; options->rekey_limit = -1; options->rekey_interval = -1; options->allow_tcp_forwarding = -1; options->allow_streamlocal_forwarding = -1; options->allow_agent_forwarding = -1; options->num_allow_users = 0; options->num_deny_users = 0; options->num_allow_groups = 0; options->num_deny_groups = 0; options->ciphers = NULL; options->macs = NULL; options->kex_algorithms = NULL; options->ca_sign_algorithms = NULL; options->fwd_opts.gateway_ports = -1; options->fwd_opts.streamlocal_bind_mask = (mode_t)-1; options->fwd_opts.streamlocal_bind_unlink = -1; options->num_subsystems = 0; options->max_startups_begin = -1; options->max_startups_rate = -1; options->max_startups = -1; options->per_source_max_startups = -1; options->per_source_masklen_ipv4 = -1; options->per_source_masklen_ipv6 = -1; options->max_authtries = -1; options->max_sessions = -1; options->banner = NULL; options->use_dns = -1; options->client_alive_interval = -1; options->client_alive_count_max = -1; options->num_authkeys_files = 0; options->num_accept_env = 0; options->num_setenv = 0; options->permit_tun = -1; options->permitted_opens = NULL; options->permitted_listens = NULL; options->adm_forced_command = NULL; options->chroot_directory = NULL; options->authorized_keys_command = NULL; options->authorized_keys_command_user = NULL; options->revoked_keys_file = NULL; options->sk_provider = NULL; options->trusted_user_ca_keys = NULL; options->authorized_principals_file = NULL; options->authorized_principals_command = NULL; options->authorized_principals_command_user = NULL; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; options->version_addendum = NULL; options->fingerprint_hash = -1; options->disable_forwarding = -1; options->expose_userauth_info = -1; options->required_rsa_size = -1; options->channel_timeouts = NULL; options->num_channel_timeouts = 0; options->unused_connection_timeout = -1; options->use_blacklist = -1; } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ static int option_clear_or_none(const char *o) { return o == NULL || strcasecmp(o, "none") == 0; } static void assemble_algorithms(ServerOptions *o) { char *all_cipher, *all_mac, *all_kex, *all_key, *all_sig; char *def_cipher, *def_mac, *def_kex, *def_key, *def_sig; int r; all_cipher = cipher_alg_list(',', 0); all_mac = mac_alg_list(','); all_kex = kex_alg_list(','); all_key = sshkey_alg_list(0, 0, 1, ','); all_sig = sshkey_alg_list(0, 1, 1, ','); /* remove unsupported algos from default lists */ def_cipher = match_filter_allowlist(KEX_SERVER_ENCRYPT, all_cipher); def_mac = match_filter_allowlist(KEX_SERVER_MAC, all_mac); def_kex = match_filter_allowlist(KEX_SERVER_KEX, all_kex); def_key = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key); def_sig = match_filter_allowlist(SSH_ALLOWED_CA_SIGALGS, all_sig); #define ASSEMBLE(what, defaults, all) \ do { \ if ((r = kex_assemble_names(&o->what, defaults, all)) != 0) \ fatal_fr(r, "%s", #what); \ } while (0) ASSEMBLE(ciphers, def_cipher, all_cipher); ASSEMBLE(macs, def_mac, all_mac); ASSEMBLE(kex_algorithms, def_kex, all_kex); ASSEMBLE(hostkeyalgorithms, def_key, all_key); ASSEMBLE(hostbased_accepted_algos, def_key, all_key); ASSEMBLE(pubkey_accepted_algos, def_key, all_key); ASSEMBLE(ca_sign_algorithms, def_sig, all_sig); #undef ASSEMBLE free(all_cipher); free(all_mac); free(all_kex); free(all_key); free(all_sig); free(def_cipher); free(def_mac); free(def_kex); free(def_key); free(def_sig); } static const char *defaultkey = "[default]"; void servconf_add_hostkey(const char *file, const int line, ServerOptions *options, const char *path, int userprovided) { char *apath = derelativise_path(path); if (file == defaultkey && access(path, R_OK) != 0) return; opt_array_append2(file, line, "HostKey", &options->host_key_files, &options->host_key_file_userprovided, &options->num_host_key_files, apath, userprovided); free(apath); } void servconf_add_hostcert(const char *file, const int line, ServerOptions *options, const char *path) { char *apath = derelativise_path(path); opt_array_append(file, line, "HostCertificate", &options->host_cert_files, &options->num_host_cert_files, apath); free(apath); } void fill_default_server_options(ServerOptions *options) { u_int i; /* Portable-specific options */ if (options->use_pam == -1) options->use_pam = 1; /* Standard Options */ if (options->num_host_key_files == 0) { /* fill default hostkeys for protocols */ servconf_add_hostkey(defaultkey, 0, options, _PATH_HOST_RSA_KEY_FILE, 0); #ifdef OPENSSL_HAS_ECC servconf_add_hostkey(defaultkey, 0, options, _PATH_HOST_ECDSA_KEY_FILE, 0); #endif servconf_add_hostkey(defaultkey, 0, options, _PATH_HOST_ED25519_KEY_FILE, 0); #ifdef WITH_XMSS servconf_add_hostkey(defaultkey, 0, options, _PATH_HOST_XMSS_KEY_FILE, 0); #endif /* WITH_XMSS */ } if (options->num_host_key_files == 0) fatal("No host key files found"); /* No certificates by default */ if (options->num_ports == 0) options->ports[options->num_ports++] = SSH_DEFAULT_PORT; if (options->address_family == -1) options->address_family = AF_UNSPEC; if (options->listen_addrs == NULL) add_listen_addr(options, NULL, NULL, 0); if (options->pid_file == NULL) options->pid_file = xstrdup(_PATH_SSH_DAEMON_PID_FILE); if (options->moduli_file == NULL) options->moduli_file = xstrdup(_PATH_DH_MODULI); if (options->login_grace_time == -1) options->login_grace_time = 120; if (options->permit_root_login == PERMIT_NOT_SET) options->permit_root_login = PERMIT_NO; if (options->ignore_rhosts == -1) options->ignore_rhosts = 1; if (options->ignore_user_known_hosts == -1) options->ignore_user_known_hosts = 0; if (options->print_motd == -1) options->print_motd = 1; if (options->print_lastlog == -1) options->print_lastlog = 1; if (options->x11_forwarding == -1) options->x11_forwarding = 0; if (options->x11_display_offset == -1) options->x11_display_offset = 10; if (options->x11_use_localhost == -1) options->x11_use_localhost = 1; if (options->xauth_location == NULL) options->xauth_location = xstrdup(_PATH_XAUTH); if (options->permit_tty == -1) options->permit_tty = 1; if (options->permit_user_rc == -1) options->permit_user_rc = 1; if (options->strict_modes == -1) options->strict_modes = 1; if (options->tcp_keep_alive == -1) options->tcp_keep_alive = 1; if (options->log_facility == SYSLOG_FACILITY_NOT_SET) options->log_facility = SYSLOG_FACILITY_AUTH; if (options->log_level == SYSLOG_LEVEL_NOT_SET) options->log_level = SYSLOG_LEVEL_INFO; if (options->hostbased_authentication == -1) options->hostbased_authentication = 0; if (options->hostbased_uses_name_from_packet_only == -1) options->hostbased_uses_name_from_packet_only = 0; if (options->pubkey_authentication == -1) options->pubkey_authentication = 1; if (options->pubkey_auth_options == -1) options->pubkey_auth_options = 0; if (options->kerberos_authentication == -1) options->kerberos_authentication = 0; if (options->kerberos_or_local_passwd == -1) options->kerberos_or_local_passwd = 1; if (options->kerberos_ticket_cleanup == -1) options->kerberos_ticket_cleanup = 1; if (options->kerberos_get_afs_token == -1) options->kerberos_get_afs_token = 0; if (options->gss_authentication == -1) options->gss_authentication = 0; if (options->gss_cleanup_creds == -1) options->gss_cleanup_creds = 1; if (options->gss_strict_acceptor == -1) options->gss_strict_acceptor = 1; if (options->password_authentication == -1) options->password_authentication = 0; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 1; if (options->permit_empty_passwd == -1) options->permit_empty_passwd = 0; if (options->permit_user_env == -1) { options->permit_user_env = 0; options->permit_user_env_allowlist = NULL; } if (options->compression == -1) #ifdef WITH_ZLIB options->compression = COMP_DELAYED; #else options->compression = COMP_NONE; #endif if (options->rekey_limit == -1) options->rekey_limit = 0; if (options->rekey_interval == -1) options->rekey_interval = 0; if (options->allow_tcp_forwarding == -1) options->allow_tcp_forwarding = FORWARD_ALLOW; if (options->allow_streamlocal_forwarding == -1) options->allow_streamlocal_forwarding = FORWARD_ALLOW; if (options->allow_agent_forwarding == -1) options->allow_agent_forwarding = 1; if (options->fwd_opts.gateway_ports == -1) options->fwd_opts.gateway_ports = 0; if (options->max_startups == -1) options->max_startups = 100; if (options->max_startups_rate == -1) options->max_startups_rate = 30; /* 30% */ if (options->max_startups_begin == -1) options->max_startups_begin = 10; if (options->per_source_max_startups == -1) options->per_source_max_startups = INT_MAX; if (options->per_source_masklen_ipv4 == -1) options->per_source_masklen_ipv4 = 32; if (options->per_source_masklen_ipv6 == -1) options->per_source_masklen_ipv6 = 128; if (options->max_authtries == -1) options->max_authtries = DEFAULT_AUTH_FAIL_MAX; if (options->max_sessions == -1) options->max_sessions = DEFAULT_SESSIONS_MAX; if (options->use_dns == -1) options->use_dns = 1; if (options->client_alive_interval == -1) options->client_alive_interval = 0; if (options->client_alive_count_max == -1) options->client_alive_count_max = 3; if (options->num_authkeys_files == 0) { opt_array_append(defaultkey, 0, "AuthorizedKeysFiles", &options->authorized_keys_files, &options->num_authkeys_files, _PATH_SSH_USER_PERMITTED_KEYS); opt_array_append(defaultkey, 0, "AuthorizedKeysFiles", &options->authorized_keys_files, &options->num_authkeys_files, _PATH_SSH_USER_PERMITTED_KEYS2); } if (options->permit_tun == -1) options->permit_tun = SSH_TUNMODE_NO; if (options->ip_qos_interactive == -1) options->ip_qos_interactive = IPTOS_DSCP_AF21; if (options->ip_qos_bulk == -1) options->ip_qos_bulk = IPTOS_DSCP_CS1; if (options->version_addendum == NULL) options->version_addendum = xstrdup(SSH_VERSION_FREEBSD); if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1) options->fwd_opts.streamlocal_bind_mask = 0177; if (options->fwd_opts.streamlocal_bind_unlink == -1) options->fwd_opts.streamlocal_bind_unlink = 0; if (options->fingerprint_hash == -1) options->fingerprint_hash = SSH_FP_HASH_DEFAULT; if (options->disable_forwarding == -1) options->disable_forwarding = 0; if (options->expose_userauth_info == -1) options->expose_userauth_info = 0; if (options->sk_provider == NULL) options->sk_provider = xstrdup("internal"); if (options->required_rsa_size == -1) options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; if (options->unused_connection_timeout == -1) options->unused_connection_timeout = 0; if (options->use_blacklist == -1) options->use_blacklist = 0; assemble_algorithms(options); /* Turn privilege separation and sandboxing on by default */ if (use_privsep == -1) use_privsep = PRIVSEP_ON; #define CLEAR_ON_NONE(v) \ do { \ if (option_clear_or_none(v)) { \ free(v); \ v = NULL; \ } \ } while(0) #define CLEAR_ON_NONE_ARRAY(v, nv, none) \ do { \ if (options->nv == 1 && \ strcasecmp(options->v[0], none) == 0) { \ free(options->v[0]); \ free(options->v); \ options->v = NULL; \ options->nv = 0; \ } \ } while (0) CLEAR_ON_NONE(options->pid_file); CLEAR_ON_NONE(options->xauth_location); CLEAR_ON_NONE(options->banner); CLEAR_ON_NONE(options->trusted_user_ca_keys); CLEAR_ON_NONE(options->revoked_keys_file); CLEAR_ON_NONE(options->sk_provider); CLEAR_ON_NONE(options->authorized_principals_file); CLEAR_ON_NONE(options->adm_forced_command); CLEAR_ON_NONE(options->chroot_directory); CLEAR_ON_NONE(options->routing_domain); CLEAR_ON_NONE(options->host_key_agent); for (i = 0; i < options->num_host_key_files; i++) CLEAR_ON_NONE(options->host_key_files[i]); for (i = 0; i < options->num_host_cert_files; i++) CLEAR_ON_NONE(options->host_cert_files[i]); CLEAR_ON_NONE_ARRAY(channel_timeouts, num_channel_timeouts, "none"); CLEAR_ON_NONE_ARRAY(auth_methods, num_auth_methods, "any"); #undef CLEAR_ON_NONE #undef CLEAR_ON_NONE_ARRAY } /* Keyword tokens. */ typedef enum { sBadOption, /* == unknown option */ /* Portable-specific options */ sUsePAM, /* Standard Options */ sPort, sHostKeyFile, sLoginGraceTime, sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, sKerberosGetAFSToken, sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sAddressFamily, sPrintMotd, sPrintLastLog, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive, sPermitUserEnvironment, sAllowTcpForwarding, sCompression, sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, sIgnoreUserKnownHosts, sCiphers, sMacs, sPidFile, sModuliFile, sGatewayPorts, sPubkeyAuthentication, sPubkeyAcceptedAlgorithms, sXAuthLocation, sSubsystem, sMaxStartups, sMaxAuthTries, sMaxSessions, sBanner, sUseDNS, sHostbasedAuthentication, sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedAlgorithms, sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, sAcceptEnv, sSetEnv, sPermitTunnel, sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, sHostCertificate, sInclude, sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser, sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum, sAuthorizedKeysCommand, sAuthorizedKeysCommandUser, sAuthenticationMethods, sHostKeyAgent, sPermitUserRC, sStreamLocalBindMask, sStreamLocalBindUnlink, sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, sUseBlacklist, sDeprecated, sIgnore, sUnsupported } ServerOpCodes; #define SSHCFG_GLOBAL 0x01 /* allowed in main section of config */ #define SSHCFG_MATCH 0x02 /* allowed inside a Match section */ #define SSHCFG_ALL (SSHCFG_GLOBAL|SSHCFG_MATCH) #define SSHCFG_NEVERMATCH 0x04 /* Match never matches; internal only */ #define SSHCFG_MATCH_ONLY 0x08 /* Match only in conditional blocks; internal only */ /* Textual representation of the tokens. */ static struct { const char *name; ServerOpCodes opcode; u_int flags; } keywords[] = { /* Portable-specific options */ #ifdef USE_PAM { "usepam", sUsePAM, SSHCFG_GLOBAL }, #else { "usepam", sUnsupported, SSHCFG_GLOBAL }, #endif { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, /* Standard Options */ { "port", sPort, SSHCFG_GLOBAL }, { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, { "pidfile", sPidFile, SSHCFG_GLOBAL }, { "modulifile", sModuliFile, SSHCFG_GLOBAL }, { "serverkeybits", sDeprecated, SSHCFG_GLOBAL }, { "logingracetime", sLoginGraceTime, SSHCFG_GLOBAL }, { "keyregenerationinterval", sDeprecated, SSHCFG_GLOBAL }, { "permitrootlogin", sPermitRootLogin, SSHCFG_ALL }, { "syslogfacility", sLogFacility, SSHCFG_GLOBAL }, { "loglevel", sLogLevel, SSHCFG_ALL }, { "logverbose", sLogVerbose, SSHCFG_ALL }, { "rhostsauthentication", sDeprecated, SSHCFG_GLOBAL }, { "rhostsrsaauthentication", sDeprecated, SSHCFG_ALL }, { "hostbasedauthentication", sHostbasedAuthentication, SSHCFG_ALL }, { "hostbasedusesnamefrompacketonly", sHostbasedUsesNameFromPacketOnly, SSHCFG_ALL }, { "hostbasedacceptedalgorithms", sHostbasedAcceptedAlgorithms, SSHCFG_ALL }, { "hostbasedacceptedkeytypes", sHostbasedAcceptedAlgorithms, SSHCFG_ALL }, /* obsolete */ { "hostkeyalgorithms", sHostKeyAlgorithms, SSHCFG_GLOBAL }, { "rsaauthentication", sDeprecated, SSHCFG_ALL }, { "pubkeyauthentication", sPubkeyAuthentication, SSHCFG_ALL }, { "pubkeyacceptedalgorithms", sPubkeyAcceptedAlgorithms, SSHCFG_ALL }, { "pubkeyacceptedkeytypes", sPubkeyAcceptedAlgorithms, SSHCFG_ALL }, /* obsolete */ { "pubkeyauthoptions", sPubkeyAuthOptions, SSHCFG_ALL }, { "dsaauthentication", sPubkeyAuthentication, SSHCFG_GLOBAL }, /* alias */ #ifdef KRB5 { "kerberosauthentication", sKerberosAuthentication, SSHCFG_ALL }, { "kerberosorlocalpasswd", sKerberosOrLocalPasswd, SSHCFG_GLOBAL }, { "kerberosticketcleanup", sKerberosTicketCleanup, SSHCFG_GLOBAL }, #ifdef USE_AFS { "kerberosgetafstoken", sKerberosGetAFSToken, SSHCFG_GLOBAL }, #else { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL }, #endif #else { "kerberosauthentication", sUnsupported, SSHCFG_ALL }, { "kerberosorlocalpasswd", sUnsupported, SSHCFG_GLOBAL }, { "kerberosticketcleanup", sUnsupported, SSHCFG_GLOBAL }, { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL }, #endif { "kerberostgtpassing", sUnsupported, SSHCFG_GLOBAL }, { "afstokenpassing", sUnsupported, SSHCFG_GLOBAL }, #ifdef GSSAPI { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, #else { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, #endif { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, { "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */ { "skeyauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */ { "checkmail", sDeprecated, SSHCFG_GLOBAL }, { "listenaddress", sListenAddress, SSHCFG_GLOBAL }, { "addressfamily", sAddressFamily, SSHCFG_GLOBAL }, { "printmotd", sPrintMotd, SSHCFG_GLOBAL }, #ifdef DISABLE_LASTLOG { "printlastlog", sUnsupported, SSHCFG_GLOBAL }, #else { "printlastlog", sPrintLastLog, SSHCFG_GLOBAL }, #endif { "ignorerhosts", sIgnoreRhosts, SSHCFG_ALL }, { "ignoreuserknownhosts", sIgnoreUserKnownHosts, SSHCFG_GLOBAL }, { "x11forwarding", sX11Forwarding, SSHCFG_ALL }, { "x11displayoffset", sX11DisplayOffset, SSHCFG_ALL }, { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL }, { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, { "uselogin", sDeprecated, SSHCFG_GLOBAL }, { "compression", sCompression, SSHCFG_GLOBAL }, { "rekeylimit", sRekeyLimit, SSHCFG_ALL }, { "tcpkeepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, { "keepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, /* obsolete alias */ { "allowtcpforwarding", sAllowTcpForwarding, SSHCFG_ALL }, { "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL }, { "allowusers", sAllowUsers, SSHCFG_ALL }, { "denyusers", sDenyUsers, SSHCFG_ALL }, { "allowgroups", sAllowGroups, SSHCFG_ALL }, { "denygroups", sDenyGroups, SSHCFG_ALL }, { "ciphers", sCiphers, SSHCFG_GLOBAL }, { "macs", sMacs, SSHCFG_GLOBAL }, { "protocol", sIgnore, SSHCFG_GLOBAL }, { "gatewayports", sGatewayPorts, SSHCFG_ALL }, - { "subsystem", sSubsystem, SSHCFG_GLOBAL }, + { "subsystem", sSubsystem, SSHCFG_ALL }, { "maxstartups", sMaxStartups, SSHCFG_GLOBAL }, { "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL }, { "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL }, { "maxauthtries", sMaxAuthTries, SSHCFG_ALL }, { "maxsessions", sMaxSessions, SSHCFG_ALL }, { "banner", sBanner, SSHCFG_ALL }, { "usedns", sUseDNS, SSHCFG_GLOBAL }, { "verifyreversemapping", sDeprecated, SSHCFG_GLOBAL }, { "reversemappingcheck", sDeprecated, SSHCFG_GLOBAL }, { "clientaliveinterval", sClientAliveInterval, SSHCFG_ALL }, { "clientalivecountmax", sClientAliveCountMax, SSHCFG_ALL }, { "authorizedkeysfile", sAuthorizedKeysFile, SSHCFG_ALL }, { "authorizedkeysfile2", sDeprecated, SSHCFG_ALL }, { "useprivilegeseparation", sDeprecated, SSHCFG_GLOBAL}, { "acceptenv", sAcceptEnv, SSHCFG_ALL }, { "setenv", sSetEnv, SSHCFG_ALL }, { "permittunnel", sPermitTunnel, SSHCFG_ALL }, { "permittty", sPermitTTY, SSHCFG_ALL }, { "permituserrc", sPermitUserRC, SSHCFG_ALL }, { "match", sMatch, SSHCFG_ALL }, { "permitopen", sPermitOpen, SSHCFG_ALL }, { "permitlisten", sPermitListen, SSHCFG_ALL }, { "forcecommand", sForceCommand, SSHCFG_ALL }, { "chrootdirectory", sChrootDirectory, SSHCFG_ALL }, { "hostcertificate", sHostCertificate, SSHCFG_GLOBAL }, { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, { "include", sInclude, SSHCFG_ALL }, { "ipqos", sIPQoS, SSHCFG_ALL }, { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL }, { "authorizedprincipalscommand", sAuthorizedPrincipalsCommand, SSHCFG_ALL }, { "authorizedprincipalscommanduser", sAuthorizedPrincipalsCommandUser, SSHCFG_ALL }, { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL }, { "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL }, { "streamlocalbindmask", sStreamLocalBindMask, SSHCFG_ALL }, { "streamlocalbindunlink", sStreamLocalBindUnlink, SSHCFG_ALL }, { "allowstreamlocalforwarding", sAllowStreamLocalForwarding, SSHCFG_ALL }, { "fingerprinthash", sFingerprintHash, SSHCFG_GLOBAL }, { "disableforwarding", sDisableForwarding, SSHCFG_ALL }, { "exposeauthinfo", sExposeAuthInfo, SSHCFG_ALL }, { "rdomain", sRDomain, SSHCFG_ALL }, { "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL }, { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL }, { "channeltimeout", sChannelTimeout, SSHCFG_ALL }, { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL }, { "useblacklist", sUseBlacklist, SSHCFG_GLOBAL }, { "useblocklist", sUseBlacklist, SSHCFG_GLOBAL }, /* alias */ { NULL, sBadOption, 0 } }; static struct { int val; char *text; } tunmode_desc[] = { { SSH_TUNMODE_NO, "no" }, { SSH_TUNMODE_POINTOPOINT, "point-to-point" }, { SSH_TUNMODE_ETHERNET, "ethernet" }, { SSH_TUNMODE_YES, "yes" }, { -1, NULL } }; /* Returns an opcode name from its number */ static const char * lookup_opcode_name(ServerOpCodes code) { u_int i; for (i = 0; keywords[i].name != NULL; i++) if (keywords[i].opcode == code) return(keywords[i].name); return "UNKNOWN"; } /* * Returns the number of the token pointed to by cp or sBadOption. */ static ServerOpCodes parse_token(const char *cp, const char *filename, int linenum, u_int *flags) { u_int i; for (i = 0; keywords[i].name; i++) if (strcasecmp(cp, keywords[i].name) == 0) { *flags = keywords[i].flags; return keywords[i].opcode; } error("%s: line %d: Bad configuration option: %s", filename, linenum, cp); return sBadOption; } char * derelativise_path(const char *path) { char *expanded, *ret, cwd[PATH_MAX]; if (strcasecmp(path, "none") == 0) return xstrdup("none"); expanded = tilde_expand_filename(path, getuid()); if (path_absolute(expanded)) return expanded; if (getcwd(cwd, sizeof(cwd)) == NULL) fatal_f("getcwd: %s", strerror(errno)); xasprintf(&ret, "%s/%s", cwd, expanded); free(expanded); return ret; } static void add_listen_addr(ServerOptions *options, const char *addr, const char *rdomain, int port) { u_int i; if (port > 0) add_one_listen_addr(options, addr, rdomain, port); else { for (i = 0; i < options->num_ports; i++) { add_one_listen_addr(options, addr, rdomain, options->ports[i]); } } } static void add_one_listen_addr(ServerOptions *options, const char *addr, const char *rdomain, int port) { struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; int gaierr; u_int i; /* Find listen_addrs entry for this rdomain */ for (i = 0; i < options->num_listen_addrs; i++) { if (rdomain == NULL && options->listen_addrs[i].rdomain == NULL) break; if (rdomain == NULL || options->listen_addrs[i].rdomain == NULL) continue; if (strcmp(rdomain, options->listen_addrs[i].rdomain) == 0) break; } if (i >= options->num_listen_addrs) { /* No entry for this rdomain; allocate one */ if (i >= INT_MAX) fatal_f("too many listen addresses"); options->listen_addrs = xrecallocarray(options->listen_addrs, options->num_listen_addrs, options->num_listen_addrs + 1, sizeof(*options->listen_addrs)); i = options->num_listen_addrs++; if (rdomain != NULL) options->listen_addrs[i].rdomain = xstrdup(rdomain); } /* options->listen_addrs[i] points to the addresses for this rdomain */ memset(&hints, 0, sizeof(hints)); hints.ai_family = options->address_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0) fatal("bad addr or host: %s (%s)", addr ? addr : "", ssh_gai_strerror(gaierr)); for (ai = aitop; ai->ai_next; ai = ai->ai_next) ; ai->ai_next = options->listen_addrs[i].addrs; options->listen_addrs[i].addrs = aitop; } /* Returns nonzero if the routing domain name is valid */ static int valid_rdomain(const char *name) { #if defined(HAVE_SYS_VALID_RDOMAIN) return sys_valid_rdomain(name); #elif defined(__OpenBSD__) const char *errstr; long long num; struct rt_tableinfo info; int mib[6]; size_t miblen = sizeof(mib); if (name == NULL) return 1; num = strtonum(name, 0, 255, &errstr); if (errstr != NULL) return 0; /* Check whether the table actually exists */ memset(mib, 0, sizeof(mib)); mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[4] = NET_RT_TABLE; mib[5] = (int)num; if (sysctl(mib, 6, &info, &miblen, NULL, 0) == -1) return 0; return 1; #else /* defined(__OpenBSD__) */ error("Routing domains are not supported on this platform"); return 0; #endif } /* * Queue a ListenAddress to be processed once we have all of the Ports * and AddressFamily options. */ static void queue_listen_addr(ServerOptions *options, const char *addr, const char *rdomain, int port) { struct queued_listenaddr *qla; options->queued_listen_addrs = xrecallocarray( options->queued_listen_addrs, options->num_queued_listens, options->num_queued_listens + 1, sizeof(*options->queued_listen_addrs)); qla = &options->queued_listen_addrs[options->num_queued_listens++]; qla->addr = xstrdup(addr); qla->port = port; qla->rdomain = rdomain == NULL ? NULL : xstrdup(rdomain); } /* * Process queued (text) ListenAddress entries. */ static void process_queued_listen_addrs(ServerOptions *options) { u_int i; struct queued_listenaddr *qla; if (options->num_ports == 0) options->ports[options->num_ports++] = SSH_DEFAULT_PORT; if (options->address_family == -1) options->address_family = AF_UNSPEC; for (i = 0; i < options->num_queued_listens; i++) { qla = &options->queued_listen_addrs[i]; add_listen_addr(options, qla->addr, qla->rdomain, qla->port); free(qla->addr); free(qla->rdomain); } free(options->queued_listen_addrs); options->queued_listen_addrs = NULL; options->num_queued_listens = 0; } /* * Inform channels layer of permitopen options for a single forwarding * direction (local/remote). */ static void process_permitopen_list(struct ssh *ssh, ServerOpCodes opcode, char **opens, u_int num_opens) { u_int i; int port; char *host, *arg, *oarg; int where = opcode == sPermitOpen ? FORWARD_LOCAL : FORWARD_REMOTE; const char *what = lookup_opcode_name(opcode); channel_clear_permission(ssh, FORWARD_ADM, where); if (num_opens == 0) return; /* permit any */ /* handle keywords: "any" / "none" */ if (num_opens == 1 && strcmp(opens[0], "any") == 0) return; if (num_opens == 1 && strcmp(opens[0], "none") == 0) { channel_disable_admin(ssh, where); return; } /* Otherwise treat it as a list of permitted host:port */ for (i = 0; i < num_opens; i++) { oarg = arg = xstrdup(opens[i]); host = hpdelim(&arg); if (host == NULL) fatal_f("missing host in %s", what); host = cleanhostname(host); if (arg == NULL || ((port = permitopen_port(arg)) < 0)) fatal_f("bad port number in %s", what); /* Send it to channels layer */ channel_add_permission(ssh, FORWARD_ADM, where, host, port); free(oarg); } } /* * Inform channels layer of permitopen options from configuration. */ void process_permitopen(struct ssh *ssh, ServerOptions *options) { process_permitopen_list(ssh, sPermitOpen, options->permitted_opens, options->num_permitted_opens); process_permitopen_list(ssh, sPermitListen, options->permitted_listens, options->num_permitted_listens); } /* Parse a ChannelTimeout clause "pattern=interval" */ static int parse_timeout(const char *s, char **typep, int *secsp) { char *cp, *sdup; int secs; if (typep != NULL) *typep = NULL; if (secsp != NULL) *secsp = 0; if (s == NULL) return -1; sdup = xstrdup(s); if ((cp = strchr(sdup, '=')) == NULL || cp == sdup) { free(sdup); return -1; } *cp++ = '\0'; if ((secs = convtime(cp)) < 0) { free(sdup); return -1; } /* success */ if (typep != NULL) *typep = xstrdup(sdup); if (secsp != NULL) *secsp = secs; free(sdup); return 0; } void process_channel_timeouts(struct ssh *ssh, ServerOptions *options) { int secs; u_int i; char *type; debug3_f("setting %u timeouts", options->num_channel_timeouts); channel_clear_timeouts(ssh); for (i = 0; i < options->num_channel_timeouts; i++) { if (parse_timeout(options->channel_timeouts[i], &type, &secs) != 0) { fatal_f("internal error: bad timeout %s", options->channel_timeouts[i]); } channel_add_timeout(ssh, type, secs); free(type); } } struct connection_info * get_connection_info(struct ssh *ssh, int populate, int use_dns) { static struct connection_info ci; if (ssh == NULL || !populate) return &ci; ci.host = auth_get_canonical_hostname(ssh, use_dns); ci.address = ssh_remote_ipaddr(ssh); ci.laddress = ssh_local_ipaddr(ssh); ci.lport = ssh_local_port(ssh); ci.rdomain = ssh_packet_rdomain_in(ssh); return &ci; } /* * The strategy for the Match blocks is that the config file is parsed twice. * * The first time is at startup. activep is initialized to 1 and the * directives in the global context are processed and acted on. Hitting a * Match directive unsets activep and the directives inside the block are * checked for syntax only. * * The second time is after a connection has been established but before * authentication. activep is initialized to 2 and global config directives * are ignored since they have already been processed. If the criteria in a * Match block is met, activep is set and the subsequent directives * processed and actioned until EOF or another Match block unsets it. Any * options set are copied into the main server config. * * Potential additions/improvements: * - Add Match support for pre-kex directives, eg. Ciphers. * * - Add a Tag directive (idea from David Leonard) ala pf, eg: * Match Address 192.168.0.* * Tag trusted * Match Group wheel * Tag trusted * Match Tag trusted * AllowTcpForwarding yes * GatewayPorts clientspecified * [...] * * - Add a PermittedChannelRequests directive * Match Group shell * PermittedChannelRequests session,forwarded-tcpip */ static int match_cfg_line_group(const char *grps, int line, const char *user) { int result = 0; struct passwd *pw; if (user == NULL) goto out; if ((pw = getpwnam(user)) == NULL) { debug("Can't match group at line %d because user %.100s does " "not exist", line, user); } else if (ga_init(pw->pw_name, pw->pw_gid) == 0) { debug("Can't Match group because user %.100s not in any group " "at line %d", user, line); } else if (ga_match_pattern_list(grps) != 1) { debug("user %.100s does not match group list %.100s at line %d", user, grps, line); } else { debug("user %.100s matched group list %.100s at line %d", user, grps, line); result = 1; } out: ga_free(); return result; } static void match_test_missing_fatal(const char *criteria, const char *attrib) { fatal("'Match %s' in configuration but '%s' not in connection " "test specification.", criteria, attrib); } /* * All of the attributes on a single Match line are ANDed together, so we need * to check every attribute and set the result to zero if any attribute does * not match. */ static int match_cfg_line(char **condition, int line, struct connection_info *ci) { int result = 1, attributes = 0, port; char *arg, *attrib, *cp = *condition; if (ci == NULL) debug3("checking syntax for 'Match %s'", cp); else debug3("checking match for '%s' user %s host %s addr %s " "laddr %s lport %d", cp, ci->user ? ci->user : "(null)", ci->host ? ci->host : "(null)", ci->address ? ci->address : "(null)", ci->laddress ? ci->laddress : "(null)", ci->lport); while ((attrib = strdelim(&cp)) && *attrib != '\0') { /* Terminate on comment */ if (*attrib == '#') { cp = NULL; /* mark all arguments consumed */ break; } arg = NULL; attributes++; /* Criterion "all" has no argument and must appear alone */ if (strcasecmp(attrib, "all") == 0) { if (attributes > 1 || ((arg = strdelim(&cp)) != NULL && *arg != '\0' && *arg != '#')) { error("'all' cannot be combined with other " "Match attributes"); return -1; } if (arg != NULL && *arg == '#') cp = NULL; /* mark all arguments consumed */ *condition = cp; return 1; } /* All other criteria require an argument */ if ((arg = strdelim(&cp)) == NULL || *arg == '\0' || *arg == '#') { error("Missing Match criteria for %s", attrib); return -1; } if (strcasecmp(attrib, "user") == 0) { if (ci == NULL || (ci->test && ci->user == NULL)) { result = 0; continue; } if (ci->user == NULL) match_test_missing_fatal("User", "user"); if (match_usergroup_pattern_list(ci->user, arg) != 1) result = 0; else debug("user %.100s matched 'User %.100s' at " "line %d", ci->user, arg, line); } else if (strcasecmp(attrib, "group") == 0) { if (ci == NULL || (ci->test && ci->user == NULL)) { result = 0; continue; } if (ci->user == NULL) match_test_missing_fatal("Group", "user"); switch (match_cfg_line_group(arg, line, ci->user)) { case -1: return -1; case 0: result = 0; } } else if (strcasecmp(attrib, "host") == 0) { if (ci == NULL || (ci->test && ci->host == NULL)) { result = 0; continue; } if (ci->host == NULL) match_test_missing_fatal("Host", "host"); if (match_hostname(ci->host, arg) != 1) result = 0; else debug("connection from %.100s matched 'Host " "%.100s' at line %d", ci->host, arg, line); } else if (strcasecmp(attrib, "address") == 0) { if (ci == NULL || (ci->test && ci->address == NULL)) { if (addr_match_list(NULL, arg) != 0) fatal("Invalid Match address argument " "'%s' at line %d", arg, line); result = 0; continue; } if (ci->address == NULL) match_test_missing_fatal("Address", "addr"); switch (addr_match_list(ci->address, arg)) { case 1: debug("connection from %.100s matched 'Address " "%.100s' at line %d", ci->address, arg, line); break; case 0: case -1: result = 0; break; case -2: return -1; } } else if (strcasecmp(attrib, "localaddress") == 0){ if (ci == NULL || (ci->test && ci->laddress == NULL)) { if (addr_match_list(NULL, arg) != 0) fatal("Invalid Match localaddress " "argument '%s' at line %d", arg, line); result = 0; continue; } if (ci->laddress == NULL) match_test_missing_fatal("LocalAddress", "laddr"); switch (addr_match_list(ci->laddress, arg)) { case 1: debug("connection from %.100s matched " "'LocalAddress %.100s' at line %d", ci->laddress, arg, line); break; case 0: case -1: result = 0; break; case -2: return -1; } } else if (strcasecmp(attrib, "localport") == 0) { if ((port = a2port(arg)) == -1) { error("Invalid LocalPort '%s' on Match line", arg); return -1; } if (ci == NULL || (ci->test && ci->lport == -1)) { result = 0; continue; } if (ci->lport == 0) match_test_missing_fatal("LocalPort", "lport"); /* TODO support port lists */ if (port == ci->lport) debug("connection from %.100s matched " "'LocalPort %d' at line %d", ci->laddress, port, line); else result = 0; } else if (strcasecmp(attrib, "rdomain") == 0) { if (ci == NULL || (ci->test && ci->rdomain == NULL)) { result = 0; continue; } if (ci->rdomain == NULL) match_test_missing_fatal("RDomain", "rdomain"); if (match_pattern_list(ci->rdomain, arg, 0) != 1) result = 0; else debug("user %.100s matched 'RDomain %.100s' at " "line %d", ci->rdomain, arg, line); } else { error("Unsupported Match attribute %s", attrib); return -1; } } if (attributes == 0) { error("One or more attributes required for Match"); return -1; } if (ci != NULL) debug3("match %sfound", result ? "" : "not "); *condition = cp; return result; } #define WHITESPACE " \t\r\n" /* Multistate option parsing */ struct multistate { char *key; int value; }; static const struct multistate multistate_flag[] = { { "yes", 1 }, { "no", 0 }, { NULL, -1 } }; static const struct multistate multistate_ignore_rhosts[] = { { "yes", IGNORE_RHOSTS_YES }, { "no", IGNORE_RHOSTS_NO }, { "shosts-only", IGNORE_RHOSTS_SHOSTS }, { NULL, -1 } }; static const struct multistate multistate_addressfamily[] = { { "inet", AF_INET }, { "inet6", AF_INET6 }, { "any", AF_UNSPEC }, { NULL, -1 } }; static const struct multistate multistate_permitrootlogin[] = { { "without-password", PERMIT_NO_PASSWD }, { "prohibit-password", PERMIT_NO_PASSWD }, { "forced-commands-only", PERMIT_FORCED_ONLY }, { "yes", PERMIT_YES }, { "no", PERMIT_NO }, { NULL, -1 } }; static const struct multistate multistate_compression[] = { #ifdef WITH_ZLIB { "yes", COMP_DELAYED }, { "delayed", COMP_DELAYED }, #endif { "no", COMP_NONE }, { NULL, -1 } }; static const struct multistate multistate_gatewayports[] = { { "clientspecified", 2 }, { "yes", 1 }, { "no", 0 }, { NULL, -1 } }; static const struct multistate multistate_tcpfwd[] = { { "yes", FORWARD_ALLOW }, { "all", FORWARD_ALLOW }, { "no", FORWARD_DENY }, { "remote", FORWARD_REMOTE }, { "local", FORWARD_LOCAL }, { NULL, -1 } }; static int process_server_config_line_depth(ServerOptions *options, char *line, const char *filename, int linenum, int *activep, struct connection_info *connectinfo, int *inc_flags, int depth, struct include_list *includes) { char *str, ***chararrayptr, **charptr, *arg, *arg2, *p, *keyword; int cmdline = 0, *intptr, value, value2, n, port, oactive, r, found; int ca_only = 0; SyslogFacility *log_facility_ptr; LogLevel *log_level_ptr; ServerOpCodes opcode; u_int i, *uintptr, uvalue, flags = 0; size_t len; long long val64; const struct multistate *multistate_ptr; const char *errstr; struct include_item *item; glob_t gbuf; char **oav = NULL, **av; int oac = 0, ac; int ret = -1; /* Strip trailing whitespace. Allow \f (form feed) at EOL only */ if ((len = strlen(line)) == 0) return 0; for (len--; len > 0; len--) { if (strchr(WHITESPACE "\f", line[len]) == NULL) break; line[len] = '\0'; } str = line; if ((keyword = strdelim(&str)) == NULL) return 0; /* Ignore leading whitespace */ if (*keyword == '\0') keyword = strdelim(&str); if (!keyword || !*keyword || *keyword == '#') return 0; if (str == NULL || *str == '\0') { error("%s line %d: no argument after keyword \"%s\"", filename, linenum, keyword); return -1; } intptr = NULL; charptr = NULL; opcode = parse_token(keyword, filename, linenum, &flags); if (argv_split(str, &oac, &oav, 1) != 0) { error("%s line %d: invalid quotes", filename, linenum); return -1; } ac = oac; av = oav; if (activep == NULL) { /* We are processing a command line directive */ cmdline = 1; activep = &cmdline; } if (*activep && opcode != sMatch && opcode != sInclude) debug3("%s:%d setting %s %s", filename, linenum, keyword, str); if (*activep == 0 && !(flags & SSHCFG_MATCH)) { if (connectinfo == NULL) { fatal("%s line %d: Directive '%s' is not allowed " "within a Match block", filename, linenum, keyword); } else { /* this is a directive we have already processed */ ret = 0; goto out; } } switch (opcode) { /* Portable-specific options */ case sUsePAM: intptr = &options->use_pam; goto parse_flag; /* Standard Options */ case sBadOption: goto out; case sPort: /* ignore ports from configfile if cmdline specifies ports */ if (options->ports_from_cmdline) { argv_consume(&ac); break; } if (options->num_ports >= MAX_PORTS) fatal("%s line %d: too many ports.", filename, linenum); arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing port number.", filename, linenum); options->ports[options->num_ports++] = a2port(arg); if (options->ports[options->num_ports-1] <= 0) fatal("%s line %d: Badly formatted port number.", filename, linenum); break; case sLoginGraceTime: intptr = &options->login_grace_time; parse_time: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing time value.", filename, linenum); if ((value = convtime(arg)) == -1) fatal("%s line %d: invalid time value.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case sListenAddress: arg = argv_next(&ac, &av); if (arg == NULL || *arg == '\0') fatal("%s line %d: missing address", filename, linenum); /* check for bare IPv6 address: no "[]" and 2 or more ":" */ if (strchr(arg, '[') == NULL && (p = strchr(arg, ':')) != NULL && strchr(p+1, ':') != NULL) { port = 0; p = arg; } else { arg2 = NULL; p = hpdelim(&arg); if (p == NULL) fatal("%s line %d: bad address:port usage", filename, linenum); p = cleanhostname(p); if (arg == NULL) port = 0; else if ((port = a2port(arg)) <= 0) fatal("%s line %d: bad port number", filename, linenum); } /* Optional routing table */ arg2 = NULL; if ((arg = argv_next(&ac, &av)) != NULL) { if (strcmp(arg, "rdomain") != 0 || (arg2 = argv_next(&ac, &av)) == NULL) fatal("%s line %d: bad ListenAddress syntax", filename, linenum); if (!valid_rdomain(arg2)) fatal("%s line %d: bad routing domain", filename, linenum); } queue_listen_addr(options, p, arg2, port); break; case sAddressFamily: intptr = &options->address_family; multistate_ptr = multistate_addressfamily; parse_multistate: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing argument.", filename, linenum); value = -1; for (i = 0; multistate_ptr[i].key != NULL; i++) { if (strcasecmp(arg, multistate_ptr[i].key) == 0) { value = multistate_ptr[i].value; break; } } if (value == -1) fatal("%s line %d: unsupported option \"%s\".", filename, linenum, arg); if (*activep && *intptr == -1) *intptr = value; break; case sHostKeyFile: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing file name.", filename, linenum); if (*activep) { servconf_add_hostkey(filename, linenum, options, arg, 1); } break; case sHostKeyAgent: charptr = &options->host_key_agent; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing socket name.", filename, linenum); if (*activep && *charptr == NULL) *charptr = !strcmp(arg, SSH_AUTHSOCKET_ENV_NAME) ? xstrdup(arg) : derelativise_path(arg); break; case sHostCertificate: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing file name.", filename, linenum); if (*activep) servconf_add_hostcert(filename, linenum, options, arg); break; case sPidFile: charptr = &options->pid_file; parse_filename: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing file name.", filename, linenum); if (*activep && *charptr == NULL) { *charptr = derelativise_path(arg); /* increase optional counter */ if (intptr != NULL) *intptr = *intptr + 1; } break; case sModuliFile: charptr = &options->moduli_file; goto parse_filename; case sPermitRootLogin: intptr = &options->permit_root_login; multistate_ptr = multistate_permitrootlogin; goto parse_multistate; case sIgnoreRhosts: intptr = &options->ignore_rhosts; multistate_ptr = multistate_ignore_rhosts; goto parse_multistate; case sIgnoreUserKnownHosts: intptr = &options->ignore_user_known_hosts; parse_flag: multistate_ptr = multistate_flag; goto parse_multistate; case sHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; case sHostbasedUsesNameFromPacketOnly: intptr = &options->hostbased_uses_name_from_packet_only; goto parse_flag; case sHostbasedAcceptedAlgorithms: charptr = &options->hostbased_accepted_algos; ca_only = 0; parse_pubkey_algos: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: Missing argument.", filename, linenum); if (*arg != '-' && !sshkey_names_valid2(*arg == '+' || *arg == '^' ? arg + 1 : arg, 1, ca_only)) fatal("%s line %d: Bad key types '%s'.", filename, linenum, arg ? arg : ""); if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case sHostKeyAlgorithms: charptr = &options->hostkeyalgorithms; ca_only = 0; goto parse_pubkey_algos; case sCASignatureAlgorithms: charptr = &options->ca_sign_algorithms; ca_only = 1; goto parse_pubkey_algos; case sPubkeyAuthentication: intptr = &options->pubkey_authentication; ca_only = 0; goto parse_flag; case sPubkeyAcceptedAlgorithms: charptr = &options->pubkey_accepted_algos; ca_only = 0; goto parse_pubkey_algos; case sPubkeyAuthOptions: intptr = &options->pubkey_auth_options; value = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (strcasecmp(arg, "none") == 0) continue; if (strcasecmp(arg, "touch-required") == 0) value |= PUBKEYAUTH_TOUCH_REQUIRED; else if (strcasecmp(arg, "verify-required") == 0) value |= PUBKEYAUTH_VERIFY_REQUIRED; else { error("%s line %d: unsupported %s option %s", filename, linenum, keyword, arg); goto out; } } if (*activep && *intptr == -1) *intptr = value; break; case sKerberosAuthentication: intptr = &options->kerberos_authentication; goto parse_flag; case sKerberosOrLocalPasswd: intptr = &options->kerberos_or_local_passwd; goto parse_flag; case sKerberosTicketCleanup: intptr = &options->kerberos_ticket_cleanup; goto parse_flag; case sKerberosGetAFSToken: intptr = &options->kerberos_get_afs_token; goto parse_flag; case sGssAuthentication: intptr = &options->gss_authentication; goto parse_flag; case sGssCleanupCreds: intptr = &options->gss_cleanup_creds; goto parse_flag; case sGssStrictAcceptor: intptr = &options->gss_strict_acceptor; goto parse_flag; case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; case sKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; case sPrintMotd: intptr = &options->print_motd; goto parse_flag; case sPrintLastLog: intptr = &options->print_lastlog; goto parse_flag; case sX11Forwarding: intptr = &options->x11_forwarding; goto parse_flag; case sX11DisplayOffset: intptr = &options->x11_display_offset; parse_int: arg = argv_next(&ac, &av); if ((errstr = atoi_err(arg, &value)) != NULL) fatal("%s line %d: %s integer value %s.", filename, linenum, keyword, errstr); if (*activep && *intptr == -1) *intptr = value; break; case sX11UseLocalhost: intptr = &options->x11_use_localhost; goto parse_flag; case sXAuthLocation: charptr = &options->xauth_location; goto parse_filename; case sPermitTTY: intptr = &options->permit_tty; goto parse_flag; case sPermitUserRC: intptr = &options->permit_user_rc; goto parse_flag; case sStrictModes: intptr = &options->strict_modes; goto parse_flag; case sTCPKeepAlive: intptr = &options->tcp_keep_alive; goto parse_flag; case sEmptyPasswd: intptr = &options->permit_empty_passwd; goto parse_flag; case sPermitUserEnvironment: intptr = &options->permit_user_env; charptr = &options->permit_user_env_allowlist; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); value = 0; p = NULL; if (strcmp(arg, "yes") == 0) value = 1; else if (strcmp(arg, "no") == 0) value = 0; else { /* Pattern-list specified */ value = 1; p = xstrdup(arg); } if (*activep && *intptr == -1) { *intptr = value; *charptr = p; p = NULL; } free(p); break; case sCompression: intptr = &options->compression; multistate_ptr = multistate_compression; goto parse_multistate; case sRekeyLimit: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (strcmp(arg, "default") == 0) { val64 = 0; } else { if (scan_scaled(arg, &val64) == -1) fatal("%.200s line %d: Bad %s number '%s': %s", filename, linenum, keyword, arg, strerror(errno)); if (val64 != 0 && val64 < 16) fatal("%.200s line %d: %s too small", filename, linenum, keyword); } if (*activep && options->rekey_limit == -1) options->rekey_limit = val64; if (ac != 0) { /* optional rekey interval present */ if (strcmp(av[0], "none") == 0) { (void)argv_next(&ac, &av); /* discard */ break; } intptr = &options->rekey_interval; goto parse_time; } break; case sGatewayPorts: intptr = &options->fwd_opts.gateway_ports; multistate_ptr = multistate_gatewayports; goto parse_multistate; case sUseDNS: intptr = &options->use_dns; goto parse_flag; case sLogFacility: log_facility_ptr = &options->log_facility; arg = argv_next(&ac, &av); value = log_facility_number(arg); if (value == SYSLOG_FACILITY_NOT_SET) fatal("%.200s line %d: unsupported log facility '%s'", filename, linenum, arg ? arg : ""); if (*log_facility_ptr == -1) *log_facility_ptr = (SyslogFacility) value; break; case sLogLevel: log_level_ptr = &options->log_level; arg = argv_next(&ac, &av); value = log_level_number(arg); if (value == SYSLOG_LEVEL_NOT_SET) fatal("%.200s line %d: unsupported log level '%s'", filename, linenum, arg ? arg : ""); if (*activep && *log_level_ptr == -1) *log_level_ptr = (LogLevel) value; break; case sLogVerbose: found = options->num_log_verbose == 0; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (!found || !*activep) continue; opt_array_append(filename, linenum, keyword, &options->log_verbose, &options->num_log_verbose, arg); } break; case sAllowTcpForwarding: intptr = &options->allow_tcp_forwarding; multistate_ptr = multistate_tcpfwd; goto parse_multistate; case sAllowStreamLocalForwarding: intptr = &options->allow_streamlocal_forwarding; multistate_ptr = multistate_tcpfwd; goto parse_multistate; case sAllowAgentForwarding: intptr = &options->allow_agent_forwarding; goto parse_flag; case sDisableForwarding: intptr = &options->disable_forwarding; goto parse_flag; case sAllowUsers: chararrayptr = &options->allow_users; uintptr = &options->num_allow_users; parse_allowdenyusers: while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0' || match_user(NULL, NULL, NULL, arg) == -1) fatal("%s line %d: invalid %s pattern: \"%s\"", filename, linenum, keyword, arg); if (!*activep) continue; opt_array_append(filename, linenum, keyword, chararrayptr, uintptr, arg); } break; case sDenyUsers: chararrayptr = &options->deny_users; uintptr = &options->num_deny_users; goto parse_allowdenyusers; case sAllowGroups: chararrayptr = &options->allow_groups; uintptr = &options->num_allow_groups; parse_allowdenygroups: while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') fatal("%s line %d: empty %s pattern", filename, linenum, keyword); if (!*activep) continue; opt_array_append(filename, linenum, keyword, chararrayptr, uintptr, arg); } break; case sDenyGroups: chararrayptr = &options->deny_groups; uintptr = &options->num_deny_groups; goto parse_allowdenygroups; case sCiphers: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (*arg != '-' && !ciphers_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) fatal("%s line %d: Bad SSH2 cipher spec '%s'.", filename, linenum, arg ? arg : ""); if (options->ciphers == NULL) options->ciphers = xstrdup(arg); break; case sMacs: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (*arg != '-' && !mac_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) fatal("%s line %d: Bad SSH2 mac spec '%s'.", filename, linenum, arg ? arg : ""); if (options->macs == NULL) options->macs = xstrdup(arg); break; case sKexAlgorithms: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (*arg != '-' && !kex_names_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) fatal("%s line %d: Bad SSH2 KexAlgorithms '%s'.", filename, linenum, arg ? arg : ""); if (options->kex_algorithms == NULL) options->kex_algorithms = xstrdup(arg); break; case sSubsystem: - if (options->num_subsystems >= MAX_SUBSYSTEMS) { - fatal("%s line %d: too many subsystems defined.", - filename, linenum); - } arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (!*activep) { - arg = argv_next(&ac, &av); + argv_consume(&ac); + break; + } + found = 0; + for (i = 0; i < options->num_subsystems; i++) { + if (strcmp(arg, options->subsystem_name[i]) == 0) { + found = 1; + break; + } + } + if (found) { + debug("%s line %d: Subsystem '%s' already defined.", + filename, linenum, arg); + argv_consume(&ac); break; } - for (i = 0; i < options->num_subsystems; i++) - if (strcmp(arg, options->subsystem_name[i]) == 0) - fatal("%s line %d: Subsystem '%s' " - "already defined.", filename, linenum, arg); + options->subsystem_name = xrecallocarray( + options->subsystem_name, options->num_subsystems, + options->num_subsystems + 1, + sizeof(*options->subsystem_name)); + options->subsystem_command = xrecallocarray( + options->subsystem_command, options->num_subsystems, + options->num_subsystems + 1, + sizeof(*options->subsystem_command)); + options->subsystem_args = xrecallocarray( + options->subsystem_args, options->num_subsystems, + options->num_subsystems + 1, + sizeof(*options->subsystem_args)); options->subsystem_name[options->num_subsystems] = xstrdup(arg); arg = argv_next(&ac, &av); - if (!arg || *arg == '\0') + if (!arg || *arg == '\0') { fatal("%s line %d: Missing subsystem command.", filename, linenum); - options->subsystem_command[options->num_subsystems] = xstrdup(arg); - - /* Collect arguments (separate to executable) */ - p = xstrdup(arg); - len = strlen(p) + 1; - while ((arg = argv_next(&ac, &av)) != NULL) { - len += 1 + strlen(arg); - p = xreallocarray(p, 1, len); - strlcat(p, " ", len); - strlcat(p, arg, len); } - options->subsystem_args[options->num_subsystems] = p; + options->subsystem_command[options->num_subsystems] = + xstrdup(arg); + /* Collect arguments (separate to executable) */ + arg = argv_assemble(1, &arg); /* quote command correctly */ + arg2 = argv_assemble(ac, av); /* rest of command */ + xasprintf(&options->subsystem_args[options->num_subsystems], + "%s %s", arg, arg2); + free(arg2); + argv_consume(&ac); options->num_subsystems++; break; case sMaxStartups: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if ((n = sscanf(arg, "%d:%d:%d", &options->max_startups_begin, &options->max_startups_rate, &options->max_startups)) == 3) { if (options->max_startups_begin > options->max_startups || options->max_startups_rate > 100 || options->max_startups_rate < 1) fatal("%s line %d: Invalid %s spec.", filename, linenum, keyword); } else if (n != 1) fatal("%s line %d: Invalid %s spec.", filename, linenum, keyword); else options->max_startups = options->max_startups_begin; if (options->max_startups <= 0 || options->max_startups_begin <= 0) fatal("%s line %d: Invalid %s spec.", filename, linenum, keyword); break; case sPerSourceNetBlockSize: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); switch (n = sscanf(arg, "%d:%d", &value, &value2)) { case 2: if (value2 < 0 || value2 > 128) n = -1; /* FALLTHROUGH */ case 1: if (value < 0 || value > 32) n = -1; } if (n != 1 && n != 2) fatal("%s line %d: Invalid %s spec.", filename, linenum, keyword); if (*activep) { options->per_source_masklen_ipv4 = value; options->per_source_masklen_ipv6 = value2; } break; case sPerSourceMaxStartups: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (strcmp(arg, "none") == 0) { /* no limit */ value = INT_MAX; } else { if ((errstr = atoi_err(arg, &value)) != NULL) fatal("%s line %d: %s integer value %s.", filename, linenum, keyword, errstr); } - if (*activep) + if (*activep && options->per_source_max_startups == -1) options->per_source_max_startups = value; break; case sMaxAuthTries: intptr = &options->max_authtries; goto parse_int; case sMaxSessions: intptr = &options->max_sessions; goto parse_int; case sBanner: charptr = &options->banner; goto parse_filename; /* * These options can contain %X options expanded at * connect time, so that you can specify paths like: * * AuthorizedKeysFile /etc/ssh_keys/%u */ case sAuthorizedKeysFile: uvalue = options->num_authkeys_files; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } arg2 = tilde_expand_filename(arg, getuid()); if (*activep && uvalue == 0) { opt_array_append(filename, linenum, keyword, &options->authorized_keys_files, &options->num_authkeys_files, arg2); } free(arg2); } break; case sAuthorizedPrincipalsFile: charptr = &options->authorized_principals_file; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (*activep && *charptr == NULL) { *charptr = tilde_expand_filename(arg, getuid()); /* increase optional counter */ if (intptr != NULL) *intptr = *intptr + 1; } break; case sClientAliveInterval: intptr = &options->client_alive_interval; goto parse_time; case sClientAliveCountMax: intptr = &options->client_alive_count_max; goto parse_int; case sAcceptEnv: while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0' || strchr(arg, '=') != NULL) fatal("%s line %d: Invalid environment name.", filename, linenum); if (!*activep) continue; opt_array_append(filename, linenum, keyword, &options->accept_env, &options->num_accept_env, arg); } break; case sSetEnv: uvalue = options->num_setenv; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0' || strchr(arg, '=') == NULL) fatal("%s line %d: Invalid environment.", filename, linenum); if (!*activep || uvalue != 0) continue; if (lookup_setenv_in_list(arg, options->setenv, options->num_setenv) != NULL) { debug2("%s line %d: ignoring duplicate env " "name \"%.64s\"", filename, linenum, arg); continue; } opt_array_append(filename, linenum, keyword, &options->setenv, &options->num_setenv, arg); } break; case sPermitTunnel: intptr = &options->permit_tun; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); value = -1; for (i = 0; tunmode_desc[i].val != -1; i++) if (strcmp(tunmode_desc[i].text, arg) == 0) { value = tunmode_desc[i].val; break; } if (value == -1) fatal("%s line %d: bad %s argument %s", filename, linenum, keyword, arg); if (*activep && *intptr == -1) *intptr = value; break; case sInclude: if (cmdline) { fatal("Include directive not supported as a " "command-line option"); } value = 0; while ((arg2 = argv_next(&ac, &av)) != NULL) { if (*arg2 == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } value++; found = 0; if (*arg2 != '/' && *arg2 != '~') { xasprintf(&arg, "%s/%s", SSHDIR, arg2); } else arg = xstrdup(arg2); /* * Don't let included files clobber the containing * file's Match state. */ oactive = *activep; /* consult cache of include files */ TAILQ_FOREACH(item, includes, entry) { if (strcmp(item->selector, arg) != 0) continue; if (item->filename != NULL) { parse_server_config_depth(options, item->filename, item->contents, includes, connectinfo, (*inc_flags & SSHCFG_MATCH_ONLY ? SSHCFG_MATCH_ONLY : (oactive ? 0 : SSHCFG_NEVERMATCH)), activep, depth + 1); } found = 1; *activep = oactive; } if (found != 0) { free(arg); continue; } /* requested glob was not in cache */ debug2("%s line %d: new include %s", filename, linenum, arg); if ((r = glob(arg, 0, NULL, &gbuf)) != 0) { if (r != GLOB_NOMATCH) { fatal("%s line %d: include \"%s\" glob " "failed", filename, linenum, arg); } /* * If no entry matched then record a * placeholder to skip later glob calls. */ debug2("%s line %d: no match for %s", filename, linenum, arg); item = xcalloc(1, sizeof(*item)); item->selector = strdup(arg); TAILQ_INSERT_TAIL(includes, item, entry); } if (gbuf.gl_pathc > INT_MAX) fatal_f("too many glob results"); for (n = 0; n < (int)gbuf.gl_pathc; n++) { debug2("%s line %d: including %s", filename, linenum, gbuf.gl_pathv[n]); item = xcalloc(1, sizeof(*item)); item->selector = strdup(arg); item->filename = strdup(gbuf.gl_pathv[n]); if ((item->contents = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); load_server_config(item->filename, item->contents); parse_server_config_depth(options, item->filename, item->contents, includes, connectinfo, (*inc_flags & SSHCFG_MATCH_ONLY ? SSHCFG_MATCH_ONLY : (oactive ? 0 : SSHCFG_NEVERMATCH)), activep, depth + 1); *activep = oactive; TAILQ_INSERT_TAIL(includes, item, entry); } globfree(&gbuf); free(arg); } if (value == 0) { fatal("%s line %d: %s missing filename argument", filename, linenum, keyword); } break; case sMatch: if (cmdline) fatal("Match directive not supported as a command-line " "option"); value = match_cfg_line(&str, linenum, (*inc_flags & SSHCFG_NEVERMATCH ? NULL : connectinfo)); if (value < 0) fatal("%s line %d: Bad Match condition", filename, linenum); *activep = (*inc_flags & SSHCFG_NEVERMATCH) ? 0 : value; /* * The MATCH_ONLY flag is applicable only until the first * match block. */ *inc_flags &= ~SSHCFG_MATCH_ONLY; /* * If match_cfg_line() didn't consume all its arguments then * arrange for the extra arguments check below to fail. */ if (str == NULL || *str == '\0') argv_consume(&ac); break; case sPermitListen: case sPermitOpen: if (opcode == sPermitListen) { uintptr = &options->num_permitted_listens; chararrayptr = &options->permitted_listens; } else { uintptr = &options->num_permitted_opens; chararrayptr = &options->permitted_opens; } arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); uvalue = *uintptr; /* modified later */ if (strcmp(arg, "any") == 0 || strcmp(arg, "none") == 0) { if (*activep && uvalue == 0) { *uintptr = 1; *chararrayptr = xcalloc(1, sizeof(**chararrayptr)); (*chararrayptr)[0] = xstrdup(arg); } break; } for (; arg != NULL && *arg != '\0'; arg = argv_next(&ac, &av)) { if (opcode == sPermitListen && strchr(arg, ':') == NULL) { /* * Allow bare port number for PermitListen * to indicate a wildcard listen host. */ xasprintf(&arg2, "*:%s", arg); } else { arg2 = xstrdup(arg); p = hpdelim(&arg); if (p == NULL) { fatal("%s line %d: %s missing host", filename, linenum, keyword); } p = cleanhostname(p); } if (arg == NULL || ((port = permitopen_port(arg)) < 0)) { fatal("%s line %d: %s bad port number", filename, linenum, keyword); } if (*activep && uvalue == 0) { opt_array_append(filename, linenum, keyword, chararrayptr, uintptr, arg2); } free(arg2); } break; case sForceCommand: if (str == NULL || *str == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); len = strspn(str, WHITESPACE); if (*activep && options->adm_forced_command == NULL) options->adm_forced_command = xstrdup(str + len); argv_consume(&ac); break; case sChrootDirectory: charptr = &options->chroot_directory; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case sTrustedUserCAKeys: charptr = &options->trusted_user_ca_keys; goto parse_filename; case sRevokedKeys: charptr = &options->revoked_keys_file; goto parse_filename; case sSecurityKeyProvider: charptr = &options->sk_provider; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (*activep && *charptr == NULL) { *charptr = strcasecmp(arg, "internal") == 0 ? xstrdup(arg) : derelativise_path(arg); /* increase optional counter */ if (intptr != NULL) *intptr = *intptr + 1; } break; case sIPQoS: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if ((value = parse_ipqos(arg)) == -1) fatal("%s line %d: Bad %s value: %s", filename, linenum, keyword, arg); arg = argv_next(&ac, &av); if (arg == NULL) value2 = value; else if ((value2 = parse_ipqos(arg)) == -1) fatal("%s line %d: Bad %s value: %s", filename, linenum, keyword, arg); if (*activep) { options->ip_qos_interactive = value; options->ip_qos_bulk = value2; } break; case sVersionAddendum: if (str == NULL || *str == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); len = strspn(str, WHITESPACE); if (strchr(str + len, '\r') != NULL) { fatal("%.200s line %d: Invalid %s argument", filename, linenum, keyword); } if ((arg = strchr(line, '#')) != NULL) { *arg = '\0'; rtrim(line); } if (*activep && options->version_addendum == NULL) { if (strcasecmp(str + len, "none") == 0) options->version_addendum = xstrdup(""); else options->version_addendum = xstrdup(str + len); } argv_consume(&ac); break; case sAuthorizedKeysCommand: charptr = &options->authorized_keys_command; parse_command: len = strspn(str, WHITESPACE); if (str[len] != '/' && strcasecmp(str + len, "none") != 0) { fatal("%.200s line %d: %s must be an absolute path", filename, linenum, keyword); } if (*activep && *charptr == NULL) *charptr = xstrdup(str + len); argv_consume(&ac); break; case sAuthorizedKeysCommandUser: charptr = &options->authorized_keys_command_user; parse_localuser: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { fatal("%s line %d: missing %s argument.", filename, linenum, keyword); } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case sAuthorizedPrincipalsCommand: charptr = &options->authorized_principals_command; goto parse_command; case sAuthorizedPrincipalsCommandUser: charptr = &options->authorized_principals_command_user; goto parse_localuser; case sAuthenticationMethods: found = options->num_auth_methods == 0; value = 0; /* seen "any" pseudo-method */ value2 = 0; /* successfully parsed any method */ while ((arg = argv_next(&ac, &av)) != NULL) { if (strcmp(arg, "any") == 0) { if (options->num_auth_methods > 0) { fatal("%s line %d: \"any\" must " "appear alone in %s", filename, linenum, keyword); } value = 1; } else if (value) { fatal("%s line %d: \"any\" must appear " "alone in %s", filename, linenum, keyword); } else if (auth2_methods_valid(arg, 0) != 0) { fatal("%s line %d: invalid %s method list.", filename, linenum, keyword); } value2 = 1; if (!found || !*activep) continue; opt_array_append(filename, linenum, keyword, &options->auth_methods, &options->num_auth_methods, arg); } if (value2 == 0) { fatal("%s line %d: no %s specified", filename, linenum, keyword); } break; case sStreamLocalBindMask: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); /* Parse mode in octal format */ value = strtol(arg, &p, 8); if (arg == p || value < 0 || value > 0777) fatal("%s line %d: Invalid %s.", filename, linenum, keyword); if (*activep) options->fwd_opts.streamlocal_bind_mask = (mode_t)value; break; case sStreamLocalBindUnlink: intptr = &options->fwd_opts.streamlocal_bind_unlink; goto parse_flag; case sFingerprintHash: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if ((value = ssh_digest_alg_by_name(arg)) == -1) fatal("%.200s line %d: Invalid %s algorithm \"%s\".", filename, linenum, keyword, arg); if (*activep) options->fingerprint_hash = value; break; case sExposeAuthInfo: intptr = &options->expose_userauth_info; goto parse_flag; case sRDomain: #if !defined(__OpenBSD__) && !defined(HAVE_SYS_SET_PROCESS_RDOMAIN) fatal("%s line %d: setting RDomain not supported on this " "platform.", filename, linenum); #endif charptr = &options->routing_domain; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: %s missing argument.", filename, linenum, keyword); if (strcasecmp(arg, "none") != 0 && strcmp(arg, "%D") != 0 && !valid_rdomain(arg)) fatal("%s line %d: invalid routing domain", filename, linenum); if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case sRequiredRSASize: intptr = &options->required_rsa_size; goto parse_int; case sChannelTimeout: uvalue = options->num_channel_timeouts; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } else if (parse_timeout(arg, NULL, NULL) != 0) { fatal("%s line %d: invalid channel timeout %s", filename, linenum, arg); } if (!*activep || uvalue != 0) continue; opt_array_append(filename, linenum, keyword, &options->channel_timeouts, &options->num_channel_timeouts, arg); } break; case sUnusedConnectionTimeout: intptr = &options->unused_connection_timeout; /* peek at first arg for "none" so we can reuse parse_time */ if (av[0] != NULL && strcasecmp(av[0], "none") == 0) { (void)argv_next(&ac, &av); /* consume arg */ if (*activep) *intptr = 0; break; } goto parse_time; case sUseBlacklist: intptr = &options->use_blacklist; goto parse_flag; case sDeprecated: case sIgnore: case sUnsupported: do_log2(opcode == sIgnore ? SYSLOG_LEVEL_DEBUG2 : SYSLOG_LEVEL_INFO, "%s line %d: %s option %s", filename, linenum, opcode == sUnsupported ? "Unsupported" : "Deprecated", keyword); argv_consume(&ac); break; default: fatal("%s line %d: Missing handler for opcode %s (%d)", filename, linenum, keyword, opcode); } /* Check that there is no garbage at end of line. */ if (ac > 0) { error("%.200s line %d: keyword %s extra arguments " "at end of line", filename, linenum, keyword); goto out; } /* success */ ret = 0; out: argv_free(oav, oac); return ret; } int process_server_config_line(ServerOptions *options, char *line, const char *filename, int linenum, int *activep, struct connection_info *connectinfo, struct include_list *includes) { int inc_flags = 0; return process_server_config_line_depth(options, line, filename, linenum, activep, connectinfo, &inc_flags, 0, includes); } /* Reads the server configuration file. */ void load_server_config(const char *filename, struct sshbuf *conf) { struct stat st; char *line = NULL, *cp; size_t linesize = 0; FILE *f; int r; debug2_f("filename %s", filename); if ((f = fopen(filename, "r")) == NULL) { perror(filename); exit(1); } sshbuf_reset(conf); /* grow buffer, so realloc is avoided for large config files */ if (fstat(fileno(f), &st) == 0 && st.st_size > 0 && (r = sshbuf_allocate(conf, st.st_size)) != 0) fatal_fr(r, "allocate"); while (getline(&line, &linesize, f) != -1) { /* * Strip whitespace * NB - preserve newlines, they are needed to reproduce * line numbers later for error messages */ cp = line + strspn(line, " \t\r"); if ((r = sshbuf_put(conf, cp, strlen(cp))) != 0) fatal_fr(r, "sshbuf_put"); } free(line); if ((r = sshbuf_put_u8(conf, 0)) != 0) fatal_fr(r, "sshbuf_put_u8"); fclose(f); debug2_f("done config len = %zu", sshbuf_len(conf)); } void parse_server_match_config(ServerOptions *options, struct include_list *includes, struct connection_info *connectinfo) { ServerOptions mo; initialize_server_options(&mo); parse_server_config(&mo, "reprocess config", cfg, includes, connectinfo, 0); copy_set_server_options(options, &mo, 0); } int parse_server_match_testspec(struct connection_info *ci, char *spec) { char *p; while ((p = strsep(&spec, ",")) && *p != '\0') { if (strncmp(p, "addr=", 5) == 0) { ci->address = xstrdup(p + 5); } else if (strncmp(p, "host=", 5) == 0) { ci->host = xstrdup(p + 5); } else if (strncmp(p, "user=", 5) == 0) { ci->user = xstrdup(p + 5); } else if (strncmp(p, "laddr=", 6) == 0) { ci->laddress = xstrdup(p + 6); } else if (strncmp(p, "rdomain=", 8) == 0) { ci->rdomain = xstrdup(p + 8); } else if (strncmp(p, "lport=", 6) == 0) { ci->lport = a2port(p + 6); if (ci->lport == -1) { fprintf(stderr, "Invalid port '%s' in test mode" " specification %s\n", p+6, p); return -1; } } else { fprintf(stderr, "Invalid test mode specification %s\n", p); return -1; } } return 0; } +void +servconf_merge_subsystems(ServerOptions *dst, ServerOptions *src) +{ + u_int i, j, found; + + for (i = 0; i < src->num_subsystems; i++) { + found = 0; + for (j = 0; j < dst->num_subsystems; j++) { + if (strcmp(src->subsystem_name[i], + dst->subsystem_name[j]) == 0) { + found = 1; + break; + } + } + if (found) { + debug_f("override \"%s\"", dst->subsystem_name[j]); + free(dst->subsystem_command[j]); + free(dst->subsystem_args[j]); + dst->subsystem_command[j] = + xstrdup(src->subsystem_command[i]); + dst->subsystem_args[j] = + xstrdup(src->subsystem_args[i]); + continue; + } + debug_f("add \"%s\"", src->subsystem_name[i]); + dst->subsystem_name = xrecallocarray( + dst->subsystem_name, dst->num_subsystems, + dst->num_subsystems + 1, sizeof(*dst->subsystem_name)); + dst->subsystem_command = xrecallocarray( + dst->subsystem_command, dst->num_subsystems, + dst->num_subsystems + 1, sizeof(*dst->subsystem_command)); + dst->subsystem_args = xrecallocarray( + dst->subsystem_args, dst->num_subsystems, + dst->num_subsystems + 1, sizeof(*dst->subsystem_args)); + j = dst->num_subsystems++; + dst->subsystem_name[j] = xstrdup(src->subsystem_name[i]); + dst->subsystem_command[j] = xstrdup(src->subsystem_command[i]); + dst->subsystem_args[j] = xstrdup(src->subsystem_args[i]); + } +} + /* * Copy any supported values that are set. * * If the preauth flag is set, we do not bother copying the string or * array values that are not used pre-authentication, because any that we * do use must be explicitly sent in mm_getpwnamallow(). */ void copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) { #define M_CP_INTOPT(n) do {\ if (src->n != -1) \ dst->n = src->n; \ } while (0) M_CP_INTOPT(password_authentication); M_CP_INTOPT(gss_authentication); M_CP_INTOPT(pubkey_authentication); M_CP_INTOPT(pubkey_auth_options); M_CP_INTOPT(kerberos_authentication); M_CP_INTOPT(hostbased_authentication); M_CP_INTOPT(hostbased_uses_name_from_packet_only); M_CP_INTOPT(kbd_interactive_authentication); M_CP_INTOPT(permit_root_login); M_CP_INTOPT(permit_empty_passwd); M_CP_INTOPT(ignore_rhosts); M_CP_INTOPT(allow_tcp_forwarding); M_CP_INTOPT(allow_streamlocal_forwarding); M_CP_INTOPT(allow_agent_forwarding); M_CP_INTOPT(disable_forwarding); M_CP_INTOPT(expose_userauth_info); M_CP_INTOPT(permit_tun); M_CP_INTOPT(fwd_opts.gateway_ports); M_CP_INTOPT(fwd_opts.streamlocal_bind_unlink); M_CP_INTOPT(x11_display_offset); M_CP_INTOPT(x11_forwarding); M_CP_INTOPT(x11_use_localhost); M_CP_INTOPT(permit_tty); M_CP_INTOPT(permit_user_rc); M_CP_INTOPT(max_sessions); M_CP_INTOPT(max_authtries); M_CP_INTOPT(client_alive_count_max); M_CP_INTOPT(client_alive_interval); M_CP_INTOPT(ip_qos_interactive); M_CP_INTOPT(ip_qos_bulk); M_CP_INTOPT(rekey_limit); M_CP_INTOPT(rekey_interval); M_CP_INTOPT(log_level); M_CP_INTOPT(required_rsa_size); M_CP_INTOPT(unused_connection_timeout); /* * The bind_mask is a mode_t that may be unsigned, so we can't use * M_CP_INTOPT - it does a signed comparison that causes compiler * warnings. */ if (src->fwd_opts.streamlocal_bind_mask != (mode_t)-1) { dst->fwd_opts.streamlocal_bind_mask = src->fwd_opts.streamlocal_bind_mask; } /* M_CP_STROPT and M_CP_STRARRAYOPT should not appear before here */ #define M_CP_STROPT(n) do {\ if (src->n != NULL && dst->n != src->n) { \ free(dst->n); \ dst->n = src->n; \ } \ } while(0) #define M_CP_STRARRAYOPT(s, num_s) do {\ u_int i; \ if (src->num_s != 0) { \ for (i = 0; i < dst->num_s; i++) \ free(dst->s[i]); \ free(dst->s); \ dst->s = xcalloc(src->num_s, sizeof(*dst->s)); \ for (i = 0; i < src->num_s; i++) \ dst->s[i] = xstrdup(src->s[i]); \ dst->num_s = src->num_s; \ } \ } while(0) /* See comment in servconf.h */ COPY_MATCH_STRING_OPTS(); /* Arguments that accept '+...' need to be expanded */ assemble_algorithms(dst); /* * The only things that should be below this point are string options * which are only used after authentication. */ if (preauth) return; /* These options may be "none" to clear a global setting */ M_CP_STROPT(adm_forced_command); if (option_clear_or_none(dst->adm_forced_command)) { free(dst->adm_forced_command); dst->adm_forced_command = NULL; } M_CP_STROPT(chroot_directory); if (option_clear_or_none(dst->chroot_directory)) { free(dst->chroot_directory); dst->chroot_directory = NULL; } + + /* Subsystems require merging. */ + servconf_merge_subsystems(dst, src); } #undef M_CP_INTOPT #undef M_CP_STROPT #undef M_CP_STRARRAYOPT #define SERVCONF_MAX_DEPTH 16 static void parse_server_config_depth(ServerOptions *options, const char *filename, struct sshbuf *conf, struct include_list *includes, struct connection_info *connectinfo, int flags, int *activep, int depth) { int linenum, bad_options = 0; char *cp, *obuf, *cbuf; if (depth < 0 || depth > SERVCONF_MAX_DEPTH) fatal("Too many recursive configuration includes"); debug2_f("config %s len %zu%s", filename, sshbuf_len(conf), (flags & SSHCFG_NEVERMATCH ? " [checking syntax only]" : "")); if ((obuf = cbuf = sshbuf_dup_string(conf)) == NULL) fatal_f("sshbuf_dup_string failed"); linenum = 1; while ((cp = strsep(&cbuf, "\n")) != NULL) { if (process_server_config_line_depth(options, cp, filename, linenum++, activep, connectinfo, &flags, depth, includes) != 0) bad_options++; } free(obuf); if (bad_options > 0) fatal("%s: terminating, %d bad configuration options", filename, bad_options); } void parse_server_config(ServerOptions *options, const char *filename, struct sshbuf *conf, struct include_list *includes, struct connection_info *connectinfo, int reexec) { int active = connectinfo ? 0 : 1; parse_server_config_depth(options, filename, conf, includes, connectinfo, (connectinfo ? SSHCFG_MATCH_ONLY : 0), &active, 0); if (!reexec) process_queued_listen_addrs(options); } static const char * fmt_multistate_int(int val, const struct multistate *m) { u_int i; for (i = 0; m[i].key != NULL; i++) { if (m[i].value == val) return m[i].key; } return "UNKNOWN"; } static const char * fmt_intarg(ServerOpCodes code, int val) { if (val == -1) return "unset"; switch (code) { case sAddressFamily: return fmt_multistate_int(val, multistate_addressfamily); case sPermitRootLogin: return fmt_multistate_int(val, multistate_permitrootlogin); case sGatewayPorts: return fmt_multistate_int(val, multistate_gatewayports); case sCompression: return fmt_multistate_int(val, multistate_compression); case sAllowTcpForwarding: return fmt_multistate_int(val, multistate_tcpfwd); case sAllowStreamLocalForwarding: return fmt_multistate_int(val, multistate_tcpfwd); case sIgnoreRhosts: return fmt_multistate_int(val, multistate_ignore_rhosts); case sFingerprintHash: return ssh_digest_alg_name(val); default: switch (val) { case 0: return "no"; case 1: return "yes"; default: return "UNKNOWN"; } } } static void dump_cfg_int(ServerOpCodes code, int val) { if (code == sUnusedConnectionTimeout && val == 0) { printf("%s none\n", lookup_opcode_name(code)); return; } printf("%s %d\n", lookup_opcode_name(code), val); } static void dump_cfg_oct(ServerOpCodes code, int val) { printf("%s 0%o\n", lookup_opcode_name(code), val); } static void dump_cfg_fmtint(ServerOpCodes code, int val) { printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val)); } static void dump_cfg_string(ServerOpCodes code, const char *val) { printf("%s %s\n", lookup_opcode_name(code), val == NULL ? "none" : val); } static void dump_cfg_strarray(ServerOpCodes code, u_int count, char **vals) { u_int i; for (i = 0; i < count; i++) printf("%s %s\n", lookup_opcode_name(code), vals[i]); } static void dump_cfg_strarray_oneline(ServerOpCodes code, u_int count, char **vals) { u_int i; switch (code) { case sAuthenticationMethods: case sChannelTimeout: break; default: if (count <= 0) return; break; } printf("%s", lookup_opcode_name(code)); for (i = 0; i < count; i++) printf(" %s", vals[i]); if (code == sAuthenticationMethods && count == 0) printf(" any"); else if (code == sChannelTimeout && count == 0) printf(" none"); printf("\n"); } static char * format_listen_addrs(struct listenaddr *la) { int r; struct addrinfo *ai; char addr[NI_MAXHOST], port[NI_MAXSERV]; char *laddr1 = xstrdup(""), *laddr2 = NULL; /* * ListenAddress must be after Port. add_one_listen_addr pushes * addresses onto a stack, so to maintain ordering we need to * print these in reverse order. */ for (ai = la->addrs; ai; ai = ai->ai_next) { if ((r = getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { error("getnameinfo: %.100s", ssh_gai_strerror(r)); continue; } laddr2 = laddr1; if (ai->ai_family == AF_INET6) { xasprintf(&laddr1, "listenaddress [%s]:%s%s%s\n%s", addr, port, la->rdomain == NULL ? "" : " rdomain ", la->rdomain == NULL ? "" : la->rdomain, laddr2); } else { xasprintf(&laddr1, "listenaddress %s:%s%s%s\n%s", addr, port, la->rdomain == NULL ? "" : " rdomain ", la->rdomain == NULL ? "" : la->rdomain, laddr2); } free(laddr2); } return laddr1; } void dump_config(ServerOptions *o) { char *s; u_int i; /* these are usually at the top of the config */ for (i = 0; i < o->num_ports; i++) printf("port %d\n", o->ports[i]); dump_cfg_fmtint(sAddressFamily, o->address_family); for (i = 0; i < o->num_listen_addrs; i++) { s = format_listen_addrs(&o->listen_addrs[i]); printf("%s", s); free(s); } /* integer arguments */ #ifdef USE_PAM dump_cfg_fmtint(sUsePAM, o->use_pam); #endif dump_cfg_int(sLoginGraceTime, o->login_grace_time); dump_cfg_int(sX11DisplayOffset, o->x11_display_offset); dump_cfg_int(sMaxAuthTries, o->max_authtries); dump_cfg_int(sMaxSessions, o->max_sessions); dump_cfg_int(sClientAliveInterval, o->client_alive_interval); dump_cfg_int(sClientAliveCountMax, o->client_alive_count_max); dump_cfg_int(sRequiredRSASize, o->required_rsa_size); dump_cfg_oct(sStreamLocalBindMask, o->fwd_opts.streamlocal_bind_mask); dump_cfg_int(sUnusedConnectionTimeout, o->unused_connection_timeout); /* formatted integer arguments */ dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login); dump_cfg_fmtint(sIgnoreRhosts, o->ignore_rhosts); dump_cfg_fmtint(sIgnoreUserKnownHosts, o->ignore_user_known_hosts); dump_cfg_fmtint(sHostbasedAuthentication, o->hostbased_authentication); dump_cfg_fmtint(sHostbasedUsesNameFromPacketOnly, o->hostbased_uses_name_from_packet_only); dump_cfg_fmtint(sPubkeyAuthentication, o->pubkey_authentication); #ifdef KRB5 dump_cfg_fmtint(sKerberosAuthentication, o->kerberos_authentication); dump_cfg_fmtint(sKerberosOrLocalPasswd, o->kerberos_or_local_passwd); dump_cfg_fmtint(sKerberosTicketCleanup, o->kerberos_ticket_cleanup); # ifdef USE_AFS dump_cfg_fmtint(sKerberosGetAFSToken, o->kerberos_get_afs_token); # endif #endif #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, o->kbd_interactive_authentication); dump_cfg_fmtint(sPrintMotd, o->print_motd); #ifndef DISABLE_LASTLOG dump_cfg_fmtint(sPrintLastLog, o->print_lastlog); #endif dump_cfg_fmtint(sX11Forwarding, o->x11_forwarding); dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost); dump_cfg_fmtint(sPermitTTY, o->permit_tty); dump_cfg_fmtint(sPermitUserRC, o->permit_user_rc); dump_cfg_fmtint(sStrictModes, o->strict_modes); dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive); dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd); dump_cfg_fmtint(sCompression, o->compression); dump_cfg_fmtint(sGatewayPorts, o->fwd_opts.gateway_ports); dump_cfg_fmtint(sUseDNS, o->use_dns); dump_cfg_fmtint(sAllowTcpForwarding, o->allow_tcp_forwarding); dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding); dump_cfg_fmtint(sDisableForwarding, o->disable_forwarding); dump_cfg_fmtint(sAllowStreamLocalForwarding, o->allow_streamlocal_forwarding); dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash); dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info); dump_cfg_fmtint(sUseBlacklist, o->use_blacklist); /* string arguments */ dump_cfg_string(sPidFile, o->pid_file); dump_cfg_string(sModuliFile, o->moduli_file); dump_cfg_string(sXAuthLocation, o->xauth_location); dump_cfg_string(sCiphers, o->ciphers); dump_cfg_string(sMacs, o->macs); dump_cfg_string(sBanner, o->banner); dump_cfg_string(sForceCommand, o->adm_forced_command); dump_cfg_string(sChrootDirectory, o->chroot_directory); dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys); dump_cfg_string(sRevokedKeys, o->revoked_keys_file); dump_cfg_string(sSecurityKeyProvider, o->sk_provider); dump_cfg_string(sAuthorizedPrincipalsFile, o->authorized_principals_file); dump_cfg_string(sVersionAddendum, *o->version_addendum == '\0' ? "none" : o->version_addendum); dump_cfg_string(sAuthorizedKeysCommand, o->authorized_keys_command); dump_cfg_string(sAuthorizedKeysCommandUser, o->authorized_keys_command_user); dump_cfg_string(sAuthorizedPrincipalsCommand, o->authorized_principals_command); dump_cfg_string(sAuthorizedPrincipalsCommandUser, o->authorized_principals_command_user); dump_cfg_string(sHostKeyAgent, o->host_key_agent); dump_cfg_string(sKexAlgorithms, o->kex_algorithms); dump_cfg_string(sCASignatureAlgorithms, o->ca_sign_algorithms); dump_cfg_string(sHostbasedAcceptedAlgorithms, o->hostbased_accepted_algos); dump_cfg_string(sHostKeyAlgorithms, o->hostkeyalgorithms); dump_cfg_string(sPubkeyAcceptedAlgorithms, o->pubkey_accepted_algos); #if defined(__OpenBSD__) || defined(HAVE_SYS_SET_PROCESS_RDOMAIN) dump_cfg_string(sRDomain, o->routing_domain); #endif /* string arguments requiring a lookup */ dump_cfg_string(sLogLevel, log_level_name(o->log_level)); dump_cfg_string(sLogFacility, log_facility_name(o->log_facility)); /* string array arguments */ dump_cfg_strarray_oneline(sAuthorizedKeysFile, o->num_authkeys_files, o->authorized_keys_files); dump_cfg_strarray(sHostKeyFile, o->num_host_key_files, o->host_key_files); dump_cfg_strarray(sHostCertificate, o->num_host_cert_files, o->host_cert_files); dump_cfg_strarray(sAllowUsers, o->num_allow_users, o->allow_users); dump_cfg_strarray(sDenyUsers, o->num_deny_users, o->deny_users); dump_cfg_strarray(sAllowGroups, o->num_allow_groups, o->allow_groups); dump_cfg_strarray(sDenyGroups, o->num_deny_groups, o->deny_groups); dump_cfg_strarray(sAcceptEnv, o->num_accept_env, o->accept_env); dump_cfg_strarray(sSetEnv, o->num_setenv, o->setenv); dump_cfg_strarray_oneline(sAuthenticationMethods, o->num_auth_methods, o->auth_methods); dump_cfg_strarray_oneline(sLogVerbose, o->num_log_verbose, o->log_verbose); dump_cfg_strarray_oneline(sChannelTimeout, o->num_channel_timeouts, o->channel_timeouts); /* other arguments */ for (i = 0; i < o->num_subsystems; i++) printf("subsystem %s %s\n", o->subsystem_name[i], o->subsystem_args[i]); printf("maxstartups %d:%d:%d\n", o->max_startups_begin, o->max_startups_rate, o->max_startups); printf("persourcemaxstartups "); if (o->per_source_max_startups == INT_MAX) printf("none\n"); else printf("%d\n", o->per_source_max_startups); printf("persourcenetblocksize %d:%d\n", o->per_source_masklen_ipv4, o->per_source_masklen_ipv6); s = NULL; for (i = 0; tunmode_desc[i].val != -1; i++) { if (tunmode_desc[i].val == o->permit_tun) { s = tunmode_desc[i].text; break; } } dump_cfg_string(sPermitTunnel, s); printf("ipqos %s ", iptos2str(o->ip_qos_interactive)); printf("%s\n", iptos2str(o->ip_qos_bulk)); printf("rekeylimit %llu %d\n", (unsigned long long)o->rekey_limit, o->rekey_interval); printf("permitopen"); if (o->num_permitted_opens == 0) printf(" any"); else { for (i = 0; i < o->num_permitted_opens; i++) printf(" %s", o->permitted_opens[i]); } printf("\n"); printf("permitlisten"); if (o->num_permitted_listens == 0) printf(" any"); else { for (i = 0; i < o->num_permitted_listens; i++) printf(" %s", o->permitted_listens[i]); } printf("\n"); if (o->permit_user_env_allowlist == NULL) { dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env); } else { printf("permituserenvironment %s\n", o->permit_user_env_allowlist); } printf("pubkeyauthoptions"); if (o->pubkey_auth_options == 0) printf(" none"); if (o->pubkey_auth_options & PUBKEYAUTH_TOUCH_REQUIRED) printf(" touch-required"); if (o->pubkey_auth_options & PUBKEYAUTH_VERIFY_REQUIRED) printf(" verify-required"); printf("\n"); } diff --git a/crypto/openssh/servconf.h b/crypto/openssh/servconf.h index de103f623f50..34ecb26db336 100644 --- a/crypto/openssh/servconf.h +++ b/crypto/openssh/servconf.h @@ -1,323 +1,325 @@ -/* $OpenBSD: servconf.h,v 1.159 2023/01/17 09:44:48 djm Exp $ */ +/* $OpenBSD: servconf.h,v 1.160 2023/09/06 23:35:35 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Definitions for server configuration data and for the functions reading it. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef SERVCONF_H #define SERVCONF_H #include #define MAX_PORTS 256 /* Max # ports. */ -#define MAX_SUBSYSTEMS 256 /* Max # subsystems. */ - /* permit_root_login */ #define PERMIT_NOT_SET -1 #define PERMIT_NO 0 #define PERMIT_FORCED_ONLY 1 #define PERMIT_NO_PASSWD 2 #define PERMIT_YES 3 /* use_privsep */ #define PRIVSEP_OFF 0 #define PRIVSEP_ON 1 #define PRIVSEP_NOSANDBOX 2 /* PermitOpen */ #define PERMITOPEN_ANY 0 #define PERMITOPEN_NONE -2 /* IgnoreRhosts */ #define IGNORE_RHOSTS_NO 0 #define IGNORE_RHOSTS_YES 1 #define IGNORE_RHOSTS_SHOSTS 2 #define DEFAULT_AUTH_FAIL_MAX 6 /* Default for MaxAuthTries */ #define DEFAULT_SESSIONS_MAX 10 /* Default for MaxSessions */ /* Magic name for internal sftp-server */ #define INTERNAL_SFTP_NAME "internal-sftp" /* PubkeyAuthOptions flags */ #define PUBKEYAUTH_TOUCH_REQUIRED (1) #define PUBKEYAUTH_VERIFY_REQUIRED (1<<1) struct ssh; struct fwd_perm_list; /* * Used to store addresses from ListenAddr directives. These may be * incomplete, as they may specify addresses that need to be merged * with any ports requested by ListenPort. */ struct queued_listenaddr { char *addr; int port; /* <=0 if unspecified */ char *rdomain; }; /* Resolved listen addresses, grouped by optional routing domain */ struct listenaddr { char *rdomain; struct addrinfo *addrs; }; typedef struct { u_int num_ports; u_int ports_from_cmdline; int ports[MAX_PORTS]; /* Port number to listen on. */ struct queued_listenaddr *queued_listen_addrs; u_int num_queued_listens; struct listenaddr *listen_addrs; u_int num_listen_addrs; int address_family; /* Address family used by the server. */ char *routing_domain; /* Bind session to routing domain */ char **host_key_files; /* Files containing host keys. */ int *host_key_file_userprovided; /* Key was specified by user. */ u_int num_host_key_files; /* Number of files for host keys. */ char **host_cert_files; /* Files containing host certs. */ u_int num_host_cert_files; /* Number of files for host certs. */ char *host_key_agent; /* ssh-agent socket for host keys. */ char *pid_file; /* Where to put our pid */ char *moduli_file; /* moduli file for DH-GEX */ int login_grace_time; /* Disconnect if no auth in this time * (sec). */ int permit_root_login; /* PERMIT_*, see above */ int ignore_rhosts; /* Ignore .rhosts and .shosts. */ int ignore_user_known_hosts; /* Ignore ~/.ssh/known_hosts * for RhostsRsaAuth */ int print_motd; /* If true, print /etc/motd. */ int print_lastlog; /* If true, print lastlog */ int x11_forwarding; /* If true, permit inet (spoofing) X11 fwd. */ int x11_display_offset; /* What DISPLAY number to start * searching at */ int x11_use_localhost; /* If true, use localhost for fake X11 server. */ char *xauth_location; /* Location of xauth program */ int permit_tty; /* If false, deny pty allocation */ int permit_user_rc; /* If false, deny ~/.ssh/rc execution */ int strict_modes; /* If true, require string home dir modes. */ int tcp_keep_alive; /* If true, set SO_KEEPALIVE. */ int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ char *ciphers; /* Supported SSH2 ciphers. */ char *macs; /* Supported SSH2 macs. */ char *kex_algorithms; /* SSH2 kex methods in order of preference. */ struct ForwardOptions fwd_opts; /* forwarding options */ SyslogFacility log_facility; /* Facility for system logging. */ LogLevel log_level; /* Level for system logging. */ u_int num_log_verbose; /* Verbose log overrides */ char **log_verbose; int hostbased_authentication; /* If true, permit ssh2 hostbased auth */ int hostbased_uses_name_from_packet_only; /* experimental */ char *hostbased_accepted_algos; /* Algos allowed for hostbased */ char *hostkeyalgorithms; /* SSH2 server key types */ char *ca_sign_algorithms; /* Allowed CA signature algorithms */ int pubkey_authentication; /* If true, permit ssh2 pubkey authentication. */ char *pubkey_accepted_algos; /* Signature algos allowed for pubkey */ int pubkey_auth_options; /* -1 or mask of PUBKEYAUTH_* flags */ int kerberos_authentication; /* If true, permit Kerberos * authentication. */ int kerberos_or_local_passwd; /* If true, permit kerberos * and any other password * authentication mechanism, * such as SecurID or * /etc/passwd */ int kerberos_ticket_cleanup; /* If true, destroy ticket * file on logout. */ int kerberos_get_afs_token; /* If true, try to get AFS token if * authenticated with Kerberos. */ int gss_authentication; /* If true, permit GSSAPI authentication */ int gss_cleanup_creds; /* If true, destroy cred cache on logout */ int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ int password_authentication; /* If true, permit password * authentication. */ int kbd_interactive_authentication; /* If true, permit */ int permit_empty_passwd; /* If false, do not permit empty * passwords. */ int permit_user_env; /* If true, read ~/.ssh/environment */ char *permit_user_env_allowlist; /* pattern-list of allowed env names */ int compression; /* If true, compression is allowed */ int allow_tcp_forwarding; /* One of FORWARD_* */ int allow_streamlocal_forwarding; /* One of FORWARD_* */ int allow_agent_forwarding; int disable_forwarding; u_int num_allow_users; char **allow_users; u_int num_deny_users; char **deny_users; u_int num_allow_groups; char **allow_groups; u_int num_deny_groups; char **deny_groups; u_int num_subsystems; - char *subsystem_name[MAX_SUBSYSTEMS]; - char *subsystem_command[MAX_SUBSYSTEMS]; - char *subsystem_args[MAX_SUBSYSTEMS]; + char **subsystem_name; + char **subsystem_command; + char **subsystem_args; u_int num_accept_env; char **accept_env; u_int num_setenv; char **setenv; int max_startups_begin; int max_startups_rate; int max_startups; int per_source_max_startups; int per_source_masklen_ipv4; int per_source_masklen_ipv6; int max_authtries; int max_sessions; char *banner; /* SSH-2 banner message */ int use_dns; int client_alive_interval; /* * poke the client this often to * see if it's still there */ int client_alive_count_max; /* * If the client is unresponsive * for this many intervals above, * disconnect the session */ u_int num_authkeys_files; /* Files containing public keys */ char **authorized_keys_files; char *adm_forced_command; int use_pam; /* Enable auth via PAM */ int permit_tun; char **permitted_opens; /* May also be one of PERMITOPEN_* */ u_int num_permitted_opens; char **permitted_listens; /* May also be one of PERMITOPEN_* */ u_int num_permitted_listens; char *chroot_directory; char *revoked_keys_file; char *trusted_user_ca_keys; char *authorized_keys_command; char *authorized_keys_command_user; char *authorized_principals_file; char *authorized_principals_command; char *authorized_principals_command_user; int64_t rekey_limit; int rekey_interval; char *version_addendum; /* Appended to SSH banner */ u_int num_auth_methods; char **auth_methods; int fingerprint_hash; int expose_userauth_info; u_int64_t timing_secret; char *sk_provider; int required_rsa_size; /* minimum size of RSA keys */ char **channel_timeouts; /* inactivity timeout by channel type */ u_int num_channel_timeouts; int unused_connection_timeout; int use_blacklist; } ServerOptions; /* Information about the incoming connection as used by Match */ struct connection_info { const char *user; const char *host; /* possibly resolved hostname */ const char *address; /* remote address */ const char *laddress; /* local address */ int lport; /* local port */ const char *rdomain; /* routing domain if available */ int test; /* test mode, allow some attributes to be * unspecified */ }; /* List of included files for re-exec from the parsed configuration */ struct include_item { char *selector; char *filename; struct sshbuf *contents; TAILQ_ENTRY(include_item) entry; }; TAILQ_HEAD(include_list, include_item); /* * These are string config options that must be copied between the * Match sub-config and the main config, and must be sent from the * privsep child to the privsep master. We use a macro to ensure all * the options are copied and the copies are done in the correct order. * * NB. an option must appear in servconf.c:copy_set_server_options() or * COPY_MATCH_STRING_OPTS here but never both. */ #define COPY_MATCH_STRING_OPTS() do { \ M_CP_STROPT(banner); \ M_CP_STROPT(trusted_user_ca_keys); \ M_CP_STROPT(revoked_keys_file); \ M_CP_STROPT(authorized_keys_command); \ M_CP_STROPT(authorized_keys_command_user); \ M_CP_STROPT(authorized_principals_file); \ M_CP_STROPT(authorized_principals_command); \ M_CP_STROPT(authorized_principals_command_user); \ M_CP_STROPT(hostbased_accepted_algos); \ M_CP_STROPT(pubkey_accepted_algos); \ M_CP_STROPT(ca_sign_algorithms); \ M_CP_STROPT(routing_domain); \ M_CP_STROPT(permit_user_env_allowlist); \ M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \ M_CP_STRARRAYOPT(allow_users, num_allow_users); \ M_CP_STRARRAYOPT(deny_users, num_deny_users); \ M_CP_STRARRAYOPT(allow_groups, num_allow_groups); \ M_CP_STRARRAYOPT(deny_groups, num_deny_groups); \ M_CP_STRARRAYOPT(accept_env, num_accept_env); \ M_CP_STRARRAYOPT(setenv, num_setenv); \ M_CP_STRARRAYOPT(auth_methods, num_auth_methods); \ M_CP_STRARRAYOPT(permitted_opens, num_permitted_opens); \ M_CP_STRARRAYOPT(permitted_listens, num_permitted_listens); \ M_CP_STRARRAYOPT(channel_timeouts, num_channel_timeouts); \ M_CP_STRARRAYOPT(log_verbose, num_log_verbose); \ + M_CP_STRARRAYOPT(subsystem_name, num_subsystems); \ + M_CP_STRARRAYOPT(subsystem_command, num_subsystems); \ + M_CP_STRARRAYOPT(subsystem_args, num_subsystems); \ } while (0) struct connection_info *get_connection_info(struct ssh *, int, int); void initialize_server_options(ServerOptions *); void fill_default_server_options(ServerOptions *); int process_server_config_line(ServerOptions *, char *, const char *, int, int *, struct connection_info *, struct include_list *includes); void process_permitopen(struct ssh *ssh, ServerOptions *options); void process_channel_timeouts(struct ssh *ssh, ServerOptions *); void load_server_config(const char *, struct sshbuf *); void parse_server_config(ServerOptions *, const char *, struct sshbuf *, struct include_list *includes, struct connection_info *, int); void parse_server_match_config(ServerOptions *, struct include_list *includes, struct connection_info *); int parse_server_match_testspec(struct connection_info *, char *); int server_match_spec_complete(struct connection_info *); +void servconf_merge_subsystems(ServerOptions *, ServerOptions *); void copy_set_server_options(ServerOptions *, ServerOptions *, int); void dump_config(ServerOptions *); char *derelativise_path(const char *); void servconf_add_hostkey(const char *, const int, ServerOptions *, const char *path, int); void servconf_add_hostcert(const char *, const int, ServerOptions *, const char *path); #endif /* SERVCONF_H */ diff --git a/crypto/openssh/serverloop.c b/crypto/openssh/serverloop.c index de5fa2e3c2e8..f3683c2e4a6d 100644 --- a/crypto/openssh/serverloop.c +++ b/crypto/openssh/serverloop.c @@ -1,932 +1,932 @@ -/* $OpenBSD: serverloop.c,v 1.236 2023/03/08 04:43:12 guenther Exp $ */ +/* $OpenBSD: serverloop.c,v 1.237 2023/08/21 04:59:54 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Server main loop for handling the interactive session. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "packet.h" #include "sshbuf.h" #include "log.h" #include "misc.h" #include "servconf.h" #include "canohost.h" #include "sshpty.h" #include "channels.h" #include "ssh2.h" #include "sshkey.h" #include "cipher.h" #include "kex.h" #include "hostfile.h" #include "auth.h" #include "session.h" #include "dispatch.h" #include "auth-options.h" #include "serverloop.h" #include "ssherr.h" extern ServerOptions options; /* XXX */ extern Authctxt *the_authctxt; extern struct sshauthopt *auth_opts; extern int use_privsep; static int no_more_sessions = 0; /* Disallow further sessions. */ static volatile sig_atomic_t child_terminated = 0; /* The child has terminated. */ /* Cleanup on signals (!use_privsep case only) */ static volatile sig_atomic_t received_sigterm = 0; /* prototypes */ static void server_init_dispatch(struct ssh *); /* requested tunnel forwarding interface(s), shared with session.c */ char *tun_fwd_ifnames = NULL; /* returns 1 if bind to specified port by specified user is permitted */ static int bind_permitted(int port, uid_t uid) { if (use_privsep) return 1; /* allow system to decide */ if (port < IPPORT_RESERVED && uid != 0) return 0; return 1; } static void sigchld_handler(int sig) { child_terminated = 1; } static void sigterm_handler(int sig) { received_sigterm = sig; } static void client_alive_check(struct ssh *ssh) { char remote_id[512]; int r, channel_id; /* timeout, check to see how many we have had */ if (options.client_alive_count_max > 0 && ssh_packet_inc_alive_timeouts(ssh) > options.client_alive_count_max) { sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); logit("Timeout, client not responding from %s", remote_id); cleanup_exit(255); } /* * send a bogus global/channel request with "wantreply", * we should get back a failure */ if ((channel_id = channel_find_open(ssh)) == -1) { if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "keepalive@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 1)) != 0) /* boolean: want reply */ fatal_fr(r, "compose"); } else { channel_request_start(ssh, channel_id, "keepalive@openssh.com", 1); } if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send"); } /* * Sleep in ppoll() until we can do something. * Optionally, a maximum time can be specified for the duration of * the wait (0 = infinite). */ static void wait_until_can_do_something(struct ssh *ssh, int connection_in, int connection_out, struct pollfd **pfdp, u_int *npfd_allocp, u_int *npfd_activep, sigset_t *sigsetp, int *conn_in_readyp, int *conn_out_readyp) { struct timespec timeout; char remote_id[512]; int ret; int client_alive_scheduled = 0; u_int p; time_t now; static time_t last_client_time, unused_connection_expiry; *conn_in_readyp = *conn_out_readyp = 0; /* Prepare channel poll. First two pollfd entries are reserved */ ptimeout_init(&timeout); channel_prepare_poll(ssh, pfdp, npfd_allocp, npfd_activep, 2, &timeout); now = monotime(); if (*npfd_activep < 2) fatal_f("bad npfd %u", *npfd_activep); /* shouldn't happen */ if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) { ptimeout_deadline_sec(&timeout, ssh_packet_get_rekey_timeout(ssh)); } /* * If no channels are open and UnusedConnectionTimeout is set, then * start the clock to terminate the connection. */ if (options.unused_connection_timeout != 0) { if (channel_still_open(ssh) || unused_connection_expiry == 0) { unused_connection_expiry = now + options.unused_connection_timeout; } ptimeout_deadline_monotime(&timeout, unused_connection_expiry); } /* * if using client_alive, set the max timeout accordingly, * and indicate that this particular timeout was for client * alive by setting the client_alive_scheduled flag. * * this could be randomized somewhat to make traffic * analysis more difficult, but we're not doing it yet. */ if (options.client_alive_interval) { /* Time we last heard from the client OR sent a keepalive */ if (last_client_time == 0) last_client_time = now; ptimeout_deadline_sec(&timeout, options.client_alive_interval); /* XXX ? deadline_monotime(last_client_time + alive_interval) */ client_alive_scheduled = 1; } #if 0 /* wrong: bad condition XXX */ if (channel_not_very_much_buffered_data()) #endif /* Monitor client connection on reserved pollfd entries */ (*pfdp)[0].fd = connection_in; (*pfdp)[0].events = POLLIN; (*pfdp)[1].fd = connection_out; (*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0; /* * If child has terminated and there is enough buffer space to read * from it, then read as much as is available and exit. */ if (child_terminated && ssh_packet_not_very_much_data_to_write(ssh)) ptimeout_deadline_ms(&timeout, 100); /* Wait for something to happen, or the timeout to expire. */ ret = ppoll(*pfdp, *npfd_activep, ptimeout_get_tsp(&timeout), sigsetp); if (ret == -1) { for (p = 0; p < *npfd_activep; p++) (*pfdp)[p].revents = 0; if (errno != EINTR) fatal_f("ppoll: %.100s", strerror(errno)); return; } *conn_in_readyp = (*pfdp)[0].revents != 0; *conn_out_readyp = (*pfdp)[1].revents != 0; now = monotime(); /* need to reset after ppoll() */ /* ClientAliveInterval probing */ if (client_alive_scheduled) { if (ret == 0 && - now > last_client_time + options.client_alive_interval) { + now >= last_client_time + options.client_alive_interval) { /* ppoll timed out and we're due to probe */ client_alive_check(ssh); last_client_time = now; } else if (ret != 0 && *conn_in_readyp) { /* Data from peer; reset probe timer. */ last_client_time = now; } } /* UnusedConnectionTimeout handling */ if (unused_connection_expiry != 0 && now > unused_connection_expiry && !channel_still_open(ssh)) { sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); logit("terminating inactive connection from %s", remote_id); cleanup_exit(255); } } /* * Processes input from the client and the program. Input data is stored * in buffers and processed later. */ static int process_input(struct ssh *ssh, int connection_in) { int r; if ((r = ssh_packet_process_read(ssh, connection_in)) == 0) return 0; /* success */ if (r == SSH_ERR_SYSTEM_ERROR) { if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) return 0; if (errno == EPIPE) { verbose("Connection closed by %.100s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); return -1; } verbose("Read error from remote host %s port %d: %s", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), strerror(errno)); cleanup_exit(255); } return -1; } /* * Sends data from internal buffers to client program stdin. */ static void process_output(struct ssh *ssh, int connection_out) { int r; /* Send any buffered packet data to the client. */ if ((r = ssh_packet_write_poll(ssh)) != 0) { sshpkt_fatal(ssh, r, "%s: ssh_packet_write_poll", __func__); } } static void process_buffered_input_packets(struct ssh *ssh) { ssh_dispatch_run_fatal(ssh, DISPATCH_NONBLOCK, NULL); } static void collect_children(struct ssh *ssh) { pid_t pid; int status; if (child_terminated) { debug("Received SIGCHLD."); while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid == -1 && errno == EINTR)) if (pid > 0) session_close_by_pid(ssh, pid, status); child_terminated = 0; } } void server_loop2(struct ssh *ssh, Authctxt *authctxt) { struct pollfd *pfd = NULL; u_int npfd_alloc = 0, npfd_active = 0; int r, conn_in_ready, conn_out_ready; u_int connection_in, connection_out; sigset_t bsigset, osigset; debug("Entering interactive session for SSH2."); if (sigemptyset(&bsigset) == -1 || sigaddset(&bsigset, SIGCHLD) == -1) error_f("bsigset setup: %s", strerror(errno)); ssh_signal(SIGCHLD, sigchld_handler); child_terminated = 0; connection_in = ssh_packet_get_connection_in(ssh); connection_out = ssh_packet_get_connection_out(ssh); if (!use_privsep) { ssh_signal(SIGTERM, sigterm_handler); ssh_signal(SIGINT, sigterm_handler); ssh_signal(SIGQUIT, sigterm_handler); } server_init_dispatch(ssh); for (;;) { process_buffered_input_packets(ssh); if (!ssh_packet_is_rekeying(ssh) && ssh_packet_not_very_much_data_to_write(ssh)) channel_output_poll(ssh); /* * Block SIGCHLD while we check for dead children, then pass * the old signal mask through to ppoll() so that it'll wake * up immediately if a child exits after we've called waitpid(). */ if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1) error_f("bsigset sigprocmask: %s", strerror(errno)); collect_children(ssh); wait_until_can_do_something(ssh, connection_in, connection_out, &pfd, &npfd_alloc, &npfd_active, &osigset, &conn_in_ready, &conn_out_ready); if (sigprocmask(SIG_UNBLOCK, &bsigset, &osigset) == -1) error_f("osigset sigprocmask: %s", strerror(errno)); if (received_sigterm) { logit("Exiting on signal %d", (int)received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } channel_after_poll(ssh, pfd, npfd_active); if (conn_in_ready && process_input(ssh, connection_in) < 0) break; /* A timeout may have triggered rekeying */ if ((r = ssh_packet_check_rekey(ssh)) != 0) fatal_fr(r, "cannot start rekeying"); if (conn_out_ready) process_output(ssh, connection_out); } collect_children(ssh); free(pfd); /* free all channels, no more reads and writes */ channel_free_all(ssh); /* free remaining sessions, e.g. remove wtmp entries */ session_destroy_all(ssh, NULL); } static int server_input_keep_alive(int type, u_int32_t seq, struct ssh *ssh) { debug("Got %d/%u for keepalive", type, seq); /* * reset timeout, since we got a sane answer from the client. * even if this was generated by something other than * the bogus CHANNEL_REQUEST we send for keepalives. */ ssh_packet_set_alive_timeouts(ssh, 0); return 0; } static Channel * server_request_direct_tcpip(struct ssh *ssh, int *reason, const char **errmsg) { Channel *c = NULL; char *target = NULL, *originator = NULL; u_int target_port = 0, originator_port = 0; int r; if ((r = sshpkt_get_cstring(ssh, &target, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &target_port)) != 0 || (r = sshpkt_get_cstring(ssh, &originator, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &originator_port)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (target_port > 0xFFFF) { error_f("invalid target port"); *reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; goto out; } if (originator_port > 0xFFFF) { error_f("invalid originator port"); *reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; goto out; } debug_f("originator %s port %u, target %s port %u", originator, originator_port, target, target_port); /* XXX fine grained permissions */ if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0 && auth_opts->permit_port_forwarding_flag && !options.disable_forwarding) { c = channel_connect_to_port(ssh, target, target_port, "direct-tcpip", "direct-tcpip", reason, errmsg); } else { logit("refused local port forward: " "originator %s port %d, target %s port %d", originator, originator_port, target, target_port); if (reason != NULL) *reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; } out: free(originator); free(target); return c; } static Channel * server_request_direct_streamlocal(struct ssh *ssh) { Channel *c = NULL; char *target = NULL, *originator = NULL; u_int originator_port = 0; struct passwd *pw = the_authctxt->pw; int r; if (pw == NULL || !the_authctxt->valid) fatal_f("no/invalid user"); if ((r = sshpkt_get_cstring(ssh, &target, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &originator, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &originator_port)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (originator_port > 0xFFFF) { error_f("invalid originator port"); goto out; } debug_f("originator %s port %d, target %s", originator, originator_port, target); /* XXX fine grained permissions */ if ((options.allow_streamlocal_forwarding & FORWARD_LOCAL) != 0 && auth_opts->permit_port_forwarding_flag && !options.disable_forwarding && (pw->pw_uid == 0 || use_privsep)) { c = channel_connect_to_path(ssh, target, "direct-streamlocal@openssh.com", "direct-streamlocal"); } else { logit("refused streamlocal port forward: " "originator %s port %d, target %s", originator, originator_port, target); } out: free(originator); free(target); return c; } static Channel * server_request_tun(struct ssh *ssh) { Channel *c = NULL; u_int mode, tun; int r, sock; char *tmp, *ifname = NULL; if ((r = sshpkt_get_u32(ssh, &mode)) != 0) sshpkt_fatal(ssh, r, "%s: parse mode", __func__); switch (mode) { case SSH_TUNMODE_POINTOPOINT: case SSH_TUNMODE_ETHERNET: break; default: ssh_packet_send_debug(ssh, "Unsupported tunnel device mode."); return NULL; } if ((options.permit_tun & mode) == 0) { ssh_packet_send_debug(ssh, "Server has rejected tunnel device " "forwarding"); return NULL; } if ((r = sshpkt_get_u32(ssh, &tun)) != 0) sshpkt_fatal(ssh, r, "%s: parse device", __func__); if (tun > INT_MAX) { debug_f("invalid tun"); goto done; } if (auth_opts->force_tun_device != -1) { if (tun != SSH_TUNID_ANY && auth_opts->force_tun_device != (int)tun) goto done; tun = auth_opts->force_tun_device; } sock = tun_open(tun, mode, &ifname); if (sock < 0) goto done; debug("Tunnel forwarding using interface %s", ifname); c = channel_new(ssh, "tun", SSH_CHANNEL_OPEN, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); c->datagram = 1; #if defined(SSH_TUN_FILTER) if (mode == SSH_TUNMODE_POINTOPOINT) channel_register_filter(ssh, c->self, sys_tun_infilter, sys_tun_outfilter, NULL, NULL); #endif /* * Update the list of names exposed to the session * XXX remove these if the tunnels are closed (won't matter * much if they are already in the environment though) */ tmp = tun_fwd_ifnames; xasprintf(&tun_fwd_ifnames, "%s%s%s", tun_fwd_ifnames == NULL ? "" : tun_fwd_ifnames, tun_fwd_ifnames == NULL ? "" : ",", ifname); free(tmp); free(ifname); done: if (c == NULL) ssh_packet_send_debug(ssh, "Failed to open the tunnel device."); return c; } static Channel * server_request_session(struct ssh *ssh) { Channel *c; int r; debug("input_session_request"); if ((r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (no_more_sessions) { ssh_packet_disconnect(ssh, "Possible attack: attempt to open a " "session after additional sessions disabled"); } /* * A server session has no fd to read or write until a * CHANNEL_REQUEST for a shell is made, so we set the type to * SSH_CHANNEL_LARVAL. Additionally, a callback for handling all * CHANNEL_REQUEST messages is registered. */ c = channel_new(ssh, "session", SSH_CHANNEL_LARVAL, -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, 0, "server-session", 1); if (session_open(the_authctxt, c->self) != 1) { debug("session open failed, free channel %d", c->self); channel_free(ssh, c); return NULL; } channel_register_cleanup(ssh, c->self, session_close_by_channel, 0); return c; } static int server_input_channel_open(int type, u_int32_t seq, struct ssh *ssh) { Channel *c = NULL; char *ctype = NULL; const char *errmsg = NULL; int r, reason = SSH2_OPEN_CONNECT_FAILED; u_int rchan = 0, rmaxpack = 0, rwindow = 0; if ((r = sshpkt_get_cstring(ssh, &ctype, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &rchan)) != 0 || (r = sshpkt_get_u32(ssh, &rwindow)) != 0 || (r = sshpkt_get_u32(ssh, &rmaxpack)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); debug_f("ctype %s rchan %u win %u max %u", ctype, rchan, rwindow, rmaxpack); if (strcmp(ctype, "session") == 0) { c = server_request_session(ssh); } else if (strcmp(ctype, "direct-tcpip") == 0) { c = server_request_direct_tcpip(ssh, &reason, &errmsg); } else if (strcmp(ctype, "direct-streamlocal@openssh.com") == 0) { c = server_request_direct_streamlocal(ssh); } else if (strcmp(ctype, "tun@openssh.com") == 0) { c = server_request_tun(ssh); } if (c != NULL) { debug_f("confirm %s", ctype); c->remote_id = rchan; c->have_remote_id = 1; c->remote_window = rwindow; c->remote_maxpacket = rmaxpack; if (c->type != SSH_CHANNEL_CONNECTING) { if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_put_u32(ssh, c->self)) != 0 || (r = sshpkt_put_u32(ssh, c->local_window)) != 0 || (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0 || (r = sshpkt_send(ssh)) != 0) { sshpkt_fatal(ssh, r, "%s: send open confirm", __func__); } } } else { debug_f("failure %s", ctype); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 || (r = sshpkt_put_u32(ssh, rchan)) != 0 || (r = sshpkt_put_u32(ssh, reason)) != 0 || (r = sshpkt_put_cstring(ssh, errmsg ? errmsg : "open failed")) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0) { sshpkt_fatal(ssh, r, "%s: send open failure", __func__); } } free(ctype); return 0; } static int server_input_hostkeys_prove(struct ssh *ssh, struct sshbuf **respp) { struct sshbuf *resp = NULL; struct sshbuf *sigbuf = NULL; struct sshkey *key = NULL, *key_pub = NULL, *key_prv = NULL; int r, ndx, success = 0; const u_char *blob; const char *sigalg, *kex_rsa_sigalg = NULL; u_char *sig = 0; size_t blen, slen; if ((resp = sshbuf_new()) == NULL || (sigbuf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); if (sshkey_type_plain(sshkey_type_from_name( ssh->kex->hostkey_alg)) == KEY_RSA) kex_rsa_sigalg = ssh->kex->hostkey_alg; while (ssh_packet_remaining(ssh) > 0) { sshkey_free(key); key = NULL; if ((r = sshpkt_get_string_direct(ssh, &blob, &blen)) != 0 || (r = sshkey_from_blob(blob, blen, &key)) != 0) { error_fr(r, "parse key"); goto out; } /* * Better check that this is actually one of our hostkeys * before attempting to sign anything with it. */ if ((ndx = ssh->kex->host_key_index(key, 1, ssh)) == -1) { error_f("unknown host %s key", sshkey_type(key)); goto out; } /* * XXX refactor: make kex->sign just use an index rather * than passing in public and private keys */ if ((key_prv = get_hostkey_by_index(ndx)) == NULL && (key_pub = get_hostkey_public_by_index(ndx, ssh)) == NULL) { error_f("can't retrieve hostkey %d", ndx); goto out; } sshbuf_reset(sigbuf); free(sig); sig = NULL; /* * For RSA keys, prefer to use the signature type negotiated * during KEX to the default (SHA1). */ sigalg = NULL; if (sshkey_type_plain(key->type) == KEY_RSA) { if (kex_rsa_sigalg != NULL) sigalg = kex_rsa_sigalg; else if (ssh->kex->flags & KEX_RSA_SHA2_512_SUPPORTED) sigalg = "rsa-sha2-512"; else if (ssh->kex->flags & KEX_RSA_SHA2_256_SUPPORTED) sigalg = "rsa-sha2-256"; } debug3_f("sign %s key (index %d) using sigalg %s", sshkey_type(key), ndx, sigalg == NULL ? "default" : sigalg); if ((r = sshbuf_put_cstring(sigbuf, "hostkeys-prove-00@openssh.com")) != 0 || (r = sshbuf_put_stringb(sigbuf, ssh->kex->session_id)) != 0 || (r = sshkey_puts(key, sigbuf)) != 0 || (r = ssh->kex->sign(ssh, key_prv, key_pub, &sig, &slen, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf), sigalg)) != 0 || (r = sshbuf_put_string(resp, sig, slen)) != 0) { error_fr(r, "assemble signature"); goto out; } } /* Success */ *respp = resp; resp = NULL; /* don't free it */ success = 1; out: free(sig); sshbuf_free(resp); sshbuf_free(sigbuf); sshkey_free(key); return success; } static int server_input_global_request(int type, u_int32_t seq, struct ssh *ssh) { char *rtype = NULL; u_char want_reply = 0; int r, success = 0, allocated_listen_port = 0; u_int port = 0; struct sshbuf *resp = NULL; struct passwd *pw = the_authctxt->pw; struct Forward fwd; memset(&fwd, 0, sizeof(fwd)); if (pw == NULL || !the_authctxt->valid) fatal_f("no/invalid user"); if ((r = sshpkt_get_cstring(ssh, &rtype, NULL)) != 0 || (r = sshpkt_get_u8(ssh, &want_reply)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); debug_f("rtype %s want_reply %d", rtype, want_reply); /* -R style forwarding */ if (strcmp(rtype, "tcpip-forward") == 0) { if ((r = sshpkt_get_cstring(ssh, &fwd.listen_host, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &port)) != 0) sshpkt_fatal(ssh, r, "%s: parse tcpip-forward", __func__); debug_f("tcpip-forward listen %s port %u", fwd.listen_host, port); if (port <= INT_MAX) fwd.listen_port = (int)port; /* check permissions */ if (port > INT_MAX || (options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 || !auth_opts->permit_port_forwarding_flag || options.disable_forwarding || (!want_reply && fwd.listen_port == 0) || (fwd.listen_port != 0 && !bind_permitted(fwd.listen_port, pw->pw_uid))) { success = 0; ssh_packet_send_debug(ssh, "Server has disabled port forwarding."); } else { /* Start listening on the port */ success = channel_setup_remote_fwd_listener(ssh, &fwd, &allocated_listen_port, &options.fwd_opts); } if ((resp = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); if (allocated_listen_port != 0 && (r = sshbuf_put_u32(resp, allocated_listen_port)) != 0) fatal_fr(r, "sshbuf_put_u32"); } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) { if ((r = sshpkt_get_cstring(ssh, &fwd.listen_host, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &port)) != 0) sshpkt_fatal(ssh, r, "%s: parse cancel-tcpip-forward", __func__); debug_f("cancel-tcpip-forward addr %s port %d", fwd.listen_host, port); if (port <= INT_MAX) { fwd.listen_port = (int)port; success = channel_cancel_rport_listener(ssh, &fwd); } } else if (strcmp(rtype, "streamlocal-forward@openssh.com") == 0) { if ((r = sshpkt_get_cstring(ssh, &fwd.listen_path, NULL)) != 0) sshpkt_fatal(ssh, r, "%s: parse streamlocal-forward@openssh.com", __func__); debug_f("streamlocal-forward listen path %s", fwd.listen_path); /* check permissions */ if ((options.allow_streamlocal_forwarding & FORWARD_REMOTE) == 0 || !auth_opts->permit_port_forwarding_flag || options.disable_forwarding || (pw->pw_uid != 0 && !use_privsep)) { success = 0; ssh_packet_send_debug(ssh, "Server has disabled " "streamlocal forwarding."); } else { /* Start listening on the socket */ success = channel_setup_remote_fwd_listener(ssh, &fwd, NULL, &options.fwd_opts); } } else if (strcmp(rtype, "cancel-streamlocal-forward@openssh.com") == 0) { if ((r = sshpkt_get_cstring(ssh, &fwd.listen_path, NULL)) != 0) sshpkt_fatal(ssh, r, "%s: parse cancel-streamlocal-forward@openssh.com", __func__); debug_f("cancel-streamlocal-forward path %s", fwd.listen_path); success = channel_cancel_rport_listener(ssh, &fwd); } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) { no_more_sessions = 1; success = 1; } else if (strcmp(rtype, "hostkeys-prove-00@openssh.com") == 0) { success = server_input_hostkeys_prove(ssh, &resp); } /* XXX sshpkt_get_end() */ if (want_reply) { if ((r = sshpkt_start(ssh, success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE)) != 0 || (success && resp != NULL && (r = sshpkt_putb(ssh, resp)) != 0) || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send reply", __func__); } free(fwd.listen_host); free(fwd.listen_path); free(rtype); sshbuf_free(resp); return 0; } static int server_input_channel_req(int type, u_int32_t seq, struct ssh *ssh) { Channel *c; int r, success = 0; char *rtype = NULL; u_char want_reply = 0; u_int id = 0; if ((r = sshpkt_get_u32(ssh, &id)) != 0 || (r = sshpkt_get_cstring(ssh, &rtype, NULL)) != 0 || (r = sshpkt_get_u8(ssh, &want_reply)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); debug("server_input_channel_req: channel %u request %s reply %d", id, rtype, want_reply); if (id >= INT_MAX || (c = channel_lookup(ssh, (int)id)) == NULL) { ssh_packet_disconnect(ssh, "%s: unknown channel %d", __func__, id); } if (!strcmp(rtype, "eow@openssh.com")) { if ((r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); chan_rcvd_eow(ssh, c); } else if ((c->type == SSH_CHANNEL_LARVAL || c->type == SSH_CHANNEL_OPEN) && strcmp(c->ctype, "session") == 0) success = session_input_channel_req(ssh, c, rtype); if (want_reply && !(c->flags & CHAN_CLOSE_SENT)) { if (!c->have_remote_id) fatal_f("channel %d: no remote_id", c->self); if ((r = sshpkt_start(ssh, success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send reply", __func__); } free(rtype); return 0; } static void server_init_dispatch(struct ssh *ssh) { debug("server_init_dispatch"); ssh_dispatch_init(ssh, &dispatch_protocol_error); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_DATA, &channel_input_data); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_OPEN, &server_input_channel_open); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_REQUEST, &server_input_channel_req); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); ssh_dispatch_set(ssh, SSH2_MSG_GLOBAL_REQUEST, &server_input_global_request); /* client_alive */ ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_SUCCESS, &server_input_keep_alive); ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_FAILURE, &server_input_keep_alive); ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_SUCCESS, &server_input_keep_alive); ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_FAILURE, &server_input_keep_alive); /* rekeying */ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); } diff --git a/crypto/openssh/session.c b/crypto/openssh/session.c index 3ccda7ad821d..eb2aa6e0ef82 100644 --- a/crypto/openssh/session.c +++ b/crypto/openssh/session.c @@ -1,2743 +1,2750 @@ -/* $OpenBSD: session.c,v 1.335 2023/03/07 06:09:14 dtucker Exp $ */ +/* $OpenBSD: session.c,v 1.336 2023/08/10 23:05:48 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "sshpty.h" #include "packet.h" #include "sshbuf.h" #include "ssherr.h" #include "match.h" #include "uidswap.h" #include "channels.h" #include "sshkey.h" #include "cipher.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "hostfile.h" #include "auth.h" #include "auth-options.h" #include "authfd.h" #include "pathnames.h" #include "log.h" #include "misc.h" #include "servconf.h" #include "sshlogin.h" #include "serverloop.h" #include "canohost.h" #include "session.h" #include "kex.h" #include "monitor_wrap.h" #include "sftp.h" #include "atomicio.h" #if defined(KRB5) && defined(USE_AFS) #include #endif #ifdef WITH_SELINUX #include #endif #define IS_INTERNAL_SFTP(c) \ (!strncmp(c, INTERNAL_SFTP_NAME, sizeof(INTERNAL_SFTP_NAME) - 1) && \ (c[sizeof(INTERNAL_SFTP_NAME) - 1] == '\0' || \ c[sizeof(INTERNAL_SFTP_NAME) - 1] == ' ' || \ c[sizeof(INTERNAL_SFTP_NAME) - 1] == '\t')) /* func */ Session *session_new(void); void session_set_fds(struct ssh *, Session *, int, int, int, int, int); void session_pty_cleanup(Session *); void session_proctitle(Session *); int session_setup_x11fwd(struct ssh *, Session *); int do_exec_pty(struct ssh *, Session *, const char *); int do_exec_no_pty(struct ssh *, Session *, const char *); int do_exec(struct ssh *, Session *, const char *); void do_login(struct ssh *, Session *, const char *); void do_child(struct ssh *, Session *, const char *); void do_motd(void); int check_quietlogin(Session *, const char *); static void do_authenticated2(struct ssh *, Authctxt *); static int session_pty_req(struct ssh *, Session *); /* import */ extern ServerOptions options; extern char *__progname; extern int debug_flag; extern u_int utmp_len; extern int startup_pipe; extern void destroy_sensitive_data(void); extern struct sshbuf *loginmsg; extern struct sshauthopt *auth_opts; extern char *tun_fwd_ifnames; /* serverloop.c */ /* original command from peer. */ const char *original_command = NULL; /* data */ static int sessions_first_unused = -1; static int sessions_nalloc = 0; static Session *sessions = NULL; #define SUBSYSTEM_NONE 0 #define SUBSYSTEM_EXT 1 #define SUBSYSTEM_INT_SFTP 2 #define SUBSYSTEM_INT_SFTP_ERROR 3 #ifdef HAVE_LOGIN_CAP login_cap_t *lc; #endif static int is_child = 0; static int in_chroot = 0; /* File containing userauth info, if ExposeAuthInfo set */ static char *auth_info_file = NULL; /* Name and directory of socket for authentication agent forwarding. */ static char *auth_sock_name = NULL; static char *auth_sock_dir = NULL; /* removes the agent forwarding socket */ static void auth_sock_cleanup_proc(struct passwd *pw) { if (auth_sock_name != NULL) { temporarily_use_uid(pw); unlink(auth_sock_name); rmdir(auth_sock_dir); auth_sock_name = NULL; restore_uid(); } } static int auth_input_request_forwarding(struct ssh *ssh, struct passwd * pw) { Channel *nc; int sock = -1; if (auth_sock_name != NULL) { error("authentication forwarding requested twice."); return 0; } /* Temporarily drop privileged uid for mkdir/bind. */ temporarily_use_uid(pw); /* Allocate a buffer for the socket name, and format the name. */ auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX"); /* Create private directory for socket */ if (mkdtemp(auth_sock_dir) == NULL) { ssh_packet_send_debug(ssh, "Agent forwarding disabled: " "mkdtemp() failed: %.100s", strerror(errno)); restore_uid(); free(auth_sock_dir); auth_sock_dir = NULL; goto authsock_err; } xasprintf(&auth_sock_name, "%s/agent.%ld", auth_sock_dir, (long) getpid()); /* Start a Unix listener on auth_sock_name. */ sock = unix_listener(auth_sock_name, SSH_LISTEN_BACKLOG, 0); /* Restore the privileged uid. */ restore_uid(); /* Check for socket/bind/listen failure. */ if (sock < 0) goto authsock_err; /* Allocate a channel for the authentication agent socket. */ nc = channel_new(ssh, "auth-listener", SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "auth socket", 1); nc->path = xstrdup(auth_sock_name); return 1; authsock_err: free(auth_sock_name); if (auth_sock_dir != NULL) { temporarily_use_uid(pw); rmdir(auth_sock_dir); restore_uid(); free(auth_sock_dir); } if (sock != -1) close(sock); auth_sock_name = NULL; auth_sock_dir = NULL; return 0; } static void display_loginmsg(void) { int r; if (sshbuf_len(loginmsg) == 0) return; if ((r = sshbuf_put_u8(loginmsg, 0)) != 0) fatal_fr(r, "sshbuf_put_u8"); printf("%s", (char *)sshbuf_ptr(loginmsg)); sshbuf_reset(loginmsg); } static void prepare_auth_info_file(struct passwd *pw, struct sshbuf *info) { int fd = -1, success = 0; if (!options.expose_userauth_info || info == NULL) return; temporarily_use_uid(pw); auth_info_file = xstrdup("/tmp/sshauth.XXXXXXXXXXXXXXX"); if ((fd = mkstemp(auth_info_file)) == -1) { error_f("mkstemp: %s", strerror(errno)); goto out; } if (atomicio(vwrite, fd, sshbuf_mutable_ptr(info), sshbuf_len(info)) != sshbuf_len(info)) { error_f("write: %s", strerror(errno)); goto out; } if (close(fd) != 0) { error_f("close: %s", strerror(errno)); goto out; } success = 1; out: if (!success) { if (fd != -1) close(fd); free(auth_info_file); auth_info_file = NULL; } restore_uid(); } static void set_fwdpermit_from_authopts(struct ssh *ssh, const struct sshauthopt *opts) { char *tmp, *cp, *host; int port; size_t i; if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0) { channel_clear_permission(ssh, FORWARD_USER, FORWARD_LOCAL); for (i = 0; i < auth_opts->npermitopen; i++) { tmp = cp = xstrdup(auth_opts->permitopen[i]); /* This shouldn't fail as it has already been checked */ if ((host = hpdelim2(&cp, NULL)) == NULL) fatal_f("internal error: hpdelim"); host = cleanhostname(host); if (cp == NULL || (port = permitopen_port(cp)) < 0) fatal_f("internal error: permitopen port"); channel_add_permission(ssh, FORWARD_USER, FORWARD_LOCAL, host, port); free(tmp); } } if ((options.allow_tcp_forwarding & FORWARD_REMOTE) != 0) { channel_clear_permission(ssh, FORWARD_USER, FORWARD_REMOTE); for (i = 0; i < auth_opts->npermitlisten; i++) { tmp = cp = xstrdup(auth_opts->permitlisten[i]); /* This shouldn't fail as it has already been checked */ if ((host = hpdelim(&cp)) == NULL) fatal_f("internal error: hpdelim"); host = cleanhostname(host); if (cp == NULL || (port = permitopen_port(cp)) < 0) fatal_f("internal error: permitlisten port"); channel_add_permission(ssh, FORWARD_USER, FORWARD_REMOTE, host, port); free(tmp); } } } void do_authenticated(struct ssh *ssh, Authctxt *authctxt) { setproctitle("%s", authctxt->pw->pw_name); auth_log_authopts("active", auth_opts, 0); /* setup the channel layer */ /* XXX - streamlocal? */ set_fwdpermit_from_authopts(ssh, auth_opts); if (!auth_opts->permit_port_forwarding_flag || options.disable_forwarding) { channel_disable_admin(ssh, FORWARD_LOCAL); channel_disable_admin(ssh, FORWARD_REMOTE); } else { if ((options.allow_tcp_forwarding & FORWARD_LOCAL) == 0) channel_disable_admin(ssh, FORWARD_LOCAL); else channel_permit_all(ssh, FORWARD_LOCAL); if ((options.allow_tcp_forwarding & FORWARD_REMOTE) == 0) channel_disable_admin(ssh, FORWARD_REMOTE); else channel_permit_all(ssh, FORWARD_REMOTE); } auth_debug_send(ssh); prepare_auth_info_file(authctxt->pw, authctxt->session_info); do_authenticated2(ssh, authctxt); do_cleanup(ssh, authctxt); } /* Check untrusted xauth strings for metacharacters */ static int xauth_valid_string(const char *s) { size_t i; for (i = 0; s[i] != '\0'; i++) { if (!isalnum((u_char)s[i]) && s[i] != '.' && s[i] != ':' && s[i] != '/' && s[i] != '-' && s[i] != '_') return 0; } return 1; } #define USE_PIPES 1 /* * This is called to fork and execute a command when we have no tty. This * will call do_child from the child, and server_loop from the parent after * setting up file descriptors and such. */ int do_exec_no_pty(struct ssh *ssh, Session *s, const char *command) { pid_t pid; #ifdef USE_PIPES int pin[2], pout[2], perr[2]; if (s == NULL) fatal("do_exec_no_pty: no session"); /* Allocate pipes for communicating with the program. */ if (pipe(pin) == -1) { error_f("pipe in: %.100s", strerror(errno)); return -1; } if (pipe(pout) == -1) { error_f("pipe out: %.100s", strerror(errno)); close(pin[0]); close(pin[1]); return -1; } if (pipe(perr) == -1) { error_f("pipe err: %.100s", strerror(errno)); close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); return -1; } #else int inout[2], err[2]; if (s == NULL) fatal("do_exec_no_pty: no session"); /* Uses socket pairs to communicate with the program. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) { error_f("socketpair #1: %.100s", strerror(errno)); return -1; } if (socketpair(AF_UNIX, SOCK_STREAM, 0, err) == -1) { error_f("socketpair #2: %.100s", strerror(errno)); close(inout[0]); close(inout[1]); return -1; } #endif session_proctitle(s); /* Fork the child. */ switch ((pid = fork())) { case -1: error_f("fork: %.100s", strerror(errno)); #ifdef USE_PIPES close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); close(perr[0]); close(perr[1]); #else close(inout[0]); close(inout[1]); close(err[0]); close(err[1]); #endif return -1; case 0: is_child = 1; /* * Create a new session and process group since the 4.4BSD * setlogin() affects the entire process group. */ if (setsid() == -1) error("setsid failed: %.100s", strerror(errno)); #ifdef USE_PIPES /* * Redirect stdin. We close the parent side of the socket * pair, and make the child side the standard input. */ close(pin[1]); if (dup2(pin[0], 0) == -1) perror("dup2 stdin"); close(pin[0]); /* Redirect stdout. */ close(pout[0]); if (dup2(pout[1], 1) == -1) perror("dup2 stdout"); close(pout[1]); /* Redirect stderr. */ close(perr[0]); if (dup2(perr[1], 2) == -1) perror("dup2 stderr"); close(perr[1]); #else /* * Redirect stdin, stdout, and stderr. Stdin and stdout will * use the same socket, as some programs (particularly rdist) * seem to depend on it. */ close(inout[1]); close(err[1]); if (dup2(inout[0], 0) == -1) /* stdin */ perror("dup2 stdin"); if (dup2(inout[0], 1) == -1) /* stdout (same as stdin) */ perror("dup2 stdout"); close(inout[0]); if (dup2(err[0], 2) == -1) /* stderr */ perror("dup2 stderr"); close(err[0]); #endif /* Do processing for the child (exec command etc). */ do_child(ssh, s, command); /* NOTREACHED */ default: break; } #ifdef HAVE_CYGWIN cygwin_set_impersonation_token(INVALID_HANDLE_VALUE); #endif s->pid = pid; /* Set interactive/non-interactive mode. */ ssh_packet_set_interactive(ssh, s->display != NULL, options.ip_qos_interactive, options.ip_qos_bulk); /* * Clear loginmsg, since it's the child's responsibility to display * it to the user, otherwise multiple sessions may accumulate * multiple copies of the login messages. */ sshbuf_reset(loginmsg); #ifdef USE_PIPES /* We are the parent. Close the child sides of the pipes. */ close(pin[0]); close(pout[1]); close(perr[1]); session_set_fds(ssh, s, pin[1], pout[0], perr[0], s->is_subsystem, 0); #else /* We are the parent. Close the child sides of the socket pairs. */ close(inout[0]); close(err[0]); /* * Enter the interactive session. Note: server_loop must be able to * handle the case that fdin and fdout are the same. */ session_set_fds(ssh, s, inout[1], inout[1], err[1], s->is_subsystem, 0); #endif return 0; } /* * This is called to fork and execute a command when we have a tty. This * will call do_child from the child, and server_loop from the parent after * setting up file descriptors, controlling tty, updating wtmp, utmp, * lastlog, and other such operations. */ int do_exec_pty(struct ssh *ssh, Session *s, const char *command) { int fdout, ptyfd, ttyfd, ptymaster; pid_t pid; if (s == NULL) fatal("do_exec_pty: no session"); ptyfd = s->ptyfd; ttyfd = s->ttyfd; /* * Create another descriptor of the pty master side for use as the * standard input. We could use the original descriptor, but this * simplifies code in server_loop. The descriptor is bidirectional. * Do this before forking (and cleanup in the child) so as to * detect and gracefully fail out-of-fd conditions. */ if ((fdout = dup(ptyfd)) == -1) { error_f("dup #1: %s", strerror(errno)); close(ttyfd); close(ptyfd); return -1; } /* we keep a reference to the pty master */ if ((ptymaster = dup(ptyfd)) == -1) { error_f("dup #2: %s", strerror(errno)); close(ttyfd); close(ptyfd); close(fdout); return -1; } /* Fork the child. */ switch ((pid = fork())) { case -1: error_f("fork: %.100s", strerror(errno)); close(fdout); close(ptymaster); close(ttyfd); close(ptyfd); return -1; case 0: is_child = 1; close(fdout); close(ptymaster); /* Close the master side of the pseudo tty. */ close(ptyfd); /* Make the pseudo tty our controlling tty. */ pty_make_controlling_tty(&ttyfd, s->tty); /* Redirect stdin/stdout/stderr from the pseudo tty. */ if (dup2(ttyfd, 0) == -1) error("dup2 stdin: %s", strerror(errno)); if (dup2(ttyfd, 1) == -1) error("dup2 stdout: %s", strerror(errno)); if (dup2(ttyfd, 2) == -1) error("dup2 stderr: %s", strerror(errno)); /* Close the extra descriptor for the pseudo tty. */ close(ttyfd); /* record login, etc. similar to login(1) */ #ifndef HAVE_OSF_SIA do_login(ssh, s, command); #endif /* * Do common processing for the child, such as execing * the command. */ do_child(ssh, s, command); /* NOTREACHED */ default: break; } #ifdef HAVE_CYGWIN cygwin_set_impersonation_token(INVALID_HANDLE_VALUE); #endif s->pid = pid; /* Parent. Close the slave side of the pseudo tty. */ close(ttyfd); /* Enter interactive session. */ s->ptymaster = ptymaster; ssh_packet_set_interactive(ssh, 1, options.ip_qos_interactive, options.ip_qos_bulk); session_set_fds(ssh, s, ptyfd, fdout, -1, 1, 1); return 0; } /* * This is called to fork and execute a command. If another command is * to be forced, execute that instead. */ int do_exec(struct ssh *ssh, Session *s, const char *command) { int ret; const char *forced = NULL, *tty = NULL; char session_type[1024]; if (options.adm_forced_command) { original_command = command; command = options.adm_forced_command; forced = "(config)"; } else if (auth_opts->force_command != NULL) { original_command = command; command = auth_opts->force_command; forced = "(key-option)"; } s->forced = 0; if (forced != NULL) { s->forced = 1; if (IS_INTERNAL_SFTP(command)) { s->is_subsystem = s->is_subsystem ? SUBSYSTEM_INT_SFTP : SUBSYSTEM_INT_SFTP_ERROR; } else if (s->is_subsystem) s->is_subsystem = SUBSYSTEM_EXT; snprintf(session_type, sizeof(session_type), "forced-command %s '%.900s'", forced, command); } else if (s->is_subsystem) { snprintf(session_type, sizeof(session_type), "subsystem '%.900s'", s->subsys); } else if (command == NULL) { snprintf(session_type, sizeof(session_type), "shell"); } else { /* NB. we don't log unforced commands to preserve privacy */ snprintf(session_type, sizeof(session_type), "command"); } if (s->ttyfd != -1) { tty = s->tty; if (strncmp(tty, "/dev/", 5) == 0) tty += 5; } verbose("Starting session: %s%s%s for %s from %.200s port %d id %d", session_type, tty == NULL ? "" : " on ", tty == NULL ? "" : tty, s->pw->pw_name, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), s->self); #ifdef SSH_AUDIT_EVENTS if (command != NULL) PRIVSEP(audit_run_command(command)); else if (s->ttyfd == -1) { char *shell = s->pw->pw_shell; if (shell[0] == '\0') /* empty shell means /bin/sh */ shell =_PATH_BSHELL; PRIVSEP(audit_run_command(shell)); } #endif if (s->ttyfd != -1) ret = do_exec_pty(ssh, s, command); else ret = do_exec_no_pty(ssh, s, command); original_command = NULL; /* * Clear loginmsg: it's the child's responsibility to display * it to the user, otherwise multiple sessions may accumulate * multiple copies of the login messages. */ sshbuf_reset(loginmsg); return ret; } /* administrative, login(1)-like work */ void do_login(struct ssh *ssh, Session *s, const char *command) { socklen_t fromlen; struct sockaddr_storage from; struct passwd * pw = s->pw; pid_t pid = getpid(); /* * Get IP address of client. If the connection is not a socket, let * the address be 0.0.0.0. */ memset(&from, 0, sizeof(from)); fromlen = sizeof(from); if (ssh_packet_connection_is_on_socket(ssh)) { if (getpeername(ssh_packet_get_connection_in(ssh), (struct sockaddr *)&from, &fromlen) == -1) { debug("getpeername: %.100s", strerror(errno)); cleanup_exit(255); } } /* Record that there was a login on that tty from the remote host. */ if (!use_privsep) record_login(pid, s->tty, pw->pw_name, pw->pw_uid, session_get_remote_name_or_ip(ssh, utmp_len, options.use_dns), (struct sockaddr *)&from, fromlen); #ifdef USE_PAM /* * If password change is needed, do it now. * This needs to occur before the ~/.hushlogin check. */ if (options.use_pam && !use_privsep && s->authctxt->force_pwchange) { display_loginmsg(); do_pam_chauthtok(); s->authctxt->force_pwchange = 0; /* XXX - signal [net] parent to enable forwardings */ } #endif if (check_quietlogin(s, command)) return; display_loginmsg(); do_motd(); } /* * Display the message of the day. */ void do_motd(void) { FILE *f; char buf[256]; if (options.print_motd) { #ifdef HAVE_LOGIN_CAP f = fopen(login_getcapstr(lc, "welcome", "/etc/motd", "/etc/motd"), "r"); #else f = fopen("/etc/motd", "r"); #endif if (f) { while (fgets(buf, sizeof(buf), f)) fputs(buf, stdout); fclose(f); } } } /* * Check for quiet login, either .hushlogin or command given. */ int check_quietlogin(Session *s, const char *command) { char buf[256]; struct passwd *pw = s->pw; struct stat st; /* Return 1 if .hushlogin exists or a command given. */ if (command != NULL) return 1; snprintf(buf, sizeof(buf), "%.200s/.hushlogin", pw->pw_dir); #ifdef HAVE_LOGIN_CAP if (login_getcapbool(lc, "hushlogin", 0) || stat(buf, &st) >= 0) return 1; #else if (stat(buf, &st) >= 0) return 1; #endif return 0; } /* * Reads environment variables from the given file and adds/overrides them * into the environment. If the file does not exist, this does nothing. * Otherwise, it must consist of empty lines, comments (line starts with '#') * and assignments of the form name=value. No other forms are allowed. * If allowlist is not NULL, then it is interpreted as a pattern list and * only variable names that match it will be accepted. */ static void read_environment_file(char ***env, u_int *envsize, const char *filename, const char *allowlist) { FILE *f; char *line = NULL, *cp, *value; size_t linesize = 0; u_int lineno = 0; f = fopen(filename, "r"); if (!f) return; while (getline(&line, &linesize, f) != -1) { if (++lineno > 1000) fatal("Too many lines in environment file %s", filename); for (cp = line; *cp == ' ' || *cp == '\t'; cp++) ; if (!*cp || *cp == '#' || *cp == '\n') continue; cp[strcspn(cp, "\n")] = '\0'; value = strchr(cp, '='); if (value == NULL) { fprintf(stderr, "Bad line %u in %.100s\n", lineno, filename); continue; } /* * Replace the equals sign by nul, and advance value to * the value string. */ *value = '\0'; value++; if (allowlist != NULL && match_pattern_list(cp, allowlist, 0) != 1) continue; child_set_env(env, envsize, cp, value); } free(line); fclose(f); } #ifdef HAVE_ETC_DEFAULT_LOGIN /* * Return named variable from specified environment, or NULL if not present. */ static char * child_get_env(char **env, const char *name) { int i; size_t len; len = strlen(name); for (i=0; env[i] != NULL; i++) if (strncmp(name, env[i], len) == 0 && env[i][len] == '=') return(env[i] + len + 1); return NULL; } /* * Read /etc/default/login. * We pick up the PATH (or SUPATH for root) and UMASK. */ static void read_etc_default_login(char ***env, u_int *envsize, uid_t uid) { char **tmpenv = NULL, *var; u_int i, tmpenvsize = 0; u_long mask; /* * We don't want to copy the whole file to the child's environment, * so we use a temporary environment and copy the variables we're * interested in. */ read_environment_file(&tmpenv, &tmpenvsize, "/etc/default/login", options.permit_user_env_allowlist); if (tmpenv == NULL) return; if (uid == 0) var = child_get_env(tmpenv, "SUPATH"); else var = child_get_env(tmpenv, "PATH"); if (var != NULL) child_set_env(env, envsize, "PATH", var); if ((var = child_get_env(tmpenv, "UMASK")) != NULL) if (sscanf(var, "%5lo", &mask) == 1) umask((mode_t)mask); for (i = 0; tmpenv[i] != NULL; i++) free(tmpenv[i]); free(tmpenv); } #endif /* HAVE_ETC_DEFAULT_LOGIN */ #if defined(USE_PAM) || defined(HAVE_CYGWIN) static void copy_environment_denylist(char **source, char ***env, u_int *envsize, const char *denylist) { char *var_name, *var_val; int i; if (source == NULL) return; for(i = 0; source[i] != NULL; i++) { var_name = xstrdup(source[i]); if ((var_val = strstr(var_name, "=")) == NULL) { free(var_name); continue; } *var_val++ = '\0'; if (denylist == NULL || match_pattern_list(var_name, denylist, 0) != 1) { debug3("Copy environment: %s=%s", var_name, var_val); child_set_env(env, envsize, var_name, var_val); } free(var_name); } } #endif /* defined(USE_PAM) || defined(HAVE_CYGWIN) */ #ifdef HAVE_CYGWIN static void copy_environment(char **source, char ***env, u_int *envsize) { copy_environment_denylist(source, env, envsize, NULL); } #endif static char ** do_setup_env(struct ssh *ssh, Session *s, const char *shell) { char buf[256]; size_t n; u_int i, envsize; char *ocp, *cp, *value, **env, *laddr; struct passwd *pw = s->pw; #if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN) char *path = NULL; #else extern char **environ; char **senv, **var, *val; #endif /* Initialize the environment. */ envsize = 100; env = xcalloc(envsize, sizeof(char *)); env[0] = NULL; #ifdef HAVE_CYGWIN /* * The Windows environment contains some setting which are * important for a running system. They must not be dropped. */ { char **p; p = fetch_windows_environment(); copy_environment(p, &env, &envsize); free_windows_environment(p); } #endif if (getenv("TZ")) child_set_env(&env, &envsize, "TZ", getenv("TZ")); #ifdef GSSAPI /* Allow any GSSAPI methods that we've used to alter * the child's environment as they see fit */ ssh_gssapi_do_child(&env, &envsize); #endif /* Set basic environment. */ for (i = 0; i < s->num_env; i++) child_set_env(&env, &envsize, s->env[i].name, s->env[i].val); child_set_env(&env, &envsize, "USER", pw->pw_name); child_set_env(&env, &envsize, "LOGNAME", pw->pw_name); #ifdef _AIX child_set_env(&env, &envsize, "LOGIN", pw->pw_name); #endif child_set_env(&env, &envsize, "HOME", pw->pw_dir); snprintf(buf, sizeof buf, "%.200s/%.50s", _PATH_MAILDIR, pw->pw_name); child_set_env(&env, &envsize, "MAIL", buf); #ifdef HAVE_LOGIN_CAP child_set_env(&env, &envsize, "PATH", _PATH_STDPATH); child_set_env(&env, &envsize, "TERM", "su"); /* * Temporarily swap out our real environment with an empty one, * let setusercontext() apply any environment variables defined * for the user's login class, copy those variables to the child, * free the temporary environment, and restore the original. */ senv = environ; environ = xmalloc(sizeof(*environ)); *environ = NULL; (void)setusercontext(lc, pw, pw->pw_uid, LOGIN_SETENV|LOGIN_SETPATH); for (var = environ; *var != NULL; ++var) { if ((val = strchr(*var, '=')) != NULL) { *val++ = '\0'; child_set_env(&env, &envsize, *var, val); } free(*var); } free(environ); environ = senv; #else /* HAVE_LOGIN_CAP */ # ifndef HAVE_CYGWIN /* * There's no standard path on Windows. The path contains * important components pointing to the system directories, * needed for loading shared libraries. So the path better * remains intact here. */ # ifdef HAVE_ETC_DEFAULT_LOGIN read_etc_default_login(&env, &envsize, pw->pw_uid); path = child_get_env(env, "PATH"); # endif /* HAVE_ETC_DEFAULT_LOGIN */ if (path == NULL || *path == '\0') { child_set_env(&env, &envsize, "PATH", s->pw->pw_uid == 0 ? SUPERUSER_PATH : _PATH_STDPATH); } # endif /* HAVE_CYGWIN */ #endif /* HAVE_LOGIN_CAP */ /* Normal systems set SHELL by default. */ child_set_env(&env, &envsize, "SHELL", shell); if (s->term) child_set_env(&env, &envsize, "TERM", s->term); if (s->display) child_set_env(&env, &envsize, "DISPLAY", s->display); /* * Since we clear KRB5CCNAME at startup, if it's set now then it * must have been set by a native authentication method (eg AIX or * SIA), so copy it to the child. */ { char *cp; if ((cp = getenv("KRB5CCNAME")) != NULL) child_set_env(&env, &envsize, "KRB5CCNAME", cp); } #ifdef _AIX { char *cp; if ((cp = getenv("AUTHSTATE")) != NULL) child_set_env(&env, &envsize, "AUTHSTATE", cp); read_environment_file(&env, &envsize, "/etc/environment", options.permit_user_env_allowlist); } #endif #ifdef KRB5 if (s->authctxt->krb5_ccname) child_set_env(&env, &envsize, "KRB5CCNAME", s->authctxt->krb5_ccname); #endif if (auth_sock_name != NULL) child_set_env(&env, &envsize, SSH_AUTHSOCKET_ENV_NAME, auth_sock_name); /* Set custom environment options from pubkey authentication. */ if (options.permit_user_env) { for (n = 0 ; n < auth_opts->nenv; n++) { ocp = xstrdup(auth_opts->env[n]); cp = strchr(ocp, '='); if (cp != NULL) { *cp = '\0'; /* Apply PermitUserEnvironment allowlist */ if (options.permit_user_env_allowlist == NULL || match_pattern_list(ocp, options.permit_user_env_allowlist, 0) == 1) child_set_env(&env, &envsize, ocp, cp + 1); } free(ocp); } } /* read $HOME/.ssh/environment. */ if (options.permit_user_env) { snprintf(buf, sizeof buf, "%.200s/%s/environment", pw->pw_dir, _PATH_SSH_USER_DIR); read_environment_file(&env, &envsize, buf, options.permit_user_env_allowlist); } #ifdef USE_PAM /* * Pull in any environment variables that may have * been set by PAM. */ if (options.use_pam) { char **p; /* * Don't allow PAM-internal env vars to leak * back into the session environment. */ #define PAM_ENV_DENYLIST "SSH_AUTH_INFO*,SSH_CONNECTION*" p = fetch_pam_child_environment(); copy_environment_denylist(p, &env, &envsize, PAM_ENV_DENYLIST); free_pam_environment(p); p = fetch_pam_environment(); copy_environment_denylist(p, &env, &envsize, PAM_ENV_DENYLIST); free_pam_environment(p); } #endif /* USE_PAM */ /* Environment specified by admin */ for (i = 0; i < options.num_setenv; i++) { cp = xstrdup(options.setenv[i]); if ((value = strchr(cp, '=')) == NULL) { /* shouldn't happen; vars are checked in servconf.c */ fatal("Invalid config SetEnv: %s", options.setenv[i]); } *value++ = '\0'; child_set_env(&env, &envsize, cp, value); free(cp); } /* SSH_CLIENT deprecated */ snprintf(buf, sizeof buf, "%.50s %d %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), ssh_local_port(ssh)); child_set_env(&env, &envsize, "SSH_CLIENT", buf); laddr = get_local_ipaddr(ssh_packet_get_connection_in(ssh)); snprintf(buf, sizeof buf, "%.50s %d %.50s %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), laddr, ssh_local_port(ssh)); free(laddr); child_set_env(&env, &envsize, "SSH_CONNECTION", buf); if (tun_fwd_ifnames != NULL) child_set_env(&env, &envsize, "SSH_TUNNEL", tun_fwd_ifnames); if (auth_info_file != NULL) child_set_env(&env, &envsize, "SSH_USER_AUTH", auth_info_file); if (s->ttyfd != -1) child_set_env(&env, &envsize, "SSH_TTY", s->tty); if (original_command) child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", original_command); if (debug_flag) { /* dump the environment */ fprintf(stderr, "Environment:\n"); for (i = 0; env[i]; i++) fprintf(stderr, " %.200s\n", env[i]); } return env; } /* * Run $HOME/.ssh/rc, /etc/ssh/sshrc, or xauth (whichever is found * first in this order). */ static void do_rc_files(struct ssh *ssh, Session *s, const char *shell) { FILE *f = NULL; char *cmd = NULL, *user_rc = NULL; int do_xauth; struct stat st; do_xauth = s->display != NULL && s->auth_proto != NULL && s->auth_data != NULL; xasprintf(&user_rc, "%s/%s", s->pw->pw_dir, _PATH_SSH_USER_RC); /* ignore _PATH_SSH_USER_RC for subsystems and admin forced commands */ if (!s->is_subsystem && options.adm_forced_command == NULL && auth_opts->permit_user_rc && options.permit_user_rc && stat(user_rc, &st) >= 0) { if (xasprintf(&cmd, "%s -c '%s %s'", shell, _PATH_BSHELL, user_rc) == -1) fatal_f("xasprintf: %s", strerror(errno)); if (debug_flag) fprintf(stderr, "Running %s\n", cmd); f = popen(cmd, "w"); if (f) { if (do_xauth) fprintf(f, "%s %s\n", s->auth_proto, s->auth_data); pclose(f); } else fprintf(stderr, "Could not run %s\n", user_rc); } else if (stat(_PATH_SSH_SYSTEM_RC, &st) >= 0) { if (debug_flag) fprintf(stderr, "Running %s %s\n", _PATH_BSHELL, _PATH_SSH_SYSTEM_RC); f = popen(_PATH_BSHELL " " _PATH_SSH_SYSTEM_RC, "w"); if (f) { if (do_xauth) fprintf(f, "%s %s\n", s->auth_proto, s->auth_data); pclose(f); } else fprintf(stderr, "Could not run %s\n", _PATH_SSH_SYSTEM_RC); } else if (do_xauth && options.xauth_location != NULL) { /* Add authority data to .Xauthority if appropriate. */ if (debug_flag) { fprintf(stderr, "Running %.500s remove %.100s\n", options.xauth_location, s->auth_display); fprintf(stderr, "%.500s add %.100s %.100s %.100s\n", options.xauth_location, s->auth_display, s->auth_proto, s->auth_data); } if (xasprintf(&cmd, "%s -q -", options.xauth_location) == -1) fatal_f("xasprintf: %s", strerror(errno)); f = popen(cmd, "w"); if (f) { fprintf(f, "remove %s\n", s->auth_display); fprintf(f, "add %s %s %s\n", s->auth_display, s->auth_proto, s->auth_data); pclose(f); } else { fprintf(stderr, "Could not run %s\n", cmd); } } free(cmd); free(user_rc); } static void do_nologin(struct passwd *pw) { FILE *f = NULL; const char *nl; char buf[1024], *def_nl = _PATH_NOLOGIN; struct stat sb; #ifdef HAVE_LOGIN_CAP if (login_getcapbool(lc, "ignorenologin", 0) || pw->pw_uid == 0) return; nl = login_getcapstr(lc, "nologin", def_nl, def_nl); #else if (pw->pw_uid == 0) return; nl = def_nl; #endif if (stat(nl, &sb) == -1) return; /* /etc/nologin exists. Print its contents if we can and exit. */ logit("User %.100s not allowed because %s exists", pw->pw_name, nl); if ((f = fopen(nl, "r")) != NULL) { while (fgets(buf, sizeof(buf), f)) fputs(buf, stderr); fclose(f); } exit(254); } /* * Chroot into a directory after checking it for safety: all path components * must be root-owned directories with strict permissions. */ static void safely_chroot(const char *path, uid_t uid) { const char *cp; char component[PATH_MAX]; struct stat st; if (!path_absolute(path)) fatal("chroot path does not begin at root"); if (strlen(path) >= sizeof(component)) fatal("chroot path too long"); /* * Descend the path, checking that each component is a * root-owned directory with strict permissions. */ for (cp = path; cp != NULL;) { if ((cp = strchr(cp, '/')) == NULL) strlcpy(component, path, sizeof(component)); else { cp++; memcpy(component, path, cp - path); component[cp - path] = '\0'; } debug3_f("checking '%s'", component); if (stat(component, &st) != 0) fatal_f("stat(\"%s\"): %s", component, strerror(errno)); if (st.st_uid != 0 || (st.st_mode & 022) != 0) fatal("bad ownership or modes for chroot " "directory %s\"%s\"", cp == NULL ? "" : "component ", component); if (!S_ISDIR(st.st_mode)) fatal("chroot path %s\"%s\" is not a directory", cp == NULL ? "" : "component ", component); } if (chdir(path) == -1) fatal("Unable to chdir to chroot path \"%s\": " "%s", path, strerror(errno)); if (chroot(path) == -1) fatal("chroot(\"%s\"): %s", path, strerror(errno)); if (chdir("/") == -1) fatal_f("chdir(/) after chroot: %s", strerror(errno)); verbose("Changed root directory to \"%s\"", path); } /* Set login name, uid, gid, and groups. */ void do_setusercontext(struct passwd *pw) { char uidstr[32], *chroot_path, *tmp; platform_setusercontext(pw); if (platform_privileged_uidswap()) { #ifdef HAVE_LOGIN_CAP if (setusercontext(lc, pw, pw->pw_uid, (LOGIN_SETALL & ~(LOGIN_SETENV|LOGIN_SETPATH|LOGIN_SETUSER))) < 0) { perror("unable to set user context"); exit(1); } #else if (setlogin(pw->pw_name) < 0) error("setlogin failed: %s", strerror(errno)); if (setgid(pw->pw_gid) < 0) { perror("setgid"); exit(1); } /* Initialize the group list. */ if (initgroups(pw->pw_name, pw->pw_gid) < 0) { perror("initgroups"); exit(1); } endgrent(); #endif platform_setusercontext_post_groups(pw); if (!in_chroot && options.chroot_directory != NULL && strcasecmp(options.chroot_directory, "none") != 0) { tmp = tilde_expand_filename(options.chroot_directory, pw->pw_uid); snprintf(uidstr, sizeof(uidstr), "%llu", (unsigned long long)pw->pw_uid); chroot_path = percent_expand(tmp, "h", pw->pw_dir, "u", pw->pw_name, "U", uidstr, (char *)NULL); safely_chroot(chroot_path, pw->pw_uid); free(tmp); free(chroot_path); /* Make sure we don't attempt to chroot again */ free(options.chroot_directory); options.chroot_directory = NULL; in_chroot = 1; } #ifdef HAVE_LOGIN_CAP if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETUSER) < 0) { perror("unable to set user context (setuser)"); exit(1); } /* * FreeBSD's setusercontext() will not apply the user's * own umask setting unless running with the user's UID. */ (void) setusercontext(lc, pw, pw->pw_uid, LOGIN_SETUMASK); #else # ifdef USE_LIBIAF /* * In a chroot environment, the set_id() will always fail; * typically because of the lack of necessary authentication * services and runtime such as ./usr/lib/libiaf.so, * ./usr/lib/libpam.so.1, and ./etc/passwd We skip it in the * internal sftp chroot case. We'll lose auditing and ACLs but * permanently_set_uid will take care of the rest. */ if (!in_chroot && set_id(pw->pw_name) != 0) fatal("set_id(%s) Failed", pw->pw_name); # endif /* USE_LIBIAF */ /* Permanently switch to the desired uid. */ permanently_set_uid(pw); #endif } else if (options.chroot_directory != NULL && strcasecmp(options.chroot_directory, "none") != 0) { fatal("server lacks privileges to chroot to ChrootDirectory"); } if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) fatal("Failed to set uids to %u.", (u_int) pw->pw_uid); } static void do_pwchange(Session *s) { fflush(NULL); fprintf(stderr, "WARNING: Your password has expired.\n"); if (s->ttyfd != -1) { fprintf(stderr, "You must change your password now and login again!\n"); #ifdef WITH_SELINUX setexeccon(NULL); #endif #ifdef PASSWD_NEEDS_USERNAME execl(_PATH_PASSWD_PROG, "passwd", s->pw->pw_name, (char *)NULL); #else execl(_PATH_PASSWD_PROG, "passwd", (char *)NULL); #endif perror("passwd"); } else { fprintf(stderr, "Password change required but no TTY available.\n"); } exit(1); } static void child_close_fds(struct ssh *ssh) { extern int auth_sock; if (auth_sock != -1) { close(auth_sock); auth_sock = -1; } if (ssh_packet_get_connection_in(ssh) == ssh_packet_get_connection_out(ssh)) close(ssh_packet_get_connection_in(ssh)); else { close(ssh_packet_get_connection_in(ssh)); close(ssh_packet_get_connection_out(ssh)); } /* * Close all descriptors related to channels. They will still remain * open in the parent. */ /* XXX better use close-on-exec? -markus */ channel_close_all(ssh); /* * Close any extra file descriptors. Note that there may still be * descriptors left by system functions. They will be closed later. */ endpwent(); /* Stop directing logs to a high-numbered fd before we close it */ log_redirect_stderr_to(NULL); /* * Close any extra open file descriptors so that we don't have them * hanging around in clients. Note that we want to do this after * initgroups, because at least on Solaris 2.3 it leaves file * descriptors open. */ closefrom(STDERR_FILENO + 1); } /* * Performs common processing for the child, such as setting up the * environment, closing extra file descriptors, setting the user and group * ids, and executing the command or shell. */ #define ARGV_MAX 10 void do_child(struct ssh *ssh, Session *s, const char *command) { extern char **environ; char **env, *argv[ARGV_MAX], remote_id[512]; const char *shell, *shell0; struct passwd *pw = s->pw; int r = 0; sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); /* remove hostkey from the child's memory */ destroy_sensitive_data(); ssh_packet_clear_keys(ssh); /* Force a password change */ if (s->authctxt->force_pwchange) { do_setusercontext(pw); child_close_fds(ssh); do_pwchange(s); exit(1); } /* * Login(1) does this as well, and it needs uid 0 for the "-h" * switch, so we let login(1) to this for us. */ #ifdef HAVE_OSF_SIA session_setup_sia(pw, s->ttyfd == -1 ? NULL : s->tty); if (!check_quietlogin(s, command)) do_motd(); #else /* HAVE_OSF_SIA */ /* When PAM is enabled we rely on it to do the nologin check */ if (!options.use_pam) do_nologin(pw); do_setusercontext(pw); /* * PAM session modules in do_setusercontext may have * generated messages, so if this in an interactive * login then display them too. */ if (!check_quietlogin(s, command)) display_loginmsg(); #endif /* HAVE_OSF_SIA */ #ifdef USE_PAM if (options.use_pam && !is_pam_session_open()) { debug3("PAM session not opened, exiting"); display_loginmsg(); exit(254); } #endif /* * Get the shell from the password data. An empty shell field is * legal, and means /bin/sh. */ shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell; /* * Make sure $SHELL points to the shell from the password file, * even if shell is overridden from login.conf */ env = do_setup_env(ssh, s, shell); #ifdef HAVE_LOGIN_CAP shell = login_getcapstr(lc, "shell", (char *)shell, (char *)shell); #endif /* * Close the connection descriptors; note that this is the child, and * the server will still have the socket open, and it is important * that we do not shutdown it. Note that the descriptors cannot be * closed before building the environment, as we call * ssh_remote_ipaddr there. */ child_close_fds(ssh); /* * Must take new environment into use so that .ssh/rc, * /etc/ssh/sshrc and xauth are run in the proper environment. */ environ = env; #if defined(KRB5) && defined(USE_AFS) /* * At this point, we check to see if AFS is active and if we have * a valid Kerberos 5 TGT. If so, it seems like a good idea to see * if we can (and need to) extend the ticket into an AFS token. If * we don't do this, we run into potential problems if the user's * home directory is in AFS and it's not world-readable. */ if (options.kerberos_get_afs_token && k_hasafs() && (s->authctxt->krb5_ctx != NULL)) { char cell[64]; debug("Getting AFS token"); k_setpag(); if (k_afs_cell_of_file(pw->pw_dir, cell, sizeof(cell)) == 0) krb5_afslog(s->authctxt->krb5_ctx, s->authctxt->krb5_fwd_ccache, cell, NULL); krb5_afslog_home(s->authctxt->krb5_ctx, s->authctxt->krb5_fwd_ccache, NULL, NULL, pw->pw_dir); } #endif /* Change current directory to the user's home directory. */ if (chdir(pw->pw_dir) == -1) { /* Suppress missing homedir warning for chroot case */ #ifdef HAVE_LOGIN_CAP r = login_getcapbool(lc, "requirehome", 0); #endif if (r || !in_chroot) { fprintf(stderr, "Could not chdir to home " "directory %s: %s\n", pw->pw_dir, strerror(errno)); } if (r) exit(1); } closefrom(STDERR_FILENO + 1); do_rc_files(ssh, s, shell); /* restore SIGPIPE for child */ ssh_signal(SIGPIPE, SIG_DFL); if (s->is_subsystem == SUBSYSTEM_INT_SFTP_ERROR) { error("Connection from %s: refusing non-sftp session", remote_id); printf("This service allows sftp connections only.\n"); fflush(NULL); exit(1); } else if (s->is_subsystem == SUBSYSTEM_INT_SFTP) { extern int optind, optreset; int i; char *p, *args; setproctitle("%s@%s", s->pw->pw_name, INTERNAL_SFTP_NAME); args = xstrdup(command ? command : "sftp-server"); for (i = 0, (p = strtok(args, " ")); p; (p = strtok(NULL, " "))) if (i < ARGV_MAX - 1) argv[i++] = p; argv[i] = NULL; optind = optreset = 1; __progname = argv[0]; #ifdef WITH_SELINUX ssh_selinux_change_context("sftpd_t"); #endif exit(sftp_server_main(i, argv, s->pw)); } fflush(NULL); /* Get the last component of the shell name. */ if ((shell0 = strrchr(shell, '/')) != NULL) shell0++; else shell0 = shell; /* * If we have no command, execute the shell. In this case, the shell * name to be passed in argv[0] is preceded by '-' to indicate that * this is a login shell. */ if (!command) { char argv0[256]; /* Start the shell. Set initial character to '-'. */ argv0[0] = '-'; if (strlcpy(argv0 + 1, shell0, sizeof(argv0) - 1) >= sizeof(argv0) - 1) { errno = EINVAL; perror(shell); exit(1); } /* Execute the shell. */ argv[0] = argv0; argv[1] = NULL; execve(shell, argv, env); /* Executing the shell failed. */ perror(shell); exit(1); } /* * Execute the command using the user's shell. This uses the -c * option to execute the command. */ argv[0] = (char *) shell0; argv[1] = "-c"; argv[2] = (char *) command; argv[3] = NULL; execve(shell, argv, env); perror(shell); exit(1); } void session_unused(int id) { debug3_f("session id %d unused", id); if (id >= options.max_sessions || id >= sessions_nalloc) { fatal_f("insane session id %d (max %d nalloc %d)", id, options.max_sessions, sessions_nalloc); } memset(&sessions[id], 0, sizeof(*sessions)); sessions[id].self = id; sessions[id].used = 0; sessions[id].chanid = -1; sessions[id].ptyfd = -1; sessions[id].ttyfd = -1; sessions[id].ptymaster = -1; sessions[id].x11_chanids = NULL; sessions[id].next_unused = sessions_first_unused; sessions_first_unused = id; } Session * session_new(void) { Session *s, *tmp; if (sessions_first_unused == -1) { if (sessions_nalloc >= options.max_sessions) return NULL; debug2_f("allocate (allocated %d max %d)", sessions_nalloc, options.max_sessions); tmp = xrecallocarray(sessions, sessions_nalloc, sessions_nalloc + 1, sizeof(*sessions)); if (tmp == NULL) { error_f("cannot allocate %d sessions", sessions_nalloc + 1); return NULL; } sessions = tmp; session_unused(sessions_nalloc++); } if (sessions_first_unused >= sessions_nalloc || sessions_first_unused < 0) { fatal_f("insane first_unused %d max %d nalloc %d", sessions_first_unused, options.max_sessions, sessions_nalloc); } s = &sessions[sessions_first_unused]; if (s->used) fatal_f("session %d already used", sessions_first_unused); sessions_first_unused = s->next_unused; s->used = 1; s->next_unused = -1; debug("session_new: session %d", s->self); return s; } static void session_dump(void) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; debug("dump: used %d next_unused %d session %d " "channel %d pid %ld", s->used, s->next_unused, s->self, s->chanid, (long)s->pid); } } int session_open(Authctxt *authctxt, int chanid) { Session *s = session_new(); debug("session_open: channel %d", chanid); if (s == NULL) { error("no more sessions"); return 0; } s->authctxt = authctxt; s->pw = authctxt->pw; if (s->pw == NULL || !authctxt->valid) fatal("no user for session %d", s->self); debug("session_open: session %d: link with channel %d", s->self, chanid); s->chanid = chanid; return 1; } Session * session_by_tty(char *tty) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->ttyfd != -1 && strcmp(s->tty, tty) == 0) { debug("session_by_tty: session %d tty %s", i, tty); return s; } } debug("session_by_tty: unknown tty %.100s", tty); session_dump(); return NULL; } static Session * session_by_channel(int id) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->chanid == id) { debug("session_by_channel: session %d channel %d", i, id); return s; } } debug("session_by_channel: unknown channel %d", id); session_dump(); return NULL; } static Session * session_by_x11_channel(int id) { int i, j; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->x11_chanids == NULL || !s->used) continue; for (j = 0; s->x11_chanids[j] != -1; j++) { if (s->x11_chanids[j] == id) { debug("session_by_x11_channel: session %d " "channel %d", s->self, id); return s; } } } debug("session_by_x11_channel: unknown channel %d", id); session_dump(); return NULL; } static Session * session_by_pid(pid_t pid) { int i; debug("session_by_pid: pid %ld", (long)pid); for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->pid == pid) return s; } error("session_by_pid: unknown pid %ld", (long)pid); session_dump(); return NULL; } static int session_window_change_req(struct ssh *ssh, Session *s) { int r; if ((r = sshpkt_get_u32(ssh, &s->col)) != 0 || (r = sshpkt_get_u32(ssh, &s->row)) != 0 || (r = sshpkt_get_u32(ssh, &s->xpixel)) != 0 || (r = sshpkt_get_u32(ssh, &s->ypixel)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); return 1; } static int session_pty_req(struct ssh *ssh, Session *s) { int r; if (!auth_opts->permit_pty_flag || !options.permit_tty) { debug("Allocating a pty not permitted for this connection."); return 0; } if (s->ttyfd != -1) { ssh_packet_disconnect(ssh, "Protocol error: you already have a pty."); return 0; } if ((r = sshpkt_get_cstring(ssh, &s->term, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &s->col)) != 0 || (r = sshpkt_get_u32(ssh, &s->row)) != 0 || (r = sshpkt_get_u32(ssh, &s->xpixel)) != 0 || (r = sshpkt_get_u32(ssh, &s->ypixel)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (strcmp(s->term, "") == 0) { free(s->term); s->term = NULL; } /* Allocate a pty and open it. */ debug("Allocating pty."); if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)))) { free(s->term); s->term = NULL; s->ptyfd = -1; s->ttyfd = -1; error("session_pty_req: session %d alloc failed", s->self); return 0; } debug("session_pty_req: session %d alloc %s", s->self, s->tty); ssh_tty_parse_modes(ssh, s->ttyfd); if ((r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (!use_privsep) pty_setowner(s->pw, s->tty); /* Set window size from the packet. */ pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); session_proctitle(s); return 1; } static int session_subsystem_req(struct ssh *ssh, Session *s) { struct stat st; int r, success = 0; char *prog, *cmd, *type; u_int i; if ((r = sshpkt_get_cstring(ssh, &s->subsys, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); debug2("subsystem request for %.100s by user %s", s->subsys, s->pw->pw_name); for (i = 0; i < options.num_subsystems; i++) { if (strcmp(s->subsys, options.subsystem_name[i]) == 0) { prog = options.subsystem_command[i]; cmd = options.subsystem_args[i]; if (strcmp(INTERNAL_SFTP_NAME, prog) == 0) { s->is_subsystem = SUBSYSTEM_INT_SFTP; debug("subsystem: %s", prog); } else { if (stat(prog, &st) == -1) debug("subsystem: cannot stat %s: %s", prog, strerror(errno)); s->is_subsystem = SUBSYSTEM_EXT; debug("subsystem: exec() %s", cmd); } xasprintf(&type, "session:subsystem:%s", options.subsystem_name[i]); channel_set_xtype(ssh, s->chanid, type); free(type); success = do_exec(ssh, s, cmd) == 0; break; } } if (!success) logit("subsystem request for %.100s by user %s failed, " "subsystem not found", s->subsys, s->pw->pw_name); return success; } static int session_x11_req(struct ssh *ssh, Session *s) { int r, success; u_char single_connection = 0; if (s->auth_proto != NULL || s->auth_data != NULL) { error("session_x11_req: session %d: " "x11 forwarding already active", s->self); return 0; } if ((r = sshpkt_get_u8(ssh, &single_connection)) != 0 || (r = sshpkt_get_cstring(ssh, &s->auth_proto, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &s->auth_data, NULL)) != 0 || (r = sshpkt_get_u32(ssh, &s->screen)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); s->single_connection = single_connection; if (xauth_valid_string(s->auth_proto) && xauth_valid_string(s->auth_data)) success = session_setup_x11fwd(ssh, s); else { success = 0; error("Invalid X11 forwarding data"); } if (!success) { free(s->auth_proto); free(s->auth_data); s->auth_proto = NULL; s->auth_data = NULL; } return success; } static int session_shell_req(struct ssh *ssh, Session *s) { int r; if ((r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); channel_set_xtype(ssh, s->chanid, "session:shell"); return do_exec(ssh, s, NULL) == 0; } static int session_exec_req(struct ssh *ssh, Session *s) { u_int success; int r; char *command = NULL; if ((r = sshpkt_get_cstring(ssh, &command, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); channel_set_xtype(ssh, s->chanid, "session:command"); success = do_exec(ssh, s, command) == 0; free(command); return success; } static int session_break_req(struct ssh *ssh, Session *s) { int r; if ((r = sshpkt_get_u32(ssh, NULL)) != 0 || /* ignore */ (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (s->ptymaster == -1 || tcsendbreak(s->ptymaster, 0) == -1) return 0; return 1; } static int session_env_req(struct ssh *ssh, Session *s) { char *name, *val; u_int i; int r; if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0 || (r = sshpkt_get_cstring(ssh, &val, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); /* Don't set too many environment variables */ if (s->num_env > 128) { debug2("Ignoring env request %s: too many env vars", name); goto fail; } for (i = 0; i < options.num_accept_env; i++) { if (match_pattern(name, options.accept_env[i])) { debug2("Setting env %d: %s=%s", s->num_env, name, val); s->env = xrecallocarray(s->env, s->num_env, s->num_env + 1, sizeof(*s->env)); s->env[s->num_env].name = name; s->env[s->num_env].val = val; s->num_env++; return (1); } } debug2("Ignoring env request %s: disallowed name", name); fail: free(name); free(val); return (0); } /* * Conversion of signals from ssh channel request names. * Subset of signals from RFC 4254 section 6.10C, with SIGINFO as * local extension. */ static int name2sig(char *name) { #define SSH_SIG(x) if (strcmp(name, #x) == 0) return SIG ## x SSH_SIG(HUP); SSH_SIG(INT); SSH_SIG(KILL); SSH_SIG(QUIT); SSH_SIG(TERM); SSH_SIG(USR1); SSH_SIG(USR2); #undef SSH_SIG #ifdef SIGINFO if (strcmp(name, "INFO@openssh.com") == 0) return SIGINFO; #endif return -1; } static int session_signal_req(struct ssh *ssh, Session *s) { char *signame = NULL; int r, sig, success = 0; if ((r = sshpkt_get_cstring(ssh, &signame, NULL)) != 0 || (r = sshpkt_get_end(ssh)) != 0) { error_fr(r, "parse"); goto out; } if ((sig = name2sig(signame)) == -1) { error_f("unsupported signal \"%s\"", signame); goto out; } if (s->pid <= 0) { error_f("no pid for session %d", s->self); goto out; } if (s->forced || s->is_subsystem) { error_f("refusing to send signal %s to %s session", signame, s->forced ? "forced-command" : "subsystem"); goto out; } if (!use_privsep || mm_is_monitor()) { error_f("session signalling requires privilege separation"); goto out; } debug_f("signal %s, killpg(%ld, %d)", signame, (long)s->pid, sig); temporarily_use_uid(s->pw); r = killpg(s->pid, sig); restore_uid(); if (r != 0) { error_f("killpg(%ld, %d): %s", (long)s->pid, sig, strerror(errno)); goto out; } /* success */ success = 1; out: free(signame); return success; } static int session_auth_agent_req(struct ssh *ssh, Session *s) { static int called = 0; int r; if ((r = sshpkt_get_end(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: parse packet", __func__); if (!auth_opts->permit_agent_forwarding_flag || !options.allow_agent_forwarding) { debug_f("agent forwarding disabled"); return 0; } if (called) { return 0; } else { called = 1; return auth_input_request_forwarding(ssh, s->pw); } } int session_input_channel_req(struct ssh *ssh, Channel *c, const char *rtype) { int success = 0; Session *s; if ((s = session_by_channel(c->self)) == NULL) { logit_f("no session %d req %.100s", c->self, rtype); return 0; } debug_f("session %d req %s", s->self, rtype); /* * a session is in LARVAL state until a shell, a command * or a subsystem is executed */ if (c->type == SSH_CHANNEL_LARVAL) { if (strcmp(rtype, "shell") == 0) { success = session_shell_req(ssh, s); } else if (strcmp(rtype, "exec") == 0) { success = session_exec_req(ssh, s); } else if (strcmp(rtype, "pty-req") == 0) { success = session_pty_req(ssh, s); } else if (strcmp(rtype, "x11-req") == 0) { success = session_x11_req(ssh, s); } else if (strcmp(rtype, "auth-agent-req@openssh.com") == 0) { success = session_auth_agent_req(ssh, s); } else if (strcmp(rtype, "subsystem") == 0) { success = session_subsystem_req(ssh, s); } else if (strcmp(rtype, "env") == 0) { success = session_env_req(ssh, s); } } if (strcmp(rtype, "window-change") == 0) { success = session_window_change_req(ssh, s); } else if (strcmp(rtype, "break") == 0) { success = session_break_req(ssh, s); } else if (strcmp(rtype, "signal") == 0) { success = session_signal_req(ssh, s); } return success; } void session_set_fds(struct ssh *ssh, Session *s, int fdin, int fdout, int fderr, int ignore_fderr, int is_tty) { /* * now that have a child and a pipe to the child, * we can activate our channel and register the fd's */ if (s->chanid == -1) fatal("no channel for session %d", s->self); channel_set_fds(ssh, s->chanid, fdout, fdin, fderr, ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, 1, is_tty, CHAN_SES_WINDOW_DEFAULT); } /* * Function to perform pty cleanup. Also called if we get aborted abnormally * (e.g., due to a dropped connection). */ void session_pty_cleanup2(Session *s) { if (s == NULL) { error_f("no session"); return; } if (s->ttyfd == -1) return; debug_f("session %d release %s", s->self, s->tty); /* Record that the user has logged out. */ if (s->pid != 0) record_logout(s->pid, s->tty, s->pw->pw_name); /* Release the pseudo-tty. */ if (getuid() == 0) pty_release(s->tty); /* * Close the server side of the socket pairs. We must do this after * the pty cleanup, so that another process doesn't get this pty * while we're still cleaning up. */ if (s->ptymaster != -1 && close(s->ptymaster) == -1) error("close(s->ptymaster/%d): %s", s->ptymaster, strerror(errno)); /* unlink pty from session */ s->ttyfd = -1; } void session_pty_cleanup(Session *s) { PRIVSEP(session_pty_cleanup2(s)); } static char * sig2name(int sig) { #define SSH_SIG(x) if (sig == SIG ## x) return #x SSH_SIG(ABRT); SSH_SIG(ALRM); SSH_SIG(FPE); SSH_SIG(HUP); SSH_SIG(ILL); SSH_SIG(INT); SSH_SIG(KILL); SSH_SIG(PIPE); SSH_SIG(QUIT); SSH_SIG(SEGV); SSH_SIG(TERM); SSH_SIG(USR1); SSH_SIG(USR2); #undef SSH_SIG return "SIG@openssh.com"; } static void session_close_x11(struct ssh *ssh, int id) { Channel *c; if ((c = channel_by_id(ssh, id)) == NULL) { debug_f("x11 channel %d missing", id); } else { /* Detach X11 listener */ debug_f("detach x11 channel %d", id); channel_cancel_cleanup(ssh, id); if (c->ostate != CHAN_OUTPUT_CLOSED) chan_mark_dead(ssh, c); } } static void session_close_single_x11(struct ssh *ssh, int id, int force, void *arg) { Session *s; u_int i; debug3_f("channel %d", id); channel_cancel_cleanup(ssh, id); if ((s = session_by_x11_channel(id)) == NULL) fatal_f("no x11 channel %d", id); for (i = 0; s->x11_chanids[i] != -1; i++) { debug_f("session %d: closing channel %d", s->self, s->x11_chanids[i]); /* * The channel "id" is already closing, but make sure we * close all of its siblings. */ if (s->x11_chanids[i] != id) session_close_x11(ssh, s->x11_chanids[i]); } free(s->x11_chanids); s->x11_chanids = NULL; free(s->display); s->display = NULL; free(s->auth_proto); s->auth_proto = NULL; free(s->auth_data); s->auth_data = NULL; free(s->auth_display); s->auth_display = NULL; } static void session_exit_message(struct ssh *ssh, Session *s, int status) { Channel *c; int r; + char *note = NULL; if ((c = channel_lookup(ssh, s->chanid)) == NULL) fatal_f("session %d: no channel %d", s->self, s->chanid); - debug_f("session %d channel %d pid %ld", - s->self, s->chanid, (long)s->pid); if (WIFEXITED(status)) { channel_request_start(ssh, s->chanid, "exit-status", 0); if ((r = sshpkt_put_u32(ssh, WEXITSTATUS(status))) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: exit reply", __func__); + xasprintf(¬e, "exit %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { channel_request_start(ssh, s->chanid, "exit-signal", 0); #ifndef WCOREDUMP # define WCOREDUMP(x) (0) #endif if ((r = sshpkt_put_cstring(ssh, sig2name(WTERMSIG(status)))) != 0 || (r = sshpkt_put_u8(ssh, WCOREDUMP(status)? 1 : 0)) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_put_cstring(ssh, "")) != 0 || (r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: exit reply", __func__); + xasprintf(¬e, "signal %d%s", WTERMSIG(status), + WCOREDUMP(status) ? " core dumped" : ""); } else { /* Some weird exit cause. Just exit. */ - ssh_packet_disconnect(ssh, "wait returned status %04x.", status); + ssh_packet_disconnect(ssh, "wait returned status %04x.", + status); } + debug_f("session %d channel %d pid %ld %s", s->self, s->chanid, + (long)s->pid, note == NULL ? "UNKNOWN" : note); + free(note); + /* disconnect channel */ debug_f("release channel %d", s->chanid); /* * Adjust cleanup callback attachment to send close messages when * the channel gets EOF. The session will be then be closed * by session_close_by_channel when the child sessions close their fds. */ channel_register_cleanup(ssh, c->self, session_close_by_channel, 1); /* * emulate a write failure with 'chan_write_failed', nobody will be * interested in data we write. * Note that we must not call 'chan_read_failed', since there could * be some more data waiting in the pipe. */ if (c->ostate != CHAN_OUTPUT_CLOSED) chan_write_failed(ssh, c); } void session_close(struct ssh *ssh, Session *s) { u_int i; verbose("Close session: user %s from %.200s port %d id %d", s->pw->pw_name, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), s->self); if (s->ttyfd != -1) session_pty_cleanup(s); free(s->term); free(s->display); free(s->x11_chanids); free(s->auth_display); free(s->auth_data); free(s->auth_proto); free(s->subsys); if (s->env != NULL) { for (i = 0; i < s->num_env; i++) { free(s->env[i].name); free(s->env[i].val); } free(s->env); } session_proctitle(s); session_unused(s->self); } void session_close_by_pid(struct ssh *ssh, pid_t pid, int status) { Session *s = session_by_pid(pid); if (s == NULL) { debug_f("no session for pid %ld", (long)pid); return; } if (s->chanid != -1) session_exit_message(ssh, s, status); if (s->ttyfd != -1) session_pty_cleanup(s); s->pid = 0; } /* * this is called when a channel dies before * the session 'child' itself dies */ void session_close_by_channel(struct ssh *ssh, int id, int force, void *arg) { Session *s = session_by_channel(id); u_int i; if (s == NULL) { debug_f("no session for id %d", id); return; } debug_f("channel %d child %ld", id, (long)s->pid); if (s->pid != 0) { debug_f("channel %d: has child, ttyfd %d", id, s->ttyfd); /* * delay detach of session (unless this is a forced close), * but release pty, since the fd's to the child are already * closed */ if (s->ttyfd != -1) session_pty_cleanup(s); if (!force) return; } /* detach by removing callback */ channel_cancel_cleanup(ssh, s->chanid); /* Close any X11 listeners associated with this session */ if (s->x11_chanids != NULL) { for (i = 0; s->x11_chanids[i] != -1; i++) { session_close_x11(ssh, s->x11_chanids[i]); s->x11_chanids[i] = -1; } } s->chanid = -1; session_close(ssh, s); } void session_destroy_all(struct ssh *ssh, void (*closefunc)(Session *)) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used) { if (closefunc != NULL) closefunc(s); else session_close(ssh, s); } } } static char * session_tty_list(void) { static char buf[1024]; int i; char *cp; buf[0] = '\0'; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->ttyfd != -1) { if (strncmp(s->tty, "/dev/", 5) != 0) { cp = strrchr(s->tty, '/'); cp = (cp == NULL) ? s->tty : cp + 1; } else cp = s->tty + 5; if (buf[0] != '\0') strlcat(buf, ",", sizeof buf); strlcat(buf, cp, sizeof buf); } } if (buf[0] == '\0') strlcpy(buf, "notty", sizeof buf); return buf; } void session_proctitle(Session *s) { if (s->pw == NULL) error("no user for session %d", s->self); else setproctitle("%s@%s", s->pw->pw_name, session_tty_list()); } int session_setup_x11fwd(struct ssh *ssh, Session *s) { struct stat st; char display[512], auth_display[512]; char hostname[NI_MAXHOST]; u_int i; if (!auth_opts->permit_x11_forwarding_flag) { ssh_packet_send_debug(ssh, "X11 forwarding disabled by key options."); return 0; } if (!options.x11_forwarding) { debug("X11 forwarding disabled in server configuration file."); return 0; } if (options.xauth_location == NULL || (stat(options.xauth_location, &st) == -1)) { ssh_packet_send_debug(ssh, "No xauth program; cannot forward X11."); return 0; } if (s->display != NULL) { debug("X11 display already set."); return 0; } if (x11_create_display_inet(ssh, options.x11_display_offset, options.x11_use_localhost, s->single_connection, &s->display_number, &s->x11_chanids) == -1) { debug("x11_create_display_inet failed."); return 0; } for (i = 0; s->x11_chanids[i] != -1; i++) { channel_register_cleanup(ssh, s->x11_chanids[i], session_close_single_x11, 0); } /* Set up a suitable value for the DISPLAY variable. */ if (gethostname(hostname, sizeof(hostname)) == -1) fatal("gethostname: %.100s", strerror(errno)); /* * auth_display must be used as the displayname when the * authorization entry is added with xauth(1). This will be * different than the DISPLAY string for localhost displays. */ if (options.x11_use_localhost) { snprintf(display, sizeof display, "localhost:%u.%u", s->display_number, s->screen); snprintf(auth_display, sizeof auth_display, "unix:%u.%u", s->display_number, s->screen); s->display = xstrdup(display); s->auth_display = xstrdup(auth_display); } else { #ifdef IPADDR_IN_DISPLAY struct hostent *he; struct in_addr my_addr; he = gethostbyname(hostname); if (he == NULL) { error("Can't get IP address for X11 DISPLAY."); ssh_packet_send_debug(ssh, "Can't get IP address for X11 DISPLAY."); return 0; } memcpy(&my_addr, he->h_addr_list[0], sizeof(struct in_addr)); snprintf(display, sizeof display, "%.50s:%u.%u", inet_ntoa(my_addr), s->display_number, s->screen); #else snprintf(display, sizeof display, "%.400s:%u.%u", hostname, s->display_number, s->screen); #endif s->display = xstrdup(display); s->auth_display = xstrdup(display); } return 1; } static void do_authenticated2(struct ssh *ssh, Authctxt *authctxt) { server_loop2(ssh, authctxt); } void do_cleanup(struct ssh *ssh, Authctxt *authctxt) { static int called = 0; debug("do_cleanup"); /* no cleanup if we're in the child for login shell */ if (is_child) return; /* avoid double cleanup */ if (called) return; called = 1; if (authctxt == NULL) return; #ifdef USE_PAM if (options.use_pam) { sshpam_cleanup(); sshpam_thread_cleanup(); } #endif if (!authctxt->authenticated) return; #ifdef KRB5 if (options.kerberos_ticket_cleanup && authctxt->krb5_ctx) krb5_cleanup_proc(authctxt); #endif #ifdef GSSAPI if (options.gss_cleanup_creds) ssh_gssapi_cleanup_creds(); #endif /* remove agent socket */ auth_sock_cleanup_proc(authctxt->pw); /* remove userauth info */ if (auth_info_file != NULL) { temporarily_use_uid(authctxt->pw); unlink(auth_info_file); restore_uid(); free(auth_info_file); auth_info_file = NULL; } /* * Cleanup ptys/utmp only if privsep is disabled, * or if running in monitor. */ if (!use_privsep || mm_is_monitor()) session_destroy_all(ssh, session_pty_cleanup2); } /* Return a name for the remote host that fits inside utmp_size */ const char * session_get_remote_name_or_ip(struct ssh *ssh, u_int utmp_size, int use_dns) { const char *remote = ""; if (utmp_size > 0) remote = auth_get_canonical_hostname(ssh, use_dns); if (utmp_size == 0 || strlen(remote) > utmp_size) remote = ssh_remote_ipaddr(ssh); return remote; } diff --git a/crypto/openssh/sftp-client.c b/crypto/openssh/sftp-client.c index 098b9121a015..2598029f7bd2 100644 --- a/crypto/openssh/sftp-client.c +++ b/crypto/openssh/sftp-client.c @@ -1,2978 +1,3009 @@ -/* $OpenBSD: sftp-client.c,v 1.171 2023/04/30 22:54:22 djm Exp $ */ +/* $OpenBSD: sftp-client.c,v 1.174 2023/09/08 06:10:02 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * * 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. */ /* XXX: memleaks */ /* XXX: signed vs unsigned */ /* XXX: remove all logging, only return status codes */ /* XXX: copy between two remote sites */ #include "includes.h" #include #ifdef HAVE_SYS_STATVFS_H #include #endif #include "openbsd-compat/sys-queue.h" #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #ifdef HAVE_POLL_H #include #else # ifdef HAVE_SYS_POLL_H # include # endif #endif #include #include #include #include #include #include #include #include "xmalloc.h" #include "ssherr.h" #include "sshbuf.h" #include "log.h" #include "atomicio.h" #include "progressmeter.h" #include "misc.h" #include "utf8.h" #include "sftp.h" #include "sftp-common.h" #include "sftp-client.h" extern volatile sig_atomic_t interrupted; extern int showprogress; /* Default size of buffer for up/download (fix sftp.1 scp.1 if changed) */ #define DEFAULT_COPY_BUFLEN 32768 /* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */ #define DEFAULT_NUM_REQUESTS 64 /* Minimum amount of data to read at a time */ #define MIN_READ_SIZE 512 /* Maximum depth to descend in directory trees */ #define MAX_DIR_DEPTH 64 /* Directory separator characters */ #ifdef HAVE_CYGWIN # define SFTP_DIRECTORY_CHARS "/\\" #else /* HAVE_CYGWIN */ # define SFTP_DIRECTORY_CHARS "/" #endif /* HAVE_CYGWIN */ struct sftp_conn { int fd_in; int fd_out; u_int download_buflen; u_int upload_buflen; u_int num_requests; u_int version; u_int msg_id; #define SFTP_EXT_POSIX_RENAME 0x00000001 #define SFTP_EXT_STATVFS 0x00000002 #define SFTP_EXT_FSTATVFS 0x00000004 #define SFTP_EXT_HARDLINK 0x00000008 #define SFTP_EXT_FSYNC 0x00000010 #define SFTP_EXT_LSETSTAT 0x00000020 #define SFTP_EXT_LIMITS 0x00000040 #define SFTP_EXT_PATH_EXPAND 0x00000080 #define SFTP_EXT_COPY_DATA 0x00000100 #define SFTP_EXT_GETUSERSGROUPS_BY_ID 0x00000200 u_int exts; u_int64_t limit_kbps; struct bwlimit bwlimit_in, bwlimit_out; }; /* Tracks in-progress requests during file transfers */ struct request { u_int id; size_t len; u_int64_t offset; TAILQ_ENTRY(request) tq; }; TAILQ_HEAD(requests, request); static u_char * get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, const char *errfmt, ...) __attribute__((format(printf, 4, 5))); static struct request * request_enqueue(struct requests *requests, u_int id, size_t len, uint64_t offset) { struct request *req; req = xcalloc(1, sizeof(*req)); req->id = id; req->len = len; req->offset = offset; TAILQ_INSERT_TAIL(requests, req, tq); return req; } static struct request * request_find(struct requests *requests, u_int id) { struct request *req; for (req = TAILQ_FIRST(requests); req != NULL && req->id != id; req = TAILQ_NEXT(req, tq)) ; return req; } static int sftpio(void *_bwlimit, size_t amount) { struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit; refresh_progress_meter(0); if (bwlimit != NULL) bandwidth_limit(bwlimit, amount); return 0; } static void send_msg(struct sftp_conn *conn, struct sshbuf *m) { u_char mlen[4]; struct iovec iov[2]; if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH) fatal("Outbound message too long %zu", sshbuf_len(m)); /* Send length first */ put_u32(mlen, sshbuf_len(m)); iov[0].iov_base = mlen; iov[0].iov_len = sizeof(mlen); iov[1].iov_base = (u_char *)sshbuf_ptr(m); iov[1].iov_len = sshbuf_len(m); if (atomiciov6(writev, conn->fd_out, iov, 2, sftpio, conn->limit_kbps > 0 ? &conn->bwlimit_out : NULL) != sshbuf_len(m) + sizeof(mlen)) fatal("Couldn't send packet: %s", strerror(errno)); sshbuf_reset(m); } static void get_msg_extended(struct sftp_conn *conn, struct sshbuf *m, int initial) { u_int msg_len; u_char *p; int r; sshbuf_reset(m); if ((r = sshbuf_reserve(m, 4, &p)) != 0) fatal_fr(r, "reserve"); if (atomicio6(read, conn->fd_in, p, 4, sftpio, conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != 4) { if (errno == EPIPE || errno == ECONNRESET) fatal("Connection closed"); else fatal("Couldn't read packet: %s", strerror(errno)); } if ((r = sshbuf_get_u32(m, &msg_len)) != 0) fatal_fr(r, "sshbuf_get_u32"); if (msg_len > SFTP_MAX_MSG_LENGTH) { do_log2(initial ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_FATAL, "Received message too long %u", msg_len); fatal("Ensure the remote shell produces no output " "for non-interactive sessions."); } if ((r = sshbuf_reserve(m, msg_len, &p)) != 0) fatal_fr(r, "reserve"); if (atomicio6(read, conn->fd_in, p, msg_len, sftpio, conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != msg_len) { if (errno == EPIPE) fatal("Connection closed"); else fatal("Read packet: %s", strerror(errno)); } } static void get_msg(struct sftp_conn *conn, struct sshbuf *m) { get_msg_extended(conn, m, 0); } static void send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s, u_int len) { struct sshbuf *msg; int r; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, code)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_string(msg, s, len)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); sshbuf_free(msg); } static void send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code, const void *s, u_int len, Attrib *a) { struct sshbuf *msg; int r; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, code)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_string(msg, s, len)) != 0 || (r = encode_attrib(msg, a)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message fd %d T:%u I:%u F:0x%04x M:%05o", conn->fd_out, code, id, a->flags, a->perm); sshbuf_free(msg); } static u_int get_status(struct sftp_conn *conn, u_int expected_id) { struct sshbuf *msg; u_char type; u_int id, status; int r; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "compose"); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type != SSH2_FXP_STATUS) fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", SSH2_FXP_STATUS, type); if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse"); sshbuf_free(msg); debug3("SSH2_FXP_STATUS %u", status); return status; } static u_char * get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, const char *errfmt, ...) { struct sshbuf *msg; u_int id, status; u_char type; u_char *handle; char errmsg[256]; va_list args; int r; va_start(args, errfmt); if (errfmt != NULL) vsnprintf(errmsg, sizeof(errmsg), errfmt, args); va_end(args); if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); if (id != expected_id) fatal("%s: ID mismatch (%u != %u)", errfmt == NULL ? __func__ : errmsg, id, expected_id); if (type == SSH2_FXP_STATUS) { if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); if (errfmt != NULL) error("%s: %s", errmsg, fx2txt(status)); sshbuf_free(msg); return(NULL); } else if (type != SSH2_FXP_HANDLE) fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); if ((r = sshbuf_get_string(msg, &handle, len)) != 0) fatal_fr(r, "parse handle"); sshbuf_free(msg); return handle; } -/* XXX returning &static is error-prone. Refactor to fill *Attrib argument */ -static Attrib * -get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet) +static int +get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet, Attrib *a) { struct sshbuf *msg; u_int id; u_char type; int r; - static Attrib a; + Attrib attr; + if (a != NULL) + memset(a, '\0', sizeof(*a)); if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type == SSH2_FXP_STATUS) { u_int status; if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); if (quiet) debug("stat remote: %s", fx2txt(status)); else error("stat remote: %s", fx2txt(status)); sshbuf_free(msg); - return(NULL); + return -1; } else if (type != SSH2_FXP_ATTRS) { fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", SSH2_FXP_ATTRS, type); } - if ((r = decode_attrib(msg, &a)) != 0) { + if ((r = decode_attrib(msg, &attr)) != 0) { error_fr(r, "decode_attrib"); sshbuf_free(msg); - return NULL; + return -1; } + /* success */ + if (a != NULL) + *a = attr; debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o", - type, id, a.flags, a.perm); + type, id, attr.flags, attr.perm); sshbuf_free(msg); - return &a; + return 0; } static int get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st, u_int expected_id, int quiet) { struct sshbuf *msg; u_char type; u_int id; u_int64_t flag; int r; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); debug3("Received statvfs reply T:%u I:%u", type, id); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type == SSH2_FXP_STATUS) { u_int status; if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); if (quiet) debug("remote statvfs: %s", fx2txt(status)); else error("remote statvfs: %s", fx2txt(status)); sshbuf_free(msg); return -1; } else if (type != SSH2_FXP_EXTENDED_REPLY) { fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", SSH2_FXP_EXTENDED_REPLY, type); } memset(st, 0, sizeof(*st)); if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 || (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 || (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 || (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 || (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 || (r = sshbuf_get_u64(msg, &st->f_files)) != 0 || (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 || (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 || (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 || (r = sshbuf_get_u64(msg, &flag)) != 0 || (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0) fatal_fr(r, "parse statvfs"); st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; sshbuf_free(msg); return 0; } struct sftp_conn * -do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, +sftp_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, u_int64_t limit_kbps) { u_char type; struct sshbuf *msg; struct sftp_conn *ret; int r; ret = xcalloc(1, sizeof(*ret)); ret->msg_id = 1; ret->fd_in = fd_in; ret->fd_out = fd_out; ret->download_buflen = ret->upload_buflen = transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN; ret->num_requests = num_requests ? num_requests : DEFAULT_NUM_REQUESTS; ret->exts = 0; ret->limit_kbps = 0; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 || (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) fatal_fr(r, "parse"); send_msg(ret, msg); get_msg_extended(ret, msg, 1); /* Expecting a VERSION reply */ if ((r = sshbuf_get_u8(msg, &type)) != 0) fatal_fr(r, "parse type"); if (type != SSH2_FXP_VERSION) { error("Invalid packet back from SSH2_FXP_INIT (type %u)", type); sshbuf_free(msg); free(ret); return(NULL); } if ((r = sshbuf_get_u32(msg, &ret->version)) != 0) fatal_fr(r, "parse version"); debug2("Remote version: %u", ret->version); /* Check for extensions */ while (sshbuf_len(msg) > 0) { char *name; u_char *value; size_t vlen; int known = 0; if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 || (r = sshbuf_get_string(msg, &value, &vlen)) != 0) fatal_fr(r, "parse extension"); if (strcmp(name, "posix-rename@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_POSIX_RENAME; known = 1; } else if (strcmp(name, "statvfs@openssh.com") == 0 && strcmp((char *)value, "2") == 0) { ret->exts |= SFTP_EXT_STATVFS; known = 1; } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && strcmp((char *)value, "2") == 0) { ret->exts |= SFTP_EXT_FSTATVFS; known = 1; } else if (strcmp(name, "hardlink@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_HARDLINK; known = 1; } else if (strcmp(name, "fsync@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_FSYNC; known = 1; } else if (strcmp(name, "lsetstat@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_LSETSTAT; known = 1; } else if (strcmp(name, "limits@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_LIMITS; known = 1; } else if (strcmp(name, "expand-path@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_PATH_EXPAND; known = 1; } else if (strcmp(name, "copy-data") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_COPY_DATA; known = 1; } else if (strcmp(name, "users-groups-by-id@openssh.com") == 0 && strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_GETUSERSGROUPS_BY_ID; known = 1; } if (known) { debug2("Server supports extension \"%s\" revision %s", name, value); } else { debug2("Unrecognised server extension \"%s\"", name); } free(name); free(value); } sshbuf_free(msg); /* Query the server for its limits */ if (ret->exts & SFTP_EXT_LIMITS) { struct sftp_limits limits; - if (do_limits(ret, &limits) != 0) + if (sftp_get_limits(ret, &limits) != 0) fatal_f("limits failed"); /* If the caller did not specify, find a good value */ if (transfer_buflen == 0) { ret->download_buflen = MINIMUM(limits.read_length, SFTP_MAX_MSG_LENGTH - 1024); ret->upload_buflen = MINIMUM(limits.write_length, SFTP_MAX_MSG_LENGTH - 1024); ret->download_buflen = MAXIMUM(ret->download_buflen, 64); ret->upload_buflen = MAXIMUM(ret->upload_buflen, 64); debug3("server upload/download buffer sizes " "%llu / %llu; using %u / %u", (unsigned long long)limits.write_length, (unsigned long long)limits.read_length, ret->upload_buflen, ret->download_buflen); } /* Use the server limit to scale down our value only */ if (num_requests == 0 && limits.open_handles) { ret->num_requests = MINIMUM(DEFAULT_NUM_REQUESTS, limits.open_handles); if (ret->num_requests == 0) ret->num_requests = 1; debug3("server handle limit %llu; using %u", (unsigned long long)limits.open_handles, ret->num_requests); } } /* Some filexfer v.0 servers don't support large packets */ if (ret->version == 0) { ret->download_buflen = MINIMUM(ret->download_buflen, 20480); ret->upload_buflen = MINIMUM(ret->upload_buflen, 20480); } ret->limit_kbps = limit_kbps; if (ret->limit_kbps > 0) { bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps, ret->download_buflen); bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps, ret->upload_buflen); } return ret; } u_int sftp_proto_version(struct sftp_conn *conn) { return conn->version; } int -do_limits(struct sftp_conn *conn, struct sftp_limits *limits) +sftp_get_limits(struct sftp_conn *conn, struct sftp_limits *limits) { u_int id, msg_id; u_char type; struct sshbuf *msg; int r; if ((conn->exts & SFTP_EXT_LIMITS) == 0) { error("Server does not support limits@openssh.com extension"); return -1; } if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "limits@openssh.com")) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message limits@openssh.com I:%u", id); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &msg_id)) != 0) fatal_fr(r, "parse"); debug3("Received limits reply T:%u I:%u", type, msg_id); if (id != msg_id) fatal("ID mismatch (%u != %u)", msg_id, id); if (type != SSH2_FXP_EXTENDED_REPLY) { debug_f("expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", SSH2_FXP_EXTENDED_REPLY, type); /* Disable the limits extension */ conn->exts &= ~SFTP_EXT_LIMITS; sshbuf_free(msg); return 0; } memset(limits, 0, sizeof(*limits)); if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 || (r = sshbuf_get_u64(msg, &limits->read_length)) != 0 || (r = sshbuf_get_u64(msg, &limits->write_length)) != 0 || (r = sshbuf_get_u64(msg, &limits->open_handles)) != 0) fatal_fr(r, "parse limits"); sshbuf_free(msg); return 0; } int -do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) +sftp_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) { u_int id, status; struct sshbuf *msg; int r; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0) fatal_fr(r, "parse"); send_msg(conn, msg); debug3("Sent message SSH2_FXP_CLOSE I:%u", id); status = get_status(conn, id); if (status != SSH2_FX_OK) error("close remote: %s", fx2txt(status)); sshbuf_free(msg); return status == SSH2_FX_OK ? 0 : -1; } static int -do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, +sftp_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, SFTP_DIRENT ***dir) { struct sshbuf *msg; u_int count, id, i, expected_id, ents = 0; size_t handle_len; u_char type, *handle; int status = SSH2_FX_FAILURE; int r; if (dir) *dir = NULL; id = conn->msg_id++; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, path)) != 0) fatal_fr(r, "compose OPENDIR"); send_msg(conn, msg); handle = get_handle(conn, id, &handle_len, "remote readdir(\"%s\")", path); if (handle == NULL) { sshbuf_free(msg); return -1; } if (dir) { ents = 0; *dir = xcalloc(1, sizeof(**dir)); (*dir)[0] = NULL; } for (; !interrupted;) { id = expected_id = conn->msg_id++; debug3("Sending SSH2_FXP_READDIR I:%u", id); sshbuf_reset(msg); if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0) fatal_fr(r, "compose READDIR"); send_msg(conn, msg); sshbuf_reset(msg); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); debug3("Received reply T:%u I:%u", type, id); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type == SSH2_FXP_STATUS) { u_int rstatus; if ((r = sshbuf_get_u32(msg, &rstatus)) != 0) fatal_fr(r, "parse status"); debug3("Received SSH2_FXP_STATUS %d", rstatus); if (rstatus == SSH2_FX_EOF) break; error("Couldn't read directory: %s", fx2txt(rstatus)); goto out; } else if (type != SSH2_FXP_NAME) fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", SSH2_FXP_NAME, type); if ((r = sshbuf_get_u32(msg, &count)) != 0) fatal_fr(r, "parse count"); if (count > SSHBUF_SIZE_MAX) fatal_f("nonsensical number of entries"); if (count == 0) break; debug3("Received %d SSH2_FXP_NAME responses", count); for (i = 0; i < count; i++) { char *filename, *longname; Attrib a; if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0) fatal_fr(r, "parse filenames"); if ((r = decode_attrib(msg, &a)) != 0) { error_fr(r, "couldn't decode attrib"); free(filename); free(longname); goto out; } if (print_flag) mprintf("%s\n", longname); /* * Directory entries should never contain '/' * These can be used to attack recursive ops * (e.g. send '../../../../etc/passwd') */ if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) { error("Server sent suspect path \"%s\" " "during readdir of \"%s\"", filename, path); } else if (dir) { *dir = xreallocarray(*dir, ents + 2, sizeof(**dir)); (*dir)[ents] = xcalloc(1, sizeof(***dir)); (*dir)[ents]->filename = xstrdup(filename); (*dir)[ents]->longname = xstrdup(longname); memcpy(&(*dir)[ents]->a, &a, sizeof(a)); (*dir)[++ents] = NULL; } free(filename); free(longname); } } status = 0; out: sshbuf_free(msg); - do_close(conn, handle, handle_len); + sftp_close(conn, handle, handle_len); free(handle); if (status != 0 && dir != NULL) { /* Don't return results on error */ - free_sftp_dirents(*dir); + sftp_free_dirents(*dir); *dir = NULL; } else if (interrupted && dir != NULL && *dir != NULL) { /* Don't return partial matches on interrupt */ - free_sftp_dirents(*dir); + sftp_free_dirents(*dir); *dir = xcalloc(1, sizeof(**dir)); **dir = NULL; } return status == SSH2_FX_OK ? 0 : -1; } int -do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) +sftp_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) { - return(do_lsreaddir(conn, path, 0, dir)); + return sftp_lsreaddir(conn, path, 0, dir); } -void free_sftp_dirents(SFTP_DIRENT **s) +void sftp_free_dirents(SFTP_DIRENT **s) { int i; if (s == NULL) return; for (i = 0; s[i]; i++) { free(s[i]->filename); free(s[i]->longname); free(s[i]); } free(s); } int -do_rm(struct sftp_conn *conn, const char *path) +sftp_rm(struct sftp_conn *conn, const char *path) { u_int status, id; debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); id = conn->msg_id++; send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path)); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote delete %s: %s", path, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } int -do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) +sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) { u_int status, id; debug2("Sending SSH2_FXP_MKDIR \"%s\"", path); id = conn->msg_id++; send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path, strlen(path), a); status = get_status(conn, id); if (status != SSH2_FX_OK && print_flag) error("remote mkdir \"%s\": %s", path, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } int -do_rmdir(struct sftp_conn *conn, const char *path) +sftp_rmdir(struct sftp_conn *conn, const char *path) { u_int status, id; debug2("Sending SSH2_FXP_RMDIR \"%s\"", path); id = conn->msg_id++; send_string_request(conn, id, SSH2_FXP_RMDIR, path, strlen(path)); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote rmdir \"%s\": %s", path, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } -Attrib * -do_stat(struct sftp_conn *conn, const char *path, int quiet) +int +sftp_stat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a) { u_int id; debug2("Sending SSH2_FXP_STAT \"%s\"", path); id = conn->msg_id++; send_string_request(conn, id, conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, path, strlen(path)); - return(get_decode_stat(conn, id, quiet)); + return get_decode_stat(conn, id, quiet, a); } -Attrib * -do_lstat(struct sftp_conn *conn, const char *path, int quiet) +int +sftp_lstat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a) { u_int id; if (conn->version == 0) { - if (quiet) - debug("Server version does not support lstat operation"); - else - logit("Server version does not support lstat operation"); - return(do_stat(conn, path, quiet)); + do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, + "Server version does not support lstat operation"); + return sftp_stat(conn, path, quiet, a); } id = conn->msg_id++; send_string_request(conn, id, SSH2_FXP_LSTAT, path, strlen(path)); - return(get_decode_stat(conn, id, quiet)); + return get_decode_stat(conn, id, quiet, a); } #ifdef notyet -Attrib * -do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, - int quiet) +int +sftp_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, + int quiet, Attrib *a) { u_int id; debug2("Sending SSH2_FXP_FSTAT \"%s\""); id = conn->msg_id++; send_string_request(conn, id, SSH2_FXP_FSTAT, handle, handle_len); - return(get_decode_stat(conn, id, quiet)); + return get_decode_stat(conn, id, quiet, a); } #endif int -do_setstat(struct sftp_conn *conn, const char *path, Attrib *a) +sftp_setstat(struct sftp_conn *conn, const char *path, Attrib *a) { u_int status, id; debug2("Sending SSH2_FXP_SETSTAT \"%s\"", path); id = conn->msg_id++; send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path, strlen(path), a); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote setstat \"%s\": %s", path, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } int -do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, +sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, Attrib *a) { u_int status, id; debug2("Sending SSH2_FXP_FSETSTAT"); id = conn->msg_id++; send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle, handle_len, a); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote fsetstat: %s", fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } /* Implements both the realpath and expand-path operations */ static char * -do_realpath_expand(struct sftp_conn *conn, const char *path, int expand) +sftp_realpath_expand(struct sftp_conn *conn, const char *path, int expand) { struct sshbuf *msg; u_int expected_id, count, id; char *filename, *longname; Attrib a; u_char type; int r; const char *what = "SSH2_FXP_REALPATH"; if (expand) what = "expand-path@openssh.com"; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); expected_id = id = conn->msg_id++; if (expand) { debug2("Sending SSH2_FXP_EXTENDED(expand-path@openssh.com) " "\"%s\"", path); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "expand-path@openssh.com")) != 0 || (r = sshbuf_put_cstring(msg, path)) != 0) fatal_fr(r, "compose %s", what); send_msg(conn, msg); } else { debug2("Sending SSH2_FXP_REALPATH \"%s\"", path); send_string_request(conn, id, SSH2_FXP_REALPATH, path, strlen(path)); } get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type == SSH2_FXP_STATUS) { u_int status; char *errmsg; if ((r = sshbuf_get_u32(msg, &status)) != 0 || (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0) fatal_fr(r, "parse status"); error("%s %s: %s", expand ? "expand" : "realpath", path, *errmsg == '\0' ? fx2txt(status) : errmsg); free(errmsg); sshbuf_free(msg); return NULL; } else if (type != SSH2_FXP_NAME) fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", SSH2_FXP_NAME, type); if ((r = sshbuf_get_u32(msg, &count)) != 0) fatal_fr(r, "parse count"); if (count != 1) fatal("Got multiple names (%d) from %s", count, what); if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || (r = decode_attrib(msg, &a)) != 0) fatal_fr(r, "parse filename/attrib"); debug3("%s %s -> %s", what, path, filename); free(longname); sshbuf_free(msg); return(filename); } char * -do_realpath(struct sftp_conn *conn, const char *path) +sftp_realpath(struct sftp_conn *conn, const char *path) { - return do_realpath_expand(conn, path, 0); + return sftp_realpath_expand(conn, path, 0); } int -can_expand_path(struct sftp_conn *conn) +sftp_can_expand_path(struct sftp_conn *conn) { return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0; } char * -do_expand_path(struct sftp_conn *conn, const char *path) +sftp_expand_path(struct sftp_conn *conn, const char *path) { - if (!can_expand_path(conn)) { + if (!sftp_can_expand_path(conn)) { debug3_f("no server support, fallback to realpath"); - return do_realpath_expand(conn, path, 0); + return sftp_realpath_expand(conn, path, 0); } - return do_realpath_expand(conn, path, 1); + return sftp_realpath_expand(conn, path, 1); } int -do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) +sftp_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) { - Attrib junk, *a; + Attrib junk, attr; struct sshbuf *msg; u_char *old_handle, *new_handle; u_int mode, status, id; size_t old_handle_len, new_handle_len; int r; /* Return if the extension is not supported */ if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) { error("Server does not support copy-data extension"); return -1; } /* Make sure the file exists, and we can copy its perms */ - if ((a = do_stat(conn, oldpath, 0)) == NULL) + if (sftp_stat(conn, oldpath, 0, &attr) != 0) return -1; /* Do not preserve set[ug]id here, as we do not preserve ownership */ - if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { - mode = a->perm & 0777; + if (attr.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { + mode = attr.perm & 0777; - if (!S_ISREG(a->perm)) { + if (!S_ISREG(attr.perm)) { error("Cannot copy non-regular file: %s", oldpath); return -1; } } else { /* NB: The user's umask will apply to this */ mode = 0666; } /* Set up the new perms for the new file */ - attrib_clear(a); - a->perm = mode; - a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; + attrib_clear(&attr); + attr.perm = mode; + attr.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); attrib_clear(&junk); /* Send empty attributes */ /* Open the old file for reading */ id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, oldpath)) != 0 || (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || (r = encode_attrib(msg, &junk)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); send_msg(conn, msg); debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath); sshbuf_reset(msg); old_handle = get_handle(conn, id, &old_handle_len, "remote open(\"%s\")", oldpath); if (old_handle == NULL) { sshbuf_free(msg); return -1; } /* Open the new file for writing */ id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, newpath)) != 0 || (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| SSH2_FXF_TRUNC)) != 0 || - (r = encode_attrib(msg, a)) != 0) + (r = encode_attrib(msg, &attr)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); send_msg(conn, msg); debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath); sshbuf_reset(msg); new_handle = get_handle(conn, id, &new_handle_len, "remote open(\"%s\")", newpath); if (new_handle == NULL) { sshbuf_free(msg); free(old_handle); return -1; } /* Copy the file data */ id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "copy-data")) != 0 || (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 || (r = sshbuf_put_u64(msg, 0)) != 0 || (r = sshbuf_put_u64(msg, 0)) != 0 || (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 || (r = sshbuf_put_u64(msg, 0)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); send_msg(conn, msg); debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0", oldpath, newpath); status = get_status(conn, id); if (status != SSH2_FX_OK) error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath, newpath, fx2txt(status)); /* Clean up everything */ sshbuf_free(msg); - do_close(conn, old_handle, old_handle_len); - do_close(conn, new_handle, new_handle_len); + sftp_close(conn, old_handle, old_handle_len); + sftp_close(conn, new_handle, new_handle_len); free(old_handle); free(new_handle); return status == SSH2_FX_OK ? 0 : -1; } int -do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, +sftp_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, int force_legacy) { struct sshbuf *msg; u_int status, id; int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* Send rename request */ id = conn->msg_id++; if (use_ext) { debug2("Sending SSH2_FXP_EXTENDED(posix-rename@openssh.com) " "\"%s\" to \"%s\"", oldpath, newpath); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "posix-rename@openssh.com")) != 0) fatal_fr(r, "compose posix-rename"); } else { debug2("Sending SSH2_FXP_RENAME \"%s\" to \"%s\"", oldpath, newpath); if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0) fatal_fr(r, "compose rename"); } if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 || (r = sshbuf_put_cstring(msg, newpath)) != 0) fatal_fr(r, "compose paths"); send_msg(conn, msg); debug3("Sent message %s \"%s\" -> \"%s\"", use_ext ? "posix-rename@openssh.com" : "SSH2_FXP_RENAME", oldpath, newpath); sshbuf_free(msg); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote rename \"%s\" to \"%s\": %s", oldpath, newpath, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } int -do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) +sftp_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) { struct sshbuf *msg; u_int status, id; int r; if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { error("Server does not support hardlink@openssh.com extension"); return -1; } debug2("Sending SSH2_FXP_EXTENDED(hardlink@openssh.com) " "\"%s\" to \"%s\"", oldpath, newpath); if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* Send link request */ id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 || (r = sshbuf_put_cstring(msg, oldpath)) != 0 || (r = sshbuf_put_cstring(msg, newpath)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", oldpath, newpath); sshbuf_free(msg); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote link \"%s\" to \"%s\": %s", oldpath, newpath, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } int -do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) +sftp_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) { struct sshbuf *msg; u_int status, id; int r; if (conn->version < 3) { error("This server does not support the symlink operation"); return(SSH2_FX_OP_UNSUPPORTED); } debug2("Sending SSH2_FXP_SYMLINK \"%s\" to \"%s\"", oldpath, newpath); if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* Send symlink request */ id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, oldpath)) != 0 || (r = sshbuf_put_cstring(msg, newpath)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, newpath); sshbuf_free(msg); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote symlink file \"%s\" to \"%s\": %s", oldpath, newpath, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } int -do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) +sftp_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) { struct sshbuf *msg; u_int status, id; int r; /* Silently return if the extension is not supported */ if ((conn->exts & SFTP_EXT_FSYNC) == 0) return -1; debug2("Sending SSH2_FXP_EXTENDED(fsync@openssh.com)"); /* Send fsync request */ if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message fsync@openssh.com I:%u", id); sshbuf_free(msg); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote fsync: %s", fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } #ifdef notyet char * -do_readlink(struct sftp_conn *conn, const char *path) +sftp_readlink(struct sftp_conn *conn, const char *path) { struct sshbuf *msg; u_int expected_id, count, id; char *filename, *longname; Attrib a; u_char type; int r; debug2("Sending SSH2_FXP_READLINK \"%s\"", path); expected_id = id = conn->msg_id++; send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path)); if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type == SSH2_FXP_STATUS) { u_int status; if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); error("Couldn't readlink: %s", fx2txt(status)); sshbuf_free(msg); return(NULL); } else if (type != SSH2_FXP_NAME) fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", SSH2_FXP_NAME, type); if ((r = sshbuf_get_u32(msg, &count)) != 0) fatal_fr(r, "parse count"); if (count != 1) fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || (r = decode_attrib(msg, &a)) != 0) fatal_fr(r, "parse filenames/attrib"); debug3("SSH_FXP_READLINK %s -> %s", path, filename); free(longname); sshbuf_free(msg); return filename; } #endif int -do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, +sftp_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, int quiet) { struct sshbuf *msg; u_int id; int r; if ((conn->exts & SFTP_EXT_STATVFS) == 0) { error("Server does not support statvfs@openssh.com extension"); return -1; } debug2("Sending SSH2_FXP_EXTENDED(statvfs@openssh.com) \"%s\"", path); id = conn->msg_id++; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || (r = sshbuf_put_cstring(msg, path)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); sshbuf_free(msg); return get_decode_statvfs(conn, st, id, quiet); } #ifdef notyet int -do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, +sftp_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, struct sftp_statvfs *st, int quiet) { struct sshbuf *msg; u_int id; if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { error("Server does not support fstatvfs@openssh.com extension"); return -1; } debug2("Sending SSH2_FXP_EXTENDED(fstatvfs@openssh.com)"); id = conn->msg_id++; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); sshbuf_free(msg); return get_decode_statvfs(conn, st, id, quiet); } #endif int -do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) +sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) { struct sshbuf *msg; u_int status, id; int r; if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) { error("Server does not support lsetstat@openssh.com extension"); return -1; } debug2("Sending SSH2_FXP_EXTENDED(lsetstat@openssh.com) \"%s\"", path); id = conn->msg_id++; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 || (r = sshbuf_put_cstring(msg, path)) != 0 || (r = encode_attrib(msg, a)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); sshbuf_free(msg); status = get_status(conn, id); if (status != SSH2_FX_OK) error("remote lsetstat \"%s\": %s", path, fx2txt(status)); return status == SSH2_FX_OK ? 0 : -1; } static void send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, u_int len, const u_char *handle, u_int handle_len) { struct sshbuf *msg; int r; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || (r = sshbuf_put_u64(msg, offset)) != 0 || (r = sshbuf_put_u32(msg, len)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); sshbuf_free(msg); } static int send_open(struct sftp_conn *conn, const char *path, const char *tag, u_int openmode, Attrib *a, u_char **handlep, size_t *handle_lenp) { Attrib junk; u_char *handle; size_t handle_len; struct sshbuf *msg; int r; u_int id; debug2("Sending SSH2_FXP_OPEN \"%s\"", path); *handlep = NULL; *handle_lenp = 0; if (a == NULL) { attrib_clear(&junk); /* Send empty attributes */ a = &junk; } /* Send open request */ if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); id = conn->msg_id++; if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, path)) != 0 || (r = sshbuf_put_u32(msg, openmode)) != 0 || (r = encode_attrib(msg, a)) != 0) fatal_fr(r, "compose %s open", tag); send_msg(conn, msg); sshbuf_free(msg); debug3("Sent %s message SSH2_FXP_OPEN I:%u P:%s M:0x%04x", tag, id, path, openmode); if ((handle = get_handle(conn, id, &handle_len, "%s open \"%s\"", tag, path)) == NULL) return -1; /* success */ *handlep = handle; *handle_lenp = handle_len; return 0; } static const char * progress_meter_path(const char *path) { const char *progresspath; if ((progresspath = strrchr(path, '/')) == NULL) return path; progresspath++; if (*progresspath == '\0') return path; return progresspath; } int -do_download(struct sftp_conn *conn, const char *remote_path, +sftp_download(struct sftp_conn *conn, const char *remote_path, const char *local_path, Attrib *a, int preserve_flag, int resume_flag, int fsync_flag, int inplace_flag) { struct sshbuf *msg; u_char *handle; int local_fd = -1, write_error; int read_error, write_errno, lmodified = 0, reordered = 0, r; u_int64_t offset = 0, size, highwater = 0, maxack = 0; u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK; off_t progress_counter; size_t handle_len; struct stat st; struct requests requests; struct request *req; u_char type; + Attrib attr; debug2_f("download remote \"%s\" to local \"%s\"", remote_path, local_path); TAILQ_INIT(&requests); - if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) - return -1; + if (a == NULL) { + if (sftp_stat(conn, remote_path, 0, &attr) != 0) + return -1; + a = &attr; + } /* Do not preserve set[ug]id here, as we do not preserve ownership */ if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) mode = a->perm & 0777; else mode = 0666; if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && (!S_ISREG(a->perm))) { error("download %s: not a regular file", remote_path); return(-1); } if (a->flags & SSH2_FILEXFER_ATTR_SIZE) size = a->size; else size = 0; buflen = conn->download_buflen; /* Send open request */ if (send_open(conn, remote_path, "remote", SSH2_FXF_READ, NULL, &handle, &handle_len) != 0) return -1; local_fd = open(local_path, O_WRONLY | O_CREAT | ((resume_flag || inplace_flag) ? 0 : O_TRUNC), mode | S_IWUSR); if (local_fd == -1) { error("open local \"%s\": %s", local_path, strerror(errno)); goto fail; } if (resume_flag) { if (fstat(local_fd, &st) == -1) { error("stat local \"%s\": %s", local_path, strerror(errno)); goto fail; } if (st.st_size < 0) { error("\"%s\" has negative size", local_path); goto fail; } if ((u_int64_t)st.st_size > size) { error("Unable to resume download of \"%s\": " "local file is larger than remote", local_path); fail: - do_close(conn, handle, handle_len); + sftp_close(conn, handle, handle_len); free(handle); if (local_fd != -1) close(local_fd); return -1; } offset = highwater = maxack = st.st_size; } /* Read from remote and write to local */ write_error = read_error = write_errno = num_req = 0; max_req = 1; progress_counter = offset; if (showprogress && size != 0) { start_progress_meter(progress_meter_path(remote_path), size, &progress_counter); } if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); while (num_req > 0 || max_req > 0) { u_char *data; size_t len; /* * Simulate EOF on interrupt: stop sending new requests and * allow outstanding requests to drain gracefully */ if (interrupted) { if (num_req == 0) /* If we haven't started yet... */ break; max_req = 0; } /* Send some more requests */ while (num_req < max_req) { debug3("Request range %llu -> %llu (%d/%d)", (unsigned long long)offset, (unsigned long long)offset + buflen - 1, num_req, max_req); req = request_enqueue(&requests, conn->msg_id++, buflen, offset); offset += buflen; num_req++; send_read_request(conn, req->id, req->offset, req->len, handle, handle_len); } sshbuf_reset(msg); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); debug3("Received reply T:%u I:%u R:%d", type, id, max_req); /* Find the request in our queue */ if ((req = request_find(&requests, id)) == NULL) fatal("Unexpected reply %u", id); switch (type) { case SSH2_FXP_STATUS: if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); if (status != SSH2_FX_EOF) read_error = 1; max_req = 0; TAILQ_REMOVE(&requests, req, tq); free(req); num_req--; break; case SSH2_FXP_DATA: if ((r = sshbuf_get_string(msg, &data, &len)) != 0) fatal_fr(r, "parse data"); debug3("Received data %llu -> %llu", (unsigned long long)req->offset, (unsigned long long)req->offset + len - 1); if (len > req->len) fatal("Received more data than asked for " "%zu > %zu", len, req->len); lmodified = 1; if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || atomicio(vwrite, local_fd, data, len) != len) && !write_error) { write_errno = errno; write_error = 1; max_req = 0; } else { /* * Track both the highest offset acknowledged * and the highest *contiguous* offset * acknowledged. * We'll need the latter for ftruncate()ing * interrupted transfers. */ if (maxack < req->offset + len) maxack = req->offset + len; if (!reordered && req->offset <= highwater) highwater = maxack; else if (!reordered && req->offset > highwater) reordered = 1; } progress_counter += len; free(data); if (len == req->len) { TAILQ_REMOVE(&requests, req, tq); free(req); num_req--; } else { /* Resend the request for the missing data */ debug3("Short data block, re-requesting " "%llu -> %llu (%2d)", (unsigned long long)req->offset + len, (unsigned long long)req->offset + req->len - 1, num_req); req->id = conn->msg_id++; req->len -= len; req->offset += len; send_read_request(conn, req->id, req->offset, req->len, handle, handle_len); /* Reduce the request size */ if (len < buflen) buflen = MAXIMUM(MIN_READ_SIZE, len); } if (max_req > 0) { /* max_req = 0 iff EOF received */ if (size > 0 && offset > size) { /* Only one request at a time * after the expected EOF */ debug3("Finish at %llu (%2d)", (unsigned long long)offset, num_req); max_req = 1; } else if (max_req < conn->num_requests) { ++max_req; } } break; default: fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", SSH2_FXP_DATA, type); } } if (showprogress && size) stop_progress_meter(); /* Sanity check */ if (TAILQ_FIRST(&requests) != NULL) fatal("Transfer complete, but requests still in queue"); if (!read_error && !write_error && !interrupted) { /* we got everything */ highwater = maxack; } /* * Truncate at highest contiguous point to avoid holes on interrupt, * or unconditionally if writing in place. */ if (inplace_flag || read_error || write_error || interrupted) { if (reordered && resume_flag && (read_error || write_error || interrupted)) { error("Unable to resume download of \"%s\": " "server reordered requests", local_path); } debug("truncating at %llu", (unsigned long long)highwater); if (ftruncate(local_fd, highwater) == -1) error("local ftruncate \"%s\": %s", local_path, strerror(errno)); } if (read_error) { error("read remote \"%s\" : %s", remote_path, fx2txt(status)); status = -1; - do_close(conn, handle, handle_len); + sftp_close(conn, handle, handle_len); } else if (write_error) { error("write local \"%s\": %s", local_path, strerror(write_errno)); status = SSH2_FX_FAILURE; - do_close(conn, handle, handle_len); + sftp_close(conn, handle, handle_len); } else { - if (do_close(conn, handle, handle_len) != 0 || interrupted) + if (sftp_close(conn, handle, handle_len) != 0 || interrupted) status = SSH2_FX_FAILURE; else status = SSH2_FX_OK; /* Override umask and utimes if asked */ #ifdef HAVE_FCHMOD if (preserve_flag && fchmod(local_fd, mode) == -1) #else if (preserve_flag && chmod(local_path, mode) == -1) #endif /* HAVE_FCHMOD */ error("local chmod \"%s\": %s", local_path, strerror(errno)); if (preserve_flag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { struct timeval tv[2]; tv[0].tv_sec = a->atime; tv[1].tv_sec = a->mtime; tv[0].tv_usec = tv[1].tv_usec = 0; if (utimes(local_path, tv) == -1) error("local set times \"%s\": %s", local_path, strerror(errno)); } if (resume_flag && !lmodified) logit("File \"%s\" was not modified", local_path); else if (fsync_flag) { debug("syncing \"%s\"", local_path); if (fsync(local_fd) == -1) error("local sync \"%s\": %s", local_path, strerror(errno)); } } close(local_fd); sshbuf_free(msg); free(handle); return status == SSH2_FX_OK ? 0 : -1; } static int download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, int depth, Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag) { int i, ret = 0; SFTP_DIRENT **dir_entries; char *filename, *new_src = NULL, *new_dst = NULL; mode_t mode = 0777, tmpmode = mode; + Attrib *a, ldirattrib, lsym; if (depth >= MAX_DIR_DEPTH) { error("Maximum directory depth exceeded: %d levels", depth); return -1; } debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst); - if (dirattrib == NULL && - (dirattrib = do_stat(conn, src, 1)) == NULL) { - error("stat remote \"%s\" directory failed", src); - return -1; + if (dirattrib == NULL) { + if (sftp_stat(conn, src, 1, &ldirattrib) != 0) { + error("stat remote \"%s\" directory failed", src); + return -1; + } + dirattrib = &ldirattrib; } if (!S_ISDIR(dirattrib->perm)) { error("\"%s\" is not a directory", src); return -1; } if (print_flag && print_flag != SFTP_PROGRESS_ONLY) mprintf("Retrieving %s\n", src); if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { mode = dirattrib->perm & 01777; tmpmode = mode | (S_IWUSR|S_IXUSR); } else { debug("download remote \"%s\": server " "did not send permissions", dst); } if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) { error("mkdir %s: %s", dst, strerror(errno)); return -1; } - if (do_readdir(conn, src, &dir_entries) == -1) { + if (sftp_readdir(conn, src, &dir_entries) == -1) { error("remote readdir \"%s\" failed", src); return -1; } for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { free(new_dst); free(new_src); filename = dir_entries[i]->filename; - new_dst = path_append(dst, filename); - new_src = path_append(src, filename); + new_dst = sftp_path_append(dst, filename); + new_src = sftp_path_append(src, filename); + + a = &dir_entries[i]->a; + if (S_ISLNK(a->perm)) { + if (!follow_link_flag) { + logit("download \"%s\": not a regular file", + new_src); + continue; + } + /* Replace the stat contents with the symlink target */ + if (sftp_stat(conn, new_src, 1, &lsym) != 0) { + logit("remote stat \"%s\" failed", new_src); + ret = -1; + continue; + } + a = &lsym; + } - if (S_ISDIR(dir_entries[i]->a.perm)) { + if (S_ISDIR(a->perm)) { if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) continue; if (download_dir_internal(conn, new_src, new_dst, - depth + 1, &(dir_entries[i]->a), preserve_flag, + depth + 1, a, preserve_flag, print_flag, resume_flag, fsync_flag, follow_link_flag, inplace_flag) == -1) ret = -1; - } else if (S_ISREG(dir_entries[i]->a.perm) || - (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) { - /* - * If this is a symlink then don't send the link's - * Attrib. do_download() will do a FXP_STAT operation - * and get the link target's attributes. - */ - if (do_download(conn, new_src, new_dst, - S_ISLNK(dir_entries[i]->a.perm) ? NULL : - &(dir_entries[i]->a), + } else if (S_ISREG(a->perm)) { + if (sftp_download(conn, new_src, new_dst, a, preserve_flag, resume_flag, fsync_flag, inplace_flag) == -1) { error("Download of file %s to %s failed", new_src, new_dst); ret = -1; } } else logit("download \"%s\": not a regular file", new_src); } free(new_dst); free(new_src); if (preserve_flag) { if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { struct timeval tv[2]; tv[0].tv_sec = dirattrib->atime; tv[1].tv_sec = dirattrib->mtime; tv[0].tv_usec = tv[1].tv_usec = 0; if (utimes(dst, tv) == -1) error("local set times on \"%s\": %s", dst, strerror(errno)); } else debug("Server did not send times for directory " "\"%s\"", dst); } if (mode != tmpmode && chmod(dst, mode) == -1) error("local chmod directory \"%s\": %s", dst, strerror(errno)); - free_sftp_dirents(dir_entries); + sftp_free_dirents(dir_entries); return ret; } int -download_dir(struct sftp_conn *conn, const char *src, const char *dst, +sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst, Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag) { char *src_canon; int ret; - if ((src_canon = do_realpath(conn, src)) == NULL) { + if ((src_canon = sftp_realpath(conn, src)) == NULL) { error("download \"%s\": path canonicalization failed", src); return -1; } ret = download_dir_internal(conn, src_canon, dst, 0, dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag, follow_link_flag, inplace_flag); free(src_canon); return ret; } int -do_upload(struct sftp_conn *conn, const char *local_path, +sftp_upload(struct sftp_conn *conn, const char *local_path, const char *remote_path, int preserve_flag, int resume, int fsync_flag, int inplace_flag) { int r, local_fd; u_int openmode, id, status = SSH2_FX_OK, reordered = 0; off_t offset, progress_counter; u_char type, *handle, *data; struct sshbuf *msg; struct stat sb; - Attrib a, t, *c = NULL; + Attrib a, t, c; u_int32_t startid, ackid; u_int64_t highwater = 0, maxack = 0; struct request *ack = NULL; struct requests acks; size_t handle_len; debug2_f("upload local \"%s\" to remote \"%s\"", local_path, remote_path); TAILQ_INIT(&acks); if ((local_fd = open(local_path, O_RDONLY)) == -1) { error("open local \"%s\": %s", local_path, strerror(errno)); return(-1); } if (fstat(local_fd, &sb) == -1) { error("fstat local \"%s\": %s", local_path, strerror(errno)); close(local_fd); return(-1); } if (!S_ISREG(sb.st_mode)) { error("local \"%s\" is not a regular file", local_path); close(local_fd); return(-1); } stat_to_attrib(&sb, &a); a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; a.perm &= 0777; if (!preserve_flag) a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; if (resume) { /* Get remote file size if it exists */ - if ((c = do_stat(conn, remote_path, 0)) == NULL) { + if (sftp_stat(conn, remote_path, 0, &c) != 0) { close(local_fd); return -1; } - if ((off_t)c->size >= sb.st_size) { + if ((off_t)c.size >= sb.st_size) { error("resume \"%s\": destination file " "same size or larger", local_path); close(local_fd); return -1; } - if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) { + if (lseek(local_fd, (off_t)c.size, SEEK_SET) == -1) { close(local_fd); return -1; } } openmode = SSH2_FXF_WRITE|SSH2_FXF_CREAT; if (resume) openmode |= SSH2_FXF_APPEND; else if (!inplace_flag) openmode |= SSH2_FXF_TRUNC; /* Send open request */ if (send_open(conn, remote_path, "dest", openmode, &a, &handle, &handle_len) != 0) { close(local_fd); return -1; } id = conn->msg_id; startid = ackid = id + 1; data = xmalloc(conn->upload_buflen); /* Read from local and write to remote */ - offset = progress_counter = (resume ? c->size : 0); + offset = progress_counter = (resume ? c.size : 0); if (showprogress) { start_progress_meter(progress_meter_path(local_path), sb.st_size, &progress_counter); } if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); for (;;) { int len; /* * Can't use atomicio here because it returns 0 on EOF, * thus losing the last block of the file. * Simulate an EOF on interrupt, allowing ACKs from the * server to drain. */ if (interrupted || status != SSH2_FX_OK) len = 0; else do len = read(local_fd, data, conn->upload_buflen); while ((len == -1) && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); if (len == -1) { fatal("read local \"%s\": %s", local_path, strerror(errno)); } else if (len != 0) { ack = request_enqueue(&acks, ++id, len, offset); sshbuf_reset(msg); if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || (r = sshbuf_put_u32(msg, ack->id)) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || (r = sshbuf_put_u64(msg, offset)) != 0 || (r = sshbuf_put_string(msg, data, len)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", id, (unsigned long long)offset, len); } else if (TAILQ_FIRST(&acks) == NULL) break; if (ack == NULL) fatal("Unexpected ACK %u", id); if (id == startid || len == 0 || id - ackid >= conn->num_requests) { u_int rid; sshbuf_reset(msg); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &rid)) != 0) fatal_fr(r, "parse"); if (type != SSH2_FXP_STATUS) fatal("Expected SSH2_FXP_STATUS(%d) packet, " "got %d", SSH2_FXP_STATUS, type); if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); debug3("SSH2_FXP_STATUS %u", status); /* Find the request in our queue */ if ((ack = request_find(&acks, rid)) == NULL) fatal("Can't find request for ID %u", rid); TAILQ_REMOVE(&acks, ack, tq); debug3("In write loop, ack for %u %zu bytes at %lld", ack->id, ack->len, (unsigned long long)ack->offset); ++ackid; progress_counter += ack->len; /* * Track both the highest offset acknowledged and the * highest *contiguous* offset acknowledged. * We'll need the latter for ftruncate()ing * interrupted transfers. */ if (maxack < ack->offset + ack->len) maxack = ack->offset + ack->len; if (!reordered && ack->offset <= highwater) highwater = maxack; else if (!reordered && ack->offset > highwater) { debug3_f("server reordered ACKs"); reordered = 1; } free(ack); } offset += len; if (offset < 0) fatal_f("offset < 0"); } sshbuf_free(msg); if (showprogress) stop_progress_meter(); free(data); if (status == SSH2_FX_OK && !interrupted) { /* we got everything */ highwater = maxack; } if (status != SSH2_FX_OK) { error("write remote \"%s\": %s", remote_path, fx2txt(status)); status = SSH2_FX_FAILURE; } if (inplace_flag || (resume && (status != SSH2_FX_OK || interrupted))) { debug("truncating at %llu", (unsigned long long)highwater); attrib_clear(&t); t.flags = SSH2_FILEXFER_ATTR_SIZE; t.size = highwater; - do_fsetstat(conn, handle, handle_len, &t); + sftp_fsetstat(conn, handle, handle_len, &t); } if (close(local_fd) == -1) { error("close local \"%s\": %s", local_path, strerror(errno)); status = SSH2_FX_FAILURE; } /* Override umask and utimes if asked */ if (preserve_flag) - do_fsetstat(conn, handle, handle_len, &a); + sftp_fsetstat(conn, handle, handle_len, &a); if (fsync_flag) - (void)do_fsync(conn, handle, handle_len); + (void)sftp_fsync(conn, handle, handle_len); - if (do_close(conn, handle, handle_len) != 0) + if (sftp_close(conn, handle, handle_len) != 0) status = SSH2_FX_FAILURE; free(handle); return status == SSH2_FX_OK ? 0 : -1; } static int upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, int depth, int preserve_flag, int print_flag, int resume, int fsync_flag, int follow_link_flag, int inplace_flag) { int ret = 0; DIR *dirp; struct dirent *dp; char *filename, *new_src = NULL, *new_dst = NULL; struct stat sb; - Attrib a, *dirattrib; + Attrib a, dirattrib; u_int32_t saved_perm; debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst); if (depth >= MAX_DIR_DEPTH) { error("Maximum directory depth exceeded: %d levels", depth); return -1; } if (stat(src, &sb) == -1) { error("stat local \"%s\": %s", src, strerror(errno)); return -1; } if (!S_ISDIR(sb.st_mode)) { error("\"%s\" is not a directory", src); return -1; } if (print_flag && print_flag != SFTP_PROGRESS_ONLY) mprintf("Entering %s\n", src); stat_to_attrib(&sb, &a); a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; a.perm &= 01777; if (!preserve_flag) a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; /* * sftp lacks a portable status value to match errno EEXIST, * so if we get a failure back then we must check whether * the path already existed and is a directory. Ensure we can * write to the directory we create for the duration of the transfer. */ saved_perm = a.perm; a.perm |= (S_IWUSR|S_IXUSR); - if (do_mkdir(conn, dst, &a, 0) != 0) { - if ((dirattrib = do_stat(conn, dst, 0)) == NULL) + if (sftp_mkdir(conn, dst, &a, 0) != 0) { + if (sftp_stat(conn, dst, 0, &dirattrib) != 0) return -1; - if (!S_ISDIR(dirattrib->perm)) { + if (!S_ISDIR(dirattrib.perm)) { error("\"%s\" exists but is not a directory", dst); return -1; } } a.perm = saved_perm; if ((dirp = opendir(src)) == NULL) { error("local opendir \"%s\": %s", src, strerror(errno)); return -1; } while (((dp = readdir(dirp)) != NULL) && !interrupted) { if (dp->d_ino == 0) continue; free(new_dst); free(new_src); filename = dp->d_name; - new_dst = path_append(dst, filename); - new_src = path_append(src, filename); + new_dst = sftp_path_append(dst, filename); + new_src = sftp_path_append(src, filename); + if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) + continue; if (lstat(new_src, &sb) == -1) { logit("local lstat \"%s\": %s", filename, strerror(errno)); ret = -1; - } else if (S_ISDIR(sb.st_mode)) { - if (strcmp(filename, ".") == 0 || - strcmp(filename, "..") == 0) + continue; + } + if (S_ISLNK(sb.st_mode)) { + if (!follow_link_flag) { + logit("%s: not a regular file", filename); continue; - + } + /* Replace the stat contents with the symlink target */ + if (stat(new_src, &sb) == -1) { + logit("local stat \"%s\": %s", filename, + strerror(errno)); + ret = -1; + continue; + } + } + if (S_ISDIR(sb.st_mode)) { if (upload_dir_internal(conn, new_src, new_dst, depth + 1, preserve_flag, print_flag, resume, fsync_flag, follow_link_flag, inplace_flag) == -1) ret = -1; - } else if (S_ISREG(sb.st_mode) || - (follow_link_flag && S_ISLNK(sb.st_mode))) { - if (do_upload(conn, new_src, new_dst, + } else if (S_ISREG(sb.st_mode)) { + if (sftp_upload(conn, new_src, new_dst, preserve_flag, resume, fsync_flag, inplace_flag) == -1) { error("upload \"%s\" to \"%s\" failed", new_src, new_dst); ret = -1; } } else logit("%s: not a regular file", filename); } free(new_dst); free(new_src); - do_setstat(conn, dst, &a); + sftp_setstat(conn, dst, &a); (void) closedir(dirp); return ret; } int -upload_dir(struct sftp_conn *conn, const char *src, const char *dst, +sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst, int preserve_flag, int print_flag, int resume, int fsync_flag, int follow_link_flag, int inplace_flag) { char *dst_canon; int ret; - if ((dst_canon = do_realpath(conn, dst)) == NULL) { + if ((dst_canon = sftp_realpath(conn, dst)) == NULL) { error("upload \"%s\": path canonicalization failed", dst); return -1; } ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag, print_flag, resume, fsync_flag, follow_link_flag, inplace_flag); free(dst_canon); return ret; } static void handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous, u_int *nreqsp, u_int *write_errorp) { struct sshbuf *msg; u_char type; u_int id, status; int r; struct pollfd pfd; if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* Try to eat replies from the upload side */ while (*nreqsp > 0) { debug3_f("%u outstanding replies", *nreqsp); if (!synchronous) { /* Bail out if no data is ready to be read */ pfd.fd = to->fd_in; pfd.events = POLLIN; if ((r = poll(&pfd, 1, 0)) == -1) { if (errno == EINTR) break; fatal_f("poll: %s", strerror(errno)); } else if (r == 0) break; /* fd not ready */ } sshbuf_reset(msg); get_msg(to, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "dest parse"); debug3("Received dest reply T:%u I:%u R:%u", type, id, *nreqsp); if (type != SSH2_FXP_STATUS) { fatal_f("Expected SSH2_FXP_STATUS(%d) packet, got %d", SSH2_FXP_STATUS, type); } if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse dest status"); debug3("dest SSH2_FXP_STATUS %u", status); if (status != SSH2_FX_OK) { /* record first error */ if (*write_errorp == 0) *write_errorp = status; } /* - * XXX this doesn't do full reply matching like do_upload and + * XXX this doesn't do full reply matching like sftp_upload and * so cannot gracefully truncate terminated uploads at a * high-water mark. ATM the only caller of this function (scp) * doesn't support transfer resumption, so this doesn't matter * a whole lot. * - * To be safe, do_crossload truncates the destination file to + * To be safe, sftp_crossload truncates the destination file to * zero length on upload failure, since we can't trust the * server not to have reordered replies that could have * inserted holes where none existed in the source file. * * XXX we could get a more accutate progress bar if we updated * the counter based on the reply from the destination... */ (*nreqsp)--; } debug3_f("done: %u outstanding replies", *nreqsp); sshbuf_free(msg); } int -do_crossload(struct sftp_conn *from, struct sftp_conn *to, +sftp_crossload(struct sftp_conn *from, struct sftp_conn *to, const char *from_path, const char *to_path, Attrib *a, int preserve_flag) { struct sshbuf *msg; int write_error, read_error, r; u_int64_t offset = 0, size; u_int id, buflen, num_req, max_req, status = SSH2_FX_OK; u_int num_upload_req; off_t progress_counter; u_char *from_handle, *to_handle; size_t from_handle_len, to_handle_len; struct requests requests; struct request *req; u_char type; + Attrib attr; debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path); TAILQ_INIT(&requests); - if (a == NULL && (a = do_stat(from, from_path, 0)) == NULL) - return -1; + if (a == NULL) { + if (sftp_stat(from, from_path, 0, &attr) != 0) + return -1; + a = &attr; + } if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && (!S_ISREG(a->perm))) { error("download \"%s\": not a regular file", from_path); return(-1); } if (a->flags & SSH2_FILEXFER_ATTR_SIZE) size = a->size; else size = 0; buflen = from->download_buflen; if (buflen > to->upload_buflen) buflen = to->upload_buflen; /* Send open request to read side */ if (send_open(from, from_path, "origin", SSH2_FXF_READ, NULL, &from_handle, &from_handle_len) != 0) return -1; /* Send open request to write side */ a->flags &= ~SSH2_FILEXFER_ATTR_SIZE; a->flags &= ~SSH2_FILEXFER_ATTR_UIDGID; a->perm &= 0777; if (!preserve_flag) a->flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; if (send_open(to, to_path, "dest", SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, &to_handle, &to_handle_len) != 0) { - do_close(from, from_handle, from_handle_len); + sftp_close(from, from_handle, from_handle_len); return -1; } /* Read from remote "from" and write to remote "to" */ offset = 0; write_error = read_error = num_req = num_upload_req = 0; max_req = 1; progress_counter = 0; if (showprogress && size != 0) { start_progress_meter(progress_meter_path(from_path), size, &progress_counter); } if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); while (num_req > 0 || max_req > 0) { u_char *data; size_t len; /* * Simulate EOF on interrupt: stop sending new requests and * allow outstanding requests to drain gracefully */ if (interrupted) { if (num_req == 0) /* If we haven't started yet... */ break; max_req = 0; } /* Send some more requests */ while (num_req < max_req) { debug3("Request range %llu -> %llu (%d/%d)", (unsigned long long)offset, (unsigned long long)offset + buflen - 1, num_req, max_req); req = request_enqueue(&requests, from->msg_id++, buflen, offset); offset += buflen; num_req++; send_read_request(from, req->id, req->offset, req->len, from_handle, from_handle_len); } /* Try to eat replies from the upload side (nonblocking) */ handle_dest_replies(to, to_path, 0, &num_upload_req, &write_error); sshbuf_reset(msg); get_msg(from, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); debug3("Received origin reply T:%u I:%u R:%d", type, id, max_req); /* Find the request in our queue */ if ((req = request_find(&requests, id)) == NULL) fatal("Unexpected reply %u", id); switch (type) { case SSH2_FXP_STATUS: if ((r = sshbuf_get_u32(msg, &status)) != 0) fatal_fr(r, "parse status"); if (status != SSH2_FX_EOF) read_error = 1; max_req = 0; TAILQ_REMOVE(&requests, req, tq); free(req); num_req--; break; case SSH2_FXP_DATA: if ((r = sshbuf_get_string(msg, &data, &len)) != 0) fatal_fr(r, "parse data"); debug3("Received data %llu -> %llu", (unsigned long long)req->offset, (unsigned long long)req->offset + len - 1); if (len > req->len) fatal("Received more data than asked for " "%zu > %zu", len, req->len); /* Write this chunk out to the destination */ sshbuf_reset(msg); if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || (r = sshbuf_put_u32(msg, to->msg_id++)) != 0 || (r = sshbuf_put_string(msg, to_handle, to_handle_len)) != 0 || (r = sshbuf_put_u64(msg, req->offset)) != 0 || (r = sshbuf_put_string(msg, data, len)) != 0) fatal_fr(r, "compose write"); send_msg(to, msg); debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%zu", id, (unsigned long long)offset, len); num_upload_req++; progress_counter += len; free(data); if (len == req->len) { TAILQ_REMOVE(&requests, req, tq); free(req); num_req--; } else { /* Resend the request for the missing data */ debug3("Short data block, re-requesting " "%llu -> %llu (%2d)", (unsigned long long)req->offset + len, (unsigned long long)req->offset + req->len - 1, num_req); req->id = from->msg_id++; req->len -= len; req->offset += len; send_read_request(from, req->id, req->offset, req->len, from_handle, from_handle_len); /* Reduce the request size */ if (len < buflen) buflen = MAXIMUM(MIN_READ_SIZE, len); } if (max_req > 0) { /* max_req = 0 iff EOF received */ if (size > 0 && offset > size) { /* Only one request at a time * after the expected EOF */ debug3("Finish at %llu (%2d)", (unsigned long long)offset, num_req); max_req = 1; } else if (max_req < from->num_requests) { ++max_req; } } break; default: fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", SSH2_FXP_DATA, type); } } if (showprogress && size) stop_progress_meter(); /* Drain replies from the server (blocking) */ debug3_f("waiting for %u replies from destination", num_upload_req); handle_dest_replies(to, to_path, 1, &num_upload_req, &write_error); /* Sanity check */ if (TAILQ_FIRST(&requests) != NULL) fatal("Transfer complete, but requests still in queue"); /* Truncate at 0 length on interrupt or error to avoid holes at dest */ if (read_error || write_error || interrupted) { debug("truncating \"%s\" at 0", to_path); - do_close(to, to_handle, to_handle_len); + sftp_close(to, to_handle, to_handle_len); free(to_handle); if (send_open(to, to_path, "dest", SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, &to_handle, &to_handle_len) != 0) { error("dest truncate \"%s\" failed", to_path); to_handle = NULL; } } if (read_error) { error("read origin \"%s\": %s", from_path, fx2txt(status)); status = -1; - do_close(from, from_handle, from_handle_len); + sftp_close(from, from_handle, from_handle_len); if (to_handle != NULL) - do_close(to, to_handle, to_handle_len); + sftp_close(to, to_handle, to_handle_len); } else if (write_error) { error("write dest \"%s\": %s", to_path, fx2txt(write_error)); status = SSH2_FX_FAILURE; - do_close(from, from_handle, from_handle_len); + sftp_close(from, from_handle, from_handle_len); if (to_handle != NULL) - do_close(to, to_handle, to_handle_len); + sftp_close(to, to_handle, to_handle_len); } else { - if (do_close(from, from_handle, from_handle_len) != 0 || + if (sftp_close(from, from_handle, from_handle_len) != 0 || interrupted) status = -1; else status = SSH2_FX_OK; if (to_handle != NULL) { /* Need to resend utimes after write */ if (preserve_flag) - do_fsetstat(to, to_handle, to_handle_len, a); - do_close(to, to_handle, to_handle_len); + sftp_fsetstat(to, to_handle, to_handle_len, a); + sftp_close(to, to_handle, to_handle_len); } } sshbuf_free(msg); free(from_handle); free(to_handle); return status == SSH2_FX_OK ? 0 : -1; } static int crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to, const char *from_path, const char *to_path, int depth, Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag) { int i, ret = 0; SFTP_DIRENT **dir_entries; char *filename, *new_from_path = NULL, *new_to_path = NULL; mode_t mode = 0777; - Attrib curdir; + Attrib *a, curdir, ldirattrib, newdir, lsym; debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path); if (depth >= MAX_DIR_DEPTH) { error("Maximum directory depth exceeded: %d levels", depth); return -1; } - if (dirattrib == NULL && - (dirattrib = do_stat(from, from_path, 1)) == NULL) { - error("stat remote \"%s\" failed", from_path); - return -1; + if (dirattrib == NULL) { + if (sftp_stat(from, from_path, 1, &ldirattrib) != 0) { + error("stat remote \"%s\" failed", from_path); + return -1; + } + dirattrib = &ldirattrib; } if (!S_ISDIR(dirattrib->perm)) { error("\"%s\" is not a directory", from_path); return -1; } if (print_flag && print_flag != SFTP_PROGRESS_ONLY) mprintf("Retrieving %s\n", from_path); curdir = *dirattrib; /* dirattrib will be clobbered */ curdir.flags &= ~SSH2_FILEXFER_ATTR_SIZE; curdir.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; if ((curdir.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) == 0) { debug("Origin did not send permissions for " "directory \"%s\"", to_path); curdir.perm = S_IWUSR|S_IXUSR; curdir.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; } /* We need to be able to write to the directory while we transfer it */ mode = curdir.perm & 01777; curdir.perm = mode | (S_IWUSR|S_IXUSR); /* * sftp lacks a portable status value to match errno EEXIST, * so if we get a failure back then we must check whether * the path already existed and is a directory. Ensure we can * write to the directory we create for the duration of the transfer. */ - if (do_mkdir(to, to_path, &curdir, 0) != 0) { - if ((dirattrib = do_stat(to, to_path, 0)) == NULL) + if (sftp_mkdir(to, to_path, &curdir, 0) != 0) { + if (sftp_stat(to, to_path, 0, &newdir) != 0) return -1; - if (!S_ISDIR(dirattrib->perm)) { + if (!S_ISDIR(newdir.perm)) { error("\"%s\" exists but is not a directory", to_path); return -1; } } curdir.perm = mode; - if (do_readdir(from, from_path, &dir_entries) == -1) { + if (sftp_readdir(from, from_path, &dir_entries) == -1) { error("origin readdir \"%s\" failed", from_path); return -1; } for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { free(new_from_path); free(new_to_path); filename = dir_entries[i]->filename; - new_from_path = path_append(from_path, filename); - new_to_path = path_append(to_path, filename); + new_from_path = sftp_path_append(from_path, filename); + new_to_path = sftp_path_append(to_path, filename); - if (S_ISDIR(dir_entries[i]->a.perm)) { + a = &dir_entries[i]->a; + if (S_ISLNK(a->perm)) { + if (!follow_link_flag) { + logit("%s: not a regular file", filename); + continue; + } + /* Replace the stat contents with the symlink target */ + if (sftp_stat(from, new_from_path, 1, &lsym) != 0) { + logit("remote stat \"%s\" failed", + new_from_path); + ret = -1; + continue; + } + a = &lsym; + } + if (S_ISDIR(a->perm)) { if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) continue; if (crossload_dir_internal(from, to, new_from_path, new_to_path, - depth + 1, &(dir_entries[i]->a), preserve_flag, + depth + 1, a, preserve_flag, print_flag, follow_link_flag) == -1) ret = -1; - } else if (S_ISREG(dir_entries[i]->a.perm) || - (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) { - /* - * If this is a symlink then don't send the link's - * Attrib. do_download() will do a FXP_STAT operation - * and get the link target's attributes. - */ - if (do_crossload(from, to, new_from_path, new_to_path, - S_ISLNK(dir_entries[i]->a.perm) ? NULL : - &(dir_entries[i]->a), preserve_flag) == -1) { + } else if (S_ISREG(a->perm)) { + if (sftp_crossload(from, to, new_from_path, + new_to_path, a, preserve_flag) == -1) { error("crossload \"%s\" to \"%s\" failed", new_from_path, new_to_path); ret = -1; } } else { logit("origin \"%s\": not a regular file", new_from_path); } } free(new_to_path); free(new_from_path); - do_setstat(to, to_path, &curdir); + sftp_setstat(to, to_path, &curdir); - free_sftp_dirents(dir_entries); + sftp_free_dirents(dir_entries); return ret; } int -crossload_dir(struct sftp_conn *from, struct sftp_conn *to, +sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to, const char *from_path, const char *to_path, Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag) { char *from_path_canon; int ret; - if ((from_path_canon = do_realpath(from, from_path)) == NULL) { + if ((from_path_canon = sftp_realpath(from, from_path)) == NULL) { error("crossload \"%s\": path canonicalization failed", from_path); return -1; } ret = crossload_dir_internal(from, to, from_path_canon, to_path, 0, dirattrib, preserve_flag, print_flag, follow_link_flag); free(from_path_canon); return ret; } int -can_get_users_groups_by_id(struct sftp_conn *conn) +sftp_can_get_users_groups_by_id(struct sftp_conn *conn) { return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0; } int -do_get_users_groups_by_id(struct sftp_conn *conn, +sftp_get_users_groups_by_id(struct sftp_conn *conn, const u_int *uids, u_int nuids, const u_int *gids, u_int ngids, char ***usernamesp, char ***groupnamesp) { struct sshbuf *msg, *uidbuf, *gidbuf; u_int i, expected_id, id; char *name, **usernames = NULL, **groupnames = NULL; u_char type; int r; *usernamesp = *groupnamesp = NULL; - if (!can_get_users_groups_by_id(conn)) + if (!sftp_can_get_users_groups_by_id(conn)) return SSH_ERR_FEATURE_UNSUPPORTED; if ((msg = sshbuf_new()) == NULL || (uidbuf = sshbuf_new()) == NULL || (gidbuf = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); expected_id = id = conn->msg_id++; debug2("Sending SSH2_FXP_EXTENDED(users-groups-by-id@openssh.com)"); for (i = 0; i < nuids; i++) { if ((r = sshbuf_put_u32(uidbuf, uids[i])) != 0) fatal_fr(r, "compose uids"); } for (i = 0; i < ngids; i++) { if ((r = sshbuf_put_u32(gidbuf, gids[i])) != 0) fatal_fr(r, "compose gids"); } if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "users-groups-by-id@openssh.com")) != 0 || (r = sshbuf_put_stringb(msg, uidbuf)) != 0 || (r = sshbuf_put_stringb(msg, gidbuf)) != 0) fatal_fr(r, "compose"); send_msg(conn, msg); get_msg(conn, msg); if ((r = sshbuf_get_u8(msg, &type)) != 0 || (r = sshbuf_get_u32(msg, &id)) != 0) fatal_fr(r, "parse"); if (id != expected_id) fatal("ID mismatch (%u != %u)", id, expected_id); if (type == SSH2_FXP_STATUS) { u_int status; char *errmsg; if ((r = sshbuf_get_u32(msg, &status)) != 0 || (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0) fatal_fr(r, "parse status"); error("users-groups-by-id %s", *errmsg == '\0' ? fx2txt(status) : errmsg); free(errmsg); sshbuf_free(msg); sshbuf_free(uidbuf); sshbuf_free(gidbuf); return -1; } else if (type != SSH2_FXP_EXTENDED_REPLY) fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", SSH2_FXP_EXTENDED_REPLY, type); /* reuse */ sshbuf_free(uidbuf); sshbuf_free(gidbuf); uidbuf = gidbuf = NULL; if ((r = sshbuf_froms(msg, &uidbuf)) != 0 || (r = sshbuf_froms(msg, &gidbuf)) != 0) fatal_fr(r, "parse response"); if (nuids > 0) { usernames = xcalloc(nuids, sizeof(*usernames)); for (i = 0; i < nuids; i++) { if ((r = sshbuf_get_cstring(uidbuf, &name, NULL)) != 0) fatal_fr(r, "parse user name"); /* Handle unresolved names */ if (*name == '\0') { free(name); name = NULL; } usernames[i] = name; } } if (ngids > 0) { groupnames = xcalloc(ngids, sizeof(*groupnames)); for (i = 0; i < ngids; i++) { if ((r = sshbuf_get_cstring(gidbuf, &name, NULL)) != 0) fatal_fr(r, "parse user name"); /* Handle unresolved names */ if (*name == '\0') { free(name); name = NULL; } groupnames[i] = name; } } if (sshbuf_len(uidbuf) != 0) fatal_f("unexpected extra username data"); if (sshbuf_len(gidbuf) != 0) fatal_f("unexpected extra groupname data"); sshbuf_free(uidbuf); sshbuf_free(gidbuf); sshbuf_free(msg); /* success */ *usernamesp = usernames; *groupnamesp = groupnames; return 0; } char * -path_append(const char *p1, const char *p2) +sftp_path_append(const char *p1, const char *p2) { char *ret; size_t len = strlen(p1) + strlen(p2) + 2; ret = xmalloc(len); strlcpy(ret, p1, len); if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') strlcat(ret, "/", len); strlcat(ret, p2, len); return(ret); } /* * Arg p must be dynamically allocated. It will either be returned or * freed and a replacement allocated. Caller must free returned string. */ char * -make_absolute(char *p, const char *pwd) +sftp_make_absolute(char *p, const char *pwd) { char *abs_str; /* Derelativise */ if (p && !path_absolute(p)) { - abs_str = path_append(pwd, p); + abs_str = sftp_path_append(pwd, p); free(p); return(abs_str); } else return(p); } int -remote_is_dir(struct sftp_conn *conn, const char *path) +sftp_remote_is_dir(struct sftp_conn *conn, const char *path) { - Attrib *a; + Attrib a; /* XXX: report errors? */ - if ((a = do_stat(conn, path, 1)) == NULL) + if (sftp_stat(conn, path, 1, &a) != 0) return(0); - if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) + if (!(a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) return(0); - return(S_ISDIR(a->perm)); + return S_ISDIR(a.perm); } -int -local_is_dir(const char *path) -{ - struct stat sb; - - /* XXX: report errors? */ - if (stat(path, &sb) == -1) - return(0); - - return(S_ISDIR(sb.st_mode)); -} - /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ int -globpath_is_dir(const char *pathname) +sftp_globpath_is_dir(const char *pathname) { size_t l = strlen(pathname); return l > 0 && pathname[l - 1] == '/'; } diff --git a/crypto/openssh/sftp-client.h b/crypto/openssh/sftp-client.h index d7deab17e4cb..74cdae7dc687 100644 --- a/crypto/openssh/sftp-client.h +++ b/crypto/openssh/sftp-client.h @@ -1,211 +1,207 @@ -/* $OpenBSD: sftp-client.h,v 1.38 2022/09/19 10:43:12 djm Exp $ */ +/* $OpenBSD: sftp-client.h,v 1.39 2023/09/08 05:56:13 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * * 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. */ /* Client side of SSH2 filexfer protocol */ #ifndef _SFTP_CLIENT_H #define _SFTP_CLIENT_H #ifdef USE_SYSTEM_GLOB # include #else # include "openbsd-compat/glob.h" #endif typedef struct SFTP_DIRENT SFTP_DIRENT; struct SFTP_DIRENT { char *filename; char *longname; Attrib a; }; /* * Used for statvfs responses on the wire from the server, because the * server's native format may be larger than the client's. */ struct sftp_statvfs { u_int64_t f_bsize; u_int64_t f_frsize; u_int64_t f_blocks; u_int64_t f_bfree; u_int64_t f_bavail; u_int64_t f_files; u_int64_t f_ffree; u_int64_t f_favail; u_int64_t f_fsid; u_int64_t f_flag; u_int64_t f_namemax; }; /* Used for limits response on the wire from the server */ struct sftp_limits { u_int64_t packet_length; u_int64_t read_length; u_int64_t write_length; u_int64_t open_handles; }; /* print flag values */ #define SFTP_QUIET 0 /* be quiet during transfers */ #define SFTP_PRINT 1 /* list files and show progress bar */ #define SFTP_PROGRESS_ONLY 2 /* progress bar only */ /* * Initialise a SSH filexfer connection. Returns NULL on error or * a pointer to a initialized sftp_conn struct on success. */ -struct sftp_conn *do_init(int, int, u_int, u_int, u_int64_t); +struct sftp_conn *sftp_init(int, int, u_int, u_int, u_int64_t); u_int sftp_proto_version(struct sftp_conn *); /* Query server limits */ -int do_limits(struct sftp_conn *, struct sftp_limits *); +int sftp_get_limits(struct sftp_conn *, struct sftp_limits *); /* Close file referred to by 'handle' */ -int do_close(struct sftp_conn *, const u_char *, u_int); +int sftp_close(struct sftp_conn *, const u_char *, u_int); /* Read contents of 'path' to NULL-terminated array 'dir' */ -int do_readdir(struct sftp_conn *, const char *, SFTP_DIRENT ***); +int sftp_readdir(struct sftp_conn *, const char *, SFTP_DIRENT ***); -/* Frees a NULL-terminated array of SFTP_DIRENTs (eg. from do_readdir) */ -void free_sftp_dirents(SFTP_DIRENT **); +/* Frees a NULL-terminated array of SFTP_DIRENTs (eg. from sftp_readdir) */ +void sftp_free_dirents(SFTP_DIRENT **); /* Delete file 'path' */ -int do_rm(struct sftp_conn *, const char *); +int sftp_rm(struct sftp_conn *, const char *); /* Create directory 'path' */ -int do_mkdir(struct sftp_conn *, const char *, Attrib *, int); +int sftp_mkdir(struct sftp_conn *, const char *, Attrib *, int); /* Remove directory 'path' */ -int do_rmdir(struct sftp_conn *, const char *); +int sftp_rmdir(struct sftp_conn *, const char *); /* Get file attributes of 'path' (follows symlinks) */ -Attrib *do_stat(struct sftp_conn *, const char *, int); +int sftp_stat(struct sftp_conn *, const char *, int, Attrib *); /* Get file attributes of 'path' (does not follow symlinks) */ -Attrib *do_lstat(struct sftp_conn *, const char *, int); +int sftp_lstat(struct sftp_conn *, const char *, int, Attrib *); /* Set file attributes of 'path' */ -int do_setstat(struct sftp_conn *, const char *, Attrib *); +int sftp_setstat(struct sftp_conn *, const char *, Attrib *); /* Set file attributes of open file 'handle' */ -int do_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *); +int sftp_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *); /* Set file attributes of 'path', not following symlinks */ -int do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a); +int sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a); /* Canonicalise 'path' - caller must free result */ -char *do_realpath(struct sftp_conn *, const char *); +char *sftp_realpath(struct sftp_conn *, const char *); /* Canonicalisation with tilde expansion (requires server extension) */ -char *do_expand_path(struct sftp_conn *, const char *); +char *sftp_expand_path(struct sftp_conn *, const char *); /* Returns non-zero if server can tilde-expand paths */ -int can_expand_path(struct sftp_conn *); +int sftp_can_expand_path(struct sftp_conn *); /* Get statistics for filesystem hosting file at "path" */ -int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int); +int sftp_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int); /* Rename 'oldpath' to 'newpath' */ -int do_rename(struct sftp_conn *, const char *, const char *, int); +int sftp_rename(struct sftp_conn *, const char *, const char *, int); /* Copy 'oldpath' to 'newpath' */ -int do_copy(struct sftp_conn *, const char *, const char *); +int sftp_copy(struct sftp_conn *, const char *, const char *); /* Link 'oldpath' to 'newpath' */ -int do_hardlink(struct sftp_conn *, const char *, const char *); +int sftp_hardlink(struct sftp_conn *, const char *, const char *); /* Rename 'oldpath' to 'newpath' */ -int do_symlink(struct sftp_conn *, const char *, const char *); +int sftp_symlink(struct sftp_conn *, const char *, const char *); /* Call fsync() on open file 'handle' */ -int do_fsync(struct sftp_conn *conn, u_char *, u_int); +int sftp_fsync(struct sftp_conn *conn, u_char *, u_int); /* * Download 'remote_path' to 'local_path'. Preserve permissions and times * if 'pflag' is set */ -int do_download(struct sftp_conn *, const char *, const char *, Attrib *, +int sftp_download(struct sftp_conn *, const char *, const char *, Attrib *, int, int, int, int); /* * Recursively download 'remote_directory' to 'local_directory'. Preserve * times if 'pflag' is set */ -int download_dir(struct sftp_conn *, const char *, const char *, Attrib *, +int sftp_download_dir(struct sftp_conn *, const char *, const char *, Attrib *, int, int, int, int, int, int); /* * Upload 'local_path' to 'remote_path'. Preserve permissions and times * if 'pflag' is set */ -int do_upload(struct sftp_conn *, const char *, const char *, +int sftp_upload(struct sftp_conn *, const char *, const char *, int, int, int, int); /* * Recursively upload 'local_directory' to 'remote_directory'. Preserve * times if 'pflag' is set */ -int upload_dir(struct sftp_conn *, const char *, const char *, +int sftp_upload_dir(struct sftp_conn *, const char *, const char *, int, int, int, int, int, int); /* * Download a 'from_path' from the 'from' connection and upload it to * to 'to' connection at 'to_path'. */ -int -do_crossload(struct sftp_conn *from, struct sftp_conn *to, +int sftp_crossload(struct sftp_conn *from, struct sftp_conn *to, const char *from_path, const char *to_path, Attrib *a, int preserve_flag); /* * Recursively download a directory from 'from_path' from the 'from' * connection and upload it to 'to' connection at 'to_path'. */ -int crossload_dir(struct sftp_conn *from, struct sftp_conn *to, +int sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to, const char *from_path, const char *to_path, Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag); /* * User/group ID to name translation. */ -int can_get_users_groups_by_id(struct sftp_conn *conn); -int do_get_users_groups_by_id(struct sftp_conn *conn, +int sftp_can_get_users_groups_by_id(struct sftp_conn *conn); +int sftp_get_users_groups_by_id(struct sftp_conn *conn, const u_int *uids, u_int nuids, const u_int *gids, u_int ngids, char ***usernamesp, char ***groupnamesp); /* Concatenate paths, taking care of slashes. Caller must free result. */ -char *path_append(const char *, const char *); +char *sftp_path_append(const char *, const char *); /* Make absolute path if relative path and CWD is given. Does not modify * original if the path is already absolute. */ -char *make_absolute(char *, const char *); +char *sftp_make_absolute(char *, const char *); /* Check if remote path is directory */ -int remote_is_dir(struct sftp_conn *conn, const char *path); - -/* Check if local path is directory */ -int local_is_dir(const char *path); +int sftp_remote_is_dir(struct sftp_conn *conn, const char *path); /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ -int globpath_is_dir(const char *pathname); +int sftp_globpath_is_dir(const char *pathname); #endif diff --git a/crypto/openssh/sftp-glob.c b/crypto/openssh/sftp-glob.c index afeb15f9ec11..1b82759b04d6 100644 --- a/crypto/openssh/sftp-glob.c +++ b/crypto/openssh/sftp-glob.c @@ -1,180 +1,180 @@ -/* $OpenBSD: sftp-glob.c,v 1.31 2022/10/24 21:51:55 djm Exp $ */ +/* $OpenBSD: sftp-glob.c,v 1.33 2023/09/10 23:12:32 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * * 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 "includes.h" #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include "xmalloc.h" #include "sftp.h" #include "sftp-common.h" #include "sftp-client.h" -int remote_glob(struct sftp_conn *, const char *, int, +int sftp_glob(struct sftp_conn *, const char *, int, int (*)(const char *, int), glob_t *); struct SFTP_OPENDIR { SFTP_DIRENT **dir; int offset; }; static struct { struct sftp_conn *conn; } cur; static void * fudge_opendir(const char *path) { struct SFTP_OPENDIR *r; r = xcalloc(1, sizeof(*r)); - if (do_readdir(cur.conn, path, &r->dir)) { + if (sftp_readdir(cur.conn, path, &r->dir)) { free(r); return(NULL); } r->offset = 0; return((void *)r); } static struct dirent * fudge_readdir(struct SFTP_OPENDIR *od) { /* Solaris needs sizeof(dirent) + path length (see below) */ static char buf[sizeof(struct dirent) + MAXPATHLEN]; struct dirent *ret = (struct dirent *)buf; #ifdef __GNU_LIBRARY__ static int inum = 1; #endif /* __GNU_LIBRARY__ */ if (od->dir[od->offset] == NULL) return(NULL); memset(buf, 0, sizeof(buf)); /* * Solaris defines dirent->d_name as a one byte array and expects * you to hack around it. */ #ifdef BROKEN_ONE_BYTE_DIRENT_D_NAME strlcpy(ret->d_name, od->dir[od->offset++]->filename, MAXPATHLEN); #else strlcpy(ret->d_name, od->dir[od->offset++]->filename, sizeof(ret->d_name)); #endif #ifdef __GNU_LIBRARY__ /* * Idiot glibc uses extensions to struct dirent for readdir with * ALTDIRFUNCs. Not that this is documented anywhere but the * source... Fake an inode number to appease it. */ ret->d_ino = inum++; if (!inum) inum = 1; #endif /* __GNU_LIBRARY__ */ return(ret); } static void fudge_closedir(struct SFTP_OPENDIR *od) { - free_sftp_dirents(od->dir); + sftp_free_dirents(od->dir); free(od); } static int fudge_lstat(const char *path, struct stat *st) { - Attrib *a; + Attrib a; - if (!(a = do_lstat(cur.conn, path, 1))) - return(-1); + if (sftp_lstat(cur.conn, path, 1, &a) != 0) + return -1; - attrib_to_stat(a, st); + attrib_to_stat(&a, st); - return(0); + return 0; } static int fudge_stat(const char *path, struct stat *st) { - Attrib *a; + Attrib a; - if (!(a = do_stat(cur.conn, path, 1))) - return(-1); + if (sftp_stat(cur.conn, path, 1, &a) != 0) + return -1; - attrib_to_stat(a, st); + attrib_to_stat(&a, st); return(0); } int -remote_glob(struct sftp_conn *conn, const char *pattern, int flags, +sftp_glob(struct sftp_conn *conn, const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) { int r; size_t l; char *s; struct stat sb; pglob->gl_opendir = fudge_opendir; pglob->gl_readdir = (struct dirent *(*)(void *))fudge_readdir; pglob->gl_closedir = (void (*)(void *))fudge_closedir; pglob->gl_lstat = fudge_lstat; pglob->gl_stat = fudge_stat; memset(&cur, 0, sizeof(cur)); cur.conn = conn; if ((r = glob(pattern, flags | GLOB_ALTDIRFUNC, errfunc, pglob)) != 0) return r; /* * When both GLOB_NOCHECK and GLOB_MARK are active, a single gl_pathv * entry has been returned and that entry has not already been marked, * then check whether it needs a '/' appended as a directory mark. * * This ensures that a NOCHECK result is annotated as a directory. * The glob(3) spec doesn't promise to mark NOCHECK entries, but doing * it simplifies our callers (sftp/scp) considerably. * * XXX doesn't try to handle gl_offs. */ if ((flags & (GLOB_NOCHECK|GLOB_MARK)) == (GLOB_NOCHECK|GLOB_MARK) && pglob->gl_matchc == 0 && pglob->gl_offs == 0 && pglob->gl_pathc == 1 && (s = pglob->gl_pathv[0]) != NULL && (l = strlen(s)) > 0 && s[l-1] != '/') { if (fudge_stat(s, &sb) == 0 && S_ISDIR(sb.st_mode)) { /* NOCHECK on a directory; annotate */ if ((s = realloc(s, l + 2)) != NULL) { memcpy(s + l, "/", 2); pglob->gl_pathv[0] = s; } } } return 0; } diff --git a/crypto/openssh/sftp-usergroup.c b/crypto/openssh/sftp-usergroup.c index 083930a4a327..93396ffc63db 100644 --- a/crypto/openssh/sftp-usergroup.c +++ b/crypto/openssh/sftp-usergroup.c @@ -1,239 +1,239 @@ /* * Copyright (c) 2022 Damien Miller * * 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. */ /* sftp client user/group lookup and caching */ #include "includes.h" #include #include #include #include #include #include "log.h" #include "xmalloc.h" #include "sftp-common.h" #include "sftp-client.h" #include "sftp-usergroup.h" /* Tree of id, name */ struct idname { u_int id; char *name; RB_ENTRY(idname) entry; /* XXX implement bounded cache as TAILQ */ }; static int idname_cmp(struct idname *a, struct idname *b) { if (a->id == b->id) return 0; return a->id > b->id ? 1 : -1; } RB_HEAD(idname_tree, idname); RB_GENERATE_STATIC(idname_tree, idname, entry, idname_cmp) static struct idname_tree user_idname = RB_INITIALIZER(&user_idname); static struct idname_tree group_idname = RB_INITIALIZER(&group_idname); static void idname_free(struct idname *idname) { if (idname == NULL) return; free(idname->name); free(idname); } static void idname_enter(struct idname_tree *tree, u_int id, const char *name) { struct idname *idname; if ((idname = xcalloc(1, sizeof(*idname))) == NULL) fatal_f("alloc"); idname->id = id; idname->name = xstrdup(name); if (RB_INSERT(idname_tree, tree, idname) != NULL) idname_free(idname); } static const char * idname_lookup(struct idname_tree *tree, u_int id) { struct idname idname, *found; memset(&idname, 0, sizeof(idname)); idname.id = id; if ((found = RB_FIND(idname_tree, tree, &idname)) != NULL) return found->name; return NULL; } static void freenames(char **names, u_int nnames) { u_int i; if (names == NULL) return; for (i = 0; i < nnames; i++) free(names[i]); free(names); } static void lookup_and_record(struct sftp_conn *conn, u_int *uids, u_int nuids, u_int *gids, u_int ngids) { int r; u_int i; char **usernames = NULL, **groupnames = NULL; - if ((r = do_get_users_groups_by_id(conn, uids, nuids, gids, ngids, + if ((r = sftp_get_users_groups_by_id(conn, uids, nuids, gids, ngids, &usernames, &groupnames)) != 0) { - debug_fr(r, "do_get_users_groups_by_id"); + debug_fr(r, "sftp_get_users_groups_by_id"); return; } for (i = 0; i < nuids; i++) { if (usernames[i] == NULL) { debug3_f("uid %u not resolved", uids[i]); continue; } debug3_f("record uid %u => \"%s\"", uids[i], usernames[i]); idname_enter(&user_idname, uids[i], usernames[i]); } for (i = 0; i < ngids; i++) { if (groupnames[i] == NULL) { debug3_f("gid %u not resolved", gids[i]); continue; } debug3_f("record gid %u => \"%s\"", gids[i], groupnames[i]); idname_enter(&group_idname, gids[i], groupnames[i]); } freenames(usernames, nuids); freenames(groupnames, ngids); } static int has_id(u_int id, u_int *ids, u_int nids) { u_int i; if (nids == 0) return 0; /* XXX O(N^2) */ for (i = 0; i < nids; i++) { if (ids[i] == id) break; } return i < nids; } static void collect_ids_from_glob(glob_t *g, int user, u_int **idsp, u_int *nidsp) { u_int id, i, n = 0, *ids = NULL; for (i = 0; g->gl_pathv[i] != NULL; i++) { if (user) { if (ruser_name(g->gl_statv[i]->st_uid) != NULL) continue; /* Already seen */ id = (u_int)g->gl_statv[i]->st_uid; } else { if (rgroup_name(g->gl_statv[i]->st_gid) != NULL) continue; /* Already seen */ id = (u_int)g->gl_statv[i]->st_gid; } if (has_id(id, ids, n)) continue; ids = xrecallocarray(ids, n, n + 1, sizeof(*ids)); ids[n++] = id; } *idsp = ids; *nidsp = n; } void get_remote_user_groups_from_glob(struct sftp_conn *conn, glob_t *g) { u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0; - if (!can_get_users_groups_by_id(conn)) + if (!sftp_can_get_users_groups_by_id(conn)) return; collect_ids_from_glob(g, 1, &uids, &nuids); collect_ids_from_glob(g, 0, &gids, &ngids); lookup_and_record(conn, uids, nuids, gids, ngids); free(uids); free(gids); } static void collect_ids_from_dirents(SFTP_DIRENT **d, int user, u_int **idsp, u_int *nidsp) { u_int id, i, n = 0, *ids = NULL; for (i = 0; d[i] != NULL; i++) { if (user) { if (ruser_name((uid_t)(d[i]->a.uid)) != NULL) continue; /* Already seen */ id = d[i]->a.uid; } else { if (rgroup_name((gid_t)(d[i]->a.gid)) != NULL) continue; /* Already seen */ id = d[i]->a.gid; } if (has_id(id, ids, n)) continue; ids = xrecallocarray(ids, n, n + 1, sizeof(*ids)); ids[n++] = id; } *idsp = ids; *nidsp = n; } void get_remote_user_groups_from_dirents(struct sftp_conn *conn, SFTP_DIRENT **d) { u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0; - if (!can_get_users_groups_by_id(conn)) + if (!sftp_can_get_users_groups_by_id(conn)) return; collect_ids_from_dirents(d, 1, &uids, &nuids); collect_ids_from_dirents(d, 0, &gids, &ngids); lookup_and_record(conn, uids, nuids, gids, ngids); free(uids); free(gids); } const char * ruser_name(uid_t uid) { return idname_lookup(&user_idname, (u_int)uid); } const char * rgroup_name(uid_t gid) { return idname_lookup(&group_idname, (u_int)gid); } diff --git a/crypto/openssh/sftp.c b/crypto/openssh/sftp.c index b113f930973d..c609b4153d79 100644 --- a/crypto/openssh/sftp.c +++ b/crypto/openssh/sftp.c @@ -1,2684 +1,2696 @@ -/* $OpenBSD: sftp.c,v 1.234 2023/04/12 08:53:54 jsg Exp $ */ +/* $OpenBSD: sftp.c,v 1.236 2023/09/10 23:12:32 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * * 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 "includes.h" #include #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #ifdef HAVE_SYS_STATVFS_H #include #endif #include #include #ifdef HAVE_PATHS_H # include #endif #ifdef HAVE_LIBGEN_H #include #endif #ifdef HAVE_LOCALE_H # include #endif #ifdef USE_LIBEDIT #include #else typedef void EditLine; #endif #include #include #include #include #include #include #include #ifdef HAVE_UTIL_H # include #endif #include "xmalloc.h" #include "log.h" #include "pathnames.h" #include "misc.h" #include "utf8.h" #include "sftp.h" #include "ssherr.h" #include "sshbuf.h" #include "sftp-common.h" #include "sftp-client.h" #include "sftp-usergroup.h" /* File to read commands from */ FILE* infile; /* Are we in batchfile mode? */ int batchmode = 0; /* PID of ssh transport process */ static volatile pid_t sshpid = -1; /* Suppress diagnostic messages */ int quiet = 0; /* This is set to 0 if the progressmeter is not desired. */ int showprogress = 1; /* When this option is set, we always recursively download/upload directories */ int global_rflag = 0; /* When this option is set, we resume download or upload if possible */ int global_aflag = 0; /* When this option is set, the file transfers will always preserve times */ int global_pflag = 0; /* When this option is set, transfers will have fsync() called on each file */ int global_fflag = 0; /* SIGINT received during command processing */ volatile sig_atomic_t interrupted = 0; /* I wish qsort() took a separate ctx for the comparison function...*/ int sort_flag; glob_t *sort_glob; /* Context used for commandline completion */ struct complete_ctx { struct sftp_conn *conn; char **remote_pathp; }; -int remote_glob(struct sftp_conn *, const char *, int, +int sftp_glob(struct sftp_conn *, const char *, int, int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ extern char *__progname; /* Separators for interactive commands */ #define WHITESPACE " \t\r\n" /* ls flags */ #define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */ #define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */ #define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */ #define LS_NAME_SORT 0x0008 /* Sort by name (default) */ #define LS_TIME_SORT 0x0010 /* Sort by mtime */ #define LS_SIZE_SORT 0x0020 /* Sort by file size */ #define LS_REVERSE_SORT 0x0040 /* Reverse sort order */ #define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */ #define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */ #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS) #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) /* Commands for interactive mode */ enum sftp_command { I_CHDIR = 1, I_CHGRP, I_CHMOD, I_CHOWN, I_COPY, I_DF, I_GET, I_HELP, I_LCHDIR, I_LINK, I_LLS, I_LMKDIR, I_LPWD, I_LS, I_LUMASK, I_MKDIR, I_PUT, I_PWD, I_QUIT, I_REGET, I_RENAME, I_REPUT, I_RM, I_RMDIR, I_SHELL, I_SYMLINK, I_VERSION, I_PROGRESS, }; struct CMD { const char *c; const int n; const int t; /* Completion type for the first argument */ const int t2; /* completion type for the optional second argument */ }; /* Type of completion */ #define NOARGS 0 #define REMOTE 1 #define LOCAL 2 static const struct CMD cmds[] = { { "bye", I_QUIT, NOARGS, NOARGS }, { "cd", I_CHDIR, REMOTE, NOARGS }, { "chdir", I_CHDIR, REMOTE, NOARGS }, { "chgrp", I_CHGRP, REMOTE, NOARGS }, { "chmod", I_CHMOD, REMOTE, NOARGS }, { "chown", I_CHOWN, REMOTE, NOARGS }, { "copy", I_COPY, REMOTE, LOCAL }, { "cp", I_COPY, REMOTE, LOCAL }, { "df", I_DF, REMOTE, NOARGS }, { "dir", I_LS, REMOTE, NOARGS }, { "exit", I_QUIT, NOARGS, NOARGS }, { "get", I_GET, REMOTE, LOCAL }, { "help", I_HELP, NOARGS, NOARGS }, { "lcd", I_LCHDIR, LOCAL, NOARGS }, { "lchdir", I_LCHDIR, LOCAL, NOARGS }, { "lls", I_LLS, LOCAL, NOARGS }, { "lmkdir", I_LMKDIR, LOCAL, NOARGS }, { "ln", I_LINK, REMOTE, REMOTE }, { "lpwd", I_LPWD, LOCAL, NOARGS }, { "ls", I_LS, REMOTE, NOARGS }, { "lumask", I_LUMASK, NOARGS, NOARGS }, { "mkdir", I_MKDIR, REMOTE, NOARGS }, { "mget", I_GET, REMOTE, LOCAL }, { "mput", I_PUT, LOCAL, REMOTE }, { "progress", I_PROGRESS, NOARGS, NOARGS }, { "put", I_PUT, LOCAL, REMOTE }, { "pwd", I_PWD, REMOTE, NOARGS }, { "quit", I_QUIT, NOARGS, NOARGS }, { "reget", I_REGET, REMOTE, LOCAL }, { "rename", I_RENAME, REMOTE, REMOTE }, { "reput", I_REPUT, LOCAL, REMOTE }, { "rm", I_RM, REMOTE, NOARGS }, { "rmdir", I_RMDIR, REMOTE, NOARGS }, { "symlink", I_SYMLINK, REMOTE, REMOTE }, { "version", I_VERSION, NOARGS, NOARGS }, { "!", I_SHELL, NOARGS, NOARGS }, { "?", I_HELP, NOARGS, NOARGS }, { NULL, -1, -1, -1 } }; static void killchild(int signo) { pid_t pid; pid = sshpid; if (pid > 1) { kill(pid, SIGTERM); (void)waitpid(pid, NULL, 0); } _exit(1); } static void suspchild(int signo) { if (sshpid > 1) { kill(sshpid, signo); while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR) continue; } kill(getpid(), SIGSTOP); } static void cmd_interrupt(int signo) { const char msg[] = "\rInterrupt \n"; int olderrno = errno; (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); interrupted = 1; errno = olderrno; } static void read_interrupt(int signo) { interrupted = 1; } static void sigchld_handler(int sig) { int save_errno = errno; pid_t pid; const char msg[] = "\rConnection closed. \n"; /* Report if ssh transport process dies. */ while ((pid = waitpid(sshpid, NULL, WNOHANG)) == -1 && errno == EINTR) continue; if (pid == sshpid) { if (!quiet) (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); sshpid = -1; } errno = save_errno; } static void help(void) { printf("Available commands:\n" "bye Quit sftp\n" "cd path Change remote directory to 'path'\n" "chgrp [-h] grp path Change group of file 'path' to 'grp'\n" "chmod [-h] mode path Change permissions of file 'path' to 'mode'\n" "chown [-h] own path Change owner of file 'path' to 'own'\n" "copy oldpath newpath Copy remote file\n" "cp oldpath newpath Copy remote file\n" "df [-hi] [path] Display statistics for current directory or\n" " filesystem containing 'path'\n" "exit Quit sftp\n" "get [-afpR] remote [local] Download file\n" "help Display this help text\n" "lcd path Change local directory to 'path'\n" "lls [ls-options [path]] Display local directory listing\n" "lmkdir path Create local directory\n" "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" "lpwd Print local working directory\n" "ls [-1afhlnrSt] [path] Display remote directory listing\n" "lumask umask Set local umask to 'umask'\n" "mkdir path Create remote directory\n" "progress Toggle display of progress meter\n" "put [-afpR] local [remote] Upload file\n" "pwd Display remote working directory\n" "quit Quit sftp\n" "reget [-fpR] remote [local] Resume download file\n" "rename oldpath newpath Rename remote file\n" "reput [-fpR] local [remote] Resume upload file\n" "rm path Delete remote file\n" "rmdir path Remove remote directory\n" "symlink oldpath newpath Symlink remote file\n" "version Show SFTP version\n" "!command Execute 'command' in local shell\n" "! Escape to local shell\n" "? Synonym for help\n"); } static void local_do_shell(const char *args) { int status; char *shell; pid_t pid; if (!*args) args = NULL; if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; if ((pid = fork()) == -1) fatal("Couldn't fork: %s", strerror(errno)); if (pid == 0) { /* XXX: child has pipe fds to ssh subproc open - issue? */ if (args) { debug3("Executing %s -c \"%s\"", shell, args); execl(shell, shell, "-c", args, (char *)NULL); } else { debug3("Executing %s", shell); execl(shell, shell, (char *)NULL); } fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, strerror(errno)); _exit(1); } while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for child: %s", strerror(errno)); if (!WIFEXITED(status)) error("Shell exited abnormally"); else if (WEXITSTATUS(status)) error("Shell exited with status %d", WEXITSTATUS(status)); } static void local_do_ls(const char *args) { if (!args || !*args) local_do_shell(_PATH_LS); else { int len = strlen(_PATH_LS " ") + strlen(args) + 1; char *buf = xmalloc(len); /* XXX: quoting - rip quoting code from ftp? */ snprintf(buf, len, _PATH_LS " %s", args); local_do_shell(buf); free(buf); } } /* Strip one path (usually the pwd) from the start of another */ static char * path_strip(const char *path, const char *strip) { size_t len; if (strip == NULL) return (xstrdup(path)); len = strlen(strip); if (strncmp(path, strip, len) == 0) { if (strip[len - 1] != '/' && path[len] == '/') len++; return (xstrdup(path + len)); } return (xstrdup(path)); } static int parse_getput_flags(const char *cmd, char **argv, int argc, int *aflag, int *fflag, int *pflag, int *rflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *aflag = *fflag = *rflag = *pflag = 0; while ((ch = getopt(argc, argv, "afPpRr")) != -1) { switch (ch) { case 'a': *aflag = 1; break; case 'f': *fflag = 1; break; case 'p': case 'P': *pflag = 1; break; case 'r': case 'R': *rflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_link_flags(const char *cmd, char **argv, int argc, int *sflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *sflag = 0; while ((ch = getopt(argc, argv, "s")) != -1) { switch (ch) { case 's': *sflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *lflag = 0; while ((ch = getopt(argc, argv, "l")) != -1) { switch (ch) { case 'l': *lflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_ls_flags(char **argv, int argc, int *lflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *lflag = LS_NAME_SORT; while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) { switch (ch) { case '1': *lflag &= ~VIEW_FLAGS; *lflag |= LS_SHORT_VIEW; break; case 'S': *lflag &= ~SORT_FLAGS; *lflag |= LS_SIZE_SORT; break; case 'a': *lflag |= LS_SHOW_ALL; break; case 'f': *lflag &= ~SORT_FLAGS; break; case 'h': *lflag |= LS_SI_UNITS; break; case 'l': *lflag &= ~LS_SHORT_VIEW; *lflag |= LS_LONG_VIEW; break; case 'n': *lflag &= ~LS_SHORT_VIEW; *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; break; case 'r': *lflag |= LS_REVERSE_SORT; break; case 't': *lflag &= ~SORT_FLAGS; *lflag |= LS_TIME_SORT; break; default: error("ls: Invalid flag -%c", optopt); return -1; } } return optind; } static int parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *hflag = *iflag = 0; while ((ch = getopt(argc, argv, "hi")) != -1) { switch (ch) { case 'h': *hflag = 1; break; case 'i': *iflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_ch_flags(const char *cmd, char **argv, int argc, int *hflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *hflag = 0; while ((ch = getopt(argc, argv, "h")) != -1) { switch (ch) { case 'h': *hflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_no_flags(const char *cmd, char **argv, int argc) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static char * escape_glob(const char *s) { size_t i, o, len; char *ret; len = strlen(s); ret = xcalloc(2, len + 1); for (i = o = 0; i < len; i++) { if (strchr("[]?*\\", s[i]) != NULL) ret[o++] = '\\'; ret[o++] = s[i]; } ret[o++] = '\0'; return ret; } /* * Arg p must be dynamically allocated. make_absolute will either return it * or free it and allocate a new one. Caller must free returned string. */ static char * make_absolute_pwd_glob(char *p, const char *pwd) { char *ret, *escpwd; escpwd = escape_glob(pwd); if (p == NULL) return escpwd; - ret = make_absolute(p, escpwd); + ret = sftp_make_absolute(p, escpwd); free(escpwd); return ret; } +static int +local_is_dir(const char *path) +{ + struct stat sb; + + if (stat(path, &sb) == -1) + return 0; + return S_ISDIR(sb.st_mode); +} + static int process_get(struct sftp_conn *conn, const char *src, const char *dst, const char *pwd, int pflag, int rflag, int resume, int fflag) { char *filename, *abs_src = NULL, *abs_dst = NULL, *tmp = NULL; glob_t g; int i, r, err = 0; abs_src = make_absolute_pwd_glob(xstrdup(src), pwd); memset(&g, 0, sizeof(g)); debug3("Looking up %s", abs_src); - if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) { + if ((r = sftp_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) { if (r == GLOB_NOSPACE) { error("Too many matches for \"%s\".", abs_src); } else { error("File \"%s\" not found.", abs_src); } err = -1; goto out; } /* * If multiple matches then dst must be a directory or * unspecified. */ if (g.gl_matchc > 1 && dst != NULL && !local_is_dir(dst)) { error("Multiple source paths, but destination " "\"%s\" is not a directory", dst); err = -1; goto out; } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); free(tmp); err = -1; goto out; } if (g.gl_matchc == 1 && dst) { if (local_is_dir(dst)) { - abs_dst = path_append(dst, filename); + abs_dst = sftp_path_append(dst, filename); } else { abs_dst = xstrdup(dst); } } else if (dst) { - abs_dst = path_append(dst, filename); + abs_dst = sftp_path_append(dst, filename); } else { abs_dst = xstrdup(filename); } free(tmp); resume |= global_aflag; if (!quiet && resume) mprintf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst); else if (!quiet && !resume) mprintf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); /* XXX follow link flag */ - if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { - if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, - pflag || global_pflag, 1, resume, + if (sftp_globpath_is_dir(g.gl_pathv[i]) && + (rflag || global_rflag)) { + if (sftp_download_dir(conn, g.gl_pathv[i], abs_dst, + NULL, pflag || global_pflag, 1, resume, fflag || global_fflag, 0, 0) == -1) err = -1; } else { - if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, + if (sftp_download(conn, g.gl_pathv[i], abs_dst, NULL, pflag || global_pflag, resume, fflag || global_fflag, 0) == -1) err = -1; } free(abs_dst); abs_dst = NULL; } out: free(abs_src); globfree(&g); return(err); } static int process_put(struct sftp_conn *conn, const char *src, const char *dst, const char *pwd, int pflag, int rflag, int resume, int fflag) { char *tmp_dst = NULL; char *abs_dst = NULL; char *tmp = NULL, *filename = NULL; glob_t g; int err = 0; int i, dst_is_dir = 1; struct stat sb; if (dst) { tmp_dst = xstrdup(dst); - tmp_dst = make_absolute(tmp_dst, pwd); + tmp_dst = sftp_make_absolute(tmp_dst, pwd); } memset(&g, 0, sizeof(g)); debug3("Looking up %s", src); if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) { error("File \"%s\" not found.", src); err = -1; goto out; } /* If we aren't fetching to pwd then stash this status for later */ if (tmp_dst != NULL) - dst_is_dir = remote_is_dir(conn, tmp_dst); + dst_is_dir = sftp_remote_is_dir(conn, tmp_dst); /* If multiple matches, dst may be directory or unspecified */ if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) { error("Multiple paths match, but destination " "\"%s\" is not a directory", tmp_dst); err = -1; goto out; } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (stat(g.gl_pathv[i], &sb) == -1) { err = -1; error("stat %s: %s", g.gl_pathv[i], strerror(errno)); continue; } tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); free(tmp); err = -1; goto out; } free(abs_dst); abs_dst = NULL; if (g.gl_matchc == 1 && tmp_dst) { /* If directory specified, append filename */ if (dst_is_dir) - abs_dst = path_append(tmp_dst, filename); + abs_dst = sftp_path_append(tmp_dst, filename); else abs_dst = xstrdup(tmp_dst); } else if (tmp_dst) { - abs_dst = path_append(tmp_dst, filename); + abs_dst = sftp_path_append(tmp_dst, filename); } else { - abs_dst = make_absolute(xstrdup(filename), pwd); + abs_dst = sftp_make_absolute(xstrdup(filename), pwd); } free(tmp); resume |= global_aflag; if (!quiet && resume) mprintf("Resuming upload of %s to %s\n", g.gl_pathv[i], abs_dst); else if (!quiet && !resume) mprintf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); /* XXX follow_link_flag */ - if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { - if (upload_dir(conn, g.gl_pathv[i], abs_dst, + if (sftp_globpath_is_dir(g.gl_pathv[i]) && + (rflag || global_rflag)) { + if (sftp_upload_dir(conn, g.gl_pathv[i], abs_dst, pflag || global_pflag, 1, resume, fflag || global_fflag, 0, 0) == -1) err = -1; } else { - if (do_upload(conn, g.gl_pathv[i], abs_dst, + if (sftp_upload(conn, g.gl_pathv[i], abs_dst, pflag || global_pflag, resume, fflag || global_fflag, 0) == -1) err = -1; } } out: free(abs_dst); free(tmp_dst); globfree(&g); return(err); } static int sdirent_comp(const void *aa, const void *bb) { SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) if (sort_flag & LS_NAME_SORT) return (rmul * strcmp(a->filename, b->filename)); else if (sort_flag & LS_TIME_SORT) return (rmul * NCMP(a->a.mtime, b->a.mtime)); else if (sort_flag & LS_SIZE_SORT) return (rmul * NCMP(a->a.size, b->a.size)); fatal("Unknown ls sort type"); } /* sftp ls.1 replacement for directories */ static int do_ls_dir(struct sftp_conn *conn, const char *path, const char *strip_path, int lflag) { int n; u_int c = 1, colspace = 0, columns = 1; SFTP_DIRENT **d; - if ((n = do_readdir(conn, path, &d)) != 0) + if ((n = sftp_readdir(conn, path, &d)) != 0) return (n); if (!(lflag & LS_SHORT_VIEW)) { u_int m = 0, width = 80; struct winsize ws; char *tmp; /* Count entries for sort and find longest filename */ for (n = 0; d[n] != NULL; n++) { if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) m = MAXIMUM(m, strlen(d[n]->filename)); } /* Add any subpath that also needs to be counted */ tmp = path_strip(path, strip_path); m += strlen(tmp); free(tmp); if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; columns = width / (m + 2); columns = MAXIMUM(columns, 1); colspace = width / columns; colspace = MINIMUM(colspace, width); } if (lflag & SORT_FLAGS) { for (n = 0; d[n] != NULL; n++) ; /* count entries */ sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); qsort(d, n, sizeof(*d), sdirent_comp); } get_remote_user_groups_from_dirents(conn, d); for (n = 0; d[n] != NULL && !interrupted; n++) { char *tmp, *fname; if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) continue; - tmp = path_append(path, d[n]->filename); + tmp = sftp_path_append(path, d[n]->filename); fname = path_strip(tmp, strip_path); free(tmp); if (lflag & LS_LONG_VIEW) { if ((lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) != 0 || - can_get_users_groups_by_id(conn)) { + sftp_can_get_users_groups_by_id(conn)) { char *lname; struct stat sb; memset(&sb, 0, sizeof(sb)); attrib_to_stat(&d[n]->a, &sb); lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS), ruser_name(sb.st_uid), rgroup_name(sb.st_gid)); mprintf("%s\n", lname); free(lname); } else mprintf("%s\n", d[n]->longname); } else { mprintf("%-*s", colspace, fname); if (c >= columns) { printf("\n"); c = 1; } else c++; } free(fname); } if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); - free_sftp_dirents(d); + sftp_free_dirents(d); return (0); } static int sglob_comp(const void *aa, const void *bb) { u_int a = *(const u_int *)aa; u_int b = *(const u_int *)bb; const char *ap = sort_glob->gl_pathv[a]; const char *bp = sort_glob->gl_pathv[b]; const struct stat *as = sort_glob->gl_statv[a]; const struct stat *bs = sort_glob->gl_statv[b]; int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) if (sort_flag & LS_NAME_SORT) return (rmul * strcmp(ap, bp)); else if (sort_flag & LS_TIME_SORT) { #if defined(HAVE_STRUCT_STAT_ST_MTIM) if (timespeccmp(&as->st_mtim, &bs->st_mtim, ==)) return 0; return timespeccmp(&as->st_mtim, &bs->st_mtim, <) ? rmul : -rmul; #elif defined(HAVE_STRUCT_STAT_ST_MTIME) return (rmul * NCMP(as->st_mtime, bs->st_mtime)); #else return rmul * 1; #endif } else if (sort_flag & LS_SIZE_SORT) return (rmul * NCMP(as->st_size, bs->st_size)); fatal("Unknown ls sort type"); } /* sftp ls.1 replacement which handles path globs */ static int do_globbed_ls(struct sftp_conn *conn, const char *path, const char *strip_path, int lflag) { char *fname, *lname; glob_t g; int err, r; struct winsize ws; u_int i, j, nentries, *indices = NULL, c = 1; u_int colspace = 0, columns = 1, m = 0, width = 80; memset(&g, 0, sizeof(g)); - if ((r = remote_glob(conn, path, + if ((r = sftp_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT, NULL, &g)) != 0 || (g.gl_pathc && !g.gl_matchc)) { if (g.gl_pathc) globfree(&g); if (r == GLOB_NOSPACE) { error("Can't ls: Too many matches for \"%s\"", path); } else { error("Can't ls: \"%s\" not found", path); } return -1; } if (interrupted) goto out; /* * If the glob returns a single match and it is a directory, * then just list its contents. */ if (g.gl_matchc == 1 && g.gl_statv[0] != NULL && S_ISDIR(g.gl_statv[0]->st_mode)) { err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); globfree(&g); return err; } if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; if (!(lflag & LS_SHORT_VIEW)) { /* Count entries for sort and find longest filename */ for (i = 0; g.gl_pathv[i]; i++) m = MAXIMUM(m, strlen(g.gl_pathv[i])); columns = width / (m + 2); columns = MAXIMUM(columns, 1); colspace = width / columns; } /* * Sorting: rather than mess with the contents of glob_t, prepare * an array of indices into it and sort that. For the usual * unsorted case, the indices are just the identity 1=1, 2=2, etc. */ for (nentries = 0; g.gl_pathv[nentries] != NULL; nentries++) ; /* count entries */ indices = xcalloc(nentries, sizeof(*indices)); for (i = 0; i < nentries; i++) indices[i] = i; if (lflag & SORT_FLAGS) { sort_glob = &g; sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); qsort(indices, nentries, sizeof(*indices), sglob_comp); sort_glob = NULL; } get_remote_user_groups_from_glob(conn, &g); for (j = 0; j < nentries && !interrupted; j++) { i = indices[j]; fname = path_strip(g.gl_pathv[i], strip_path); if (lflag & LS_LONG_VIEW) { if (g.gl_statv[i] == NULL) { error("no stat information for %s", fname); free(fname); continue; } lname = ls_file(fname, g.gl_statv[i], 1, (lflag & LS_SI_UNITS), ruser_name(g.gl_statv[i]->st_uid), rgroup_name(g.gl_statv[i]->st_gid)); mprintf("%s\n", lname); free(lname); } else { mprintf("%-*s", colspace, fname); if (c >= columns) { printf("\n"); c = 1; } else c++; } free(fname); } if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); out: if (g.gl_pathc) globfree(&g); free(indices); return 0; } static int do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag) { struct sftp_statvfs st; char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE]; char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE]; char s_icapacity[16], s_dcapacity[16]; - if (do_statvfs(conn, path, &st, 1) == -1) + if (sftp_statvfs(conn, path, &st, 1) == -1) return -1; if (st.f_files == 0) strlcpy(s_icapacity, "ERR", sizeof(s_icapacity)); else { snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%", (unsigned long long)(100 * (st.f_files - st.f_ffree) / st.f_files)); } if (st.f_blocks == 0) strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity)); else { snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%", (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / st.f_blocks)); } if (iflag) { printf(" Inodes Used Avail " "(root) %%Capacity\n"); printf("%11llu %11llu %11llu %11llu %s\n", (unsigned long long)st.f_files, (unsigned long long)(st.f_files - st.f_ffree), (unsigned long long)st.f_favail, (unsigned long long)st.f_ffree, s_icapacity); } else if (hflag) { strlcpy(s_used, "error", sizeof(s_used)); strlcpy(s_avail, "error", sizeof(s_avail)); strlcpy(s_root, "error", sizeof(s_root)); strlcpy(s_total, "error", sizeof(s_total)); fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); fmt_scaled(st.f_bavail * st.f_frsize, s_avail); fmt_scaled(st.f_bfree * st.f_frsize, s_root); fmt_scaled(st.f_blocks * st.f_frsize, s_total); printf(" Size Used Avail (root) %%Capacity\n"); printf("%7sB %7sB %7sB %7sB %s\n", s_total, s_used, s_avail, s_root, s_dcapacity); } else { printf(" Size Used Avail " "(root) %%Capacity\n"); printf("%12llu %12llu %12llu %12llu %s\n", (unsigned long long)(st.f_frsize * st.f_blocks / 1024), (unsigned long long)(st.f_frsize * (st.f_blocks - st.f_bfree) / 1024), (unsigned long long)(st.f_frsize * st.f_bavail / 1024), (unsigned long long)(st.f_frsize * st.f_bfree / 1024), s_dcapacity); } return 0; } /* * Undo escaping of glob sequences in place. Used to undo extra escaping * applied in makeargv() when the string is destined for a function that * does not glob it. */ static void undo_glob_escape(char *s) { size_t i, j; for (i = j = 0;;) { if (s[i] == '\0') { s[j] = '\0'; return; } if (s[i] != '\\') { s[j++] = s[i++]; continue; } /* s[i] == '\\' */ ++i; switch (s[i]) { case '?': case '[': case '*': case '\\': s[j++] = s[i++]; break; case '\0': s[j++] = '\\'; s[j] = '\0'; return; default: s[j++] = '\\'; s[j++] = s[i++]; break; } } } /* * Split a string into an argument vector using sh(1)-style quoting, * comment and escaping rules, but with some tweaks to handle glob(3) * wildcards. * The "sloppy" flag allows for recovery from missing terminating quote, for * use in parsing incomplete commandlines during tab autocompletion. * * Returns NULL on error or a NULL-terminated array of arguments. * * If "lastquote" is not NULL, the quoting character used for the last * argument is placed in *lastquote ("\0", "'" or "\""). * * If "terminated" is not NULL, *terminated will be set to 1 when the * last argument's quote has been properly terminated or 0 otherwise. * This parameter is only of use if "sloppy" is set. */ #define MAXARGS 128 #define MAXARGLEN 8192 static char ** makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, u_int *terminated) { int argc, quot; size_t i, j; static char argvs[MAXARGLEN]; static char *argv[MAXARGS + 1]; enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; *argcp = argc = 0; if (strlen(arg) > sizeof(argvs) - 1) { args_too_longs: error("string too long"); return NULL; } if (terminated != NULL) *terminated = 1; if (lastquote != NULL) *lastquote = '\0'; state = MA_START; i = j = 0; for (;;) { if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){ error("Too many arguments."); return NULL; } if (isspace((unsigned char)arg[i])) { if (state == MA_UNQUOTED) { /* Terminate current argument */ argvs[j++] = '\0'; argc++; state = MA_START; } else if (state != MA_START) argvs[j++] = arg[i]; } else if (arg[i] == '"' || arg[i] == '\'') { q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; if (state == MA_START) { argv[argc] = argvs + j; state = q; if (lastquote != NULL) *lastquote = arg[i]; } else if (state == MA_UNQUOTED) state = q; else if (state == q) state = MA_UNQUOTED; else argvs[j++] = arg[i]; } else if (arg[i] == '\\') { if (state == MA_SQUOTE || state == MA_DQUOTE) { quot = state == MA_SQUOTE ? '\'' : '"'; /* Unescape quote we are in */ /* XXX support \n and friends? */ if (arg[i + 1] == quot) { i++; argvs[j++] = arg[i]; } else if (arg[i + 1] == '?' || arg[i + 1] == '[' || arg[i + 1] == '*') { /* * Special case for sftp: append * double-escaped glob sequence - * glob will undo one level of * escaping. NB. string can grow here. */ if (j >= sizeof(argvs) - 5) goto args_too_longs; argvs[j++] = '\\'; argvs[j++] = arg[i++]; argvs[j++] = '\\'; argvs[j++] = arg[i]; } else { argvs[j++] = arg[i++]; argvs[j++] = arg[i]; } } else { if (state == MA_START) { argv[argc] = argvs + j; state = MA_UNQUOTED; if (lastquote != NULL) *lastquote = '\0'; } if (arg[i + 1] == '?' || arg[i + 1] == '[' || arg[i + 1] == '*' || arg[i + 1] == '\\') { /* * Special case for sftp: append * escaped glob sequence - * glob will undo one level of * escaping. */ argvs[j++] = arg[i++]; argvs[j++] = arg[i]; } else { /* Unescape everything */ /* XXX support \n and friends? */ i++; argvs[j++] = arg[i]; } } } else if (arg[i] == '#') { if (state == MA_SQUOTE || state == MA_DQUOTE) argvs[j++] = arg[i]; else goto string_done; } else if (arg[i] == '\0') { if (state == MA_SQUOTE || state == MA_DQUOTE) { if (sloppy) { state = MA_UNQUOTED; if (terminated != NULL) *terminated = 0; goto string_done; } error("Unterminated quoted argument"); return NULL; } string_done: if (state == MA_UNQUOTED) { argvs[j++] = '\0'; argc++; } break; } else { if (state == MA_START) { argv[argc] = argvs + j; state = MA_UNQUOTED; if (lastquote != NULL) *lastquote = '\0'; } if ((state == MA_SQUOTE || state == MA_DQUOTE) && (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { /* * Special case for sftp: escape quoted * glob(3) wildcards. NB. string can grow * here. */ if (j >= sizeof(argvs) - 3) goto args_too_longs; argvs[j++] = '\\'; argvs[j++] = arg[i]; } else argvs[j++] = arg[i]; } i++; } *argcp = argc; return argv; } static int parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag, int *fflag, int *hflag, int *iflag, int *lflag, int *pflag, int *rflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) { const char *cmd, *cp = *cpp; char *cp2, **argv; int base = 0; long long ll; int path1_mandatory = 0, i, cmdnum, optidx, argc; /* Skip leading whitespace */ cp = cp + strspn(cp, WHITESPACE); /* * Check for leading '-' (disable error processing) and '@' (suppress * command echo) */ *ignore_errors = 0; *disable_echo = 0; for (;*cp != '\0'; cp++) { if (*cp == '-') { *ignore_errors = 1; } else if (*cp == '@') { *disable_echo = 1; } else { /* all other characters terminate prefix processing */ break; } } cp = cp + strspn(cp, WHITESPACE); /* Ignore blank lines and lines which begin with comment '#' char */ if (*cp == '\0' || *cp == '#') return (0); if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) return -1; /* Figure out which command we have */ for (i = 0; cmds[i].c != NULL; i++) { if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0) break; } cmdnum = cmds[i].n; cmd = cmds[i].c; /* Special case */ if (*cp == '!') { cp++; cmdnum = I_SHELL; } else if (cmdnum == -1) { error("Invalid command."); return -1; } /* Get arguments and parse flags */ *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0; *rflag = *sflag = 0; *path1 = *path2 = NULL; optidx = 1; switch (cmdnum) { case I_GET: case I_REGET: case I_REPUT: case I_PUT: if ((optidx = parse_getput_flags(cmd, argv, argc, aflag, fflag, pflag, rflag)) == -1) return -1; /* Get first pathname (mandatory) */ if (argc - optidx < 1) { error("You must specify at least one path after a " "%s command.", cmd); return -1; } *path1 = xstrdup(argv[optidx]); /* Get second pathname (optional) */ if (argc - optidx > 1) { *path2 = xstrdup(argv[optidx + 1]); /* Destination is not globbed */ undo_glob_escape(*path2); } break; case I_LINK: if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) return -1; goto parse_two_paths; case I_COPY: if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) return -1; goto parse_two_paths; case I_RENAME: if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) return -1; goto parse_two_paths; case I_SYMLINK: if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) return -1; parse_two_paths: if (argc - optidx < 2) { error("You must specify two paths after a %s " "command.", cmd); return -1; } *path1 = xstrdup(argv[optidx]); *path2 = xstrdup(argv[optidx + 1]); /* Paths are not globbed */ undo_glob_escape(*path1); undo_glob_escape(*path2); break; case I_RM: case I_MKDIR: case I_RMDIR: case I_LMKDIR: path1_mandatory = 1; /* FALLTHROUGH */ case I_CHDIR: case I_LCHDIR: if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) return -1; /* Get pathname (mandatory) */ if (argc - optidx < 1) { if (!path1_mandatory) break; /* return a NULL path1 */ error("You must specify a path after a %s command.", cmd); return -1; } *path1 = xstrdup(argv[optidx]); /* Only "rm" globs */ if (cmdnum != I_RM) undo_glob_escape(*path1); break; case I_DF: if ((optidx = parse_df_flags(cmd, argv, argc, hflag, iflag)) == -1) return -1; /* Default to current directory if no path specified */ if (argc - optidx < 1) *path1 = NULL; else { *path1 = xstrdup(argv[optidx]); undo_glob_escape(*path1); } break; case I_LS: if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) return(-1); /* Path is optional */ if (argc - optidx > 0) *path1 = xstrdup(argv[optidx]); break; case I_LLS: /* Skip ls command and following whitespace */ cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); case I_SHELL: /* Uses the rest of the line */ break; case I_LUMASK: case I_CHMOD: base = 8; /* FALLTHROUGH */ case I_CHOWN: case I_CHGRP: if ((optidx = parse_ch_flags(cmd, argv, argc, hflag)) == -1) return -1; /* Get numeric arg (mandatory) */ if (argc - optidx < 1) goto need_num_arg; errno = 0; ll = strtoll(argv[optidx], &cp2, base); if (cp2 == argv[optidx] || *cp2 != '\0' || ((ll == LLONG_MIN || ll == LLONG_MAX) && errno == ERANGE) || ll < 0 || ll > UINT32_MAX) { need_num_arg: error("You must supply a numeric argument " "to the %s command.", cmd); return -1; } *n_arg = ll; if (cmdnum == I_LUMASK) break; /* Get pathname (mandatory) */ if (argc - optidx < 2) { error("You must specify a path after a %s command.", cmd); return -1; } *path1 = xstrdup(argv[optidx + 1]); break; case I_QUIT: case I_PWD: case I_LPWD: case I_HELP: case I_VERSION: case I_PROGRESS: if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) return -1; break; default: fatal("Command not implemented"); } *cpp = cp; return(cmdnum); } static int parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, const char *startdir, int err_abort, int echo_command) { const char *ocmd = cmd; char *path1, *path2, *tmp; int ignore_errors = 0, disable_echo = 1; int aflag = 0, fflag = 0, hflag = 0, iflag = 0; int lflag = 0, pflag = 0, rflag = 0, sflag = 0; int cmdnum, i; unsigned long n_arg = 0; - Attrib a, *aa; + Attrib a, aa; char path_buf[PATH_MAX]; int err = 0; glob_t g; path1 = path2 = NULL; cmdnum = parse_args(&cmd, &ignore_errors, &disable_echo, &aflag, &fflag, &hflag, &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2); if (ignore_errors != 0) err_abort = 0; if (echo_command && !disable_echo) mprintf("sftp> %s\n", ocmd); memset(&g, 0, sizeof(g)); /* Perform command */ switch (cmdnum) { case 0: /* Blank line */ break; case -1: /* Unrecognized command */ err = -1; break; case I_REGET: aflag = 1; /* FALLTHROUGH */ case I_GET: err = process_get(conn, path1, path2, *pwd, pflag, rflag, aflag, fflag); break; case I_REPUT: aflag = 1; /* FALLTHROUGH */ case I_PUT: err = process_put(conn, path1, path2, *pwd, pflag, rflag, aflag, fflag); break; case I_COPY: - path1 = make_absolute(path1, *pwd); - path2 = make_absolute(path2, *pwd); - err = do_copy(conn, path1, path2); + path1 = sftp_make_absolute(path1, *pwd); + path2 = sftp_make_absolute(path2, *pwd); + err = sftp_copy(conn, path1, path2); break; case I_RENAME: - path1 = make_absolute(path1, *pwd); - path2 = make_absolute(path2, *pwd); - err = do_rename(conn, path1, path2, lflag); + path1 = sftp_make_absolute(path1, *pwd); + path2 = sftp_make_absolute(path2, *pwd); + err = sftp_rename(conn, path1, path2, lflag); break; case I_SYMLINK: sflag = 1; /* FALLTHROUGH */ case I_LINK: if (!sflag) - path1 = make_absolute(path1, *pwd); - path2 = make_absolute(path2, *pwd); - err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); + path1 = sftp_make_absolute(path1, *pwd); + path2 = sftp_make_absolute(path2, *pwd); + err = (sflag ? sftp_symlink : sftp_hardlink)(conn, + path1, path2); break; case I_RM: path1 = make_absolute_pwd_glob(path1, *pwd); - remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); + sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (!quiet) mprintf("Removing %s\n", g.gl_pathv[i]); - err = do_rm(conn, g.gl_pathv[i]); + err = sftp_rm(conn, g.gl_pathv[i]); if (err != 0 && err_abort) break; } break; case I_MKDIR: - path1 = make_absolute(path1, *pwd); + path1 = sftp_make_absolute(path1, *pwd); attrib_clear(&a); a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = 0777; - err = do_mkdir(conn, path1, &a, 1); + err = sftp_mkdir(conn, path1, &a, 1); break; case I_RMDIR: - path1 = make_absolute(path1, *pwd); - err = do_rmdir(conn, path1); + path1 = sftp_make_absolute(path1, *pwd); + err = sftp_rmdir(conn, path1); break; case I_CHDIR: if (path1 == NULL || *path1 == '\0') path1 = xstrdup(startdir); - path1 = make_absolute(path1, *pwd); - if ((tmp = do_realpath(conn, path1)) == NULL) { + path1 = sftp_make_absolute(path1, *pwd); + if ((tmp = sftp_realpath(conn, path1)) == NULL) { err = 1; break; } - if ((aa = do_stat(conn, tmp, 0)) == NULL) { + if (sftp_stat(conn, tmp, 0, &aa) != 0) { free(tmp); err = 1; break; } - if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { + if (!(aa.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { error("Can't change directory: Can't check target"); free(tmp); err = 1; break; } - if (!S_ISDIR(aa->perm)) { + if (!S_ISDIR(aa.perm)) { error("Can't change directory: \"%s\" is not " "a directory", tmp); free(tmp); err = 1; break; } free(*pwd); *pwd = tmp; break; case I_LS: if (!path1) { do_ls_dir(conn, *pwd, *pwd, lflag); break; } /* Strip pwd off beginning of non-absolute paths */ tmp = NULL; if (!path_absolute(path1)) tmp = *pwd; path1 = make_absolute_pwd_glob(path1, *pwd); err = do_globbed_ls(conn, path1, tmp, lflag); break; case I_DF: /* Default to current directory if no path specified */ if (path1 == NULL) path1 = xstrdup(*pwd); - path1 = make_absolute(path1, *pwd); + path1 = sftp_make_absolute(path1, *pwd); err = do_df(conn, path1, hflag, iflag); break; case I_LCHDIR: if (path1 == NULL || *path1 == '\0') path1 = xstrdup("~"); tmp = tilde_expand_filename(path1, getuid()); free(path1); path1 = tmp; if (chdir(path1) == -1) { error("Couldn't change local directory to " "\"%s\": %s", path1, strerror(errno)); err = 1; } break; case I_LMKDIR: if (mkdir(path1, 0777) == -1) { error("Couldn't create local directory " "\"%s\": %s", path1, strerror(errno)); err = 1; } break; case I_LLS: local_do_ls(cmd); break; case I_SHELL: local_do_shell(cmd); break; case I_LUMASK: umask(n_arg); printf("Local umask: %03lo\n", n_arg); break; case I_CHMOD: path1 = make_absolute_pwd_glob(path1, *pwd); attrib_clear(&a); a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = n_arg; - remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); + sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (!quiet) mprintf("Changing mode on %s\n", g.gl_pathv[i]); - err = (hflag ? do_lsetstat : do_setstat)(conn, + err = (hflag ? sftp_lsetstat : sftp_setstat)(conn, g.gl_pathv[i], &a); if (err != 0 && err_abort) break; } break; case I_CHOWN: case I_CHGRP: path1 = make_absolute_pwd_glob(path1, *pwd); - remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); + sftp_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { - if (!(aa = (hflag ? do_lstat : do_stat)(conn, - g.gl_pathv[i], 0))) { + if ((hflag ? sftp_lstat : sftp_stat)(conn, + g.gl_pathv[i], 0, &aa) != 0) { if (err_abort) { err = -1; break; } else continue; } - if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { + if (!(aa.flags & SSH2_FILEXFER_ATTR_UIDGID)) { error("Can't get current ownership of " "remote file \"%s\"", g.gl_pathv[i]); if (err_abort) { err = -1; break; } else continue; } - aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; + aa.flags &= SSH2_FILEXFER_ATTR_UIDGID; if (cmdnum == I_CHOWN) { if (!quiet) mprintf("Changing owner on %s\n", g.gl_pathv[i]); - aa->uid = n_arg; + aa.uid = n_arg; } else { if (!quiet) mprintf("Changing group on %s\n", g.gl_pathv[i]); - aa->gid = n_arg; + aa.gid = n_arg; } - err = (hflag ? do_lsetstat : do_setstat)(conn, - g.gl_pathv[i], aa); + err = (hflag ? sftp_lsetstat : sftp_setstat)(conn, + g.gl_pathv[i], &aa); if (err != 0 && err_abort) break; } break; case I_PWD: mprintf("Remote working directory: %s\n", *pwd); break; case I_LPWD: if (!getcwd(path_buf, sizeof(path_buf))) { error("Couldn't get local cwd: %s", strerror(errno)); err = -1; break; } mprintf("Local working directory: %s\n", path_buf); break; case I_QUIT: /* Processed below */ break; case I_HELP: help(); break; case I_VERSION: printf("SFTP protocol version %u\n", sftp_proto_version(conn)); break; case I_PROGRESS: showprogress = !showprogress; if (showprogress) printf("Progress meter enabled\n"); else printf("Progress meter disabled\n"); break; default: fatal("%d is not implemented", cmdnum); } if (g.gl_pathc) globfree(&g); free(path1); free(path2); /* If an unignored error occurs in batch mode we should abort. */ if (err_abort && err != 0) return (-1); else if (cmdnum == I_QUIT) return (1); return (0); } #ifdef USE_LIBEDIT static char * prompt(EditLine *el) { return ("sftp> "); } /* Display entries in 'list' after skipping the first 'len' chars */ static void complete_display(char **list, u_int len) { u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen; struct winsize ws; char *tmp; /* Count entries for sort and find longest */ for (y = 0; list[y]; y++) m = MAXIMUM(m, strlen(list[y])); if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; m = m > len ? m - len : 0; columns = width / (m + 2); columns = MAXIMUM(columns, 1); colspace = width / columns; colspace = MINIMUM(colspace, width); printf("\n"); m = 1; for (y = 0; list[y]; y++) { llen = strlen(list[y]); tmp = llen > len ? list[y] + len : ""; mprintf("%-*s", colspace, tmp); if (m >= columns) { printf("\n"); m = 1; } else m++; } printf("\n"); } /* * Given a "list" of words that begin with a common prefix of "word", * attempt to find an autocompletion to extends "word" by the next * characters common to all entries in "list". */ static char * complete_ambiguous(const char *word, char **list, size_t count) { if (word == NULL) return NULL; if (count > 0) { u_int y, matchlen = strlen(list[0]); /* Find length of common stem */ for (y = 1; list[y]; y++) { u_int x; for (x = 0; x < matchlen; x++) if (list[0][x] != list[y][x]) break; matchlen = x; } if (matchlen > strlen(word)) { char *tmp = xstrdup(list[0]); tmp[matchlen] = '\0'; return tmp; } } return xstrdup(word); } /* Autocomplete a sftp command */ static int complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, int terminated) { u_int y, count = 0, cmdlen, tmplen; char *tmp, **list, argterm[3]; const LineInfo *lf; list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *)); /* No command specified: display all available commands */ if (cmd == NULL) { for (y = 0; cmds[y].c; y++) list[count++] = xstrdup(cmds[y].c); list[count] = NULL; complete_display(list, 0); for (y = 0; list[y] != NULL; y++) free(list[y]); free(list); return count; } /* Prepare subset of commands that start with "cmd" */ cmdlen = strlen(cmd); for (y = 0; cmds[y].c; y++) { if (!strncasecmp(cmd, cmds[y].c, cmdlen)) list[count++] = xstrdup(cmds[y].c); } list[count] = NULL; if (count == 0) { free(list); return 0; } /* Complete ambiguous command */ tmp = complete_ambiguous(cmd, list, count); if (count > 1) complete_display(list, 0); for (y = 0; list[y]; y++) free(list[y]); free(list); if (tmp != NULL) { tmplen = strlen(tmp); cmdlen = strlen(cmd); /* If cmd may be extended then do so */ if (tmplen > cmdlen) if (el_insertstr(el, tmp + cmdlen) == -1) fatal("el_insertstr failed."); lf = el_line(el); /* Terminate argument cleanly */ if (count == 1) { y = 0; if (!terminated) argterm[y++] = quote; if (lastarg || *(lf->cursor) != ' ') argterm[y++] = ' '; argterm[y] = '\0'; if (y > 0 && el_insertstr(el, argterm) == -1) fatal("el_insertstr failed."); } free(tmp); } return count; } /* * Determine whether a particular sftp command's arguments (if any) represent * local or remote files. The "cmdarg" argument specifies the actual argument * and accepts values 1 or 2. */ static int complete_is_remote(char *cmd, int cmdarg) { int i; if (cmd == NULL) return -1; for (i = 0; cmds[i].c; i++) { if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) { if (cmdarg == 1) return cmds[i].t; else if (cmdarg == 2) return cmds[i].t2; break; } } return -1; } /* Autocomplete a filename "file" */ static int complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, char *file, int remote, int lastarg, char quote, int terminated) { glob_t g; char *tmp, *tmp2, ins[8]; u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs; int clen; const LineInfo *lf; /* Glob from "file" location */ if (file == NULL) tmp = xstrdup("*"); else xasprintf(&tmp, "%s*", file); /* Check if the path is absolute. */ isabs = path_absolute(tmp); memset(&g, 0, sizeof(g)); if (remote != LOCAL) { tmp = make_absolute_pwd_glob(tmp, remote_path); - remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); + sftp_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); } else (void)glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); /* Determine length of pwd so we can trim completion display */ for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) { /* Terminate counting on first unescaped glob metacharacter */ if (tmp[tmplen] == '*' || tmp[tmplen] == '?') { if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0') hadglob = 1; break; } if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0') tmplen++; if (tmp[tmplen] == '/') pwdlen = tmplen + 1; /* track last seen '/' */ } free(tmp); tmp = NULL; if (g.gl_matchc == 0) goto out; if (g.gl_matchc > 1) complete_display(g.gl_pathv, pwdlen); /* Don't try to extend globs */ if (file == NULL || hadglob) goto out; tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); tmp = path_strip(tmp2, isabs ? NULL : remote_path); free(tmp2); if (tmp == NULL) goto out; tmplen = strlen(tmp); filelen = strlen(file); /* Count the number of escaped characters in the input string. */ cesc = isesc = 0; for (i = 0; i < filelen; i++) { if (!isesc && file[i] == '\\' && i + 1 < filelen){ isesc = 1; cesc++; } else isesc = 0; } if (tmplen > (filelen - cesc)) { tmp2 = tmp + filelen - cesc; len = strlen(tmp2); /* quote argument on way out */ for (i = 0; i < len; i += clen) { if ((clen = mblen(tmp2 + i, len - i)) < 0 || (size_t)clen > sizeof(ins) - 2) fatal("invalid multibyte character"); ins[0] = '\\'; memcpy(ins + 1, tmp2 + i, clen); ins[clen + 1] = '\0'; switch (tmp2[i]) { case '\'': case '"': case '\\': case '\t': case '[': case ' ': case '#': case '*': if (quote == '\0' || tmp2[i] == quote) { if (el_insertstr(el, ins) == -1) fatal("el_insertstr " "failed."); break; } /* FALLTHROUGH */ default: if (el_insertstr(el, ins + 1) == -1) fatal("el_insertstr failed."); break; } } } lf = el_line(el); if (g.gl_matchc == 1) { i = 0; if (!terminated && quote != '\0') ins[i++] = quote; if (*(lf->cursor - 1) != '/' && (lastarg || *(lf->cursor) != ' ')) ins[i++] = ' '; ins[i] = '\0'; if (i > 0 && el_insertstr(el, ins) == -1) fatal("el_insertstr failed."); } free(tmp); out: globfree(&g); return g.gl_matchc; } /* tab-completion hook function, called via libedit */ static unsigned char complete(EditLine *el, int ch) { char **argv, *line, quote; int argc, carg; u_int cursor, len, terminated, ret = CC_ERROR; const LineInfo *lf; struct complete_ctx *complete_ctx; lf = el_line(el); if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0) fatal_f("el_get failed"); /* Figure out which argument the cursor points to */ cursor = lf->cursor - lf->buffer; line = xmalloc(cursor + 1); memcpy(line, lf->buffer, cursor); line[cursor] = '\0'; argv = makeargv(line, &carg, 1, "e, &terminated); free(line); /* Get all the arguments on the line */ len = lf->lastchar - lf->buffer; line = xmalloc(len + 1); memcpy(line, lf->buffer, len); line[len] = '\0'; argv = makeargv(line, &argc, 1, NULL, NULL); /* Ensure cursor is at EOL or a argument boundary */ if (line[cursor] != ' ' && line[cursor] != '\0' && line[cursor] != '\n') { free(line); return ret; } if (carg == 0) { /* Show all available commands */ complete_cmd_parse(el, NULL, argc == carg, '\0', 1); ret = CC_REDISPLAY; } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') { /* Handle the command parsing */ if (complete_cmd_parse(el, argv[0], argc == carg, quote, terminated) != 0) ret = CC_REDISPLAY; } else if (carg >= 1) { /* Handle file parsing */ int remote = 0; int i = 0, cmdarg = 0; char *filematch = NULL; if (carg > 1 && line[cursor-1] != ' ') filematch = argv[carg - 1]; for (i = 1; i < carg; i++) { /* Skip flags */ if (argv[i][0] != '-') cmdarg++; } /* * If previous argument is complete, then offer completion * on the next one. */ if (line[cursor - 1] == ' ') cmdarg++; remote = complete_is_remote(argv[0], cmdarg); if ((remote == REMOTE || remote == LOCAL) && complete_match(el, complete_ctx->conn, *complete_ctx->remote_pathp, filematch, remote, carg == argc, quote, terminated) != 0) ret = CC_REDISPLAY; } free(line); return ret; } #endif /* USE_LIBEDIT */ static int interactive_loop(struct sftp_conn *conn, char *file1, char *file2) { char *remote_path; char *dir = NULL, *startdir = NULL; char cmd[2048]; int err, interactive; EditLine *el = NULL; #ifdef USE_LIBEDIT History *hl = NULL; HistEvent hev; extern char *__progname; struct complete_ctx complete_ctx; if (!batchmode && isatty(STDIN_FILENO)) { if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) fatal("Couldn't initialise editline"); if ((hl = history_init()) == NULL) fatal("Couldn't initialise editline history"); history(hl, &hev, H_SETSIZE, 100); el_set(el, EL_HIST, history, hl); el_set(el, EL_PROMPT, prompt); el_set(el, EL_EDITOR, "emacs"); el_set(el, EL_TERMINAL, NULL); el_set(el, EL_SIGNAL, 1); el_source(el, NULL); /* Tab Completion */ el_set(el, EL_ADDFN, "ftp-complete", "Context sensitive argument completion", complete); complete_ctx.conn = conn; complete_ctx.remote_pathp = &remote_path; el_set(el, EL_CLIENTDATA, (void*)&complete_ctx); el_set(el, EL_BIND, "^I", "ftp-complete", NULL); /* enable ctrl-left-arrow and ctrl-right-arrow */ el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL); el_set(el, EL_BIND, "\\e\\e[C", "em-next-word", NULL); el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL); el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL); /* make ^w match ksh behaviour */ el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL); } #endif /* USE_LIBEDIT */ - remote_path = do_realpath(conn, "."); - if (remote_path == NULL) + if ((remote_path = sftp_realpath(conn, ".")) == NULL) fatal("Need cwd"); startdir = xstrdup(remote_path); if (file1 != NULL) { dir = xstrdup(file1); - dir = make_absolute(dir, remote_path); + dir = sftp_make_absolute(dir, remote_path); - if (remote_is_dir(conn, dir) && file2 == NULL) { + if (sftp_remote_is_dir(conn, dir) && file2 == NULL) { if (!quiet) mprintf("Changing to: %s\n", dir); snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); if (parse_dispatch_command(conn, cmd, &remote_path, startdir, 1, 0) != 0) { free(dir); free(startdir); free(remote_path); free(conn); return (-1); } } else { /* XXX this is wrong wrt quoting */ snprintf(cmd, sizeof cmd, "get%s %s%s%s", global_aflag ? " -a" : "", dir, file2 == NULL ? "" : " ", file2 == NULL ? "" : file2); err = parse_dispatch_command(conn, cmd, &remote_path, startdir, 1, 0); free(dir); free(startdir); free(remote_path); free(conn); return (err); } free(dir); } setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(infile, NULL, _IOLBF, 0); interactive = !batchmode && isatty(STDIN_FILENO); err = 0; for (;;) { struct sigaction sa; interrupted = 0; memset(&sa, 0, sizeof(sa)); sa.sa_handler = interactive ? read_interrupt : killchild; if (sigaction(SIGINT, &sa, NULL) == -1) { debug3("sigaction(%s): %s", strsignal(SIGINT), strerror(errno)); break; } if (el == NULL) { if (interactive) printf("sftp> "); if (fgets(cmd, sizeof(cmd), infile) == NULL) { if (interactive) printf("\n"); if (interrupted) continue; break; } } else { #ifdef USE_LIBEDIT const char *line; int count = 0; if ((line = el_gets(el, &count)) == NULL || count <= 0) { printf("\n"); if (interrupted) continue; break; } history(hl, &hev, H_ENTER, line); if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { fprintf(stderr, "Error: input line too long\n"); continue; } #endif /* USE_LIBEDIT */ } cmd[strcspn(cmd, "\n")] = '\0'; /* Handle user interrupts gracefully during commands */ interrupted = 0; ssh_signal(SIGINT, cmd_interrupt); err = parse_dispatch_command(conn, cmd, &remote_path, startdir, batchmode, !interactive && el == NULL); if (err != 0) break; } ssh_signal(SIGCHLD, SIG_DFL); free(remote_path); free(startdir); free(conn); #ifdef USE_LIBEDIT if (el != NULL) el_end(el); #endif /* USE_LIBEDIT */ /* err == 1 signifies normal "quit" exit */ return (err >= 0 ? 0 : -1); } static void connect_to_server(char *path, char **args, int *in, int *out) { int c_in, c_out; #ifdef USE_PIPES int pin[2], pout[2]; if ((pipe(pin) == -1) || (pipe(pout) == -1)) fatal("pipe: %s", strerror(errno)); *in = pin[0]; *out = pout[1]; c_in = pout[0]; c_out = pin[1]; #else /* USE_PIPES */ int inout[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) fatal("socketpair: %s", strerror(errno)); *in = *out = inout[0]; c_in = c_out = inout[1]; #endif /* USE_PIPES */ if ((sshpid = fork()) == -1) fatal("fork: %s", strerror(errno)); else if (sshpid == 0) { if ((dup2(c_in, STDIN_FILENO) == -1) || (dup2(c_out, STDOUT_FILENO) == -1)) { fprintf(stderr, "dup2: %s\n", strerror(errno)); _exit(1); } close(*in); close(*out); close(c_in); close(c_out); /* * The underlying ssh is in the same process group, so we must * ignore SIGINT if we want to gracefully abort commands, * otherwise the signal will make it to the ssh process and * kill it too. Contrawise, since sftp sends SIGTERMs to the * underlying ssh, it must *not* ignore that signal. */ ssh_signal(SIGINT, SIG_IGN); ssh_signal(SIGTERM, SIG_DFL); execvp(path, args); fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); _exit(1); } ssh_signal(SIGTERM, killchild); ssh_signal(SIGINT, killchild); ssh_signal(SIGHUP, killchild); ssh_signal(SIGTSTP, suspchild); ssh_signal(SIGTTIN, suspchild); ssh_signal(SIGTTOU, suspchild); ssh_signal(SIGCHLD, sigchld_handler); close(c_in); close(c_out); } static void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-46AaCfNpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" " [-D sftp_server_command] [-F ssh_config] [-i identity_file]\n" " [-J destination] [-l limit] [-o ssh_option] [-P port]\n" " [-R num_requests] [-S program] [-s subsystem | sftp_server]\n" " [-X sftp_option] destination\n", __progname); exit(1); } int main(int argc, char **argv) { int r, in, out, ch, err, tmp, port = -1, noisy = 0; char *host = NULL, *user, *cp, **cpp, *file2 = NULL; int debug_level = 0; char *file1 = NULL, *sftp_server = NULL; char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; const char *errstr; LogLevel ll = SYSLOG_LEVEL_INFO; arglist args; extern int optind; extern char *optarg; struct sftp_conn *conn; size_t copy_buffer_len = 0; size_t num_requests = 0; long long llv, limit_kbps = 0; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); msetlocale(); __progname = ssh_get_progname(argv[0]); memset(&args, '\0', sizeof(args)); args.list = NULL; addargs(&args, "%s", ssh_program); addargs(&args, "-oForwardX11 no"); addargs(&args, "-oPermitLocalCommand no"); addargs(&args, "-oClearAllForwardings yes"); ll = SYSLOG_LEVEL_INFO; infile = stdin; while ((ch = getopt(argc, argv, "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) { switch (ch) { /* Passed through to ssh(1) */ case 'A': case '4': case '6': case 'C': addargs(&args, "-%c", ch); break; /* Passed through to ssh(1) with argument */ case 'F': case 'J': case 'c': case 'i': case 'o': addargs(&args, "-%c", ch); addargs(&args, "%s", optarg); break; case 'q': ll = SYSLOG_LEVEL_ERROR; quiet = 1; showprogress = 0; addargs(&args, "-%c", ch); break; case 'P': port = a2port(optarg); if (port <= 0) fatal("Bad port \"%s\"\n", optarg); break; case 'v': if (debug_level < 3) { addargs(&args, "-v"); ll = SYSLOG_LEVEL_DEBUG1 + debug_level; } debug_level++; break; case '1': fatal("SSH protocol v.1 is no longer supported"); break; case '2': /* accept silently */ break; case 'a': global_aflag = 1; break; case 'B': copy_buffer_len = strtol(optarg, &cp, 10); if (copy_buffer_len == 0 || *cp != '\0') fatal("Invalid buffer size \"%s\"", optarg); break; case 'b': if (batchmode) fatal("Batch file already specified."); /* Allow "-" as stdin */ if (strcmp(optarg, "-") != 0 && (infile = fopen(optarg, "r")) == NULL) fatal("%s (%s).", strerror(errno), optarg); showprogress = 0; quiet = batchmode = 1; addargs(&args, "-obatchmode yes"); break; case 'f': global_fflag = 1; break; case 'N': noisy = 1; /* Used to clear quiet mode after getopt */ break; case 'p': global_pflag = 1; break; case 'D': sftp_direct = optarg; break; case 'l': limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, &errstr); if (errstr != NULL) usage(); limit_kbps *= 1024; /* kbps */ break; case 'r': global_rflag = 1; break; case 'R': num_requests = strtol(optarg, &cp, 10); if (num_requests == 0 || *cp != '\0') fatal("Invalid number of requests \"%s\"", optarg); break; case 's': sftp_server = optarg; break; case 'S': ssh_program = optarg; replacearg(&args, 0, "%s", ssh_program); break; case 'X': /* Please keep in sync with ssh.c -X */ if (strncmp(optarg, "buffer=", 7) == 0) { r = scan_scaled(optarg + 7, &llv); if (r == 0 && (llv <= 0 || llv > 256 * 1024)) { r = -1; errno = EINVAL; } if (r == -1) { fatal("Invalid buffer size \"%s\": %s", optarg + 7, strerror(errno)); } copy_buffer_len = (size_t)llv; } else if (strncmp(optarg, "nrequests=", 10) == 0) { llv = strtonum(optarg + 10, 1, 256 * 1024, &errstr); if (errstr != NULL) { fatal("Invalid number of requests " "\"%s\": %s", optarg + 10, errstr); } num_requests = (size_t)llv; } else { fatal("Invalid -X option"); } break; case 'h': default: usage(); } } /* Do this last because we want the user to be able to override it */ addargs(&args, "-oForwardAgent no"); if (!isatty(STDERR_FILENO)) showprogress = 0; if (noisy) quiet = 0; log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); if (sftp_direct == NULL) { if (optind == argc || argc > (optind + 2)) usage(); argv += optind; switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) { case -1: usage(); break; case 0: if (tmp != -1) port = tmp; break; default: /* Try with user, host and path. */ if (parse_user_host_path(*argv, &user, &host, &file1) == 0) break; /* Try with user and host. */ if (parse_user_host_port(*argv, &user, &host, NULL) == 0) break; /* Treat as a plain hostname. */ host = xstrdup(*argv); host = cleanhostname(host); break; } file2 = *(argv + 1); if (!*host) { fprintf(stderr, "Missing hostname\n"); usage(); } if (port != -1) addargs(&args, "-oPort %d", port); if (user != NULL) { addargs(&args, "-l"); addargs(&args, "%s", user); } /* no subsystem if the server-spec contains a '/' */ if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) addargs(&args, "-s"); addargs(&args, "--"); addargs(&args, "%s", host); addargs(&args, "%s", (sftp_server != NULL ? sftp_server : "sftp")); connect_to_server(ssh_program, args.list, &in, &out); } else { if ((r = argv_split(sftp_direct, &tmp, &cpp, 1)) != 0) fatal_r(r, "Parse -D arguments"); if (cpp[0] == 0) fatal("No sftp server specified via -D"); connect_to_server(cpp[0], cpp, &in, &out); argv_free(cpp, tmp); } freeargs(&args); - conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps); + conn = sftp_init(in, out, copy_buffer_len, num_requests, limit_kbps); if (conn == NULL) fatal("Couldn't initialise connection to server"); if (!quiet) { if (sftp_direct == NULL) fprintf(stderr, "Connected to %s.\n", host); else fprintf(stderr, "Attached to %s.\n", sftp_direct); } err = interactive_loop(conn, file1, file2); #if !defined(USE_PIPES) shutdown(in, SHUT_RDWR); shutdown(out, SHUT_RDWR); #endif close(in); close(out); if (batchmode) fclose(infile); while (waitpid(sshpid, NULL, 0) == -1 && sshpid > 1) if (errno != EINTR) fatal("Couldn't wait for ssh process: %s", strerror(errno)); exit(err == 0 ? 0 : 1); } diff --git a/crypto/openssh/ssh-agent.1 b/crypto/openssh/ssh-agent.1 index c1bd94f1b8c9..f3db48e6c97e 100644 --- a/crypto/openssh/ssh-agent.1 +++ b/crypto/openssh/ssh-agent.1 @@ -1,276 +1,276 @@ -.\" $OpenBSD: ssh-agent.1,v 1.78 2023/07/23 20:04:45 naddy Exp $ +.\" $OpenBSD: ssh-agent.1,v 1.79 2023/08/10 14:37:32 naddy Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: July 23 2023 $ +.Dd $Mdocdate: August 10 2023 $ .Dt SSH-AGENT 1 .Os .Sh NAME .Nm ssh-agent .Nd OpenSSH authentication agent .Sh SYNOPSIS .Nm ssh-agent .Op Fl c | s .Op Fl \&Ddx .Op Fl a Ar bind_address .Op Fl E Ar fingerprint_hash .Op Fl O Ar option .Op Fl P Ar allowed_providers .Op Fl t Ar life .Nm ssh-agent .Op Fl a Ar bind_address .Op Fl E Ar fingerprint_hash .Op Fl O Ar option .Op Fl P Ar allowed_providers .Op Fl t Ar life .Ar command Op Ar arg ... .Nm ssh-agent .Op Fl c | s .Fl k .Sh DESCRIPTION .Nm is a program to hold private keys used for public key authentication. Through use of environment variables the agent can be located and automatically used for authentication when logging in to other machines using .Xr ssh 1 . .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Ar bind_address Bind the agent to the .Ux Ns -domain socket .Ar bind_address . The default is .Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt . .It Fl c Generate C-shell commands on .Dv stdout . This is the default if .Ev SHELL looks like it's a csh style of shell. .It Fl D Foreground mode. When this option is specified, .Nm will not fork. .It Fl d Debug mode. When this option is specified, .Nm will not fork and will write debug information to standard error. .It Fl E Ar fingerprint_hash Specifies the hash algorithm used when displaying key fingerprints. Valid options are: .Dq md5 and .Dq sha256 . The default is .Dq sha256 . .It Fl k Kill the current agent (given by the .Ev SSH_AGENT_PID environment variable). .It Fl O Ar option Specify an option when starting .Nm . Currently two options are supported: .Cm allow-remote-pkcs11 and .Cm no-restrict-websafe . .Pp The .Cm allow-remote-pkcs11 option allows clients of a forwarded .Nm to load PKCS#11 or FIDO provider libraries. By default only local clients may perform this operation. Note that signalling that an .Nm client is remote is performed by .Xr ssh 1 , -and use of other tools to forward access to the agent socket, may circumvent +and use of other tools to forward access to the agent socket may circumvent this restriction. .Pp The .Cm no-restrict-websafe option instructs .Nm to permit signatures using FIDO keys that might be web authentication requests. By default, .Nm refuses signature requests for FIDO keys where the key application string does not start with .Dq ssh: and when the data to be signed does not appear to be a .Xr ssh 1 user authentication request or a .Xr ssh-keygen 1 signature. The default behaviour prevents forwarded access to a FIDO key from also implicitly forwarding the ability to authenticate to websites. .It Fl P Ar allowed_providers Specify a pattern-list of acceptable paths for PKCS#11 provider and FIDO authenticator middleware shared libraries that may be used with the .Fl S or .Fl s options to .Xr ssh-add 1 . Libraries that do not match the pattern list will be refused. See PATTERNS in .Xr ssh_config 5 for a description of pattern-list syntax. The default list is -.Dq /usr/lib/*,/usr/local/lib/* . +.Dq usr/lib*/*,/usr/local/lib*/* . .It Fl s Generate Bourne shell commands on .Dv stdout . This is the default if .Ev SHELL does not look like it's a csh style of shell. .It Fl t Ar life Set a default value for the maximum lifetime of identities added to the agent. The lifetime may be specified in seconds or in a time format specified in .Xr sshd_config 5 . A lifetime specified for an identity with .Xr ssh-add 1 overrides this value. Without this option the default maximum lifetime is forever. .It Fl x Exit after the last client has disconnected. .It Ar command Op Ar arg ... If a command (and optional arguments) is given, this is executed as a subprocess of the agent. The agent exits automatically when the command given on the command line terminates. .El .Pp There are two main ways to get an agent set up. The first is at the start of an X session, where all other windows or programs are started as children of the .Nm program. The agent starts a command under which its environment variables are exported, for example .Cm ssh-agent xterm & . When the command terminates, so does the agent. .Pp The second method is used for a login session. When .Nm is started, it prints the shell commands required to set its environment variables, which in turn can be evaluated in the calling shell, for example .Cm eval `ssh-agent -s` . .Pp In both cases, .Xr ssh 1 looks at these environment variables and uses them to establish a connection to the agent. .Pp The agent initially does not have any private keys. Keys are added using .Xr ssh-add 1 or by .Xr ssh 1 when .Cm AddKeysToAgent is set in .Xr ssh_config 5 . Multiple identities may be stored in .Nm concurrently and .Xr ssh 1 will automatically use them if present. .Xr ssh-add 1 is also used to remove keys from .Nm and to query the keys that are held in one. .Pp Connections to .Nm may be forwarded from further remote hosts using the .Fl A option to .Xr ssh 1 (but see the caveats documented therein), avoiding the need for authentication data to be stored on other machines. Authentication passphrases and private keys never go over the network: the connection to the agent is forwarded over SSH remote connections and the result is returned to the requester, allowing the user access to their identities anywhere in the network in a secure fashion. .Sh ENVIRONMENT .Bl -tag -width "SSH_AGENT_PID" .It Ev SSH_AGENT_PID When .Nm starts, it stores the name of the agent's process ID (PID) in this variable. .It Ev SSH_AUTH_SOCK When .Nm starts, it creates a .Ux Ns -domain socket and stores its pathname in this variable. It is accessible only to the current user, but is easily abused by root or another instance of the same user. .El .Sh FILES .Bl -tag -width Ds .It Pa $TMPDIR/ssh-XXXXXXXXXX/agent. .Ux Ns -domain sockets used to contain the connection to the authentication agent. These sockets should only be readable by the owner. The sockets should get automatically removed when the agent exits. .El .Sh SEE ALSO .Xr ssh 1 , .Xr ssh-add 1 , .Xr ssh-keygen 1 , .Xr ssh_config 5 , .Xr sshd 8 .Sh AUTHORS .An -nosplit OpenSSH is a derivative of the original and free ssh 1.2.12 release by .An Tatu Ylonen . .An Aaron Campbell , Bob Beck , Markus Friedl , Niels Provos , Theo de Raadt and .An Dug Song removed many bugs, re-added newer features and created OpenSSH. .An Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. diff --git a/crypto/openssh/ssh.c b/crypto/openssh/ssh.c index 7c3ad0fc76b0..ec5c36091d29 100644 --- a/crypto/openssh/ssh.c +++ b/crypto/openssh/ssh.c @@ -1,2428 +1,2431 @@ -/* $OpenBSD: ssh.c,v 1.593 2023/07/26 23:06:00 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.594 2023/09/03 23:59:32 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Ssh client program. This program can be used to log into a remote machine. * The software supports strong authentication, encryption, and forwarding * of X11, TCP/IP, and authentication connections. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * Copyright (c) 1999 Niels Provos. All rights reserved. * Copyright (c) 2000, 2001, 2002, 2003 Markus Friedl. All rights reserved. * * Modified to work with SSLeay by Niels Provos * in Canada (German citizen). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_OPENSSL #include #include #endif #include "openbsd-compat/openssl-compat.h" #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "canohost.h" #include "compat.h" #include "cipher.h" #include "packet.h" #include "sshbuf.h" #include "channels.h" #include "sshkey.h" #include "authfd.h" #include "authfile.h" #include "pathnames.h" #include "dispatch.h" #include "clientloop.h" #include "log.h" #include "misc.h" #include "readconf.h" #include "sshconnect.h" #include "kex.h" #include "mac.h" #include "sshpty.h" #include "match.h" #include "msg.h" #include "version.h" #include "ssherr.h" #include "myproposal.h" #include "utf8.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" #endif extern char *__progname; /* Saves a copy of argv for setproctitle emulation */ #ifndef HAVE_SETPROCTITLE static char **saved_av; #endif /* Flag indicating whether debug mode is on. May be set on the command line. */ int debug_flag = 0; /* Flag indicating whether a tty should be requested */ int tty_flag = 0; /* * Flag indicating that the current process should be backgrounded and * a new mux-client launched in the foreground for ControlPersist. */ static int need_controlpersist_detach = 0; /* Copies of flags for ControlPersist foreground mux-client */ static int ostdin_null_flag, osession_type, otty_flag, orequest_tty; static int ofork_after_authentication; /* * General data structure for command line options and options configurable * in configuration files. See readconf.h. */ Options options; /* optional user configfile */ char *config = NULL; /* * Name of the host we are connecting to. This is the name given on the * command line, or the Hostname specified for the user-supplied name in a * configuration file. */ char *host; /* * A config can specify a path to forward, overriding SSH_AUTH_SOCK. If this is * not NULL, forward the socket at this path instead. */ char *forward_agent_sock_path = NULL; /* socket address the host resolves to */ struct sockaddr_storage hostaddr; /* Private host keys. */ Sensitive sensitive_data; /* command to be executed */ struct sshbuf *command; /* # of replies received for global requests */ static int forward_confirms_pending = -1; /* mux.c */ extern int muxserver_sock; extern u_int muxclient_command; /* Prints a help message to the user. This function never returns. */ static void usage(void) { fprintf(stderr, "usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address]\n" " [-c cipher_spec] [-D [bind_address:]port] [-E log_file]\n" " [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file]\n" " [-J destination] [-L address] [-l login_name] [-m mac_spec]\n" " [-O ctl_cmd] [-o option] [-P tag] [-p port] [-Q query_option]\n" " [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" " destination [command [argument ...]]\n" ); exit(255); } static int ssh_session2(struct ssh *, const struct ssh_conn_info *); static void load_public_identity_files(const struct ssh_conn_info *); static void main_sigchld_handler(int); /* ~/ expand a list of paths. NB. assumes path[n] is heap-allocated. */ static void tilde_expand_paths(char **paths, u_int num_paths) { u_int i; char *cp; for (i = 0; i < num_paths; i++) { cp = tilde_expand_filename(paths[i], getuid()); free(paths[i]); paths[i] = cp; } } /* * Expands the set of percent_expand options used by the majority of keywords * in the client that support percent expansion. * Caller must free returned string. */ static char * default_client_percent_expand(const char *str, const struct ssh_conn_info *cinfo) { return percent_expand(str, DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), (char *)NULL); } /* * Expands the set of percent_expand options used by the majority of keywords * AND perform environment variable substitution. * Caller must free returned string. */ static char * default_client_percent_dollar_expand(const char *str, const struct ssh_conn_info *cinfo) { char *ret; ret = percent_dollar_expand(str, DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), (char *)NULL); if (ret == NULL) fatal("invalid environment variable expansion"); return ret; } /* * Attempt to resolve a host name / port to a set of addresses and * optionally return any CNAMEs encountered along the way. * Returns NULL on failure. * NB. this function must operate with a options having undefined members. */ static struct addrinfo * resolve_host(const char *name, int port, int logerr, char *cname, size_t clen) { char strport[NI_MAXSERV]; const char *errstr = NULL; struct addrinfo hints, *res; int gaierr; LogLevel loglevel = SYSLOG_LEVEL_DEBUG1; if (port <= 0) port = default_ssh_port(); if (cname != NULL) *cname = '\0'; debug3_f("lookup %s:%d", name, port); snprintf(strport, sizeof strport, "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_family = options.address_family == -1 ? AF_UNSPEC : options.address_family; hints.ai_socktype = SOCK_STREAM; if (cname != NULL) hints.ai_flags = AI_CANONNAME; if ((gaierr = getaddrinfo(name, strport, &hints, &res)) != 0) { if (logerr || (gaierr != EAI_NONAME && gaierr != EAI_NODATA)) loglevel = SYSLOG_LEVEL_ERROR; do_log2(loglevel, "%s: Could not resolve hostname %.100s: %s", __progname, name, ssh_gai_strerror(gaierr)); return NULL; } if (cname != NULL && res->ai_canonname != NULL) { if (!valid_domain(res->ai_canonname, 0, &errstr)) { error("ignoring bad CNAME \"%s\" for host \"%s\": %s", res->ai_canonname, name, errstr); } else if (strlcpy(cname, res->ai_canonname, clen) >= clen) { error_f("host \"%s\" cname \"%s\" too long (max %lu)", name, res->ai_canonname, (u_long)clen); if (clen > 0) *cname = '\0'; } } return res; } /* Returns non-zero if name can only be an address and not a hostname */ static int is_addr_fast(const char *name) { return (strchr(name, '%') != NULL || strchr(name, ':') != NULL || strspn(name, "0123456789.") == strlen(name)); } /* Returns non-zero if name represents a valid, single address */ static int is_addr(const char *name) { char strport[NI_MAXSERV]; struct addrinfo hints, *res; if (is_addr_fast(name)) return 1; snprintf(strport, sizeof strport, "%u", default_ssh_port()); memset(&hints, 0, sizeof(hints)); hints.ai_family = options.address_family == -1 ? AF_UNSPEC : options.address_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; if (getaddrinfo(name, strport, &hints, &res) != 0) return 0; if (res == NULL || res->ai_next != NULL) { freeaddrinfo(res); return 0; } freeaddrinfo(res); return 1; } /* * Attempt to resolve a numeric host address / port to a single address. * Returns a canonical address string. * Returns NULL on failure. * NB. this function must operate with a options having undefined members. */ static struct addrinfo * resolve_addr(const char *name, int port, char *caddr, size_t clen) { char addr[NI_MAXHOST], strport[NI_MAXSERV]; struct addrinfo hints, *res; int gaierr; if (port <= 0) port = default_ssh_port(); snprintf(strport, sizeof strport, "%u", port); memset(&hints, 0, sizeof(hints)); hints.ai_family = options.address_family == -1 ? AF_UNSPEC : options.address_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; if ((gaierr = getaddrinfo(name, strport, &hints, &res)) != 0) { debug2_f("could not resolve name %.100s as address: %s", name, ssh_gai_strerror(gaierr)); return NULL; } if (res == NULL) { debug_f("getaddrinfo %.100s returned no addresses", name); return NULL; } if (res->ai_next != NULL) { debug_f("getaddrinfo %.100s returned multiple addresses", name); goto fail; } if ((gaierr = getnameinfo(res->ai_addr, res->ai_addrlen, addr, sizeof(addr), NULL, 0, NI_NUMERICHOST)) != 0) { debug_f("Could not format address for name %.100s: %s", name, ssh_gai_strerror(gaierr)); goto fail; } if (strlcpy(caddr, addr, clen) >= clen) { error_f("host \"%s\" addr \"%s\" too long (max %lu)", name, addr, (u_long)clen); if (clen > 0) *caddr = '\0'; fail: freeaddrinfo(res); return NULL; } return res; } /* * Check whether the cname is a permitted replacement for the hostname * and perform the replacement if it is. * NB. this function must operate with a options having undefined members. */ static int check_follow_cname(int direct, char **namep, const char *cname) { int i; struct allowed_cname *rule; if (*cname == '\0' || !config_has_permitted_cnames(&options) || strcmp(*namep, cname) == 0) return 0; if (options.canonicalize_hostname == SSH_CANONICALISE_NO) return 0; /* * Don't attempt to canonicalize names that will be interpreted by * a proxy or jump host unless the user specifically requests so. */ if (!direct && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return 0; debug3_f("check \"%s\" CNAME \"%s\"", *namep, cname); for (i = 0; i < options.num_permitted_cnames; i++) { rule = options.permitted_cnames + i; if (match_pattern_list(*namep, rule->source_list, 1) != 1 || match_pattern_list(cname, rule->target_list, 1) != 1) continue; verbose("Canonicalized DNS aliased hostname " "\"%s\" => \"%s\"", *namep, cname); free(*namep); *namep = xstrdup(cname); return 1; } return 0; } /* * Attempt to resolve the supplied hostname after applying the user's * canonicalization rules. Returns the address list for the host or NULL * if no name was found after canonicalization. * NB. this function must operate with a options having undefined members. */ static struct addrinfo * resolve_canonicalize(char **hostp, int port) { int i, direct, ndots; char *cp, *fullhost, newname[NI_MAXHOST]; struct addrinfo *addrs; /* * Attempt to canonicalise addresses, regardless of * whether hostname canonicalisation was requested */ if ((addrs = resolve_addr(*hostp, port, newname, sizeof(newname))) != NULL) { debug2_f("hostname %.100s is address", *hostp); if (strcasecmp(*hostp, newname) != 0) { debug2_f("canonicalised address \"%s\" => \"%s\"", *hostp, newname); free(*hostp); *hostp = xstrdup(newname); } return addrs; } /* * If this looks like an address but didn't parse as one, it might * be an address with an invalid interface scope. Skip further * attempts at canonicalisation. */ if (is_addr_fast(*hostp)) { debug_f("hostname %.100s is an unrecognised address", *hostp); return NULL; } if (options.canonicalize_hostname == SSH_CANONICALISE_NO) return NULL; /* * Don't attempt to canonicalize names that will be interpreted by * a proxy unless the user specifically requests so. */ direct = option_clear_or_none(options.proxy_command) && option_clear_or_none(options.jump_host); if (!direct && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return NULL; /* If domain name is anchored, then resolve it now */ if ((*hostp)[strlen(*hostp) - 1] == '.') { debug3_f("name is fully qualified"); fullhost = xstrdup(*hostp); if ((addrs = resolve_host(fullhost, port, 0, newname, sizeof(newname))) != NULL) goto found; free(fullhost); goto notfound; } /* Don't apply canonicalization to sufficiently-qualified hostnames */ ndots = 0; for (cp = *hostp; *cp != '\0'; cp++) { if (*cp == '.') ndots++; } if (ndots > options.canonicalize_max_dots) { debug3_f("not canonicalizing hostname \"%s\" (max dots %d)", *hostp, options.canonicalize_max_dots); return NULL; } /* Attempt each supplied suffix */ for (i = 0; i < options.num_canonical_domains; i++) { if (strcasecmp(options.canonical_domains[i], "none") == 0) break; xasprintf(&fullhost, "%s.%s.", *hostp, options.canonical_domains[i]); debug3_f("attempting \"%s\" => \"%s\"", *hostp, fullhost); if ((addrs = resolve_host(fullhost, port, 0, newname, sizeof(newname))) == NULL) { free(fullhost); continue; } found: /* Remove trailing '.' */ fullhost[strlen(fullhost) - 1] = '\0'; /* Follow CNAME if requested */ if (!check_follow_cname(direct, &fullhost, newname)) { debug("Canonicalized hostname \"%s\" => \"%s\"", *hostp, fullhost); } free(*hostp); *hostp = fullhost; return addrs; } notfound: if (!options.canonicalize_fallback_local) fatal("%s: Could not resolve host \"%s\"", __progname, *hostp); debug2_f("host %s not found in any suffix", *hostp); return NULL; } /* * Check the result of hostkey loading, ignoring some errors and either * discarding the key or fatal()ing for others. */ static void check_load(int r, struct sshkey **k, const char *path, const char *message) { switch (r) { case 0: /* Check RSA keys size and discard if undersized */ if (k != NULL && *k != NULL && (r = sshkey_check_rsa_length(*k, options.required_rsa_size)) != 0) { error_r(r, "load %s \"%s\"", message, path); free(*k); *k = NULL; } break; case SSH_ERR_INTERNAL_ERROR: case SSH_ERR_ALLOC_FAIL: fatal_r(r, "load %s \"%s\"", message, path); case SSH_ERR_SYSTEM_ERROR: /* Ignore missing files */ if (errno == ENOENT) break; /* FALLTHROUGH */ default: error_r(r, "load %s \"%s\"", message, path); break; } } /* * Read per-user configuration file. Ignore the system wide config * file if the user specifies a config file on the command line. */ static void process_config_files(const char *host_name, struct passwd *pw, int final_pass, int *want_final_pass) { char buf[PATH_MAX]; int r; if (config != NULL) { if (strcasecmp(config, "none") != 0 && !read_config_file(config, pw, host, host_name, &options, SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0), want_final_pass)) fatal("Can't open user config file %.100s: " "%.100s", config, strerror(errno)); } else { r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir, _PATH_SSH_USER_CONFFILE); if (r > 0 && (size_t)r < sizeof(buf)) (void)read_config_file(buf, pw, host, host_name, &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0), want_final_pass); /* Read systemwide configuration file after user config. */ (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host, host_name, &options, final_pass ? SSHCONF_FINAL : 0, want_final_pass); } } /* Rewrite the port number in an addrinfo list of addresses */ static void set_addrinfo_port(struct addrinfo *addrs, int port) { struct addrinfo *addr; for (addr = addrs; addr != NULL; addr = addr->ai_next) { switch (addr->ai_family) { case AF_INET: ((struct sockaddr_in *)addr->ai_addr)-> sin_port = htons(port); break; case AF_INET6: ((struct sockaddr_in6 *)addr->ai_addr)-> sin6_port = htons(port); break; } } } static void ssh_conn_info_free(struct ssh_conn_info *cinfo) { if (cinfo == NULL) return; free(cinfo->conn_hash_hex); free(cinfo->shorthost); free(cinfo->uidstr); free(cinfo->keyalias); free(cinfo->thishost); free(cinfo->host_arg); free(cinfo->portstr); free(cinfo->remhost); free(cinfo->remuser); free(cinfo->homedir); free(cinfo->locuser); free(cinfo); } /* * Main program for the ssh client. */ int main(int ac, char **av) { struct ssh *ssh = NULL; int i, r, opt, exit_status, use_syslog, direct, timeout_ms; int was_addr, config_test = 0, opt_terminated = 0, want_final_pass = 0; char *p, *cp, *line, *argv0, *logfile; char cname[NI_MAXHOST], thishost[NI_MAXHOST]; struct stat st; struct passwd *pw; extern int optind, optreset; extern char *optarg; struct Forward fwd; struct addrinfo *addrs = NULL; size_t n, len; u_int j; struct ssh_conn_info *cinfo = NULL; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); /* * Discard other fds that are hanging around. These can cause problem * with backgrounded ssh processes started by ControlPersist. */ closefrom(STDERR_FILENO + 1); __progname = ssh_get_progname(av[0]); #ifndef HAVE_SETPROCTITLE /* Prepare for later setproctitle emulation */ /* Save argv so it isn't clobbered by setproctitle() emulation */ saved_av = xcalloc(ac + 1, sizeof(*saved_av)); for (i = 0; i < ac; i++) saved_av[i] = xstrdup(av[i]); saved_av[i] = NULL; compat_init_setproctitle(ac, av); av = saved_av; #endif seed_rng(); /* Get user data. */ pw = getpwuid(getuid()); if (!pw) { logit("No user exists for uid %lu", (u_long)getuid()); exit(255); } /* Take a copy of the returned structure. */ pw = pwcopy(pw); /* * Set our umask to something reasonable, as some files are created * with the default umask. This will make them world-readable but * writable only by the owner, which is ok for all files for which we * don't set the modes explicitly. */ umask(022 | umask(077)); msetlocale(); /* * Initialize option structure to indicate that no values have been * set. */ initialize_options(&options); /* * Prepare main ssh transport/connection structures */ if ((ssh = ssh_alloc_session_state()) == NULL) fatal("Couldn't allocate session state"); channel_init_channels(ssh); /* Parse command-line arguments. */ host = NULL; use_syslog = 0; logfile = NULL; argv0 = av[0]; again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" "AB:CD:E:F:GI:J:KL:MNO:P:Q:R:S:TVw:W:XYy")) != -1) { /* HUZdhjruz */ switch (opt) { case '1': fatal("SSH protocol v.1 is no longer supported"); break; case '2': /* Ignored */ break; case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'n': options.stdin_null = 1; break; case 'f': options.fork_after_authentication = 1; options.stdin_null = 1; break; case 'x': options.forward_x11 = 0; break; case 'X': options.forward_x11 = 1; break; case 'y': use_syslog = 1; break; case 'E': logfile = optarg; break; case 'G': config_test = 1; break; case 'Y': options.forward_x11 = 1; options.forward_x11_trusted = 1; break; case 'g': options.fwd_opts.gateway_ports = 1; break; case 'O': if (options.stdio_forward_host != NULL) fatal("Cannot specify multiplexing " "command with -W"); else if (muxclient_command != 0) fatal("Multiplexing command already specified"); if (strcmp(optarg, "check") == 0) muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; else if (strcmp(optarg, "forward") == 0) muxclient_command = SSHMUX_COMMAND_FORWARD; else if (strcmp(optarg, "exit") == 0) muxclient_command = SSHMUX_COMMAND_TERMINATE; else if (strcmp(optarg, "stop") == 0) muxclient_command = SSHMUX_COMMAND_STOP; else if (strcmp(optarg, "cancel") == 0) muxclient_command = SSHMUX_COMMAND_CANCEL_FWD; else if (strcmp(optarg, "proxy") == 0) muxclient_command = SSHMUX_COMMAND_PROXY; else fatal("Invalid multiplex command."); break; case 'P': if (options.tag == NULL) options.tag = xstrdup(optarg); break; case 'Q': cp = NULL; if (strcmp(optarg, "cipher") == 0 || strcasecmp(optarg, "Ciphers") == 0) cp = cipher_alg_list('\n', 0); else if (strcmp(optarg, "cipher-auth") == 0) cp = cipher_alg_list('\n', 1); else if (strcmp(optarg, "mac") == 0 || strcasecmp(optarg, "MACs") == 0) cp = mac_alg_list('\n'); else if (strcmp(optarg, "kex") == 0 || strcasecmp(optarg, "KexAlgorithms") == 0) cp = kex_alg_list('\n'); else if (strcmp(optarg, "key") == 0) cp = sshkey_alg_list(0, 0, 0, '\n'); else if (strcmp(optarg, "key-cert") == 0) cp = sshkey_alg_list(1, 0, 0, '\n'); else if (strcmp(optarg, "key-plain") == 0) cp = sshkey_alg_list(0, 1, 0, '\n'); else if (strcmp(optarg, "key-ca-sign") == 0 || strcasecmp(optarg, "CASignatureAlgorithms") == 0) cp = sshkey_alg_list(0, 1, 1, '\n'); else if (strcmp(optarg, "key-sig") == 0 || strcasecmp(optarg, "PubkeyAcceptedKeyTypes") == 0 || /* deprecated name */ strcasecmp(optarg, "PubkeyAcceptedAlgorithms") == 0 || strcasecmp(optarg, "HostKeyAlgorithms") == 0 || strcasecmp(optarg, "HostbasedKeyTypes") == 0 || /* deprecated name */ strcasecmp(optarg, "HostbasedAcceptedKeyTypes") == 0 || /* deprecated name */ strcasecmp(optarg, "HostbasedAcceptedAlgorithms") == 0) cp = sshkey_alg_list(0, 0, 1, '\n'); else if (strcmp(optarg, "sig") == 0) cp = sshkey_alg_list(0, 1, 1, '\n'); else if (strcmp(optarg, "protocol-version") == 0) cp = xstrdup("2"); else if (strcmp(optarg, "compression") == 0) { cp = xstrdup(compression_alg_list(0)); len = strlen(cp); for (n = 0; n < len; n++) if (cp[n] == ',') cp[n] = '\n'; } else if (strcmp(optarg, "help") == 0) { cp = xstrdup( "cipher\ncipher-auth\ncompression\nkex\n" "key\nkey-cert\nkey-plain\nkey-sig\nmac\n" "protocol-version\nsig"); } if (cp == NULL) fatal("Unsupported query \"%s\"", optarg); printf("%s\n", cp); free(cp); exit(0); break; case 'a': options.forward_agent = 0; break; case 'A': options.forward_agent = 1; break; case 'k': options.gss_deleg_creds = 0; break; case 'K': options.gss_authentication = 1; options.gss_deleg_creds = 1; break; case 'i': p = tilde_expand_filename(optarg, getuid()); if (stat(p, &st) == -1) fprintf(stderr, "Warning: Identity file %s " "not accessible: %s.\n", p, strerror(errno)); else add_identity_file(&options, NULL, p, 1); free(p); break; case 'I': #ifdef ENABLE_PKCS11 free(options.pkcs11_provider); options.pkcs11_provider = xstrdup(optarg); #else fprintf(stderr, "no support for PKCS#11.\n"); #endif break; case 'J': if (options.jump_host != NULL) { fatal("Only a single -J option is permitted " "(use commas to separate multiple " "jump hops)"); } if (options.proxy_command != NULL) fatal("Cannot specify -J with ProxyCommand"); if (parse_jump(optarg, &options, 1) == -1) fatal("Invalid -J argument"); options.proxy_command = xstrdup("none"); break; case 't': if (options.request_tty == REQUEST_TTY_YES) options.request_tty = REQUEST_TTY_FORCE; else options.request_tty = REQUEST_TTY_YES; break; case 'v': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else { if (options.log_level < SYSLOG_LEVEL_DEBUG3) { debug_flag++; options.log_level++; } } break; case 'V': fprintf(stderr, "%s, %s\n", SSH_RELEASE, SSH_OPENSSL_VERSION); exit(0); break; case 'w': if (options.tun_open == -1) options.tun_open = SSH_TUNMODE_DEFAULT; options.tun_local = a2tun(optarg, &options.tun_remote); if (options.tun_local == SSH_TUNID_ERR) { fprintf(stderr, "Bad tun device '%s'\n", optarg); exit(255); } break; case 'W': if (options.stdio_forward_host != NULL) fatal("stdio forward already specified"); if (muxclient_command != 0) fatal("Cannot specify stdio forward with -O"); if (parse_forward(&fwd, optarg, 1, 0)) { options.stdio_forward_host = fwd.listen_port == PORT_STREAMLOCAL ? fwd.listen_path : fwd.listen_host; options.stdio_forward_port = fwd.listen_port; free(fwd.connect_host); } else { fprintf(stderr, "Bad stdio forwarding specification '%s'\n", optarg); exit(255); } options.request_tty = REQUEST_TTY_NO; options.session_type = SESSION_TYPE_NONE; break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'e': if (optarg[0] == '^' && optarg[2] == 0 && (u_char) optarg[1] >= 64 && (u_char) optarg[1] < 128) options.escape_char = (u_char) optarg[1] & 31; else if (strlen(optarg) == 1) options.escape_char = (u_char) optarg[0]; else if (strcmp(optarg, "none") == 0) options.escape_char = SSH_ESCAPECHAR_NONE; else { fprintf(stderr, "Bad escape character '%s'.\n", optarg); exit(255); } break; case 'c': if (!ciphers_valid(*optarg == '+' || *optarg == '^' ? optarg + 1 : optarg)) { fprintf(stderr, "Unknown cipher type '%s'\n", optarg); exit(255); } free(options.ciphers); options.ciphers = xstrdup(optarg); break; case 'm': if (mac_valid(optarg)) { free(options.macs); options.macs = xstrdup(optarg); } else { fprintf(stderr, "Unknown mac type '%s'\n", optarg); exit(255); } break; case 'M': if (options.control_master == SSHCTL_MASTER_YES) options.control_master = SSHCTL_MASTER_ASK; else options.control_master = SSHCTL_MASTER_YES; break; case 'p': if (options.port == -1) { options.port = a2port(optarg); if (options.port <= 0) { fprintf(stderr, "Bad port '%s'\n", optarg); exit(255); } } break; case 'l': if (options.user == NULL) options.user = optarg; break; case 'L': if (parse_forward(&fwd, optarg, 0, 0)) add_local_forward(&options, &fwd); else { fprintf(stderr, "Bad local forwarding specification '%s'\n", optarg); exit(255); } break; case 'R': if (parse_forward(&fwd, optarg, 0, 1) || parse_forward(&fwd, optarg, 1, 1)) { add_remote_forward(&options, &fwd); } else { fprintf(stderr, "Bad remote forwarding specification " "'%s'\n", optarg); exit(255); } break; case 'D': if (parse_forward(&fwd, optarg, 1, 0)) { add_local_forward(&options, &fwd); } else { fprintf(stderr, "Bad dynamic forwarding specification " "'%s'\n", optarg); exit(255); } break; case 'C': #ifdef WITH_ZLIB options.compression = 1; #else error("Compression not supported, disabling."); #endif break; case 'N': if (options.session_type != -1 && options.session_type != SESSION_TYPE_NONE) fatal("Cannot specify -N with -s/SessionType"); options.session_type = SESSION_TYPE_NONE; options.request_tty = REQUEST_TTY_NO; break; case 'T': options.request_tty = REQUEST_TTY_NO; break; case 'o': line = xstrdup(optarg); if (process_config_line(&options, pw, host ? host : "", host ? host : "", line, "command-line", 0, NULL, SSHCONF_USERCONF) != 0) exit(255); free(line); break; case 's': if (options.session_type != -1 && options.session_type != SESSION_TYPE_SUBSYSTEM) fatal("Cannot specify -s with -N/SessionType"); options.session_type = SESSION_TYPE_SUBSYSTEM; break; case 'S': free(options.control_path); options.control_path = xstrdup(optarg); break; case 'b': options.bind_address = optarg; break; case 'B': options.bind_interface = optarg; break; case 'F': config = optarg; break; default: usage(); } } if (optind > 1 && strcmp(av[optind - 1], "--") == 0) opt_terminated = 1; ac -= optind; av += optind; if (ac > 0 && !host) { int tport; char *tuser; switch (parse_ssh_uri(*av, &tuser, &host, &tport)) { case -1: usage(); break; case 0: if (options.user == NULL) { options.user = tuser; tuser = NULL; } free(tuser); if (options.port == -1 && tport != -1) options.port = tport; break; default: p = xstrdup(*av); cp = strrchr(p, '@'); if (cp != NULL) { if (cp == p) usage(); if (options.user == NULL) { options.user = p; p = NULL; } *cp++ = '\0'; host = xstrdup(cp); free(p); } else host = p; break; } if (ac > 1 && !opt_terminated) { optind = optreset = 1; goto again; } ac--, av++; } /* Check that we got a host name. */ if (!host) usage(); options.host_arg = xstrdup(host); /* Initialize the command to execute on remote host. */ if ((command = sshbuf_new()) == NULL) fatal("sshbuf_new failed"); /* * Save the command to execute on the remote host in a buffer. There * is no limit on the length of the command, except by the maximum * packet size. Also sets the tty flag if there is no command. */ if (!ac) { /* No command specified - execute shell on a tty. */ if (options.session_type == SESSION_TYPE_SUBSYSTEM) { fprintf(stderr, "You must specify a subsystem to invoke.\n"); usage(); } } else { /* A command has been specified. Store it into the buffer. */ for (i = 0; i < ac; i++) { if ((r = sshbuf_putf(command, "%s%s", i ? " " : "", av[i])) != 0) fatal_fr(r, "buffer error"); } } ssh_signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE early */ /* * Initialize "log" output. Since we are the client all output * goes to stderr unless otherwise specified by -y or -E. */ if (use_syslog && logfile != NULL) fatal("Can't specify both -y and -E"); if (logfile != NULL) log_redirect_stderr_to(logfile); log_init(argv0, options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_USER : options.log_facility, !use_syslog); if (debug_flag) logit("%s, %s", SSH_RELEASE, SSH_OPENSSL_VERSION); /* Parse the configuration files */ process_config_files(options.host_arg, pw, 0, &want_final_pass); if (want_final_pass) debug("configuration requests final Match pass"); /* Hostname canonicalisation needs a few options filled. */ fill_default_options_for_canonicalization(&options); /* If the user has replaced the hostname then take it into use now */ if (options.hostname != NULL) { /* NB. Please keep in sync with readconf.c:match_cfg_line() */ cp = percent_expand(options.hostname, "h", host, (char *)NULL); free(host); host = cp; free(options.hostname); options.hostname = xstrdup(host); } /* Don't lowercase addresses, they will be explicitly canonicalised */ if ((was_addr = is_addr(host)) == 0) lowercase(host); /* * Try to canonicalize if requested by configuration or the * hostname is an address. */ if (options.canonicalize_hostname != SSH_CANONICALISE_NO || was_addr) addrs = resolve_canonicalize(&host, options.port); /* * If CanonicalizePermittedCNAMEs have been specified but * other canonicalization did not happen (by not being requested * or by failing with fallback) then the hostname may still be changed * as a result of CNAME following. * * Try to resolve the bare hostname name using the system resolver's * usual search rules and then apply the CNAME follow rules. * * Skip the lookup if a ProxyCommand is being used unless the user * has specifically requested canonicalisation for this case via * CanonicalizeHostname=always */ direct = option_clear_or_none(options.proxy_command) && option_clear_or_none(options.jump_host); if (addrs == NULL && config_has_permitted_cnames(&options) && (direct || options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) { if ((addrs = resolve_host(host, options.port, direct, cname, sizeof(cname))) == NULL) { /* Don't fatal proxied host names not in the DNS */ if (direct) cleanup_exit(255); /* logged in resolve_host */ } else check_follow_cname(direct, &host, cname); } /* * If canonicalisation is enabled then re-parse the configuration * files as new stanzas may match. */ if (options.canonicalize_hostname != 0 && !want_final_pass) { debug("hostname canonicalisation enabled, " "will re-parse configuration"); want_final_pass = 1; } if (want_final_pass) { debug("re-parsing configuration"); free(options.hostname); options.hostname = xstrdup(host); process_config_files(options.host_arg, pw, 1, NULL); /* * Address resolution happens early with canonicalisation * enabled and the port number may have changed since, so * reset it in address list */ if (addrs != NULL && options.port > 0) set_addrinfo_port(addrs, options.port); } /* Fill configuration defaults. */ if (fill_default_options(&options) != 0) cleanup_exit(255); if (options.user == NULL) options.user = xstrdup(pw->pw_name); /* * If ProxyJump option specified, then construct a ProxyCommand now. */ if (options.jump_host != NULL) { char port_s[8]; const char *jumpuser = options.jump_user, *sshbin = argv0; int port = options.port, jumpport = options.jump_port; if (port <= 0) port = default_ssh_port(); if (jumpport <= 0) jumpport = default_ssh_port(); if (jumpuser == NULL) jumpuser = options.user; if (strcmp(options.jump_host, host) == 0 && port == jumpport && strcmp(options.user, jumpuser) == 0) fatal("jumphost loop via %s", options.jump_host); /* * Try to use SSH indicated by argv[0], but fall back to * "ssh" if it appears unavailable. */ if (strchr(argv0, '/') != NULL && access(argv0, X_OK) != 0) sshbin = "ssh"; /* Consistency check */ if (options.proxy_command != NULL) fatal("inconsistent options: ProxyCommand+ProxyJump"); /* Never use FD passing for ProxyJump */ options.proxy_use_fdpass = 0; snprintf(port_s, sizeof(port_s), "%d", options.jump_port); xasprintf(&options.proxy_command, "%s%s%s%s%s%s%s%s%s%s%.*s -W '[%%h]:%%p' %s", sshbin, /* Optional "-l user" argument if jump_user set */ options.jump_user == NULL ? "" : " -l ", options.jump_user == NULL ? "" : options.jump_user, /* Optional "-p port" argument if jump_port set */ options.jump_port <= 0 ? "" : " -p ", options.jump_port <= 0 ? "" : port_s, /* Optional additional jump hosts ",..." */ options.jump_extra == NULL ? "" : " -J ", options.jump_extra == NULL ? "" : options.jump_extra, /* Optional "-F" argument if -F specified */ config == NULL ? "" : " -F ", config == NULL ? "" : config, /* Optional "-v" arguments if -v set */ debug_flag ? " -" : "", debug_flag, "vvv", /* Mandatory hostname */ options.jump_host); debug("Setting implicit ProxyCommand from ProxyJump: %s", options.proxy_command); } if (options.port == 0) options.port = default_ssh_port(); channel_set_af(ssh, options.address_family); /* Tidy and check options */ if (options.host_key_alias != NULL) lowercase(options.host_key_alias); if (options.proxy_command != NULL && strcmp(options.proxy_command, "-") == 0 && options.proxy_use_fdpass) fatal("ProxyCommand=- and ProxyUseFDPass are incompatible"); if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) { if (options.control_persist && options.control_path != NULL) { debug("UpdateHostKeys=ask is incompatible with " "ControlPersist; disabling"); options.update_hostkeys = 0; } else if (sshbuf_len(command) != 0 || options.remote_command != NULL || options.request_tty == REQUEST_TTY_NO) { debug("UpdateHostKeys=ask is incompatible with " "remote command execution; disabling"); options.update_hostkeys = 0; } else if (options.log_level < SYSLOG_LEVEL_INFO) { /* no point logging anything; user won't see it */ options.update_hostkeys = 0; } } if (options.connection_attempts <= 0) fatal("Invalid number of ConnectionAttempts"); if (sshbuf_len(command) != 0 && options.remote_command != NULL) fatal("Cannot execute command-line and remote command."); /* Cannot fork to background if no command. */ if (options.fork_after_authentication && sshbuf_len(command) == 0 && options.remote_command == NULL && options.session_type != SESSION_TYPE_NONE) fatal("Cannot fork into background without a command " "to execute."); /* reinit */ log_init(argv0, options.log_level, options.log_facility, !use_syslog); for (j = 0; j < options.num_log_verbose; j++) { if (strcasecmp(options.log_verbose[j], "none") == 0) break; log_verbose_add(options.log_verbose[j]); } if (options.request_tty == REQUEST_TTY_YES || options.request_tty == REQUEST_TTY_FORCE) tty_flag = 1; /* Allocate a tty by default if no command specified. */ if (sshbuf_len(command) == 0 && options.remote_command == NULL) tty_flag = options.request_tty != REQUEST_TTY_NO; /* Force no tty */ if (options.request_tty == REQUEST_TTY_NO || (muxclient_command && muxclient_command != SSHMUX_COMMAND_PROXY) || options.session_type == SESSION_TYPE_NONE) tty_flag = 0; /* Do not allocate a tty if stdin is not a tty. */ if ((!isatty(fileno(stdin)) || options.stdin_null) && options.request_tty != REQUEST_TTY_FORCE) { if (tty_flag) logit("Pseudo-terminal will not be allocated because " "stdin is not a terminal."); tty_flag = 0; } /* Set up strings used to percent_expand() arguments */ cinfo = xcalloc(1, sizeof(*cinfo)); if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); cinfo->thishost = xstrdup(thishost); thishost[strcspn(thishost, ".")] = '\0'; cinfo->shorthost = xstrdup(thishost); xasprintf(&cinfo->portstr, "%d", options.port); xasprintf(&cinfo->uidstr, "%llu", (unsigned long long)pw->pw_uid); cinfo->keyalias = xstrdup(options.host_key_alias ? options.host_key_alias : options.host_arg); cinfo->conn_hash_hex = ssh_connection_hash(cinfo->thishost, host, cinfo->portstr, options.user); cinfo->host_arg = xstrdup(options.host_arg); cinfo->remhost = xstrdup(host); cinfo->remuser = xstrdup(options.user); cinfo->homedir = xstrdup(pw->pw_dir); cinfo->locuser = xstrdup(pw->pw_name); /* Find canonic host name. */ if (strchr(host, '.') == NULL) { struct addrinfo hints; struct addrinfo *ai = NULL; int errgai; memset(&hints, 0, sizeof(hints)); hints.ai_family = options.address_family; hints.ai_flags = AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; errgai = getaddrinfo(host, NULL, &hints, &ai); if (errgai == 0) { if (ai->ai_canonname != NULL) { free(host); host = xstrdup(ai->ai_canonname); } freeaddrinfo(ai); } } /* * Expand tokens in arguments. NB. LocalCommand is expanded later, * after port-forwarding is set up, so it may pick up any local * tunnel interface name allocated. */ if (options.remote_command != NULL) { debug3("expanding RemoteCommand: %s", options.remote_command); cp = options.remote_command; options.remote_command = default_client_percent_expand(cp, cinfo); debug3("expanded RemoteCommand: %s", options.remote_command); free(cp); if ((r = sshbuf_put(command, options.remote_command, strlen(options.remote_command))) != 0) fatal_fr(r, "buffer error"); } if (options.control_path != NULL) { cp = tilde_expand_filename(options.control_path, getuid()); free(options.control_path); options.control_path = default_client_percent_dollar_expand(cp, cinfo); free(cp); } if (options.identity_agent != NULL) { p = tilde_expand_filename(options.identity_agent, getuid()); cp = default_client_percent_dollar_expand(p, cinfo); free(p); free(options.identity_agent); options.identity_agent = cp; } if (options.revoked_host_keys != NULL) { p = tilde_expand_filename(options.revoked_host_keys, getuid()); cp = default_client_percent_dollar_expand(p, cinfo); free(p); free(options.revoked_host_keys); options.revoked_host_keys = cp; } if (options.forward_agent_sock_path != NULL) { p = tilde_expand_filename(options.forward_agent_sock_path, getuid()); cp = default_client_percent_dollar_expand(p, cinfo); free(p); free(options.forward_agent_sock_path); options.forward_agent_sock_path = cp; if (stat(options.forward_agent_sock_path, &st) != 0) { error("Cannot forward agent socket path \"%s\": %s", options.forward_agent_sock_path, strerror(errno)); if (options.exit_on_forward_failure) cleanup_exit(255); } } if (options.num_system_hostfiles > 0 && strcasecmp(options.system_hostfiles[0], "none") == 0) { if (options.num_system_hostfiles > 1) fatal("Invalid GlobalKnownHostsFiles: \"none\" " "appears with other entries"); free(options.system_hostfiles[0]); options.system_hostfiles[0] = NULL; options.num_system_hostfiles = 0; } if (options.num_user_hostfiles > 0 && strcasecmp(options.user_hostfiles[0], "none") == 0) { if (options.num_user_hostfiles > 1) fatal("Invalid UserKnownHostsFiles: \"none\" " "appears with other entries"); free(options.user_hostfiles[0]); options.user_hostfiles[0] = NULL; options.num_user_hostfiles = 0; } for (j = 0; j < options.num_user_hostfiles; j++) { if (options.user_hostfiles[j] == NULL) continue; cp = tilde_expand_filename(options.user_hostfiles[j], getuid()); p = default_client_percent_dollar_expand(cp, cinfo); if (strcmp(options.user_hostfiles[j], p) != 0) debug3("expanded UserKnownHostsFile '%s' -> " "'%s'", options.user_hostfiles[j], p); free(options.user_hostfiles[j]); free(cp); options.user_hostfiles[j] = p; } for (i = 0; i < options.num_local_forwards; i++) { if (options.local_forwards[i].listen_path != NULL) { cp = options.local_forwards[i].listen_path; p = options.local_forwards[i].listen_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded LocalForward listen path " "'%s' -> '%s'", cp, p); free(cp); } if (options.local_forwards[i].connect_path != NULL) { cp = options.local_forwards[i].connect_path; p = options.local_forwards[i].connect_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded LocalForward connect path " "'%s' -> '%s'", cp, p); free(cp); } } for (i = 0; i < options.num_remote_forwards; i++) { if (options.remote_forwards[i].listen_path != NULL) { cp = options.remote_forwards[i].listen_path; p = options.remote_forwards[i].listen_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded RemoteForward listen path " "'%s' -> '%s'", cp, p); free(cp); } if (options.remote_forwards[i].connect_path != NULL) { cp = options.remote_forwards[i].connect_path; p = options.remote_forwards[i].connect_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded RemoteForward connect path " "'%s' -> '%s'", cp, p); free(cp); } } if (config_test) { dump_client_config(&options, host); exit(0); } /* Expand SecurityKeyProvider if it refers to an environment variable */ if (options.sk_provider != NULL && *options.sk_provider == '$' && strlen(options.sk_provider) > 1) { if ((cp = getenv(options.sk_provider + 1)) == NULL) { debug("Authenticator provider %s did not resolve; " "disabling", options.sk_provider); free(options.sk_provider); options.sk_provider = NULL; } else { debug2("resolved SecurityKeyProvider %s => %s", options.sk_provider, cp); free(options.sk_provider); options.sk_provider = xstrdup(cp); } } if (muxclient_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) { int sock; if ((sock = muxclient(options.control_path)) >= 0) { ssh_packet_set_connection(ssh, sock, sock); ssh_packet_set_mux(ssh); goto skip_connect; } } /* * If hostname canonicalisation was not enabled, then we may not * have yet resolved the hostname. Do so now. */ if (addrs == NULL && options.proxy_command == NULL) { debug2("resolving \"%s\" port %d", host, options.port); if ((addrs = resolve_host(host, options.port, 1, cname, sizeof(cname))) == NULL) cleanup_exit(255); /* resolve_host logs the error */ } if (options.connection_timeout >= INT_MAX/1000) timeout_ms = INT_MAX; else timeout_ms = options.connection_timeout * 1000; /* Open a connection to the remote host. */ if (ssh_connect(ssh, host, options.host_arg, addrs, &hostaddr, options.port, options.connection_attempts, &timeout_ms, options.tcp_keep_alive) != 0) exit(255); if (addrs != NULL) freeaddrinfo(addrs); ssh_packet_set_timeout(ssh, options.server_alive_interval, options.server_alive_count_max); if (timeout_ms > 0) debug3("timeout: %d ms remain after connect", timeout_ms); /* * If we successfully made the connection and we have hostbased auth * enabled, load the public keys so we can later use the ssh-keysign * helper to sign challenges. */ sensitive_data.nkeys = 0; sensitive_data.keys = NULL; if (options.hostbased_authentication) { int loaded = 0; sensitive_data.nkeys = 10; sensitive_data.keys = xcalloc(sensitive_data.nkeys, sizeof(*sensitive_data.keys)); /* XXX check errors? */ #define L_PUBKEY(p,o) do { \ if ((o) >= sensitive_data.nkeys) \ fatal_f("pubkey out of array bounds"); \ check_load(sshkey_load_public(p, &(sensitive_data.keys[o]), NULL), \ &(sensitive_data.keys[o]), p, "pubkey"); \ if (sensitive_data.keys[o] != NULL) { \ debug2("hostbased key %d: %s key from \"%s\"", o, \ sshkey_ssh_name(sensitive_data.keys[o]), p); \ loaded++; \ } \ } while (0) #define L_CERT(p,o) do { \ if ((o) >= sensitive_data.nkeys) \ fatal_f("cert out of array bounds"); \ check_load(sshkey_load_cert(p, &(sensitive_data.keys[o])), \ &(sensitive_data.keys[o]), p, "cert"); \ if (sensitive_data.keys[o] != NULL) { \ debug2("hostbased key %d: %s cert from \"%s\"", o, \ sshkey_ssh_name(sensitive_data.keys[o]), p); \ loaded++; \ } \ } while (0) if (options.hostbased_authentication == 1) { L_CERT(_PATH_HOST_ECDSA_KEY_FILE, 0); L_CERT(_PATH_HOST_ED25519_KEY_FILE, 1); L_CERT(_PATH_HOST_RSA_KEY_FILE, 2); L_CERT(_PATH_HOST_DSA_KEY_FILE, 3); L_PUBKEY(_PATH_HOST_ECDSA_KEY_FILE, 4); L_PUBKEY(_PATH_HOST_ED25519_KEY_FILE, 5); L_PUBKEY(_PATH_HOST_RSA_KEY_FILE, 6); L_PUBKEY(_PATH_HOST_DSA_KEY_FILE, 7); L_CERT(_PATH_HOST_XMSS_KEY_FILE, 8); L_PUBKEY(_PATH_HOST_XMSS_KEY_FILE, 9); if (loaded == 0) debug("HostbasedAuthentication enabled but no " "local public host keys could be loaded."); } } /* load options.identity_files */ load_public_identity_files(cinfo); /* optionally set the SSH_AUTHSOCKET_ENV_NAME variable */ if (options.identity_agent && strcmp(options.identity_agent, SSH_AUTHSOCKET_ENV_NAME) != 0) { if (strcmp(options.identity_agent, "none") == 0) { unsetenv(SSH_AUTHSOCKET_ENV_NAME); } else { cp = options.identity_agent; /* legacy (limited) format */ if (cp[0] == '$' && cp[1] != '{') { if (!valid_env_name(cp + 1)) { fatal("Invalid IdentityAgent " "environment variable name %s", cp); } if ((p = getenv(cp + 1)) == NULL) unsetenv(SSH_AUTHSOCKET_ENV_NAME); else setenv(SSH_AUTHSOCKET_ENV_NAME, p, 1); } else { /* identity_agent specifies a path directly */ setenv(SSH_AUTHSOCKET_ENV_NAME, cp, 1); } } } if (options.forward_agent && options.forward_agent_sock_path != NULL) { cp = options.forward_agent_sock_path; if (cp[0] == '$') { if (!valid_env_name(cp + 1)) { fatal("Invalid ForwardAgent environment variable name %s", cp); } if ((p = getenv(cp + 1)) != NULL) forward_agent_sock_path = xstrdup(p); else options.forward_agent = 0; free(cp); } else { forward_agent_sock_path = cp; } } /* Expand ~ in known host file names. */ tilde_expand_paths(options.system_hostfiles, options.num_system_hostfiles); tilde_expand_paths(options.user_hostfiles, options.num_user_hostfiles); ssh_signal(SIGCHLD, main_sigchld_handler); /* Log into the remote system. Never returns if the login fails. */ ssh_login(ssh, &sensitive_data, host, (struct sockaddr *)&hostaddr, options.port, pw, timeout_ms, cinfo); /* We no longer need the private host keys. Clear them now. */ if (sensitive_data.nkeys != 0) { for (i = 0; i < sensitive_data.nkeys; i++) { if (sensitive_data.keys[i] != NULL) { /* Destroys contents safely */ debug3("clear hostkey %d", i); sshkey_free(sensitive_data.keys[i]); sensitive_data.keys[i] = NULL; } } free(sensitive_data.keys); } for (i = 0; i < options.num_identity_files; i++) { free(options.identity_files[i]); options.identity_files[i] = NULL; if (options.identity_keys[i]) { sshkey_free(options.identity_keys[i]); options.identity_keys[i] = NULL; } } for (i = 0; i < options.num_certificate_files; i++) { free(options.certificate_files[i]); options.certificate_files[i] = NULL; } #ifdef ENABLE_PKCS11 (void)pkcs11_del_provider(options.pkcs11_provider); #endif skip_connect: exit_status = ssh_session2(ssh, cinfo); ssh_conn_info_free(cinfo); ssh_packet_close(ssh); if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); /* Kill ProxyCommand if it is running. */ ssh_kill_proxy_command(); return exit_status; } static void control_persist_detach(void) { pid_t pid; debug_f("backgrounding master process"); /* * master (current process) into the background, and make the * foreground process a client of the backgrounded master. */ switch ((pid = fork())) { case -1: fatal_f("fork: %s", strerror(errno)); case 0: /* Child: master process continues mainloop */ break; default: /* * Parent: set up mux client to connect to backgrounded * master. */ debug2_f("background process is %ld", (long)pid); options.stdin_null = ostdin_null_flag; options.request_tty = orequest_tty; tty_flag = otty_flag; options.fork_after_authentication = ofork_after_authentication; options.session_type = osession_type; close(muxserver_sock); muxserver_sock = -1; options.control_master = SSHCTL_MASTER_NO; (void)muxclient(options.control_path); /* muxclient() doesn't return on success. */ fatal("Failed to connect to new control master"); } if (stdfd_devnull(1, 1, !(log_is_on_stderr() && debug_flag)) == -1) error_f("stdfd_devnull failed"); daemon(1, 1); setproctitle("%s [mux]", options.control_path); } /* Do fork() after authentication. Used by "ssh -f" */ static void fork_postauth(void) { if (need_controlpersist_detach) control_persist_detach(); debug("forking to background"); options.fork_after_authentication = 0; if (daemon(1, 1) == -1) fatal("daemon() failed: %.200s", strerror(errno)); if (stdfd_devnull(1, 1, !(log_is_on_stderr() && debug_flag)) == -1) error_f("stdfd_devnull failed"); } static void forwarding_success(void) { if (forward_confirms_pending == -1) return; if (--forward_confirms_pending == 0) { debug_f("all expected forwarding replies received"); if (options.fork_after_authentication) fork_postauth(); } else { debug2_f("%d expected forwarding replies remaining", forward_confirms_pending); } } /* Callback for remote forward global requests */ static void ssh_confirm_remote_forward(struct ssh *ssh, int type, u_int32_t seq, void *ctxt) { struct Forward *rfwd = (struct Forward *)ctxt; u_int port; int r; /* XXX verbose() on failure? */ debug("remote forward %s for: listen %s%s%d, connect %s:%d", type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure", rfwd->listen_path ? rfwd->listen_path : rfwd->listen_host ? rfwd->listen_host : "", (rfwd->listen_path || rfwd->listen_host) ? ":" : "", rfwd->listen_port, rfwd->connect_path ? rfwd->connect_path : rfwd->connect_host, rfwd->connect_port); if (rfwd->listen_path == NULL && rfwd->listen_port == 0) { if (type == SSH2_MSG_REQUEST_SUCCESS) { if ((r = sshpkt_get_u32(ssh, &port)) != 0) fatal_fr(r, "parse packet"); if (port > 65535) { error("Invalid allocated port %u for remote " "forward to %s:%d", port, rfwd->connect_host, rfwd->connect_port); /* Ensure failure processing runs below */ type = SSH2_MSG_REQUEST_FAILURE; channel_update_permission(ssh, rfwd->handle, -1); } else { rfwd->allocated_port = (int)port; logit("Allocated port %u for remote " "forward to %s:%d", rfwd->allocated_port, rfwd->connect_path ? rfwd->connect_path : rfwd->connect_host, rfwd->connect_port); channel_update_permission(ssh, rfwd->handle, rfwd->allocated_port); } } else { channel_update_permission(ssh, rfwd->handle, -1); } } if (type == SSH2_MSG_REQUEST_FAILURE) { if (options.exit_on_forward_failure) { if (rfwd->listen_path != NULL) fatal("Error: remote port forwarding failed " "for listen path %s", rfwd->listen_path); else fatal("Error: remote port forwarding failed " "for listen port %d", rfwd->listen_port); } else { if (rfwd->listen_path != NULL) logit("Warning: remote port forwarding failed " "for listen path %s", rfwd->listen_path); else logit("Warning: remote port forwarding failed " "for listen port %d", rfwd->listen_port); } } forwarding_success(); } static void client_cleanup_stdio_fwd(struct ssh *ssh, int id, int force, void *arg) { debug("stdio forwarding: done"); cleanup_exit(0); } static void ssh_stdio_confirm(struct ssh *ssh, int id, int success, void *arg) { if (!success) fatal("stdio forwarding failed"); } static void ssh_tun_confirm(struct ssh *ssh, int id, int success, void *arg) { if (!success) { error("Tunnel forwarding failed"); if (options.exit_on_forward_failure) cleanup_exit(255); } debug_f("tunnel forward established, id=%d", id); forwarding_success(); } static void ssh_init_stdio_forwarding(struct ssh *ssh) { Channel *c; int in, out; if (options.stdio_forward_host == NULL) return; debug3_f("%s:%d", options.stdio_forward_host, options.stdio_forward_port); if ((in = dup(STDIN_FILENO)) == -1 || (out = dup(STDOUT_FILENO)) == -1) fatal_f("dup() in/out failed"); if ((c = channel_connect_stdio_fwd(ssh, options.stdio_forward_host, options.stdio_forward_port, in, out, CHANNEL_NONBLOCK_STDIO)) == NULL) fatal_f("channel_connect_stdio_fwd failed"); channel_register_cleanup(ssh, c->self, client_cleanup_stdio_fwd, 0); channel_register_open_confirm(ssh, c->self, ssh_stdio_confirm, NULL); } static void ssh_init_forward_permissions(struct ssh *ssh, const char *what, char **opens, u_int num_opens) { u_int i; int port; char *addr, *arg, *oarg; int where = FORWARD_LOCAL; channel_clear_permission(ssh, FORWARD_ADM, where); if (num_opens == 0) return; /* permit any */ /* handle keywords: "any" / "none" */ if (num_opens == 1 && strcmp(opens[0], "any") == 0) return; if (num_opens == 1 && strcmp(opens[0], "none") == 0) { channel_disable_admin(ssh, where); return; } /* Otherwise treat it as a list of permitted host:port */ for (i = 0; i < num_opens; i++) { oarg = arg = xstrdup(opens[i]); addr = hpdelim(&arg); if (addr == NULL) fatal_f("missing host in %s", what); addr = cleanhostname(addr); if (arg == NULL || ((port = permitopen_port(arg)) < 0)) fatal_f("bad port number in %s", what); /* Send it to channels layer */ channel_add_permission(ssh, FORWARD_ADM, where, addr, port); free(oarg); } } static void ssh_init_forwarding(struct ssh *ssh, char **ifname) { int success = 0; int i; ssh_init_forward_permissions(ssh, "permitremoteopen", options.permitted_remote_opens, options.num_permitted_remote_opens); if (options.exit_on_forward_failure) forward_confirms_pending = 0; /* track pending requests */ /* Initiate local TCP/IP port forwardings. */ for (i = 0; i < options.num_local_forwards; i++) { debug("Local connections to %.200s:%d forwarded to remote " "address %.200s:%d", (options.local_forwards[i].listen_path != NULL) ? options.local_forwards[i].listen_path : (options.local_forwards[i].listen_host == NULL) ? (options.fwd_opts.gateway_ports ? "*" : "LOCALHOST") : options.local_forwards[i].listen_host, options.local_forwards[i].listen_port, (options.local_forwards[i].connect_path != NULL) ? options.local_forwards[i].connect_path : options.local_forwards[i].connect_host, options.local_forwards[i].connect_port); success += channel_setup_local_fwd_listener(ssh, &options.local_forwards[i], &options.fwd_opts); } if (i > 0 && success != i && options.exit_on_forward_failure) fatal("Could not request local forwarding."); if (i > 0 && success == 0) error("Could not request local forwarding."); /* Initiate remote TCP/IP port forwardings. */ for (i = 0; i < options.num_remote_forwards; i++) { debug("Remote connections from %.200s:%d forwarded to " "local address %.200s:%d", (options.remote_forwards[i].listen_path != NULL) ? options.remote_forwards[i].listen_path : (options.remote_forwards[i].listen_host == NULL) ? "LOCALHOST" : options.remote_forwards[i].listen_host, options.remote_forwards[i].listen_port, (options.remote_forwards[i].connect_path != NULL) ? options.remote_forwards[i].connect_path : options.remote_forwards[i].connect_host, options.remote_forwards[i].connect_port); if ((options.remote_forwards[i].handle = channel_request_remote_forwarding(ssh, &options.remote_forwards[i])) >= 0) { client_register_global_confirm( ssh_confirm_remote_forward, &options.remote_forwards[i]); forward_confirms_pending++; } else if (options.exit_on_forward_failure) fatal("Could not request remote forwarding."); else logit("Warning: Could not request remote forwarding."); } /* Initiate tunnel forwarding. */ if (options.tun_open != SSH_TUNMODE_NO) { if ((*ifname = client_request_tun_fwd(ssh, options.tun_open, options.tun_local, options.tun_remote, ssh_tun_confirm, NULL)) != NULL) forward_confirms_pending++; else if (options.exit_on_forward_failure) fatal("Could not request tunnel forwarding."); else error("Could not request tunnel forwarding."); } if (forward_confirms_pending > 0) { debug_f("expecting replies for %d forwards", forward_confirms_pending); } } static void check_agent_present(void) { int r; if (options.forward_agent) { /* Clear agent forwarding if we don't have an agent. */ if ((r = ssh_get_authentication_socket(NULL)) != 0) { options.forward_agent = 0; if (r != SSH_ERR_AGENT_NOT_PRESENT) debug_r(r, "ssh_get_authentication_socket"); } } } static void ssh_session2_setup(struct ssh *ssh, int id, int success, void *arg) { extern char **environ; const char *display, *term; int r, interactive = tty_flag; char *proto = NULL, *data = NULL; if (!success) return; /* No need for error message, channels code sends one */ display = getenv("DISPLAY"); if (display == NULL && options.forward_x11) debug("X11 forwarding requested but DISPLAY not set"); if (options.forward_x11 && client_x11_get_proto(ssh, display, options.xauth_location, options.forward_x11_trusted, options.forward_x11_timeout, &proto, &data) == 0) { /* Request forwarding with authentication spoofing. */ debug("Requesting X11 forwarding with authentication " "spoofing."); x11_request_forwarding_with_spoofing(ssh, id, display, proto, data, 1); client_expect_confirm(ssh, id, "X11 forwarding", CONFIRM_WARN); /* XXX exit_on_forward_failure */ interactive = 1; } check_agent_present(); if (options.forward_agent) { debug("Requesting authentication agent forwarding."); channel_request_start(ssh, id, "auth-agent-req@openssh.com", 0); if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); } /* Tell the packet module whether this is an interactive session. */ ssh_packet_set_interactive(ssh, interactive, options.ip_qos_interactive, options.ip_qos_bulk); if ((term = lookup_env_in_list("TERM", options.setenv, options.num_setenv)) == NULL || *term == '\0') term = getenv("TERM"); client_session2_setup(ssh, id, tty_flag, options.session_type == SESSION_TYPE_SUBSYSTEM, term, NULL, fileno(stdin), command, environ); } /* open new channel for a session */ static int ssh_session2_open(struct ssh *ssh) { Channel *c; int window, packetmax, in, out, err; if (options.stdin_null) { in = open(_PATH_DEVNULL, O_RDONLY); } else { in = dup(STDIN_FILENO); } out = dup(STDOUT_FILENO); err = dup(STDERR_FILENO); if (in == -1 || out == -1 || err == -1) fatal("dup() in/out/err failed"); window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (tty_flag) { window >>= 1; packetmax >>= 1; } c = channel_new(ssh, "session", SSH_CHANNEL_OPENING, in, out, err, window, packetmax, CHAN_EXTENDED_WRITE, "client-session", CHANNEL_NONBLOCK_STDIO); debug3_f("channel_new: %d", c->self); channel_send_open(ssh, c->self); if (options.session_type != SESSION_TYPE_NONE) channel_register_open_confirm(ssh, c->self, ssh_session2_setup, NULL); return c->self; } static int ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo) { - int r, id = -1; + int r, interactive, id = -1; char *cp, *tun_fwd_ifname = NULL; /* XXX should be pre-session */ if (!options.control_persist) ssh_init_stdio_forwarding(ssh); ssh_init_forwarding(ssh, &tun_fwd_ifname); if (options.local_command != NULL) { debug3("expanding LocalCommand: %s", options.local_command); cp = options.local_command; options.local_command = percent_expand(cp, DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), "T", tun_fwd_ifname == NULL ? "NONE" : tun_fwd_ifname, (char *)NULL); debug3("expanded LocalCommand: %s", options.local_command); free(cp); } /* Start listening for multiplex clients */ if (!ssh_packet_get_mux(ssh)) muxserver_listen(ssh); /* * If we are in control persist mode and have a working mux listen * socket, then prepare to background ourselves and have a foreground * client attach as a control client. * NB. we must save copies of the flags that we override for * the backgrounding, since we defer attachment of the client until * after the connection is fully established (in particular, * async rfwd replies have been received for ExitOnForwardFailure). */ if (options.control_persist && muxserver_sock != -1) { ostdin_null_flag = options.stdin_null; osession_type = options.session_type; orequest_tty = options.request_tty; otty_flag = tty_flag; ofork_after_authentication = options.fork_after_authentication; options.stdin_null = 1; options.session_type = SESSION_TYPE_NONE; tty_flag = 0; if ((osession_type != SESSION_TYPE_NONE || options.stdio_forward_host != NULL)) need_controlpersist_detach = 1; options.fork_after_authentication = 1; } /* * ControlPersist mux listen socket setup failed, attempt the * stdio forward setup that we skipped earlier. */ if (options.control_persist && muxserver_sock == -1) ssh_init_stdio_forwarding(ssh); if (options.session_type != SESSION_TYPE_NONE) id = ssh_session2_open(ssh); else { - ssh_packet_set_interactive(ssh, - options.control_master == SSHCTL_MASTER_NO, + interactive = options.control_master == SSHCTL_MASTER_NO; + /* ControlPersist may have clobbered ControlMaster, so check */ + if (need_controlpersist_detach) + interactive = otty_flag != 0; + ssh_packet_set_interactive(ssh, interactive, options.ip_qos_interactive, options.ip_qos_bulk); } /* If we don't expect to open a new session, then disallow it */ if (options.control_master == SSHCTL_MASTER_NO && (ssh->compat & SSH_NEW_OPENSSH)) { debug("Requesting no-more-sessions@openssh.com"); if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "no-more-sessions@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); } /* Execute a local command */ if (options.local_command != NULL && options.permit_local_command) ssh_local_cmd(options.local_command); /* * stdout is now owned by the session channel; clobber it here * so future channel closes are propagated to the local fd. * NB. this can only happen after LocalCommand has completed, * as it may want to write to stdout. */ if (!need_controlpersist_detach && stdfd_devnull(0, 1, 0) == -1) error_f("stdfd_devnull failed"); /* * If requested and we are not interested in replies to remote * forwarding requests, then let ssh continue in the background. */ if (options.fork_after_authentication) { if (options.exit_on_forward_failure && options.num_remote_forwards > 0) { debug("deferring postauth fork until remote forward " "confirmation received"); } else fork_postauth(); } return client_loop(ssh, tty_flag, tty_flag ? options.escape_char : SSH_ESCAPECHAR_NONE, id); } /* Loads all IdentityFile and CertificateFile keys */ static void load_public_identity_files(const struct ssh_conn_info *cinfo) { char *filename, *cp; struct sshkey *public; int i; u_int n_ids, n_certs; char *identity_files[SSH_MAX_IDENTITY_FILES]; struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES]; int identity_file_userprovided[SSH_MAX_IDENTITY_FILES]; char *certificate_files[SSH_MAX_CERTIFICATE_FILES]; struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES]; #ifdef ENABLE_PKCS11 struct sshkey **keys = NULL; char **comments = NULL; int nkeys; #endif /* PKCS11 */ n_ids = n_certs = 0; memset(identity_files, 0, sizeof(identity_files)); memset(identity_keys, 0, sizeof(identity_keys)); memset(identity_file_userprovided, 0, sizeof(identity_file_userprovided)); memset(certificate_files, 0, sizeof(certificate_files)); memset(certificates, 0, sizeof(certificates)); memset(certificate_file_userprovided, 0, sizeof(certificate_file_userprovided)); #ifdef ENABLE_PKCS11 if (options.pkcs11_provider != NULL && options.num_identity_files < SSH_MAX_IDENTITY_FILES && (pkcs11_init(!options.batch_mode) == 0) && (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL, &keys, &comments)) > 0) { for (i = 0; i < nkeys; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES) { sshkey_free(keys[i]); free(comments[i]); continue; } identity_keys[n_ids] = keys[i]; identity_files[n_ids] = comments[i]; /* transferred */ n_ids++; } free(keys); free(comments); } #endif /* ENABLE_PKCS11 */ for (i = 0; i < options.num_identity_files; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES || strcasecmp(options.identity_files[i], "none") == 0) { free(options.identity_files[i]); options.identity_files[i] = NULL; continue; } cp = tilde_expand_filename(options.identity_files[i], getuid()); filename = default_client_percent_dollar_expand(cp, cinfo); free(cp); check_load(sshkey_load_public(filename, &public, NULL), &public, filename, "pubkey"); debug("identity file %s type %d", filename, public ? public->type : -1); free(options.identity_files[i]); identity_files[n_ids] = filename; identity_keys[n_ids] = public; identity_file_userprovided[n_ids] = options.identity_file_userprovided[i]; if (++n_ids >= SSH_MAX_IDENTITY_FILES) continue; /* * If no certificates have been explicitly listed then try * to add the default certificate variant too. */ if (options.num_certificate_files != 0) continue; xasprintf(&cp, "%s-cert", filename); check_load(sshkey_load_public(cp, &public, NULL), &public, filename, "pubkey"); debug("identity file %s type %d", cp, public ? public->type : -1); if (public == NULL) { free(cp); continue; } if (!sshkey_is_cert(public)) { debug_f("key %s type %s is not a certificate", cp, sshkey_type(public)); sshkey_free(public); free(cp); continue; } /* NB. leave filename pointing to private key */ identity_files[n_ids] = xstrdup(filename); identity_keys[n_ids] = public; identity_file_userprovided[n_ids] = options.identity_file_userprovided[i]; n_ids++; } if (options.num_certificate_files > SSH_MAX_CERTIFICATE_FILES) fatal_f("too many certificates"); for (i = 0; i < options.num_certificate_files; i++) { cp = tilde_expand_filename(options.certificate_files[i], getuid()); filename = default_client_percent_dollar_expand(cp, cinfo); free(cp); check_load(sshkey_load_public(filename, &public, NULL), &public, filename, "certificate"); debug("certificate file %s type %d", filename, public ? public->type : -1); free(options.certificate_files[i]); options.certificate_files[i] = NULL; if (public == NULL) { free(filename); continue; } if (!sshkey_is_cert(public)) { debug_f("key %s type %s is not a certificate", filename, sshkey_type(public)); sshkey_free(public); free(filename); continue; } certificate_files[n_certs] = filename; certificates[n_certs] = public; certificate_file_userprovided[n_certs] = options.certificate_file_userprovided[i]; ++n_certs; } options.num_identity_files = n_ids; memcpy(options.identity_files, identity_files, sizeof(identity_files)); memcpy(options.identity_keys, identity_keys, sizeof(identity_keys)); memcpy(options.identity_file_userprovided, identity_file_userprovided, sizeof(identity_file_userprovided)); options.num_certificate_files = n_certs; memcpy(options.certificate_files, certificate_files, sizeof(certificate_files)); memcpy(options.certificates, certificates, sizeof(certificates)); memcpy(options.certificate_file_userprovided, certificate_file_userprovided, sizeof(certificate_file_userprovided)); } static void main_sigchld_handler(int sig) { int save_errno = errno; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid == -1 && errno == EINTR)) ; errno = save_errno; } diff --git a/crypto/openssh/ssh2.h b/crypto/openssh/ssh2.h index 32ddae89b33b..0d48d052782f 100644 --- a/crypto/openssh/ssh2.h +++ b/crypto/openssh/ssh2.h @@ -1,174 +1,179 @@ -/* $OpenBSD: ssh2.h,v 1.19 2020/11/19 23:05:05 dtucker Exp $ */ +/* $OpenBSD: ssh2.h,v 1.21 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * RFC4251: * * Transport layer protocol: * * 1-19 Transport layer generic (e.g. disconnect, ignore, debug, * etc) * 20-29 Algorithm negotiation * 30-49 Key exchange method specific (numbers can be reused for * different authentication methods) * * User authentication protocol: * * 50-59 User authentication generic * 60-79 User authentication method specific (numbers can be reused * for different authentication methods) * * Connection protocol: * * 80-89 Connection protocol generic * 90-127 Channel related messages * * Reserved for client protocols: * * 128-191 Reserved * * Local extensions: * * 192-255 Local extensions */ /* special marker for no message */ #define SSH_MSG_NONE 0 /* ranges */ #define SSH2_MSG_TRANSPORT_MIN 1 #define SSH2_MSG_TRANSPORT_MAX 49 #define SSH2_MSG_USERAUTH_MIN 50 #define SSH2_MSG_USERAUTH_MAX 79 #define SSH2_MSG_USERAUTH_PER_METHOD_MIN 60 #define SSH2_MSG_USERAUTH_PER_METHOD_MAX SSH2_MSG_USERAUTH_MAX #define SSH2_MSG_CONNECTION_MIN 80 #define SSH2_MSG_CONNECTION_MAX 127 #define SSH2_MSG_RESERVED_MIN 128 #define SSH2_MSG_RESERVED_MAX 191 #define SSH2_MSG_LOCAL_MIN 192 #define SSH2_MSG_LOCAL_MAX 255 #define SSH2_MSG_MIN 1 #define SSH2_MSG_MAX 255 /* transport layer: generic */ #define SSH2_MSG_DISCONNECT 1 #define SSH2_MSG_IGNORE 2 #define SSH2_MSG_UNIMPLEMENTED 3 #define SSH2_MSG_DEBUG 4 #define SSH2_MSG_SERVICE_REQUEST 5 #define SSH2_MSG_SERVICE_ACCEPT 6 #define SSH2_MSG_EXT_INFO 7 +#define SSH2_MSG_NEWCOMPRESS 8 /* transport layer: alg negotiation */ #define SSH2_MSG_KEXINIT 20 #define SSH2_MSG_NEWKEYS 21 /* transport layer: kex specific messages, can be reused */ #define SSH2_MSG_KEXDH_INIT 30 #define SSH2_MSG_KEXDH_REPLY 31 /* dh-group-exchange */ #define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 #define SSH2_MSG_KEX_DH_GEX_GROUP 31 #define SSH2_MSG_KEX_DH_GEX_INIT 32 #define SSH2_MSG_KEX_DH_GEX_REPLY 33 #define SSH2_MSG_KEX_DH_GEX_REQUEST 34 /* ecdh */ #define SSH2_MSG_KEX_ECDH_INIT 30 #define SSH2_MSG_KEX_ECDH_REPLY 31 +/* transport layer: OpenSSH extensions */ +#define SSH2_MSG_PING 192 +#define SSH2_MSG_PONG 193 + /* user authentication: generic */ #define SSH2_MSG_USERAUTH_REQUEST 50 #define SSH2_MSG_USERAUTH_FAILURE 51 #define SSH2_MSG_USERAUTH_SUCCESS 52 #define SSH2_MSG_USERAUTH_BANNER 53 /* user authentication: method specific, can be reused */ #define SSH2_MSG_USERAUTH_PK_OK 60 #define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 #define SSH2_MSG_USERAUTH_INFO_REQUEST 60 #define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* connection protocol: generic */ #define SSH2_MSG_GLOBAL_REQUEST 80 #define SSH2_MSG_REQUEST_SUCCESS 81 #define SSH2_MSG_REQUEST_FAILURE 82 /* channel related messages */ #define SSH2_MSG_CHANNEL_OPEN 90 #define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 #define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 #define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 #define SSH2_MSG_CHANNEL_DATA 94 #define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 #define SSH2_MSG_CHANNEL_EOF 96 #define SSH2_MSG_CHANNEL_CLOSE 97 #define SSH2_MSG_CHANNEL_REQUEST 98 #define SSH2_MSG_CHANNEL_SUCCESS 99 #define SSH2_MSG_CHANNEL_FAILURE 100 /* disconnect reason code */ #define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 #define SSH2_DISCONNECT_PROTOCOL_ERROR 2 #define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 #define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 #define SSH2_DISCONNECT_RESERVED 4 #define SSH2_DISCONNECT_MAC_ERROR 5 #define SSH2_DISCONNECT_COMPRESSION_ERROR 6 #define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 #define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 #define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 #define SSH2_DISCONNECT_CONNECTION_LOST 10 #define SSH2_DISCONNECT_BY_APPLICATION 11 #define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 #define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 #define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 #define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* misc */ #define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 #define SSH2_OPEN_CONNECT_FAILED 2 #define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 #define SSH2_OPEN_RESOURCE_SHORTAGE 4 #define SSH2_EXTENDED_DATA_STDERR 1 /* Certificate types for OpenSSH certificate keys extension */ #define SSH2_CERT_TYPE_USER 1 #define SSH2_CERT_TYPE_HOST 2 diff --git a/crypto/openssh/ssh_config.5 b/crypto/openssh/ssh_config.5 index 079e0e2267ad..e50c17728967 100644 --- a/crypto/openssh/ssh_config.5 +++ b/crypto/openssh/ssh_config.5 @@ -1,2245 +1,2274 @@ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.383 2023/07/17 05:36:14 jsg Exp $ -.Dd $Mdocdate: July 17 2023 $ +.\" $OpenBSD: ssh_config.5,v 1.387 2023/10/04 04:03:50 djm Exp $ +.Dd $Mdocdate: October 4 2023 $ .Dt SSH_CONFIG 5 .Os .Sh NAME .Nm ssh_config .Nd OpenSSH client configuration file .Sh DESCRIPTION .Xr ssh 1 obtains configuration data from the following sources in the following order: .Pp .Bl -enum -offset indent -compact .It command-line options .It user's configuration file .Pq Pa ~/.ssh/config .It system-wide configuration file .Pq Pa /etc/ssh/ssh_config .El .Pp Unless noted otherwise, for each parameter, the first obtained value will be used. The configuration files contain sections separated by .Cm Host specifications, and that section is only applied for hosts that match one of the patterns given in the specification. The matched host name is usually the one given on the command line (see the .Cm CanonicalizeHostname option for exceptions). .Pp Since the first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the file, and general defaults at the end. .Pp The file contains keyword-argument pairs, one per line. Lines starting with .Ql # and empty lines are interpreted as comments. Arguments may optionally be enclosed in double quotes .Pq \&" in order to represent arguments containing spaces. Configuration options may be separated by whitespace or optional whitespace and exactly one .Ql = ; the latter format is useful to avoid the need to quote whitespace when specifying configuration options using the .Nm ssh , .Nm scp , and .Nm sftp .Fl o option. .Pp The possible keywords and their meanings are as follows (note that keywords are case-insensitive and arguments are case-sensitive): .Bl -tag -width Ds .It Cm Host Restricts the following declarations (up to the next .Cm Host or .Cm Match keyword) to be only for those hosts that match one of the patterns given after the keyword. If more than one pattern is provided, they should be separated by whitespace. A single .Ql * as a pattern can be used to provide global defaults for all hosts. The host is usually the .Ar hostname argument given on the command line (see the .Cm CanonicalizeHostname keyword for exceptions). .Pp A pattern entry may be negated by prefixing it with an exclamation mark .Pq Sq !\& . If a negated entry is matched, then the .Cm Host entry is ignored, regardless of whether any other patterns on the line match. Negated matches are therefore useful to provide exceptions for wildcard matches. .Pp See .Sx PATTERNS for more information on patterns. .It Cm Match Restricts the following declarations (up to the next .Cm Host or .Cm Match keyword) to be used only when the conditions following the .Cm Match keyword are satisfied. Match conditions are specified using one or more criteria or the single token .Cm all which always matches. The available criteria keywords are: .Cm canonical , .Cm final , .Cm exec , .Cm localnetwork , .Cm host , .Cm originalhost , .Cm Tag , .Cm user , and .Cm localuser . The .Cm all criteria must appear alone or immediately after .Cm canonical or .Cm final . Other criteria may be combined arbitrarily. All criteria but .Cm all , .Cm canonical , and .Cm final require an argument. Criteria may be negated by prepending an exclamation mark .Pq Sq !\& . .Pp The .Cm canonical keyword matches only when the configuration file is being re-parsed after hostname canonicalization (see the .Cm CanonicalizeHostname option). This may be useful to specify conditions that work with canonical host names only. .Pp The .Cm final keyword requests that the configuration be re-parsed (regardless of whether .Cm CanonicalizeHostname is enabled), and matches only during this final pass. If .Cm CanonicalizeHostname is enabled, then .Cm canonical and .Cm final match during the same pass. .Pp The .Cm exec keyword executes the specified command under the user's shell. If the command returns a zero exit status then the condition is considered true. Commands containing whitespace characters must be quoted. Arguments to .Cm exec accept the tokens described in the .Sx TOKENS section. .Pp The .Cm localnetwork keyword matches the addresses of active local network interfaces against the supplied list of networks in CIDR format. This may be convenient for varying the effective configuration on devices that roam between networks. Note that network address is not a trustworthy criteria in many situations (e.g. when the network is automatically configured using DHCP) and so caution should be applied if using it to control security-sensitive configuration. .Pp The other keywords' criteria must be single entries or comma-separated lists and may use the wildcard and negation operators described in the .Sx PATTERNS section. The criteria for the .Cm host keyword are matched against the target hostname, after any substitution by the .Cm Hostname or .Cm CanonicalizeHostname options. The .Cm originalhost keyword matches against the hostname as it was specified on the command-line. The .Cm tagged keyword matches a tag name specified by a prior .Cm Tag directive or on the .Xr ssh 1 command-line using the .Fl P flag. The .Cm user keyword matches against the target username on the remote host. The .Cm localuser keyword matches against the name of the local user running .Xr ssh 1 (this keyword may be useful in system-wide .Nm files). .It Cm AddKeysToAgent Specifies whether keys should be automatically added to a running .Xr ssh-agent 1 . If this option is set to .Cm yes and a key is loaded from a file, the key and its passphrase are added to the agent with the default lifetime, as if by .Xr ssh-add 1 . If this option is set to .Cm ask , .Xr ssh 1 will require confirmation using the .Ev SSH_ASKPASS program before adding a key (see .Xr ssh-add 1 for details). If this option is set to .Cm confirm , each use of the key must be confirmed, as if the .Fl c option was specified to .Xr ssh-add 1 . If this option is set to .Cm no , no keys are added to the agent. Alternately, this option may be specified as a time interval using the format described in the .Sx TIME FORMATS section of .Xr sshd_config 5 to specify the key's lifetime in .Xr ssh-agent 1 , after which it will automatically be removed. The argument must be .Cm no (the default), .Cm yes , .Cm confirm (optionally followed by a time interval), .Cm ask or a time interval. .It Cm AddressFamily Specifies which address family to use when connecting. Valid arguments are .Cm any (the default), .Cm inet (use IPv4 only), or .Cm inet6 (use IPv6 only). .It Cm BatchMode If set to .Cm yes , user interaction such as password prompts and host key confirmation requests will be disabled. This option is useful in scripts and other batch jobs where no user is present to interact with .Xr ssh 1 . The argument must be .Cm yes or .Cm no (the default). .It Cm BindAddress Use the specified address on the local machine as the source address of the connection. Only useful on systems with more than one address. .It Cm BindInterface Use the address of the specified interface on the local machine as the source address of the connection. .It Cm CanonicalDomains When .Cm CanonicalizeHostname is enabled, this option specifies the list of domain suffixes in which to search for the specified destination host. .It Cm CanonicalizeFallbackLocal Specifies whether to fail with an error when hostname canonicalization fails. The default, .Cm yes , will attempt to look up the unqualified hostname using the system resolver's search rules. A value of .Cm no will cause .Xr ssh 1 to fail instantly if .Cm CanonicalizeHostname is enabled and the target hostname cannot be found in any of the domains specified by .Cm CanonicalDomains . .It Cm CanonicalizeHostname Controls whether explicit hostname canonicalization is performed. The default, .Cm no , is not to perform any name rewriting and let the system resolver handle all hostname lookups. If set to .Cm yes then, for connections that do not use a .Cm ProxyCommand or .Cm ProxyJump , .Xr ssh 1 will attempt to canonicalize the hostname specified on the command line using the .Cm CanonicalDomains suffixes and .Cm CanonicalizePermittedCNAMEs rules. If .Cm CanonicalizeHostname is set to .Cm always , then canonicalization is applied to proxied connections too. .Pp If this option is enabled, then the configuration files are processed again using the new target name to pick up any new configuration in matching .Cm Host and .Cm Match stanzas. A value of .Cm none disables the use of a .Cm ProxyJump host. .It Cm CanonicalizeMaxDots Specifies the maximum number of dot characters in a hostname before canonicalization is disabled. The default, 1, allows a single dot (i.e. hostname.subdomain). .It Cm CanonicalizePermittedCNAMEs Specifies rules to determine whether CNAMEs should be followed when canonicalizing hostnames. The rules consist of one or more arguments of .Ar source_domain_list : Ns Ar target_domain_list , where .Ar source_domain_list is a pattern-list of domains that may follow CNAMEs in canonicalization, and .Ar target_domain_list is a pattern-list of domains that they may resolve to. .Pp For example, .Qq *.a.example.com:*.b.example.com,*.c.example.com will allow hostnames matching .Qq *.a.example.com to be canonicalized to names in the .Qq *.b.example.com or .Qq *.c.example.com domains. .Pp A single argument of .Qq none causes no CNAMEs to be considered for canonicalization. This is the default behaviour. .It Cm CASignatureAlgorithms Specifies which algorithms are allowed for signing of certificates by certificate authorities (CAs). The default is: .Bd -literal -offset indent ssh-ed25519,ecdsa-sha2-nistp256, ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. .Pp .Xr ssh 1 will not accept host certificates signed using algorithms other than those specified. .It Cm CertificateFile Specifies a file from which the user's certificate is read. A corresponding private key must be provided separately in order to use this certificate either from an .Cm IdentityFile directive or .Fl i flag to .Xr ssh 1 , via .Xr ssh-agent 1 , or via a .Cm PKCS11Provider or .Cm SecurityKeyProvider . .Pp Arguments to .Cm CertificateFile may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .Pp It is possible to have multiple certificate files specified in configuration files; these certificates will be tried in sequence. Multiple .Cm CertificateFile directives will add to the list of certificates used for authentication. .It Cm CheckHostIP If set to .Cm yes , .Xr ssh 1 will additionally check the host IP address in the .Pa known_hosts file. This allows it to detect if a host key changed due to DNS spoofing and will add addresses of destination hosts to .Pa ~/.ssh/known_hosts in the process, regardless of the setting of .Cm StrictHostKeyChecking . If the option is set to .Cm no (the default), the check will not be executed. The default is .Cm no . .It Cm Ciphers Specifies the ciphers allowed and their order of preference. Multiple ciphers must be comma-separated. If the specified list begins with a .Sq + character, then the specified ciphers will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified ciphers (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified ciphers will be placed at the head of the default set. .Pp The supported ciphers are: .Bd -literal -offset indent 3des-cbc aes128-cbc aes192-cbc aes256-cbc aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com .Ed .Pp The default is: .Bd -literal -offset indent chacha20-poly1305@openssh.com, aes128-ctr,aes192-ctr,aes256-ctr, aes128-gcm@openssh.com,aes256-gcm@openssh.com .Ed .Pp The list of available ciphers may also be obtained using .Qq ssh -Q cipher . .It Cm ClearAllForwardings Specifies that all local, remote, and dynamic port forwardings specified in the configuration files or on the command line be cleared. This option is primarily useful when used from the .Xr ssh 1 command line to clear port forwardings set in configuration files, and is automatically set by .Xr scp 1 and .Xr sftp 1 . The argument must be .Cm yes or .Cm no (the default). .It Cm Compression Specifies whether to use compression. The argument must be .Cm yes or .Cm no (the default). .It Cm ConnectionAttempts Specifies the number of tries (one per second) to make before exiting. The argument must be an integer. This may be useful in scripts if the connection sometimes fails. The default is 1. .It Cm ConnectTimeout Specifies the timeout (in seconds) used when connecting to the SSH server, instead of using the default system TCP timeout. This timeout is applied both to establishing the connection and to performing the initial SSH protocol handshake and key exchange. .It Cm ControlMaster Enables the sharing of multiple sessions over a single network connection. When set to .Cm yes , .Xr ssh 1 will listen for connections on a control socket specified using the .Cm ControlPath argument. Additional sessions can connect to this socket using the same .Cm ControlPath with .Cm ControlMaster set to .Cm no (the default). These sessions will try to reuse the master instance's network connection rather than initiating new ones, but will fall back to connecting normally if the control socket does not exist, or is not listening. .Pp Setting this to .Cm ask will cause .Xr ssh 1 to listen for control connections, but require confirmation using .Xr ssh-askpass 1 . If the .Cm ControlPath cannot be opened, .Xr ssh 1 will continue without connecting to a master instance. .Pp X11 and .Xr ssh-agent 1 forwarding is supported over these multiplexed connections, however the display and agent forwarded will be the one belonging to the master connection i.e. it is not possible to forward multiple displays or agents. .Pp Two additional options allow for opportunistic multiplexing: try to use a master connection but fall back to creating a new one if one does not already exist. These options are: .Cm auto and .Cm autoask . The latter requires confirmation like the .Cm ask option. .It Cm ControlPath Specify the path to the control socket used for connection sharing as described in the .Cm ControlMaster section above or the string .Cm none to disable connection sharing. Arguments to .Cm ControlPath may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. It is recommended that any .Cm ControlPath used for opportunistic connection sharing include at least %h, %p, and %r (or alternatively %C) and be placed in a directory that is not writable by other users. This ensures that shared connections are uniquely identified. .It Cm ControlPersist When used in conjunction with .Cm ControlMaster , specifies that the master connection should remain open in the background (waiting for future client connections) after the initial client connection has been closed. If set to .Cm no (the default), then the master connection will not be placed into the background, and will close as soon as the initial client connection is closed. If set to .Cm yes or 0, then the master connection will remain in the background indefinitely (until killed or closed via a mechanism such as the .Qq ssh -O exit ) . If set to a time in seconds, or a time in any of the formats documented in .Xr sshd_config 5 , then the backgrounded master connection will automatically terminate after it has remained idle (with no client connections) for the specified time. .It Cm DynamicForward Specifies that a TCP port on the local machine be forwarded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine. .Pp The argument must be .Sm off .Oo Ar bind_address : Oc Ar port . .Sm on IPv6 addresses can be specified by enclosing addresses in square brackets. By default, the local port is bound in accordance with the .Cm GatewayPorts setting. However, an explicit .Ar bind_address may be used to bind the connection to a specific address. The .Ar bind_address of .Cm localhost indicates that the listening port be bound for local use only, while an empty address or .Sq * indicates that the port should be available from all interfaces. .Pp Currently the SOCKS4 and SOCKS5 protocols are supported, and .Xr ssh 1 will act as a SOCKS server. Multiple forwardings may be specified, and additional forwardings can be given on the command line. Only the superuser can forward privileged ports. .It Cm EnableEscapeCommandline Enables the command line option in the .Cm EscapeChar menu for interactive sessions (default .Ql ~C ) . By default, the command line is disabled. .It Cm EnableSSHKeysign Setting this option to .Cm yes in the global client configuration file .Pa /etc/ssh/ssh_config enables the use of the helper program .Xr ssh-keysign 8 during .Cm HostbasedAuthentication . The argument must be .Cm yes or .Cm no (the default). This option should be placed in the non-hostspecific section. See .Xr ssh-keysign 8 for more information. .It Cm EscapeChar Sets the escape character (default: .Ql ~ ) . The escape character can also be set on the command line. The argument should be a single character, .Ql ^ followed by a letter, or .Cm none to disable the escape character entirely (making the connection transparent for binary data). .It Cm ExitOnForwardFailure Specifies whether .Xr ssh 1 should terminate the connection if it cannot set up all requested dynamic, tunnel, local, and remote port forwardings, (e.g.\& if either end is unable to bind and listen on a specified port). Note that .Cm ExitOnForwardFailure does not apply to connections made over port forwardings and will not, for example, cause .Xr ssh 1 to exit if TCP connections to the ultimate forwarding destination fail. The argument must be .Cm yes or .Cm no (the default). .It Cm FingerprintHash Specifies the hash algorithm used when displaying key fingerprints. Valid options are: .Cm md5 and .Cm sha256 (the default). .It Cm ForkAfterAuthentication Requests .Nm ssh to go to background just before command execution. This is useful if .Nm ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies the .Cm StdinNull configuration option being set to .Dq yes . The recommended way to start X11 programs at a remote site is with something like .Ic ssh -f host xterm , which is the same as .Ic ssh host xterm if the .Cm ForkAfterAuthentication configuration option is set to .Dq yes . .Pp If the .Cm ExitOnForwardFailure configuration option is set to .Dq yes , then a client started with the .Cm ForkAfterAuthentication configuration option being set to .Dq yes will wait for all remote port forwards to be successfully established before placing itself in the background. The argument to this keyword must be .Cm yes (same as the .Fl f option) or .Cm no (the default). .It Cm ForwardAgent Specifies whether the connection to the authentication agent (if any) will be forwarded to the remote machine. The argument may be .Cm yes , .Cm no (the default), an explicit path to an agent socket or the name of an environment variable (beginning with .Sq $ ) in which to find the path. .Pp Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's Unix-domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent. .It Cm ForwardX11 Specifies whether X11 connections will be automatically redirected over the secure channel and .Ev DISPLAY set. The argument must be .Cm yes or .Cm no (the default). .Pp X11 forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the user's X11 authorization database) can access the local X11 display through the forwarded connection. An attacker may then be able to perform activities such as keystroke monitoring if the .Cm ForwardX11Trusted option is also enabled. .It Cm ForwardX11Timeout Specify a timeout for untrusted X11 forwarding using the format described in the .Sx TIME FORMATS section of .Xr sshd_config 5 . X11 connections received by .Xr ssh 1 after this time will be refused. Setting .Cm ForwardX11Timeout to zero will disable the timeout and permit X11 forwarding for the life of the connection. The default is to disable untrusted X11 forwarding after twenty minutes has elapsed. .It Cm ForwardX11Trusted If this option is set to .Cm yes , remote X11 clients will have full access to the original X11 display. .Pp If this option is set to .Cm no (the default), remote X11 clients will be considered untrusted and prevented from stealing or tampering with data belonging to trusted X11 clients. Furthermore, the .Xr xauth 1 token used for the session will be set to expire after 20 minutes. Remote clients will be refused access after this time. .Pp See the X11 SECURITY extension specification for full details on the restrictions imposed on untrusted clients. .It Cm GatewayPorts Specifies whether remote hosts are allowed to connect to local forwarded ports. By default, .Xr ssh 1 binds local port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports. .Cm GatewayPorts can be used to specify that ssh should bind local port forwardings to the wildcard address, thus allowing remote hosts to connect to forwarded ports. The argument must be .Cm yes or .Cm no (the default). .It Cm GlobalKnownHostsFile Specifies one or more files to use for the global host key database, separated by whitespace. The default is .Pa /etc/ssh/ssh_known_hosts , .Pa /etc/ssh/ssh_known_hosts2 . .It Cm GSSAPIAuthentication Specifies whether user authentication based on GSSAPI is allowed. The default is .Cm no . .It Cm GSSAPIDelegateCredentials Forward (delegate) credentials to the server. The default is .Cm no . .It Cm HashKnownHosts Indicates that .Xr ssh 1 should hash host names and addresses when they are added to .Pa ~/.ssh/known_hosts . These hashed names may be used normally by .Xr ssh 1 and .Xr sshd 8 , but they do not visually reveal identifying information if the file's contents are disclosed. The default is .Cm no . Note that existing names and addresses in known hosts files will not be converted automatically, but may be manually hashed using .Xr ssh-keygen 1 . .It Cm HostbasedAcceptedAlgorithms Specifies the signature algorithms that will be used for hostbased authentication as a comma-separated list of patterns. Alternately if the specified list begins with a .Sq + character, then the specified signature algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified signature algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified signature algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The .Fl Q option of .Xr ssh 1 may be used to list supported signature algorithms. This was formerly named HostbasedKeyTypes. .It Cm HostbasedAuthentication Specifies whether to try rhosts based authentication with public key authentication. The argument must be .Cm yes or .Cm no (the default). .It Cm HostKeyAlgorithms Specifies the host key signature algorithms that the client wants to use in order of preference. Alternately if the specified list begins with a .Sq + character, then the specified signature algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified signature algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified signature algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp If hostkeys are known for the destination host then this default is modified to prefer their algorithms. .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q HostKeyAlgorithms . .It Cm HostKeyAlias Specifies an alias that should be used instead of the real host name when looking up or saving the host key in the host key database files and when validating host certificates. This option is useful for tunneling SSH connections or for multiple servers running on a single host. .It Cm Hostname Specifies the real host name to log into. This can be used to specify nicknames or abbreviations for hosts. Arguments to .Cm Hostname accept the tokens described in the .Sx TOKENS section. Numeric IP addresses are also permitted (both on the command line and in .Cm Hostname specifications). The default is the name given on the command line. .It Cm IdentitiesOnly Specifies that .Xr ssh 1 should only use the configured authentication identity and certificate files (either the default files, or those explicitly configured in the .Nm files or passed on the .Xr ssh 1 command-line), even if .Xr ssh-agent 1 or a .Cm PKCS11Provider or .Cm SecurityKeyProvider offers more identities. The argument to this keyword must be .Cm yes or .Cm no (the default). This option is intended for situations where ssh-agent offers many different identities. .It Cm IdentityAgent Specifies the .Ux Ns -domain socket used to communicate with the authentication agent. .Pp This option overrides the .Ev SSH_AUTH_SOCK environment variable and can be used to select a specific agent. Setting the socket name to .Cm none disables the use of an authentication agent. If the string .Qq SSH_AUTH_SOCK is specified, the location of the socket will be read from the .Ev SSH_AUTH_SOCK environment variable. Otherwise if the specified value begins with a .Sq $ character, then it will be treated as an environment variable containing the location of the socket. .Pp Arguments to .Cm IdentityAgent may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .It Cm IdentityFile Specifies a file from which the user's DSA, ECDSA, authenticator-hosted ECDSA, Ed25519, authenticator-hosted Ed25519 or RSA authentication identity is read. You can also specify a public key file to use the corresponding private key that is loaded in .Xr ssh-agent 1 when the private key file is not present locally. The default is .Pa ~/.ssh/id_rsa , .Pa ~/.ssh/id_ecdsa , .Pa ~/.ssh/id_ecdsa_sk , .Pa ~/.ssh/id_ed25519 , .Pa ~/.ssh/id_ed25519_sk and .Pa ~/.ssh/id_dsa . Additionally, any identities represented by the authentication agent will be used for authentication unless .Cm IdentitiesOnly is set. If no certificates have been explicitly specified by .Cm CertificateFile , .Xr ssh 1 will try to load certificate information from the filename obtained by appending .Pa -cert.pub to the path of a specified .Cm IdentityFile . .Pp Arguments to .Cm IdentityFile may use the tilde syntax to refer to a user's home directory or the tokens described in the .Sx TOKENS section. .Pp It is possible to have multiple identity files specified in configuration files; all these identities will be tried in sequence. Multiple .Cm IdentityFile directives will add to the list of identities tried (this behaviour differs from that of other configuration directives). .Pp .Cm IdentityFile may be used in conjunction with .Cm IdentitiesOnly to select which identities in an agent are offered during authentication. .Cm IdentityFile may also be used in conjunction with .Cm CertificateFile in order to provide any certificate also needed for authentication with the identity. .It Cm IgnoreUnknown Specifies a pattern-list of unknown options to be ignored if they are encountered in configuration parsing. This may be used to suppress errors if .Nm contains options that are unrecognised by .Xr ssh 1 . It is recommended that .Cm IgnoreUnknown be listed early in the configuration file as it will not be applied to unknown options that appear before it. .It Cm Include Include the specified configuration file(s). Multiple pathnames may be specified and each pathname may contain .Xr glob 7 wildcards and, for user configurations, shell-like .Sq ~ references to user home directories. Wildcards will be expanded and processed in lexical order. Files without absolute paths are assumed to be in .Pa ~/.ssh if included in a user configuration file or .Pa /etc/ssh if included from the system configuration file. .Cm Include directive may appear inside a .Cm Match or .Cm Host block to perform conditional inclusion. .It Cm IPQoS Specifies the IPv4 type-of-service or DSCP class for connections. Accepted values are .Cm af11 , .Cm af12 , .Cm af13 , .Cm af21 , .Cm af22 , .Cm af23 , .Cm af31 , .Cm af32 , .Cm af33 , .Cm af41 , .Cm af42 , .Cm af43 , .Cm cs0 , .Cm cs1 , .Cm cs2 , .Cm cs3 , .Cm cs4 , .Cm cs5 , .Cm cs6 , .Cm cs7 , .Cm ef , .Cm le , .Cm lowdelay , .Cm throughput , .Cm reliability , a numeric value, or .Cm none to use the operating system default. This option may take one or two arguments, separated by whitespace. If one argument is specified, it is used as the packet class unconditionally. If two values are specified, the first is automatically selected for interactive sessions and the second for non-interactive sessions. The default is .Cm af21 (Low-Latency Data) for interactive sessions and .Cm cs1 (Lower Effort) for non-interactive sessions. .It Cm KbdInteractiveAuthentication Specifies whether to use keyboard-interactive authentication. The argument to this keyword must be .Cm yes (the default) or .Cm no . .Cm ChallengeResponseAuthentication is a deprecated alias for this. .It Cm KbdInteractiveDevices Specifies the list of methods to use in keyboard-interactive authentication. Multiple method names must be comma-separated. The default is to use the server specified list. The methods available vary depending on what the server supports. For an OpenSSH server, it may be zero or more of: .Cm bsdauth and .Cm pam . .It Cm KexAlgorithms Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must be comma-separated. If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. The default is: .Bd -literal -offset indent sntrup761x25519-sha512@openssh.com, curve25519-sha256,curve25519-sha256@libssh.org, ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256 .Ed .Pp The list of available key exchange algorithms may also be obtained using .Qq ssh -Q kex . .It Cm KnownHostsCommand Specifies a command to use to obtain a list of host keys, in addition to those listed in .Cm UserKnownHostsFile and .Cm GlobalKnownHostsFile . This command is executed after the files have been read. It may write host key lines to standard output in identical format to the usual files (described in the .Sx VERIFYING HOST KEYS section in .Xr ssh 1 ) . Arguments to .Cm KnownHostsCommand accept the tokens described in the .Sx TOKENS section. The command may be invoked multiple times per connection: once when preparing the preference list of host key algorithms to use, again to obtain the host key for the requested host name and, if .Cm CheckHostIP is enabled, one more time to obtain the host key matching the server's address. If the command exits abnormally or returns a non-zero exit status then the connection is terminated. .It Cm LocalCommand Specifies a command to execute on the local machine after successfully connecting to the server. The command string extends to the end of the line, and is executed with the user's shell. Arguments to .Cm LocalCommand accept the tokens described in the .Sx TOKENS section. .Pp The command is run synchronously and does not have access to the session of the .Xr ssh 1 that spawned it. It should not be used for interactive commands. .Pp This directive is ignored unless .Cm PermitLocalCommand has been enabled. .It Cm LocalForward Specifies that a TCP port on the local machine be forwarded over the secure channel to the specified host and port from the remote machine. The first argument specifies the listener and may be .Sm off .Oo Ar bind_address : Oc Ar port .Sm on or a Unix domain socket path. The second argument is the destination and may be .Ar host : Ns Ar hostport or a Unix domain socket path if the remote host supports it. .Pp IPv6 addresses can be specified by enclosing addresses in square brackets. Multiple forwardings may be specified, and additional forwardings can be given on the command line. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the .Cm GatewayPorts setting. However, an explicit .Ar bind_address may be used to bind the connection to a specific address. The .Ar bind_address of .Cm localhost indicates that the listening port be bound for local use only, while an empty address or .Sq * indicates that the port should be available from all interfaces. Unix domain socket paths may use the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .It Cm LogLevel Gives the verbosity level that is used when logging messages from .Xr ssh 1 . The possible values are: QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is INFO. DEBUG and DEBUG1 are equivalent. DEBUG2 and DEBUG3 each specify higher levels of verbose output. .It Cm LogVerbose Specify one or more overrides to LogLevel. An override consists of a pattern lists that matches the source file, function and line number to force detailed logging for. For example, an override pattern of: .Bd -literal -offset indent kex.c:*:1000,*:kex_exchange_identification():*,packet.c:* .Ed .Pp would enable detailed logging for line 1000 of .Pa kex.c , everything in the .Fn kex_exchange_identification function, and all code in the .Pa packet.c file. This option is intended for debugging and no overrides are enabled by default. .It Cm MACs Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used for data integrity protection. Multiple algorithms must be comma-separated. If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. .Pp The algorithms that contain .Qq -etm calculate the MAC after encryption (encrypt-then-mac). These are considered safer and their use recommended. .Pp The default is: .Bd -literal -offset indent umac-64-etm@openssh.com,umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com,umac-128@openssh.com, hmac-sha2-256,hmac-sha2-512,hmac-sha1 .Ed .Pp The list of available MAC algorithms may also be obtained using .Qq ssh -Q mac . .It Cm NoHostAuthenticationForLocalhost Disable host authentication for localhost (loopback addresses). The argument to this keyword must be .Cm yes or .Cm no (the default). .It Cm NumberOfPasswordPrompts Specifies the number of password prompts before giving up. The argument to this keyword must be an integer. The default is 3. +.It Cm ObscureKeystrokeTiming +Specifies whether +.Xr ssh 1 +should try to obscure inter-keystroke timings from passive observers of +network traffic. +If enabled, then for interactive sessions, +.Xr ssh 1 +will send keystrokes at fixed intervals of a few tens of milliseconds +and will send fake keystroke packets for some time after typing ceases. +The argument to this keyword must be +.Cm yes , +.Cm no +or an interval specifier of the form +.Cm interval:milliseconds +(e.g.\& +.Cm interval:80 +for 80 milliseconds). +The default is to obscure keystrokes using a 20ms packet interval. +Note that smaller intervals will result in higher fake keystroke packet rates. .It Cm PasswordAuthentication Specifies whether to use password authentication. The argument to this keyword must be .Cm yes (the default) or .Cm no . .It Cm PermitLocalCommand Allow local command execution via the .Ic LocalCommand option or using the .Ic !\& Ns Ar command escape sequence in .Xr ssh 1 . The argument must be .Cm yes or .Cm no (the default). .It Cm PermitRemoteOpen Specifies the destinations to which remote TCP port forwarding is permitted when .Cm RemoteForward is used as a SOCKS proxy. The forwarding specification must be one of the following forms: .Pp .Bl -item -offset indent -compact .It .Cm PermitRemoteOpen .Sm off .Ar host : port .Sm on .It .Cm PermitRemoteOpen .Sm off .Ar IPv4_addr : port .Sm on .It .Cm PermitRemoteOpen .Sm off .Ar \&[ IPv6_addr \&] : port .Sm on .El .Pp Multiple forwards may be specified by separating them with whitespace. An argument of .Cm any can be used to remove all restrictions and permit any forwarding requests. An argument of .Cm none can be used to prohibit all forwarding requests. The wildcard .Sq * can be used for host or port to allow all hosts or ports respectively. Otherwise, no pattern matching or address lookups are performed on supplied names. .It Cm PKCS11Provider Specifies which PKCS#11 provider to use or .Cm none to indicate that no provider should be used (the default). The argument to this keyword is a path to the PKCS#11 shared library .Xr ssh 1 should use to communicate with a PKCS#11 token providing keys for user authentication. .It Cm Port Specifies the port number to connect on the remote host. The default is 22. .It Cm PreferredAuthentications Specifies the order in which the client should try authentication methods. This allows a client to prefer one method (e.g.\& .Cm keyboard-interactive ) over another method (e.g.\& .Cm password ) . The default is: .Bd -literal -offset indent gssapi-with-mic,hostbased,publickey, keyboard-interactive,password .Ed .It Cm ProxyCommand Specifies the command to use to connect to the server. The command string extends to the end of the line, and is executed using the user's shell .Ql exec directive to avoid a lingering shell process. .Pp Arguments to .Cm ProxyCommand accept the tokens described in the .Sx TOKENS section. The command can be basically anything, and should read from its standard input and write to its standard output. It should eventually connect an .Xr sshd 8 server running on some machine, or execute .Ic sshd -i somewhere. Host key management will be done using the .Cm Hostname of the host being connected (defaulting to the name typed by the user). Setting the command to .Cm none disables this option entirely. Note that .Cm CheckHostIP is not available for connects with a proxy command. .Pp This directive is useful in conjunction with .Xr nc 1 and its proxy support. For example, the following directive would connect via an HTTP proxy at 192.0.2.0: .Bd -literal -offset 3n ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p .Ed .It Cm ProxyJump Specifies one or more jump proxies as either .Xo .Sm off .Op Ar user No @ .Ar host .Op : Ns Ar port .Sm on or an ssh URI .Xc . Multiple proxies may be separated by comma characters and will be visited sequentially. Setting this option will cause .Xr ssh 1 to connect to the target host by first making a .Xr ssh 1 connection to the specified .Cm ProxyJump host and then establishing a TCP forwarding to the ultimate target from there. Setting the host to .Cm none disables this option entirely. .Pp Note that this option will compete with the .Cm ProxyCommand option - whichever is specified first will prevent later instances of the other from taking effect. .Pp Note also that the configuration for the destination host (either supplied via the command-line or the configuration file) is not generally applied to jump hosts. .Pa ~/.ssh/config should be used if specific configuration is required for jump hosts. .It Cm ProxyUseFdpass Specifies that .Cm ProxyCommand will pass a connected file descriptor back to .Xr ssh 1 instead of continuing to execute and pass data. The default is .Cm no . .It Cm PubkeyAcceptedAlgorithms Specifies the signature algorithms that will be used for public key authentication as a comma-separated list of patterns. If the specified list begins with a .Sq + character, then the algorithms after it will be appended to the default instead of replacing it. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q PubkeyAcceptedAlgorithms . .It Cm PubkeyAuthentication Specifies whether to try public key authentication. The argument to this keyword must be .Cm yes (the default), .Cm no , .Cm unbound or .Cm host-bound . The final two options enable public key authentication while respectively disabling or enabling the OpenSSH host-bound authentication protocol extension required for restricted .Xr ssh-agent 1 forwarding. .It Cm RekeyLimit Specifies the maximum amount of data that may be transmitted or received before the session key is renegotiated, optionally followed by a maximum amount of time that may pass before the session key is renegotiated. The first argument is specified in bytes and may have a suffix of .Sq K , .Sq M , or .Sq G to indicate Kilobytes, Megabytes, or Gigabytes, respectively. The default is between .Sq 1G and .Sq 4G , depending on the cipher. The optional second value is specified in seconds and may use any of the units documented in the TIME FORMATS section of .Xr sshd_config 5 . The default value for .Cm RekeyLimit is .Cm default none , which means that rekeying is performed after the cipher's default amount of data has been sent or received and no time based rekeying is done. .It Cm RemoteCommand Specifies a command to execute on the remote machine after successfully connecting to the server. The command string extends to the end of the line, and is executed with the user's shell. Arguments to .Cm RemoteCommand accept the tokens described in the .Sx TOKENS section. .It Cm RemoteForward Specifies that a TCP port on the remote machine be forwarded over the secure channel. The remote port may either be forwarded to a specified host and port from the local machine, or may act as a SOCKS 4/5 proxy that allows a remote client to connect to arbitrary destinations from the local machine. The first argument is the listening specification and may be .Sm off .Oo Ar bind_address : Oc Ar port .Sm on or, if the remote host supports it, a Unix domain socket path. If forwarding to a specific destination then the second argument must be .Ar host : Ns Ar hostport or a Unix domain socket path, otherwise if no destination argument is specified then the remote forwarding will be established as a SOCKS proxy. When acting as a SOCKS proxy, the destination of the connection can be restricted by .Cm PermitRemoteOpen . .Pp IPv6 addresses can be specified by enclosing addresses in square brackets. Multiple forwardings may be specified, and additional forwardings can be given on the command line. Privileged ports can be forwarded only when logging in as root on the remote machine. Unix domain socket paths may use the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .Pp If the .Ar port argument is 0, the listen port will be dynamically allocated on the server and reported to the client at run time. .Pp If the .Ar bind_address is not specified, the default is to only bind to loopback addresses. If the .Ar bind_address is .Ql * or an empty string, then the forwarding is requested to listen on all interfaces. Specifying a remote .Ar bind_address will only succeed if the server's .Cm GatewayPorts option is enabled (see .Xr sshd_config 5 ) . .It Cm RequestTTY Specifies whether to request a pseudo-tty for the session. The argument may be one of: .Cm no (never request a TTY), .Cm yes (always request a TTY when standard input is a TTY), .Cm force (always request a TTY) or .Cm auto (request a TTY when opening a login session). This option mirrors the .Fl t and .Fl T flags for .Xr ssh 1 . .It Cm RequiredRSASize Specifies the minimum RSA key size (in bits) that .Xr ssh 1 will accept. User authentication keys smaller than this limit will be ignored. Servers that present host keys smaller than this limit will cause the connection to be terminated. The default is .Cm 1024 bits. Note that this limit may only be raised from the default. .It Cm RevokedHostKeys Specifies revoked host public keys. Keys listed in this file will be refused for host authentication. Note that if this file does not exist or is not readable, then host authentication will be refused for all hosts. Keys may be specified as a text file, listing one public key per line, or as an OpenSSH Key Revocation List (KRL) as generated by .Xr ssh-keygen 1 . For more information on KRLs, see the KEY REVOCATION LISTS section in .Xr ssh-keygen 1 . Arguments to .Cm RevokedHostKeys may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .It Cm SecurityKeyProvider Specifies a path to a library that will be used when loading any FIDO authenticator-hosted keys, overriding the default of using the built-in USB HID support. .Pp If the specified value begins with a .Sq $ character, then it will be treated as an environment variable containing the path to the library. .It Cm SendEnv Specifies what variables from the local .Xr environ 7 should be sent to the server. The server must also support it, and the server must be configured to accept these environment variables. Note that the .Ev TERM environment variable is always sent whenever a pseudo-terminal is requested as it is required by the protocol. Refer to .Cm AcceptEnv in .Xr sshd_config 5 for how to configure the server. Variables are specified by name, which may contain wildcard characters. Multiple environment variables may be separated by whitespace or spread across multiple .Cm SendEnv directives. .Pp See .Sx PATTERNS for more information on patterns. .Pp It is possible to clear previously set .Cm SendEnv variable names by prefixing patterns with .Pa - . The default is not to send any environment variables. .It Cm ServerAliveCountMax Sets the number of server alive messages (see below) which may be sent without .Xr ssh 1 receiving any messages back from the server. If this threshold is reached while server alive messages are being sent, ssh will disconnect from the server, terminating the session. It is important to note that the use of server alive messages is very different from .Cm TCPKeepAlive (below). The server alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by .Cm TCPKeepAlive is spoofable. The server alive mechanism is valuable when the client or server depend on knowing when a connection has become unresponsive. .Pp The default value is 3. If, for example, .Cm ServerAliveInterval (see below) is set to 15 and .Cm ServerAliveCountMax is left at the default, if the server becomes unresponsive, ssh will disconnect after approximately 45 seconds. .It Cm ServerAliveInterval Sets a timeout interval in seconds after which if no data has been received from the server, .Xr ssh 1 will send a message through the encrypted channel to request a response from the server. The default is 0, indicating that these messages will not be sent to the server. .It Cm SessionType May be used to either request invocation of a subsystem on the remote system, or to prevent the execution of a remote command at all. The latter is useful for just forwarding ports. The argument to this keyword must be .Cm none (same as the .Fl N option), .Cm subsystem (same as the .Fl s option) or .Cm default (shell or command execution). .It Cm SetEnv Directly specify one or more environment variables and their contents to be sent to the server. Similarly to .Cm SendEnv , with the exception of the .Ev TERM variable, the server must be prepared to accept the environment variable. .It Cm StdinNull Redirects stdin from .Pa /dev/null (actually, prevents reading from stdin). Either this or the equivalent .Fl n option must be used when .Nm ssh is run in the background. The argument to this keyword must be .Cm yes (same as the .Fl n option) or .Cm no (the default). .It Cm StreamLocalBindMask Sets the octal file creation mode mask .Pq umask used when creating a Unix-domain socket file for local or remote port forwarding. This option is only used for port forwarding to a Unix-domain socket file. .Pp The default value is 0177, which creates a Unix-domain socket file that is readable and writable only by the owner. Note that not all operating systems honor the file mode on Unix-domain socket files. .It Cm StreamLocalBindUnlink Specifies whether to remove an existing Unix-domain socket file for local or remote port forwarding before creating a new one. If the socket file already exists and .Cm StreamLocalBindUnlink is not enabled, .Nm ssh will be unable to forward the port to the Unix-domain socket file. This option is only used for port forwarding to a Unix-domain socket file. .Pp The argument must be .Cm yes or .Cm no (the default). .It Cm StrictHostKeyChecking If this flag is set to .Cm yes , .Xr ssh 1 will never automatically add host keys to the .Pa ~/.ssh/known_hosts file, and refuses to connect to hosts whose host key has changed. This provides maximum protection against man-in-the-middle (MITM) attacks, though it can be annoying when the .Pa /etc/ssh/ssh_known_hosts file is poorly maintained or when connections to new hosts are frequently made. This option forces the user to manually add all new hosts. .Pp If this flag is set to .Cm accept-new then ssh will automatically add new host keys to the user's .Pa known_hosts file, but will not permit connections to hosts with changed host keys. If this flag is set to .Cm no or .Cm off , ssh will automatically add new host keys to the user known hosts files and allow connections to hosts with changed hostkeys to proceed, subject to some restrictions. If this flag is set to .Cm ask (the default), new host keys will be added to the user known host files only after the user has confirmed that is what they really want to do, and ssh will refuse to connect to hosts whose host key has changed. The host keys of known hosts will be verified automatically in all cases. .It Cm SyslogFacility Gives the facility code that is used when logging messages from .Xr ssh 1 . The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The default is USER. .It Cm TCPKeepAlive Specifies whether the system should send TCP keepalive messages to the other side. If they are sent, death of the connection or crash of one of the machines will be properly noticed. However, this means that connections will die if the route is down temporarily, and some people find it annoying. .Pp The default is .Cm yes (to send TCP keepalive messages), and the client will notice if the network goes down or the remote host dies. This is important in scripts, and many users want it too. .Pp To disable TCP keepalive messages, the value should be set to .Cm no . See also .Cm ServerAliveInterval for protocol-level keepalives. .It Cm Tag Specify a configuration tag name that may be later used by a .Cm Match directive to select a block of configuration. .It Cm Tunnel Request .Xr tun 4 device forwarding between the client and the server. The argument must be .Cm yes , .Cm point-to-point (layer 3), .Cm ethernet (layer 2), or .Cm no (the default). Specifying .Cm yes requests the default tunnel mode, which is .Cm point-to-point . .It Cm TunnelDevice Specifies the .Xr tun 4 devices to open on the client .Pq Ar local_tun and the server .Pq Ar remote_tun . .Pp The argument must be .Sm off .Ar local_tun Op : Ar remote_tun . .Sm on The devices may be specified by numerical ID or the keyword .Cm any , which uses the next available tunnel device. If .Ar remote_tun is not specified, it defaults to .Cm any . The default is .Cm any:any . .It Cm UpdateHostKeys Specifies whether .Xr ssh 1 should accept notifications of additional hostkeys from the server sent after authentication has completed and add them to .Cm UserKnownHostsFile . The argument must be .Cm yes , .Cm no or .Cm ask . This option allows learning alternate hostkeys for a server and supports graceful key rotation by allowing a server to send replacement public keys before old ones are removed. .Pp Additional hostkeys are only accepted if the key used to authenticate the host was already trusted or explicitly accepted by the user, the host was authenticated via .Cm UserKnownHostsFile (i.e. not .Cm GlobalKnownHostsFile ) and the host was authenticated using a plain key and not a certificate. .Pp .Cm UpdateHostKeys is enabled by default if the user has not overridden the default .Cm UserKnownHostsFile setting and has not enabled .Cm VerifyHostKeyDNS , otherwise .Cm UpdateHostKeys will be set to .Cm no . .Pp If .Cm UpdateHostKeys is set to .Cm ask , then the user is asked to confirm the modifications to the known_hosts file. Confirmation is currently incompatible with .Cm ControlPersist , and will be disabled if it is enabled. .Pp Presently, only .Xr sshd 8 from OpenSSH 6.8 and greater support the .Qq hostkeys@openssh.com protocol extension used to inform the client of all the server's hostkeys. .It Cm User Specifies the user to log in as. This can be useful when a different user name is used on different machines. This saves the trouble of having to remember to give the user name on the command line. .It Cm UserKnownHostsFile Specifies one or more files to use for the user host key database, separated by whitespace. Each filename may use tilde notation to refer to the user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. A value of .Cm none causes .Xr ssh 1 to ignore any user-specific known hosts files. The default is .Pa ~/.ssh/known_hosts , .Pa ~/.ssh/known_hosts2 . .It Cm VerifyHostKeyDNS Specifies whether to verify the remote key using DNS and SSHFP resource records. If this option is set to .Cm yes , the client will implicitly trust keys that match a secure fingerprint from DNS. Insecure fingerprints will be handled as if this option was set to .Cm ask . If this option is set to .Cm ask , information on fingerprint match will be displayed, but the user will still need to confirm new host keys according to the .Cm StrictHostKeyChecking option. The default is .Cm no . .Pp See also .Sx VERIFYING HOST KEYS in .Xr ssh 1 . .It Cm VisualHostKey If this flag is set to .Cm yes , an ASCII art representation of the remote host key fingerprint is printed in addition to the fingerprint string at login and for unknown host keys. If this flag is set to .Cm no (the default), no fingerprint strings are printed at login and only the fingerprint string will be printed for unknown host keys. .It Cm XAuthLocation Specifies the full pathname of the .Xr xauth 1 program. The default is .Pa /usr/local/bin/xauth . .El .Sh PATTERNS A .Em pattern consists of zero or more non-whitespace characters, .Sq * (a wildcard that matches zero or more characters), or .Sq ?\& (a wildcard that matches exactly one character). For example, to specify a set of declarations for any host in the .Qq .co.uk set of domains, the following pattern could be used: .Pp .Dl Host *.co.uk .Pp The following pattern would match any host in the 192.168.0.[0-9] network range: .Pp .Dl Host 192.168.0.? .Pp A .Em pattern-list is a comma-separated list of patterns. Patterns within pattern-lists may be negated by preceding them with an exclamation mark .Pq Sq !\& . For example, to allow a key to be used from anywhere within an organization except from the .Qq dialup pool, the following entry (in authorized_keys) could be used: .Pp .Dl from=\&"!*.dialup.example.com,*.example.com\&" .Pp Note that a negated match will never produce a positive result by itself. For example, attempting to match .Qq host3 against the following pattern-list will fail: .Pp .Dl from=\&"!host1,!host2\&" .Pp The solution here is to include a term that will yield a positive match, such as a wildcard: .Pp .Dl from=\&"!host1,!host2,*\&" .Sh TOKENS Arguments to some keywords can make use of tokens, which are expanded at runtime: .Pp .Bl -tag -width XXXX -offset indent -compact .It %% A literal .Sq % . .It \&%C Hash of %l%h%p%r. .It %d Local user's home directory. .It %f The fingerprint of the server's host key. .It %H The .Pa known_hosts hostname or address that is being searched for. .It %h The remote hostname. .It \%%I A string describing the reason for a .Cm KnownHostsCommand execution: either .Cm ADDRESS when looking up a host by address (only when .Cm CheckHostIP is enabled), .Cm HOSTNAME when searching by hostname, or .Cm ORDER when preparing the host key algorithm preference list to use for the destination host. .It %i The local user ID. .It %K The base64 encoded host key. .It %k The host key alias if specified, otherwise the original remote hostname given on the command line. .It %L The local hostname. .It %l The local hostname, including the domain name. .It %n The original remote hostname, as given on the command line. .It %p The remote port. .It %r The remote username. .It \&%T The local .Xr tun 4 or .Xr tap 4 network interface assigned if tunnel forwarding was requested, or .Qq NONE otherwise. .It %t The type of the server host key, e.g. .Cm ssh-ed25519 . .It %u The local username. .El .Pp .Cm CertificateFile , .Cm ControlPath , .Cm IdentityAgent , .Cm IdentityFile , .Cm KnownHostsCommand , .Cm LocalForward , .Cm Match exec , .Cm RemoteCommand , .Cm RemoteForward , .Cm RevokedHostKeys , and .Cm UserKnownHostsFile accept the tokens %%, %C, %d, %h, %i, %k, %L, %l, %n, %p, %r, and %u. .Pp .Cm KnownHostsCommand additionally accepts the tokens %f, %H, %I, %K and %t. .Pp .Cm Hostname accepts the tokens %% and %h. .Pp .Cm LocalCommand accepts all tokens. .Pp .Cm ProxyCommand and .Cm ProxyJump accept the tokens %%, %h, %n, %p, and %r. +.Pp +Note that some of these directives build commands for execution via the shell. +Because +.Xr ssh 1 +performs no filtering or escaping of characters that have special meaning in +shell commands (e.g. quotes), it is the user's reposibility to ensure that +the arguments passed to +.Xr ssh 1 +do not contain such characters and that tokens are appropriately quoted +when used. .Sh ENVIRONMENT VARIABLES Arguments to some keywords can be expanded at runtime from environment variables on the client by enclosing them in .Ic ${} , for example .Ic ${HOME}/.ssh would refer to the user's .ssh directory. If a specified environment variable does not exist then an error will be returned and the setting for that keyword will be ignored. .Pp The keywords .Cm CertificateFile , .Cm ControlPath , .Cm IdentityAgent , .Cm IdentityFile , .Cm KnownHostsCommand , and .Cm UserKnownHostsFile support environment variables. The keywords .Cm LocalForward and .Cm RemoteForward support environment variables only for Unix domain socket paths. .Sh FILES .Bl -tag -width Ds .It Pa ~/.ssh/config This is the per-user configuration file. The format of this file is described above. This file is used by the SSH client. Because of the potential for abuse, this file must have strict permissions: read/write for the user, and not writable by others. .It Pa /etc/ssh/ssh_config Systemwide configuration file. This file provides defaults for those values that are not specified in the user's configuration file, and for those users who do not have a configuration file. This file must be world-readable. .El .Sh SEE ALSO .Xr ssh 1 .Sh AUTHORS .An -nosplit OpenSSH is a derivative of the original and free ssh 1.2.12 release by .An Tatu Ylonen . .An Aaron Campbell , Bob Beck , Markus Friedl , .An Niels Provos , Theo de Raadt and .An Dug Song removed many bugs, re-added newer features and created OpenSSH. .An Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. diff --git a/crypto/openssh/ssh_namespace.h b/crypto/openssh/ssh_namespace.h index b4720fbd17fb..083f73ca200f 100644 --- a/crypto/openssh/ssh_namespace.h +++ b/crypto/openssh/ssh_namespace.h @@ -1,1016 +1,1019 @@ /* * This file was machine-@generated. Do not edit manually. * Run crypto/openssh/freebsd-namespace.sh to regenerate. */ #define Blowfish_decipher Fssh_Blowfish_decipher #define Blowfish_encipher Fssh_Blowfish_encipher #define Blowfish_expand0state Fssh_Blowfish_expand0state #define Blowfish_expandstate Fssh_Blowfish_expandstate #define Blowfish_initstate Fssh_Blowfish_initstate #define Blowfish_stream2word Fssh_Blowfish_stream2word #define Decode Fssh_Decode #define EVP_CIPHER_CTX_get_iv Fssh_EVP_CIPHER_CTX_get_iv #define EVP_CIPHER_CTX_set_iv Fssh_EVP_CIPHER_CTX_set_iv #define Encode Fssh_Encode #define Hide Fssh_Hide #define Rq_mult_small Fssh_Rq_mult_small #define Short_random Fssh_Short_random #define _ssh__compat_glob Fssh__ssh__compat_glob #define _ssh__compat_globfree Fssh__ssh__compat_globfree #define _ssh_exchange_banner Fssh__ssh_exchange_banner #define _ssh_host_key_sign Fssh__ssh_host_key_sign #define _ssh_host_private_key Fssh__ssh_host_private_key #define _ssh_host_public_key Fssh__ssh_host_public_key #define _ssh_mkstemp Fssh__ssh_mkstemp #define _ssh_order_hostkeyalgs Fssh__ssh_order_hostkeyalgs #define _ssh_read_banner Fssh__ssh_read_banner #define _ssh_send_banner Fssh__ssh_send_banner #define _ssh_verify_host_key Fssh__ssh_verify_host_key #define a2port Fssh_a2port #define a2tun Fssh_a2tun #define add_host_to_hostfile Fssh_add_host_to_hostfile #define add_p1p1 Fssh_add_p1p1 #define addargs Fssh_addargs #define addr_and Fssh_addr_and #define addr_cmp Fssh_addr_cmp #define addr_host_is_all0s Fssh_addr_host_is_all0s #define addr_host_to_all1s Fssh_addr_host_to_all1s #define addr_hostmask Fssh_addr_hostmask #define addr_increment Fssh_addr_increment #define addr_invert Fssh_addr_invert #define addr_is_all0s Fssh_addr_is_all0s #define addr_match_cidr_list Fssh_addr_match_cidr_list #define addr_match_list Fssh_addr_match_list #define addr_netmask Fssh_addr_netmask #define addr_netmatch Fssh_addr_netmatch #define addr_ntop Fssh_addr_ntop #define addr_or Fssh_addr_or #define addr_pton Fssh_addr_pton #define addr_pton_cidr Fssh_addr_pton_cidr #define addr_sa_pton Fssh_addr_sa_pton #define addr_sa_to_xaddr Fssh_addr_sa_to_xaddr #define addr_unicast_masklen Fssh_addr_unicast_masklen #define addr_xaddr_to_sa Fssh_addr_xaddr_to_sa #define argv_assemble Fssh_argv_assemble #define argv_consume Fssh_argv_consume #define argv_free Fssh_argv_free #define argv_next Fssh_argv_next #define argv_split Fssh_argv_split #define ask_permission Fssh_ask_permission #define asmprintf Fssh_asmprintf #define atoi_err Fssh_atoi_err #define atomicio Fssh_atomicio #define atomicio6 Fssh_atomicio6 #define atomiciov Fssh_atomiciov #define atomiciov6 Fssh_atomiciov6 #define bandwidth_limit Fssh_bandwidth_limit #define bandwidth_limit_init Fssh_bandwidth_limit_init #define barrett_reduce Fssh_barrett_reduce #define baud_to_speed Fssh_baud_to_speed #define bcrypt_hash Fssh_bcrypt_hash #define bcrypt_pbkdf Fssh_bcrypt_pbkdf #define bitmap_clear_bit Fssh_bitmap_clear_bit #define bitmap_free Fssh_bitmap_free #define bitmap_from_string Fssh_bitmap_from_string #define bitmap_nbits Fssh_bitmap_nbits #define bitmap_nbytes Fssh_bitmap_nbytes #define bitmap_new Fssh_bitmap_new #define bitmap_set_bit Fssh_bitmap_set_bit #define bitmap_test_bit Fssh_bitmap_test_bit #define bitmap_to_string Fssh_bitmap_to_string #define bitmap_zero Fssh_bitmap_zero #define blf_cbc_decrypt Fssh_blf_cbc_decrypt #define blf_cbc_encrypt Fssh_blf_cbc_encrypt #define blf_dec Fssh_blf_dec #define blf_ecb_decrypt Fssh_blf_ecb_decrypt #define blf_ecb_encrypt Fssh_blf_ecb_encrypt #define blf_enc Fssh_blf_enc #define blf_key Fssh_blf_key #define blob_section Fssh_blob_section #define cert_free Fssh_cert_free #define cert_parse Fssh_cert_parse #define chacha_encrypt_bytes Fssh_chacha_encrypt_bytes #define chacha_ivsetup Fssh_chacha_ivsetup #define chacha_keysetup Fssh_chacha_keysetup #define chachapoly_crypt Fssh_chachapoly_crypt #define chachapoly_free Fssh_chachapoly_free #define chachapoly_get_length Fssh_chachapoly_get_length #define chachapoly_new Fssh_chachapoly_new #define chan_ibuf_empty Fssh_chan_ibuf_empty #define chan_is_dead Fssh_chan_is_dead #define chan_mark_dead Fssh_chan_mark_dead #define chan_obuf_empty Fssh_chan_obuf_empty #define chan_rcvd_eow Fssh_chan_rcvd_eow #define chan_rcvd_ieof Fssh_chan_rcvd_ieof #define chan_rcvd_oclose Fssh_chan_rcvd_oclose #define chan_read_failed Fssh_chan_read_failed #define chan_send_close2 Fssh_chan_send_close2 #define chan_send_eof2 Fssh_chan_send_eof2 #define chan_shutdown_extended_read Fssh_chan_shutdown_extended_read #define chan_shutdown_read Fssh_chan_shutdown_read #define chan_shutdown_write Fssh_chan_shutdown_write #define chan_write_failed Fssh_chan_write_failed #define channel_add_permission Fssh_channel_add_permission #define channel_add_timeout Fssh_channel_add_timeout #define channel_after_poll Fssh_channel_after_poll #define channel_by_id Fssh_channel_by_id #define channel_by_remote_id Fssh_channel_by_remote_id #define channel_cancel_cleanup Fssh_channel_cancel_cleanup #define channel_cancel_lport_listener Fssh_channel_cancel_lport_listener #define channel_cancel_rport_listener Fssh_channel_cancel_rport_listener #define channel_clear_permission Fssh_channel_clear_permission #define channel_clear_timeouts Fssh_channel_clear_timeouts #define channel_close_all Fssh_channel_close_all #define channel_close_fd Fssh_channel_close_fd #define channel_connect_by_listen_address Fssh_channel_connect_by_listen_address #define channel_connect_by_listen_path Fssh_channel_connect_by_listen_path #define channel_connect_stdio_fwd Fssh_channel_connect_stdio_fwd #define channel_connect_to_path Fssh_channel_connect_to_path #define channel_connect_to_port Fssh_channel_connect_to_port #define channel_decode_socks4 Fssh_channel_decode_socks4 #define channel_decode_socks5 Fssh_channel_decode_socks5 #define channel_disable_admin Fssh_channel_disable_admin #define channel_find_open Fssh_channel_find_open #define channel_force_close Fssh_channel_force_close #define channel_format_extended_usage Fssh_channel_format_extended_usage #define channel_free Fssh_channel_free #define channel_free_all Fssh_channel_free_all #define channel_fwd_bind_addr Fssh_channel_fwd_bind_addr #define channel_handler Fssh_channel_handler #define channel_init_channels Fssh_channel_init_channels #define channel_input_data Fssh_channel_input_data #define channel_input_extended_data Fssh_channel_input_extended_data #define channel_input_ieof Fssh_channel_input_ieof #define channel_input_oclose Fssh_channel_input_oclose #define channel_input_open_confirmation Fssh_channel_input_open_confirmation #define channel_input_open_failure Fssh_channel_input_open_failure #define channel_input_status_confirm Fssh_channel_input_status_confirm #define channel_input_window_adjust Fssh_channel_input_window_adjust #define channel_lookup Fssh_channel_lookup #define channel_new Fssh_channel_new #define channel_not_very_much_buffered_data Fssh_channel_not_very_much_buffered_data #define channel_open_message Fssh_channel_open_message #define channel_output_poll Fssh_channel_output_poll #define channel_parse_id Fssh_channel_parse_id #define channel_permit_all Fssh_channel_permit_all #define channel_post_auth_listener Fssh_channel_post_auth_listener #define channel_post_connecting Fssh_channel_post_connecting #define channel_post_mux_client Fssh_channel_post_mux_client #define channel_post_mux_listener Fssh_channel_post_mux_listener #define channel_post_open Fssh_channel_post_open #define channel_post_port_listener Fssh_channel_post_port_listener #define channel_post_x11_listener Fssh_channel_post_x11_listener #define channel_pre_connecting Fssh_channel_pre_connecting #define channel_pre_dynamic Fssh_channel_pre_dynamic #define channel_pre_listener Fssh_channel_pre_listener #define channel_pre_mux_client Fssh_channel_pre_mux_client #define channel_pre_open Fssh_channel_pre_open #define channel_pre_x11_open Fssh_channel_pre_x11_open #define channel_prepare_poll Fssh_channel_prepare_poll #define channel_proxy_downstream Fssh_channel_proxy_downstream #define channel_proxy_upstream Fssh_channel_proxy_upstream #define channel_register_cleanup Fssh_channel_register_cleanup #define channel_register_fds Fssh_channel_register_fds #define channel_register_filter Fssh_channel_register_filter #define channel_register_open_confirm Fssh_channel_register_open_confirm #define channel_register_status_confirm Fssh_channel_register_status_confirm #define channel_request_remote_forwarding Fssh_channel_request_remote_forwarding #define channel_request_rforward_cancel Fssh_channel_request_rforward_cancel #define channel_request_start Fssh_channel_request_start #define channel_send_open Fssh_channel_send_open #define channel_send_window_changes Fssh_channel_send_window_changes #define channel_set_af Fssh_channel_set_af #define channel_set_fds Fssh_channel_set_fds #define channel_set_x11_refuse_time Fssh_channel_set_x11_refuse_time #define channel_set_xtype Fssh_channel_set_xtype #define channel_setup_fwd_listener_streamlocal Fssh_channel_setup_fwd_listener_streamlocal #define channel_setup_fwd_listener_tcpip Fssh_channel_setup_fwd_listener_tcpip #define channel_setup_local_fwd_listener Fssh_channel_setup_local_fwd_listener #define channel_setup_remote_fwd_listener Fssh_channel_setup_remote_fwd_listener #define channel_still_open Fssh_channel_still_open #define channel_stop_listening Fssh_channel_stop_listening #define channel_update_permission Fssh_channel_update_permission #define check_hostkeys_by_key_or_type Fssh_check_hostkeys_by_key_or_type #define check_key_in_hostkeys Fssh_check_key_in_hostkeys #define child_set_env Fssh_child_set_env #define choose_dh Fssh_choose_dh #define choose_t Fssh_choose_t #define chop Fssh_chop #define cipher_alg_list Fssh_cipher_alg_list #define cipher_authlen Fssh_cipher_authlen #define cipher_blocksize Fssh_cipher_blocksize #define cipher_by_name Fssh_cipher_by_name #define cipher_crypt Fssh_cipher_crypt #define cipher_ctx_is_plaintext Fssh_cipher_ctx_is_plaintext #define cipher_free Fssh_cipher_free #define cipher_get_keyiv Fssh_cipher_get_keyiv #define cipher_get_keyiv_len Fssh_cipher_get_keyiv_len #define cipher_get_length Fssh_cipher_get_length #define cipher_init Fssh_cipher_init #define cipher_is_cbc Fssh_cipher_is_cbc #define cipher_ivlen Fssh_cipher_ivlen #define cipher_keylen Fssh_cipher_keylen #define cipher_seclen Fssh_cipher_seclen #define cipher_set_keyiv Fssh_cipher_set_keyiv #define cipher_warning_message Fssh_cipher_warning_message #define ciphers_valid Fssh_ciphers_valid #define cleanhostname Fssh_cleanhostname #define cleanup_exit Fssh_cleanup_exit #define client_converse Fssh_client_converse #define colon Fssh_colon #define compare Fssh_compare #define compare_gps Fssh_compare_gps #define compat_banner Fssh_compat_banner #define compat_kex_proposal Fssh_compat_kex_proposal #define compression_alg_list Fssh_compression_alg_list #define connect_next Fssh_connect_next #define connect_to Fssh_connect_to #define connect_to_helper Fssh_connect_to_helper #define convtime Fssh_convtime #define crypto_hash_sha512 Fssh_crypto_hash_sha512 #define crypto_kem_sntrup761_dec Fssh_crypto_kem_sntrup761_dec #define crypto_kem_sntrup761_enc Fssh_crypto_kem_sntrup761_enc #define crypto_kem_sntrup761_keypair Fssh_crypto_kem_sntrup761_keypair #define crypto_scalarmult_curve25519 Fssh_crypto_scalarmult_curve25519 #define crypto_sign_ed25519 Fssh_crypto_sign_ed25519 #define crypto_sign_ed25519_keypair Fssh_crypto_sign_ed25519_keypair #define crypto_sign_ed25519_open Fssh_crypto_sign_ed25519_open #define crypto_sign_ed25519_ref_fe25519_getparity Fssh_crypto_sign_ed25519_ref_fe25519_getparity #define crypto_sign_ed25519_ref_fe25519_iseq_vartime Fssh_crypto_sign_ed25519_ref_fe25519_iseq_vartime #define crypto_sign_ed25519_ref_fe25519_mul Fssh_crypto_sign_ed25519_ref_fe25519_mul #define crypto_sign_ed25519_ref_isneutral_vartime Fssh_crypto_sign_ed25519_ref_isneutral_vartime #define crypto_sign_ed25519_ref_pack Fssh_crypto_sign_ed25519_ref_pack #define crypto_sign_ed25519_ref_scalarmult_base Fssh_crypto_sign_ed25519_ref_scalarmult_base #define crypto_sign_ed25519_ref_unpackneg_vartime Fssh_crypto_sign_ed25519_ref_unpackneg_vartime #define daemonized Fssh_daemonized #define dangerous_locale Fssh_dangerous_locale #define dbl_p1p1 Fssh_dbl_p1p1 #define default_key_sign Fssh_default_key_sign #define deserialise_identity2 Fssh_deserialise_identity2 #define dh_estimate Fssh_dh_estimate #define dh_gen_key Fssh_dh_gen_key #define dh_new_group Fssh_dh_new_group #define dh_new_group1 Fssh_dh_new_group1 #define dh_new_group14 Fssh_dh_new_group14 #define dh_new_group16 Fssh_dh_new_group16 #define dh_new_group18 Fssh_dh_new_group18 #define dh_new_group_asc Fssh_dh_new_group_asc #define dh_new_group_fallback Fssh_dh_new_group_fallback #define dh_pub_is_valid Fssh_dh_pub_is_valid #define dh_set_moduli_file Fssh_dh_set_moduli_file #define dispatch_protocol_error Fssh_dispatch_protocol_error #define dispatch_protocol_ignore Fssh_dispatch_protocol_ignore #define do_log Fssh_do_log #define dollar_expand Fssh_dollar_expand #define ecdsa_do_sign Fssh_ecdsa_do_sign #define encode_constraints Fssh_encode_constraints #define encode_dest_constraint_hop Fssh_encode_dest_constraint_hop #define exited_cleanly Fssh_exited_cleanly #define export_dns_rr Fssh_export_dns_rr #define extension_section Fssh_extension_section #define fd_ready Fssh_fd_ready #define fe25519_reduce_add_sub Fssh_fe25519_reduce_add_sub #define filter_list Fssh_filter_list #define fingerprint_b64 Fssh_fingerprint_b64 #define fingerprint_hex Fssh_fingerprint_hex #define fmprintf Fssh_fmprintf #define fmt_scaled Fssh_fmt_scaled #define fmt_timeframe Fssh_fmt_timeframe #define format_absolute_time Fssh_format_absolute_time #define format_timestamp Fssh_format_timestamp #define forward_equals Fssh_forward_equals #define free_hostkeys Fssh_free_hostkeys #define freeargs Fssh_freeargs #define freerrset Fssh_freerrset #define freezero Fssh_freezero #define fwd_ident Fssh_fwd_ident #define g_opendir Fssh_g_opendir #define g_stat Fssh_g_stat #define gen_candidates Fssh_gen_candidates #define get_local_ipaddr Fssh_get_local_ipaddr #define get_local_name Fssh_get_local_name #define get_local_port Fssh_get_local_port #define get_peer_ipaddr Fssh_get_peer_ipaddr #define get_peer_port Fssh_get_peer_port #define get_rdomain Fssh_get_rdomain #define get_sock_af Fssh_get_sock_af #define get_sock_port Fssh_get_sock_port #define get_socket_address Fssh_get_socket_address #define get_u16 Fssh_get_u16 #define get_u32 Fssh_get_u32 #define get_u32_le Fssh_get_u32_le #define get_u64 Fssh_get_u64 #define getrrsetbyname Fssh_getrrsetbyname #define glob0 Fssh_glob0 #define glob2 Fssh_glob2 #define globexp1 Fssh_globexp1 #define globextend Fssh_globextend #define host_delete Fssh_host_delete #define host_hash Fssh_host_hash #define hostfile_create_user_ssh_dir Fssh_hostfile_create_user_ssh_dir #define hostfile_read_key Fssh_hostfile_read_key #define hostfile_replace_entries Fssh_hostfile_replace_entries #define hostkeys_foreach Fssh_hostkeys_foreach #define hostkeys_foreach_file Fssh_hostkeys_foreach_file #define hpdelim Fssh_hpdelim #define hpdelim2 Fssh_hpdelim2 #define init_hostkeys Fssh_init_hostkeys #define input_kex_dh_gex_group Fssh_input_kex_dh_gex_group #define input_kex_dh_gex_init Fssh_input_kex_dh_gex_init #define input_kex_dh_gex_reply Fssh_input_kex_dh_gex_reply #define input_kex_dh_gex_request Fssh_input_kex_dh_gex_request #define input_kex_gen_init Fssh_input_kex_gen_init #define input_kex_gen_reply Fssh_input_kex_gen_reply #define iptos2str Fssh_iptos2str #define ipv64_normalise_mapped Fssh_ipv64_normalise_mapped #define is_cert_revoked Fssh_is_cert_revoked #define is_key_revoked Fssh_is_key_revoked #define kex_alg_by_name Fssh_kex_alg_by_name #define kex_alg_list Fssh_kex_alg_list #define kex_assemble_names Fssh_kex_assemble_names #define kex_buf2prop Fssh_kex_buf2prop #define kex_c25519_dec Fssh_kex_c25519_dec #define kex_c25519_enc Fssh_kex_c25519_enc #define kex_c25519_keypair Fssh_kex_c25519_keypair #define kex_choose_conf Fssh_kex_choose_conf #define kex_derive_keys Fssh_kex_derive_keys #define kex_dh_compute_key Fssh_kex_dh_compute_key #define kex_dh_dec Fssh_kex_dh_dec #define kex_dh_enc Fssh_kex_dh_enc #define kex_dh_keygen Fssh_kex_dh_keygen #define kex_dh_keypair Fssh_kex_dh_keypair #define kex_ecdh_dec Fssh_kex_ecdh_dec #define kex_ecdh_dec_key_group Fssh_kex_ecdh_dec_key_group #define kex_ecdh_enc Fssh_kex_ecdh_enc #define kex_ecdh_keypair Fssh_kex_ecdh_keypair #define kex_exchange_identification Fssh_kex_exchange_identification +#define kex_ext_info_check_ver Fssh_kex_ext_info_check_ver #define kex_free Fssh_kex_free #define kex_free_newkeys Fssh_kex_free_newkeys #define kex_gen_client Fssh_kex_gen_client #define kex_gen_hash Fssh_kex_gen_hash #define kex_gen_server Fssh_kex_gen_server #define kex_input_ext_info Fssh_kex_input_ext_info #define kex_input_kexinit Fssh_kex_input_kexinit #define kex_input_newkeys Fssh_kex_input_newkeys #define kex_kem_sntrup761x25519_dec Fssh_kex_kem_sntrup761x25519_dec #define kex_kem_sntrup761x25519_enc Fssh_kex_kem_sntrup761x25519_enc #define kex_kem_sntrup761x25519_keypair Fssh_kex_kem_sntrup761x25519_keypair #define kex_load_hostkey Fssh_kex_load_hostkey #define kex_names_cat Fssh_kex_names_cat #define kex_names_valid Fssh_kex_names_valid #define kex_new Fssh_kex_new #define kex_prop2buf Fssh_kex_prop2buf #define kex_prop_free Fssh_kex_prop_free #define kex_proposal_free_entries Fssh_kex_proposal_free_entries #define kex_proposal_populate_entries Fssh_kex_proposal_populate_entries #define kex_protocol_error Fssh_kex_protocol_error #define kex_ready Fssh_kex_ready #define kex_send_kexinit Fssh_kex_send_kexinit #define kex_send_newkeys Fssh_kex_send_newkeys #define kex_setup Fssh_kex_setup #define kex_start_rekex Fssh_kex_start_rekex #define kex_verify_host_key Fssh_kex_verify_host_key #define kexc25519_keygen Fssh_kexc25519_keygen #define kexc25519_shared_key Fssh_kexc25519_shared_key #define kexc25519_shared_key_ext Fssh_kexc25519_shared_key_ext #define kexgex_client Fssh_kexgex_client #define kexgex_hash Fssh_kexgex_hash #define kexgex_server Fssh_kexgex_server #define krl_dump Fssh_krl_dump #define lib_contains_symbol Fssh_lib_contains_symbol #define load_hostkeys Fssh_load_hostkeys #define load_hostkeys_file Fssh_load_hostkeys_file #define log_change_level Fssh_log_change_level #define log_facility_name Fssh_log_facility_name #define log_facility_number Fssh_log_facility_number #define log_init Fssh_log_init #define log_is_on_stderr Fssh_log_is_on_stderr #define log_level_get Fssh_log_level_get #define log_level_name Fssh_log_level_name #define log_level_number Fssh_log_level_number #define log_redirect_stderr_to Fssh_log_redirect_stderr_to #define log_verbose_add Fssh_log_verbose_add #define log_verbose_reset Fssh_log_verbose_reset #define lookup_env_in_list Fssh_lookup_env_in_list #define lookup_key_in_hostkeys_by_type Fssh_lookup_key_in_hostkeys_by_type #define lookup_marker_in_hostkeys Fssh_lookup_marker_in_hostkeys #define lookup_setenv_in_list Fssh_lookup_setenv_in_list #define lowercase Fssh_lowercase #define mac_alg_list Fssh_mac_alg_list #define mac_check Fssh_mac_check #define mac_clear Fssh_mac_clear #define mac_compute Fssh_mac_compute #define mac_init Fssh_mac_init #define mac_setup Fssh_mac_setup #define mac_valid Fssh_mac_valid #define match_filter_allowlist Fssh_match_filter_allowlist #define match_filter_denylist Fssh_match_filter_denylist #define match_host_and_ip Fssh_match_host_and_ip #define match_hostname Fssh_match_hostname #define match_list Fssh_match_list #define match_maybe_hashed Fssh_match_maybe_hashed #define match_pattern Fssh_match_pattern #define match_pattern_list Fssh_match_pattern_list #define match_user Fssh_match_user #define match_usergroup_pattern_list Fssh_match_usergroup_pattern_list #define mktemp_proto Fssh_mktemp_proto #define mm_choose_dh Fssh_mm_choose_dh #define mm_receive_fd Fssh_mm_receive_fd #define mm_send_fd Fssh_mm_send_fd #define mm_sshkey_sign Fssh_mm_sshkey_sign #define monotime Fssh_monotime #define monotime_double Fssh_monotime_double #define monotime_ts Fssh_monotime_ts #define monotime_tv Fssh_monotime_tv #define mprintf Fssh_mprintf #define ms_subtract_diff Fssh_ms_subtract_diff #define ms_to_timespec Fssh_ms_to_timespec #define msetlocale Fssh_msetlocale #define newkeys_from_blob Fssh_newkeys_from_blob #define newkeys_to_blob Fssh_newkeys_to_blob #define nh_aux Fssh_nh_aux #define nh_final Fssh_nh_final #define note_key Fssh_note_key #define notify_complete Fssh_notify_complete #define notify_start Fssh_notify_start #define open_preamble Fssh_open_preamble #define openssh_RSA_verify Fssh_openssh_RSA_verify #define opt_array_append Fssh_opt_array_append #define opt_array_append2 Fssh_opt_array_append2 #define opt_dequote Fssh_opt_dequote #define opt_flag Fssh_opt_flag #define opt_match Fssh_opt_match #define ossl_error Fssh_ossl_error #define parse_absolute_time Fssh_parse_absolute_time #define parse_ipqos Fssh_parse_ipqos #define parse_prime Fssh_parse_prime #define parse_revoked_certs Fssh_parse_revoked_certs #define parse_uri Fssh_parse_uri #define parse_user_host_path Fssh_parse_user_host_path #define parse_user_host_port Fssh_parse_user_host_port #define path_absolute Fssh_path_absolute #define pem_passphrase_cb Fssh_pem_passphrase_cb #define percent_dollar_expand Fssh_percent_dollar_expand #define percent_expand Fssh_percent_expand #define permission_set_add Fssh_permission_set_add #define permitopen_port Fssh_permitopen_port #define pkcs11_add_provider Fssh_pkcs11_add_provider #define pkcs11_del_provider Fssh_pkcs11_del_provider #define pkcs11_ecdsa_wrap Fssh_pkcs11_ecdsa_wrap #define pkcs11_fetch_certs Fssh_pkcs11_fetch_certs #define pkcs11_fetch_keys Fssh_pkcs11_fetch_keys #define pkcs11_find Fssh_pkcs11_find #define pkcs11_get_key Fssh_pkcs11_get_key #define pkcs11_init Fssh_pkcs11_init #define pkcs11_k11_free Fssh_pkcs11_k11_free #define pkcs11_login_slot Fssh_pkcs11_login_slot #define pkcs11_provider_finalize Fssh_pkcs11_provider_finalize #define pkcs11_provider_unref Fssh_pkcs11_provider_unref #define pkcs11_rsa_private_decrypt Fssh_pkcs11_rsa_private_decrypt #define pkcs11_rsa_private_encrypt Fssh_pkcs11_rsa_private_encrypt #define pkcs11_rsa_wrap Fssh_pkcs11_rsa_wrap #define pkcs11_terminate Fssh_pkcs11_terminate #define plain_key_blob Fssh_plain_key_blob #define platform_disable_tracing Fssh_platform_disable_tracing #define platform_pledge_agent Fssh_platform_pledge_agent #define platform_pledge_mux Fssh_platform_pledge_mux #define platform_pledge_sftp_server Fssh_platform_pledge_sftp_server #define platform_sys_dir_uid Fssh_platform_sys_dir_uid #define pledge Fssh_pledge #define poly1305_auth Fssh_poly1305_auth #define poly_hash Fssh_poly_hash #define port_open_helper Fssh_port_open_helper #define prime_test Fssh_prime_test #define private2_check_padding Fssh_private2_check_padding #define private2_uudecode Fssh_private2_uudecode #define proposals_match Fssh_proposals_match #define ptimeout_deadline_monotime Fssh_ptimeout_deadline_monotime +#define ptimeout_deadline_monotime_tsp Fssh_ptimeout_deadline_monotime_tsp #define ptimeout_deadline_ms Fssh_ptimeout_deadline_ms #define ptimeout_deadline_sec Fssh_ptimeout_deadline_sec #define ptimeout_get_ms Fssh_ptimeout_get_ms #define ptimeout_get_tsp Fssh_ptimeout_get_tsp #define ptimeout_init Fssh_ptimeout_init #define ptimeout_isset Fssh_ptimeout_isset #define put_bitmap Fssh_put_bitmap #define put_host_port Fssh_put_host_port #define put_u16 Fssh_put_u16 #define put_u32 Fssh_put_u32 #define put_u32_le Fssh_put_u32_le #define put_u64 Fssh_put_u64 #define pwcopy Fssh_pwcopy #define qfileout Fssh_qfileout #define read_mux Fssh_read_mux #define read_passphrase Fssh_read_passphrase #define recallocarray Fssh_recallocarray #define record_hostkey Fssh_record_hostkey #define refresh_progress_meter Fssh_refresh_progress_meter #define replacearg Fssh_replacearg #define revoke_blob Fssh_revoke_blob #define revoked_blob_tree_RB_FIND Fssh_revoked_blob_tree_RB_FIND #define revoked_blob_tree_RB_NEXT Fssh_revoked_blob_tree_RB_NEXT #define revoked_blob_tree_RB_REMOVE Fssh_revoked_blob_tree_RB_REMOVE #define revoked_certs_for_ca_key Fssh_revoked_certs_for_ca_key #define revoked_certs_generate Fssh_revoked_certs_generate #define revoked_serial_tree_RB_REMOVE Fssh_revoked_serial_tree_RB_REMOVE #define rijndaelEncrypt Fssh_rijndaelEncrypt #define rijndaelKeySetupEnc Fssh_rijndaelKeySetupEnc #define rsa_hash_id_from_keyname Fssh_rsa_hash_id_from_keyname #define rtrim Fssh_rtrim #define safe_path Fssh_safe_path #define safe_path_fd Fssh_safe_path_fd #define sanitise_stdfd Fssh_sanitise_stdfd #define scan_scaled Fssh_scan_scaled #define seed_rng Fssh_seed_rng #define send_error Fssh_send_error #define set_log_handler Fssh_set_log_handler #define set_nodelay Fssh_set_nodelay #define set_nonblock Fssh_set_nonblock #define set_rdomain Fssh_set_rdomain #define set_reuseaddr Fssh_set_reuseaddr #define set_sock_tos Fssh_set_sock_tos #define sftp_realpath Fssh_sftp_realpath #define shadow_pw Fssh_shadow_pw #define sieve_large Fssh_sieve_large #define sig_alarm Fssh_sig_alarm #define sig_winch Fssh_sig_winch #define skip_space Fssh_skip_space #define snmprintf Fssh_snmprintf #define sock_set_v6only Fssh_sock_set_v6only #define speed_to_baud Fssh_speed_to_baud #define square Fssh_square #define ssh_add_hostkey Fssh_ssh_add_hostkey #define ssh_add_identity_constrained Fssh_ssh_add_identity_constrained #define ssh_agent_bind_hostkey Fssh_ssh_agent_bind_hostkey #define ssh_agent_has_key Fssh_ssh_agent_has_key #define ssh_agent_sign Fssh_ssh_agent_sign #define ssh_alloc_session_state Fssh_ssh_alloc_session_state #define ssh_clear_newkeys Fssh_ssh_clear_newkeys #define ssh_close_authentication_socket Fssh_ssh_close_authentication_socket #define ssh_compatible_openssl Fssh_ssh_compatible_openssl #define ssh_digest_alg_by_name Fssh_ssh_digest_alg_by_name #define ssh_digest_alg_name Fssh_ssh_digest_alg_name #define ssh_digest_blocksize Fssh_ssh_digest_blocksize #define ssh_digest_buffer Fssh_ssh_digest_buffer #define ssh_digest_bytes Fssh_ssh_digest_bytes #define ssh_digest_copy_state Fssh_ssh_digest_copy_state #define ssh_digest_final Fssh_ssh_digest_final #define ssh_digest_free Fssh_ssh_digest_free #define ssh_digest_memory Fssh_ssh_digest_memory #define ssh_digest_start Fssh_ssh_digest_start #define ssh_digest_update Fssh_ssh_digest_update #define ssh_digest_update_buffer Fssh_ssh_digest_update_buffer #define ssh_dispatch_init Fssh_ssh_dispatch_init #define ssh_dispatch_range Fssh_ssh_dispatch_range #define ssh_dispatch_run Fssh_ssh_dispatch_run #define ssh_dispatch_run_fatal Fssh_ssh_dispatch_run_fatal #define ssh_dispatch_set Fssh_ssh_dispatch_set #define ssh_dss_alloc Fssh_ssh_dss_alloc #define ssh_dss_cleanup Fssh_ssh_dss_cleanup #define ssh_dss_copy_public Fssh_ssh_dss_copy_public #define ssh_dss_deserialize_private Fssh_ssh_dss_deserialize_private #define ssh_dss_deserialize_public Fssh_ssh_dss_deserialize_public #define ssh_dss_equal Fssh_ssh_dss_equal #define ssh_dss_generate Fssh_ssh_dss_generate #define ssh_dss_serialize_private Fssh_ssh_dss_serialize_private #define ssh_dss_serialize_public Fssh_ssh_dss_serialize_public #define ssh_dss_sign Fssh_ssh_dss_sign #define ssh_dss_size Fssh_ssh_dss_size #define ssh_dss_verify Fssh_ssh_dss_verify #define ssh_ecdsa_cleanup Fssh_ssh_ecdsa_cleanup #define ssh_ecdsa_copy_public Fssh_ssh_ecdsa_copy_public #define ssh_ecdsa_deserialize_private Fssh_ssh_ecdsa_deserialize_private #define ssh_ecdsa_deserialize_public Fssh_ssh_ecdsa_deserialize_public #define ssh_ecdsa_equal Fssh_ssh_ecdsa_equal #define ssh_ecdsa_generate Fssh_ssh_ecdsa_generate #define ssh_ecdsa_serialize_private Fssh_ssh_ecdsa_serialize_private #define ssh_ecdsa_serialize_public Fssh_ssh_ecdsa_serialize_public #define ssh_ecdsa_sign Fssh_ssh_ecdsa_sign #define ssh_ecdsa_size Fssh_ssh_ecdsa_size #define ssh_ecdsa_sk_cleanup Fssh_ssh_ecdsa_sk_cleanup #define ssh_ecdsa_sk_copy_public Fssh_ssh_ecdsa_sk_copy_public #define ssh_ecdsa_sk_deserialize_private Fssh_ssh_ecdsa_sk_deserialize_private #define ssh_ecdsa_sk_deserialize_public Fssh_ssh_ecdsa_sk_deserialize_public #define ssh_ecdsa_sk_equal Fssh_ssh_ecdsa_sk_equal #define ssh_ecdsa_sk_serialize_private Fssh_ssh_ecdsa_sk_serialize_private #define ssh_ecdsa_sk_serialize_public Fssh_ssh_ecdsa_sk_serialize_public #define ssh_ecdsa_sk_verify Fssh_ssh_ecdsa_sk_verify #define ssh_ecdsa_verify Fssh_ssh_ecdsa_verify #define ssh_ed25519_cleanup Fssh_ssh_ed25519_cleanup #define ssh_ed25519_copy_public Fssh_ssh_ed25519_copy_public #define ssh_ed25519_deserialize_private Fssh_ssh_ed25519_deserialize_private #define ssh_ed25519_deserialize_public Fssh_ssh_ed25519_deserialize_public #define ssh_ed25519_equal Fssh_ssh_ed25519_equal #define ssh_ed25519_generate Fssh_ssh_ed25519_generate #define ssh_ed25519_serialize_private Fssh_ssh_ed25519_serialize_private #define ssh_ed25519_serialize_public Fssh_ssh_ed25519_serialize_public #define ssh_ed25519_sign Fssh_ssh_ed25519_sign #define ssh_ed25519_sk_cleanup Fssh_ssh_ed25519_sk_cleanup #define ssh_ed25519_sk_copy_public Fssh_ssh_ed25519_sk_copy_public #define ssh_ed25519_sk_deserialize_private Fssh_ssh_ed25519_sk_deserialize_private #define ssh_ed25519_sk_deserialize_public Fssh_ssh_ed25519_sk_deserialize_public #define ssh_ed25519_sk_equal Fssh_ssh_ed25519_sk_equal #define ssh_ed25519_sk_serialize_private Fssh_ssh_ed25519_sk_serialize_private #define ssh_ed25519_sk_serialize_public Fssh_ssh_ed25519_sk_serialize_public #define ssh_ed25519_sk_verify Fssh_ssh_ed25519_sk_verify #define ssh_ed25519_verify Fssh_ssh_ed25519_verify #define ssh_err Fssh_ssh_err #define ssh_fetch_identitylist Fssh_ssh_fetch_identitylist #define ssh_free Fssh_ssh_free #define ssh_free_identitylist Fssh_ssh_free_identitylist #define ssh_gai_strerror Fssh_ssh_gai_strerror #define ssh_get_app_data Fssh_ssh_get_app_data #define ssh_get_authentication_socket Fssh_ssh_get_authentication_socket #define ssh_get_authentication_socket_path Fssh_ssh_get_authentication_socket_path #define ssh_get_progname Fssh_ssh_get_progname #define ssh_hmac_bytes Fssh_ssh_hmac_bytes #define ssh_hmac_final Fssh_ssh_hmac_final #define ssh_hmac_free Fssh_ssh_hmac_free #define ssh_hmac_init Fssh_ssh_hmac_init #define ssh_hmac_start Fssh_ssh_hmac_start #define ssh_hmac_update Fssh_ssh_hmac_update #define ssh_hmac_update_buffer Fssh_ssh_hmac_update_buffer #define ssh_init Fssh_ssh_init #define ssh_input_append Fssh_ssh_input_append #define ssh_input_space Fssh_ssh_input_space #define ssh_krl_check_key Fssh_ssh_krl_check_key #define ssh_krl_file_contains_key Fssh_ssh_krl_file_contains_key #define ssh_krl_free Fssh_ssh_krl_free #define ssh_krl_from_blob Fssh_ssh_krl_from_blob #define ssh_krl_init Fssh_ssh_krl_init #define ssh_krl_revoke_cert_by_key_id Fssh_ssh_krl_revoke_cert_by_key_id #define ssh_krl_revoke_cert_by_serial Fssh_ssh_krl_revoke_cert_by_serial #define ssh_krl_revoke_cert_by_serial_range Fssh_ssh_krl_revoke_cert_by_serial_range #define ssh_krl_revoke_key Fssh_ssh_krl_revoke_key #define ssh_krl_revoke_key_explicit Fssh_ssh_krl_revoke_key_explicit #define ssh_krl_revoke_key_sha1 Fssh_ssh_krl_revoke_key_sha1 #define ssh_krl_revoke_key_sha256 Fssh_ssh_krl_revoke_key_sha256 #define ssh_krl_set_comment Fssh_ssh_krl_set_comment #define ssh_krl_set_version Fssh_ssh_krl_set_version #define ssh_krl_to_blob Fssh_ssh_krl_to_blob #define ssh_libcrypto_init Fssh_ssh_libcrypto_init #define ssh_local_ipaddr Fssh_ssh_local_ipaddr #define ssh_local_port Fssh_ssh_local_port #define ssh_lock_agent Fssh_ssh_lock_agent #define ssh_msg_recv Fssh_ssh_msg_recv #define ssh_msg_send Fssh_ssh_msg_send #define ssh_output_consume Fssh_ssh_output_consume #define ssh_output_ptr Fssh_ssh_output_ptr #define ssh_output_space Fssh_ssh_output_space #define ssh_packet_check_rekey Fssh_ssh_packet_check_rekey #define ssh_packet_clear_keys Fssh_ssh_packet_clear_keys #define ssh_packet_close Fssh_ssh_packet_close #define ssh_packet_close_internal Fssh_ssh_packet_close_internal #define ssh_packet_connection_af Fssh_ssh_packet_connection_af #define ssh_packet_connection_is_on_socket Fssh_ssh_packet_connection_is_on_socket #define ssh_packet_disconnect Fssh_ssh_packet_disconnect #define ssh_packet_enable_delayed_compress Fssh_ssh_packet_enable_delayed_compress #define ssh_packet_get_bytes Fssh_ssh_packet_get_bytes #define ssh_packet_get_connection_in Fssh_ssh_packet_get_connection_in #define ssh_packet_get_connection_out Fssh_ssh_packet_get_connection_out #define ssh_packet_get_input Fssh_ssh_packet_get_input #define ssh_packet_get_maxsize Fssh_ssh_packet_get_maxsize #define ssh_packet_get_mux Fssh_ssh_packet_get_mux #define ssh_packet_get_output Fssh_ssh_packet_get_output #define ssh_packet_get_protocol_flags Fssh_ssh_packet_get_protocol_flags #define ssh_packet_get_rekey_timeout Fssh_ssh_packet_get_rekey_timeout #define ssh_packet_get_state Fssh_ssh_packet_get_state #define ssh_packet_have_data_to_write Fssh_ssh_packet_have_data_to_write #define ssh_packet_inc_alive_timeouts Fssh_ssh_packet_inc_alive_timeouts +#define ssh_packet_interactive_data_to_write Fssh_ssh_packet_interactive_data_to_write #define ssh_packet_is_interactive Fssh_ssh_packet_is_interactive #define ssh_packet_is_rekeying Fssh_ssh_packet_is_rekeying #define ssh_packet_log_type Fssh_ssh_packet_log_type #define ssh_packet_need_rekeying Fssh_ssh_packet_need_rekeying #define ssh_packet_next Fssh_ssh_packet_next #define ssh_packet_not_very_much_data_to_write Fssh_ssh_packet_not_very_much_data_to_write #define ssh_packet_payload Fssh_ssh_packet_payload #define ssh_packet_process_incoming Fssh_ssh_packet_process_incoming #define ssh_packet_process_read Fssh_ssh_packet_process_read #define ssh_packet_put Fssh_ssh_packet_put #define ssh_packet_rdomain_in Fssh_ssh_packet_rdomain_in #define ssh_packet_read Fssh_ssh_packet_read #define ssh_packet_read_expect Fssh_ssh_packet_read_expect #define ssh_packet_read_poll2 Fssh_ssh_packet_read_poll2 #define ssh_packet_read_poll_seqnr Fssh_ssh_packet_read_poll_seqnr #define ssh_packet_read_seqnr Fssh_ssh_packet_read_seqnr #define ssh_packet_remaining Fssh_ssh_packet_remaining #define ssh_packet_send2 Fssh_ssh_packet_send2 #define ssh_packet_send2_wrapped Fssh_ssh_packet_send2_wrapped #define ssh_packet_send_debug Fssh_ssh_packet_send_debug #define ssh_packet_set_alive_timeouts Fssh_ssh_packet_set_alive_timeouts #define ssh_packet_set_authenticated Fssh_ssh_packet_set_authenticated #define ssh_packet_set_connection Fssh_ssh_packet_set_connection #define ssh_packet_set_input_hook Fssh_ssh_packet_set_input_hook #define ssh_packet_set_interactive Fssh_ssh_packet_set_interactive #define ssh_packet_set_log_preamble Fssh_ssh_packet_set_log_preamble #define ssh_packet_set_maxsize Fssh_ssh_packet_set_maxsize #define ssh_packet_set_mux Fssh_ssh_packet_set_mux #define ssh_packet_set_nonblocking Fssh_ssh_packet_set_nonblocking #define ssh_packet_set_postauth Fssh_ssh_packet_set_postauth #define ssh_packet_set_protocol_flags Fssh_ssh_packet_set_protocol_flags #define ssh_packet_set_rekey_limits Fssh_ssh_packet_set_rekey_limits #define ssh_packet_set_server Fssh_ssh_packet_set_server #define ssh_packet_set_state Fssh_ssh_packet_set_state #define ssh_packet_set_timeout Fssh_ssh_packet_set_timeout #define ssh_packet_set_tos Fssh_ssh_packet_set_tos #define ssh_packet_start_discard Fssh_ssh_packet_start_discard #define ssh_packet_stop_discard Fssh_ssh_packet_stop_discard #define ssh_packet_write_poll Fssh_ssh_packet_write_poll #define ssh_packet_write_wait Fssh_ssh_packet_write_wait #define ssh_remote_ipaddr Fssh_ssh_remote_ipaddr #define ssh_remote_port Fssh_ssh_remote_port #define ssh_remove_all_identities Fssh_ssh_remove_all_identities #define ssh_remove_identity Fssh_ssh_remove_identity #define ssh_request_reply Fssh_ssh_request_reply #define ssh_request_reply_decode Fssh_ssh_request_reply_decode #define ssh_rsa_alloc Fssh_ssh_rsa_alloc #define ssh_rsa_cleanup Fssh_ssh_rsa_cleanup #define ssh_rsa_complete_crt_parameters Fssh_ssh_rsa_complete_crt_parameters #define ssh_rsa_copy_public Fssh_ssh_rsa_copy_public #define ssh_rsa_deserialize_private Fssh_ssh_rsa_deserialize_private #define ssh_rsa_deserialize_public Fssh_ssh_rsa_deserialize_public #define ssh_rsa_equal Fssh_ssh_rsa_equal #define ssh_rsa_generate Fssh_ssh_rsa_generate #define ssh_rsa_serialize_private Fssh_ssh_rsa_serialize_private #define ssh_rsa_serialize_public Fssh_ssh_rsa_serialize_public #define ssh_rsa_sign Fssh_ssh_rsa_sign #define ssh_rsa_size Fssh_ssh_rsa_size #define ssh_rsa_verify Fssh_ssh_rsa_verify #define ssh_set_app_data Fssh_ssh_set_app_data #define ssh_set_newkeys Fssh_ssh_set_newkeys #define ssh_set_verify_host_key_callback Fssh_ssh_set_verify_host_key_callback #define ssh_signal Fssh_ssh_signal #define ssh_tty_make_modes Fssh_ssh_tty_make_modes #define ssh_tty_parse_modes Fssh_ssh_tty_parse_modes #define ssh_update_card Fssh_ssh_update_card #define sshbuf_alloc Fssh_sshbuf_alloc #define sshbuf_allocate Fssh_sshbuf_allocate #define sshbuf_avail Fssh_sshbuf_avail #define sshbuf_b64tod Fssh_sshbuf_b64tod #define sshbuf_check_reserve Fssh_sshbuf_check_reserve #define sshbuf_cmp Fssh_sshbuf_cmp #define sshbuf_consume Fssh_sshbuf_consume #define sshbuf_consume_end Fssh_sshbuf_consume_end #define sshbuf_dtob16 Fssh_sshbuf_dtob16 #define sshbuf_dtob64 Fssh_sshbuf_dtob64 #define sshbuf_dtob64_string Fssh_sshbuf_dtob64_string #define sshbuf_dtourlb64 Fssh_sshbuf_dtourlb64 #define sshbuf_dump Fssh_sshbuf_dump #define sshbuf_dump_data Fssh_sshbuf_dump_data #define sshbuf_dup_string Fssh_sshbuf_dup_string #define sshbuf_find Fssh_sshbuf_find #define sshbuf_free Fssh_sshbuf_free #define sshbuf_from Fssh_sshbuf_from #define sshbuf_fromb Fssh_sshbuf_fromb #define sshbuf_froms Fssh_sshbuf_froms #define sshbuf_get Fssh_sshbuf_get #define sshbuf_get_bignum2 Fssh_sshbuf_get_bignum2 #define sshbuf_get_bignum2_bytes_direct Fssh_sshbuf_get_bignum2_bytes_direct #define sshbuf_get_cstring Fssh_sshbuf_get_cstring #define sshbuf_get_ec Fssh_sshbuf_get_ec #define sshbuf_get_eckey Fssh_sshbuf_get_eckey #define sshbuf_get_string Fssh_sshbuf_get_string #define sshbuf_get_string_direct Fssh_sshbuf_get_string_direct #define sshbuf_get_stringb Fssh_sshbuf_get_stringb #define sshbuf_get_u16 Fssh_sshbuf_get_u16 #define sshbuf_get_u32 Fssh_sshbuf_get_u32 #define sshbuf_get_u64 Fssh_sshbuf_get_u64 #define sshbuf_get_u8 Fssh_sshbuf_get_u8 #define sshbuf_len Fssh_sshbuf_len #define sshbuf_load_fd Fssh_sshbuf_load_fd #define sshbuf_load_file Fssh_sshbuf_load_file #define sshbuf_max_size Fssh_sshbuf_max_size #define sshbuf_maybe_pack Fssh_sshbuf_maybe_pack #define sshbuf_mutable_ptr Fssh_sshbuf_mutable_ptr #define sshbuf_new Fssh_sshbuf_new #define sshbuf_parent Fssh_sshbuf_parent #define sshbuf_peek_string_direct Fssh_sshbuf_peek_string_direct #define sshbuf_peek_u16 Fssh_sshbuf_peek_u16 #define sshbuf_peek_u32 Fssh_sshbuf_peek_u32 #define sshbuf_peek_u64 Fssh_sshbuf_peek_u64 #define sshbuf_peek_u8 Fssh_sshbuf_peek_u8 #define sshbuf_poke Fssh_sshbuf_poke #define sshbuf_poke_u16 Fssh_sshbuf_poke_u16 #define sshbuf_poke_u32 Fssh_sshbuf_poke_u32 #define sshbuf_poke_u64 Fssh_sshbuf_poke_u64 #define sshbuf_poke_u8 Fssh_sshbuf_poke_u8 #define sshbuf_ptr Fssh_sshbuf_ptr #define sshbuf_put Fssh_sshbuf_put #define sshbuf_put_bignum2 Fssh_sshbuf_put_bignum2 #define sshbuf_put_bignum2_bytes Fssh_sshbuf_put_bignum2_bytes #define sshbuf_put_cstring Fssh_sshbuf_put_cstring #define sshbuf_put_ec Fssh_sshbuf_put_ec #define sshbuf_put_eckey Fssh_sshbuf_put_eckey #define sshbuf_put_string Fssh_sshbuf_put_string #define sshbuf_put_stringb Fssh_sshbuf_put_stringb #define sshbuf_put_u16 Fssh_sshbuf_put_u16 #define sshbuf_put_u32 Fssh_sshbuf_put_u32 #define sshbuf_put_u64 Fssh_sshbuf_put_u64 #define sshbuf_put_u8 Fssh_sshbuf_put_u8 #define sshbuf_putb Fssh_sshbuf_putb #define sshbuf_putf Fssh_sshbuf_putf #define sshbuf_putfv Fssh_sshbuf_putfv #define sshbuf_read Fssh_sshbuf_read #define sshbuf_refcount Fssh_sshbuf_refcount #define sshbuf_reserve Fssh_sshbuf_reserve #define sshbuf_reset Fssh_sshbuf_reset #define sshbuf_set_max_size Fssh_sshbuf_set_max_size #define sshbuf_set_parent Fssh_sshbuf_set_parent #define sshbuf_write_file Fssh_sshbuf_write_file #define sshfatal Fssh_sshfatal #define sshkey_advance_past_options Fssh_sshkey_advance_past_options #define sshkey_alg_list Fssh_sshkey_alg_list #define sshkey_cert_check_authority Fssh_sshkey_cert_check_authority #define sshkey_cert_check_authority_now Fssh_sshkey_cert_check_authority_now #define sshkey_cert_check_host Fssh_sshkey_cert_check_host #define sshkey_cert_copy Fssh_sshkey_cert_copy #define sshkey_cert_type Fssh_sshkey_cert_type #define sshkey_certify Fssh_sshkey_certify #define sshkey_certify_custom Fssh_sshkey_certify_custom #define sshkey_check_cert_sigtype Fssh_sshkey_check_cert_sigtype #define sshkey_check_revoked Fssh_sshkey_check_revoked #define sshkey_check_rsa_length Fssh_sshkey_check_rsa_length #define sshkey_check_sigtype Fssh_sshkey_check_sigtype #define sshkey_copy_public_sk Fssh_sshkey_copy_public_sk #define sshkey_curve_name_to_nid Fssh_sshkey_curve_name_to_nid #define sshkey_curve_nid_to_bits Fssh_sshkey_curve_nid_to_bits #define sshkey_curve_nid_to_name Fssh_sshkey_curve_nid_to_name #define sshkey_deserialize_sk Fssh_sshkey_deserialize_sk #define sshkey_drop_cert Fssh_sshkey_drop_cert #define sshkey_dump_ec_key Fssh_sshkey_dump_ec_key #define sshkey_dump_ec_point Fssh_sshkey_dump_ec_point #define sshkey_ec_nid_to_hash_alg Fssh_sshkey_ec_nid_to_hash_alg #define sshkey_ec_validate_private Fssh_sshkey_ec_validate_private #define sshkey_ec_validate_public Fssh_sshkey_ec_validate_public #define sshkey_ecdsa_bits_to_nid Fssh_sshkey_ecdsa_bits_to_nid #define sshkey_ecdsa_key_to_nid Fssh_sshkey_ecdsa_key_to_nid #define sshkey_ecdsa_nid_from_name Fssh_sshkey_ecdsa_nid_from_name #define sshkey_enable_maxsign Fssh_sshkey_enable_maxsign #define sshkey_equal Fssh_sshkey_equal #define sshkey_equal_public Fssh_sshkey_equal_public #define sshkey_fingerprint Fssh_sshkey_fingerprint #define sshkey_fingerprint_raw Fssh_sshkey_fingerprint_raw #define sshkey_format_cert_validity Fssh_sshkey_format_cert_validity #define sshkey_format_text Fssh_sshkey_format_text #define sshkey_free Fssh_sshkey_free #define sshkey_free_contents Fssh_sshkey_free_contents #define sshkey_from_blob Fssh_sshkey_from_blob #define sshkey_from_blob_internal Fssh_sshkey_from_blob_internal #define sshkey_from_private Fssh_sshkey_from_private #define sshkey_fromb Fssh_sshkey_fromb #define sshkey_froms Fssh_sshkey_froms #define sshkey_generate Fssh_sshkey_generate #define sshkey_get_sigtype Fssh_sshkey_get_sigtype #define sshkey_impl_from_type Fssh_sshkey_impl_from_type #define sshkey_in_file Fssh_sshkey_in_file #define sshkey_is_cert Fssh_sshkey_is_cert #define sshkey_is_shielded Fssh_sshkey_is_shielded #define sshkey_is_sk Fssh_sshkey_is_sk #define sshkey_load_cert Fssh_sshkey_load_cert #define sshkey_load_private Fssh_sshkey_load_private #define sshkey_load_private_cert Fssh_sshkey_load_private_cert #define sshkey_load_private_type Fssh_sshkey_load_private_type #define sshkey_load_private_type_fd Fssh_sshkey_load_private_type_fd #define sshkey_load_public Fssh_sshkey_load_public #define sshkey_match_keyname_to_sigalgs Fssh_sshkey_match_keyname_to_sigalgs #define sshkey_names_valid2 Fssh_sshkey_names_valid2 #define sshkey_new Fssh_sshkey_new #define sshkey_parse_private2 Fssh_sshkey_parse_private2 #define sshkey_parse_private_fileblob Fssh_sshkey_parse_private_fileblob #define sshkey_parse_private_fileblob_type Fssh_sshkey_parse_private_fileblob_type #define sshkey_parse_pubkey_from_private_fileblob_type Fssh_sshkey_parse_pubkey_from_private_fileblob_type #define sshkey_perm_ok Fssh_sshkey_perm_ok #define sshkey_plain_to_blob Fssh_sshkey_plain_to_blob #define sshkey_private_deserialize Fssh_sshkey_private_deserialize #define sshkey_private_deserialize_sk Fssh_sshkey_private_deserialize_sk #define sshkey_private_serialize Fssh_sshkey_private_serialize #define sshkey_private_serialize_maxsign Fssh_sshkey_private_serialize_maxsign #define sshkey_private_serialize_opt Fssh_sshkey_private_serialize_opt #define sshkey_private_to_blob2 Fssh_sshkey_private_to_blob2 #define sshkey_private_to_fileblob Fssh_sshkey_private_to_fileblob #define sshkey_putb Fssh_sshkey_putb #define sshkey_putb_plain Fssh_sshkey_putb_plain #define sshkey_puts Fssh_sshkey_puts #define sshkey_puts_opts Fssh_sshkey_puts_opts #define sshkey_read Fssh_sshkey_read #define sshkey_save_private Fssh_sshkey_save_private #define sshkey_save_public Fssh_sshkey_save_public #define sshkey_serialize_private_sk Fssh_sshkey_serialize_private_sk #define sshkey_serialize_sk Fssh_sshkey_serialize_sk #define sshkey_set_filename Fssh_sshkey_set_filename #define sshkey_shield_private Fssh_sshkey_shield_private #define sshkey_sig_details_free Fssh_sshkey_sig_details_free #define sshkey_sigalg_by_name Fssh_sshkey_sigalg_by_name #define sshkey_sign Fssh_sshkey_sign #define sshkey_signatures_left Fssh_sshkey_signatures_left #define sshkey_size Fssh_sshkey_size #define sshkey_sk_cleanup Fssh_sshkey_sk_cleanup #define sshkey_sk_fields_equal Fssh_sshkey_sk_fields_equal #define sshkey_ssh_name Fssh_sshkey_ssh_name #define sshkey_ssh_name_plain Fssh_sshkey_ssh_name_plain #define sshkey_to_base64 Fssh_sshkey_to_base64 #define sshkey_to_blob Fssh_sshkey_to_blob #define sshkey_to_certified Fssh_sshkey_to_certified #define sshkey_try_load_public Fssh_sshkey_try_load_public #define sshkey_type Fssh_sshkey_type #define sshkey_type_from_name Fssh_sshkey_type_from_name #define sshkey_type_is_cert Fssh_sshkey_type_is_cert #define sshkey_type_plain Fssh_sshkey_type_plain #define sshkey_unshield_private Fssh_sshkey_unshield_private #define sshkey_verify Fssh_sshkey_verify #define sshkey_write Fssh_sshkey_write #define sshlog Fssh_sshlog #define sshlogdie Fssh_sshlogdie #define sshlogdirect Fssh_sshlogdirect #define sshlogv Fssh_sshlogv #define sshpkt_add_padding Fssh_sshpkt_add_padding #define sshpkt_disconnect Fssh_sshpkt_disconnect #define sshpkt_fatal Fssh_sshpkt_fatal #define sshpkt_fmt_connection_id Fssh_sshpkt_fmt_connection_id #define sshpkt_get Fssh_sshpkt_get #define sshpkt_get_bignum2 Fssh_sshpkt_get_bignum2 #define sshpkt_get_cstring Fssh_sshpkt_get_cstring #define sshpkt_get_ec Fssh_sshpkt_get_ec #define sshpkt_get_end Fssh_sshpkt_get_end #define sshpkt_get_string Fssh_sshpkt_get_string #define sshpkt_get_string_direct Fssh_sshpkt_get_string_direct #define sshpkt_get_u32 Fssh_sshpkt_get_u32 #define sshpkt_get_u64 Fssh_sshpkt_get_u64 #define sshpkt_get_u8 Fssh_sshpkt_get_u8 #define sshpkt_getb_froms Fssh_sshpkt_getb_froms #define sshpkt_msg_ignore Fssh_sshpkt_msg_ignore #define sshpkt_peek_string_direct Fssh_sshpkt_peek_string_direct #define sshpkt_ptr Fssh_sshpkt_ptr #define sshpkt_put Fssh_sshpkt_put #define sshpkt_put_bignum2 Fssh_sshpkt_put_bignum2 #define sshpkt_put_cstring Fssh_sshpkt_put_cstring #define sshpkt_put_ec Fssh_sshpkt_put_ec #define sshpkt_put_string Fssh_sshpkt_put_string #define sshpkt_put_stringb Fssh_sshpkt_put_stringb #define sshpkt_put_u32 Fssh_sshpkt_put_u32 #define sshpkt_put_u64 Fssh_sshpkt_put_u64 #define sshpkt_put_u8 Fssh_sshpkt_put_u8 #define sshpkt_putb Fssh_sshpkt_putb #define sshpkt_send Fssh_sshpkt_send #define sshpkt_start Fssh_sshpkt_start #define sshpkt_vfatal Fssh_sshpkt_vfatal #define sshsigdie Fssh_sshsigdie #define sshsk_enroll Fssh_sshsk_enroll #define sshsk_free_resident_keys Fssh_sshsk_free_resident_keys #define sshsk_load_resident Fssh_sshsk_load_resident #define sshsk_sign Fssh_sshsk_sign #define start_compression_in Fssh_start_compression_in #define start_compression_out Fssh_start_compression_out #define start_progress_meter Fssh_start_progress_meter #define stdfd_devnull Fssh_stdfd_devnull #define stop_progress_meter Fssh_stop_progress_meter #define stravis Fssh_stravis #define strdelim Fssh_strdelim #define strdelim_internal Fssh_strdelim_internal #define strdelimw Fssh_strdelimw #define strnvis Fssh_strnvis #define strvis Fssh_strvis #define strvisx Fssh_strvisx #define subprocess Fssh_subprocess #define sys_tun_open Fssh_sys_tun_open #define tilde_expand Fssh_tilde_expand #define tilde_expand_filename Fssh_tilde_expand_filename #define timeout_connect Fssh_timeout_connect #define to_blob Fssh_to_blob #define to_blob_buf Fssh_to_blob_buf #define tohex Fssh_tohex #define tun_open Fssh_tun_open #define umac128_delete Fssh_umac128_delete #define umac128_final Fssh_umac128_final #define umac128_new Fssh_umac128_new #define umac128_update Fssh_umac128_update #define umac_delete Fssh_umac_delete #define umac_final Fssh_umac_final #define umac_new Fssh_umac_new #define umac_update Fssh_umac_update #define uncompress_buffer Fssh_uncompress_buffer #define unix_listener Fssh_unix_listener #define unset_nonblock Fssh_unset_nonblock #define urldecode Fssh_urldecode #define valid_domain Fssh_valid_domain #define valid_env_name Fssh_valid_env_name #define vasnmprintf Fssh_vasnmprintf #define vdollar_percent_expand Fssh_vdollar_percent_expand #define verify_host_key_dns Fssh_verify_host_key_dns #define vfmprintf Fssh_vfmprintf #define vis Fssh_vis #define waitfd Fssh_waitfd #define waitrfd Fssh_waitrfd #define webauthn_check_prepare_hash Fssh_webauthn_check_prepare_hash #define write_host_entry Fssh_write_host_entry #define x11_connect_display Fssh_x11_connect_display #define x11_create_display_inet Fssh_x11_create_display_inet #define x11_request_forwarding_with_spoofing Fssh_x11_request_forwarding_with_spoofing #define xasprintf Fssh_xasprintf #define xcalloc Fssh_xcalloc #define xcrypt Fssh_xcrypt #define xextendf Fssh_xextendf #define xmalloc Fssh_xmalloc #define xreallocarray Fssh_xreallocarray #define xrecallocarray Fssh_xrecallocarray #define xstrdup Fssh_xstrdup #define xvasprintf Fssh_xvasprintf diff --git a/crypto/openssh/sshd.8 b/crypto/openssh/sshd.8 index d80153102e67..6faf6d4e6eef 100644 --- a/crypto/openssh/sshd.8 +++ b/crypto/openssh/sshd.8 @@ -1,1052 +1,1052 @@ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd.8,v 1.324 2023/02/10 06:39:27 jmc Exp $ -.Dd $Mdocdate: February 10 2023 $ +.\" $OpenBSD: sshd.8,v 1.325 2023/09/19 20:37:07 deraadt Exp $ +.Dd $Mdocdate: September 19 2023 $ .Dt SSHD 8 .Os .Sh NAME .Nm sshd .Nd OpenSSH daemon .Sh SYNOPSIS .Nm sshd .Bk -words .Op Fl 46DdeGiqTtV .Op Fl C Ar connection_spec .Op Fl c Ar host_certificate_file .Op Fl E Ar log_file .Op Fl f Ar config_file .Op Fl g Ar login_grace_time .Op Fl h Ar host_key_file .Op Fl o Ar option .Op Fl p Ar port .Op Fl u Ar len .Ek .Sh DESCRIPTION .Nm (OpenSSH Daemon) is the daemon program for .Xr ssh 1 . It provides secure encrypted communications between two untrusted hosts over an insecure network. .Pp .Nm listens for connections from clients. It is normally started at boot from .Pa /etc/rc.d/sshd . It forks a new daemon for each incoming connection. The forked daemons handle key exchange, encryption, authentication, command execution, and data exchange. .Pp .Nm can be configured using command-line options or a configuration file (by default .Xr sshd_config 5 ) ; command-line options override values specified in the configuration file. .Nm rereads its configuration file when it receives a hangup signal, .Dv SIGHUP , by executing itself with the name and options it was started with, e.g.\& .Pa /usr/sbin/sshd . .Pp The options are as follows: .Bl -tag -width Ds .It Fl 4 Forces .Nm to use IPv4 addresses only. .It Fl 6 Forces .Nm to use IPv6 addresses only. .It Fl C Ar connection_spec Specify the connection parameters to use for the .Fl T extended test mode. If provided, any .Cm Match directives in the configuration file that would apply are applied before the configuration is written to standard output. The connection parameters are supplied as keyword=value pairs and may be supplied in any order, either with multiple .Fl C options or as a comma-separated list. The keywords are .Dq addr , .Dq user , .Dq host , .Dq laddr , .Dq lport , and .Dq rdomain and correspond to source address, user, resolved source host name, local address, local port number and routing domain respectively. .It Fl c Ar host_certificate_file Specifies a path to a certificate file to identify .Nm during key exchange. The certificate file must match a host key file specified using the .Fl h option or the .Cm HostKey configuration directive. .It Fl D When this option is specified, .Nm will not detach and does not become a daemon. This allows easy monitoring of .Nm sshd . .It Fl d Debug mode. The server sends verbose debug output to standard error, and does not put itself in the background. The server also will not .Xr fork 2 and will only process one connection. This option is only intended for debugging for the server. Multiple .Fl d options increase the debugging level. Maximum is 3. .It Fl E Ar log_file Append debug logs to .Ar log_file instead of the system log. .It Fl e Write debug logs to standard error instead of the system log. .It Fl f Ar config_file Specifies the name of the configuration file. The default is .Pa /etc/ssh/sshd_config . .Nm refuses to start if there is no configuration file. .It Fl G Parse and print configuration file. Check the validity of the configuration file, output the effective configuration to stdout and then exit. Optionally, .Cm Match rules may be applied by specifying the connection parameters using one or more .Fl C options. .It Fl g Ar login_grace_time Gives the grace time for clients to authenticate themselves (default 120 seconds). If the client fails to authenticate the user within this many seconds, the server disconnects and exits. A value of zero indicates no limit. .It Fl h Ar host_key_file Specifies a file from which a host key is read. This option must be given if .Nm is not run as root (as the normal host key files are normally not readable by anyone but root). The default is .Pa /etc/ssh/ssh_host_ecdsa_key , .Pa /etc/ssh/ssh_host_ed25519_key and .Pa /etc/ssh/ssh_host_rsa_key . It is possible to have multiple host key files for the different host key algorithms. .It Fl i Specifies that .Nm is being run from .Xr inetd 8 . .It Fl o Ar option Can be used to give options in the format used in the configuration file. This is useful for specifying options for which there is no separate command-line flag. For full details of the options, and their values, see .Xr sshd_config 5 . .It Fl p Ar port Specifies the port on which the server listens for connections (default 22). Multiple port options are permitted. Ports specified in the configuration file with the .Cm Port option are ignored when a command-line port is specified. Ports specified using the .Cm ListenAddress option override command-line ports. .It Fl q Quiet mode. Nothing is sent to the system log. Normally the beginning, authentication, and termination of each connection is logged. .It Fl T Extended test mode. Check the validity of the configuration file, output the effective configuration to stdout and then exit. Optionally, .Cm Match rules may be applied by specifying the connection parameters using one or more .Fl C options. This is similar to the .Fl G flag, but it includes the additional testing performed by the .Fl t flag. .It Fl t Test mode. Only check the validity of the configuration file and sanity of the keys. This is useful for updating .Nm reliably as configuration options may change. .It Fl u Ar len This option is used to specify the size of the field in the .Vt utmp structure that holds the remote host name. If the resolved host name is longer than .Ar len , the dotted decimal value will be used instead. This allows hosts with very long host names that overflow this field to still be uniquely identified. Specifying .Fl u0 indicates that only dotted decimal addresses should be put into the .Pa utmp file. .Fl u0 may also be used to prevent .Nm from making DNS requests unless the authentication mechanism or configuration requires it. Authentication mechanisms that may require DNS include .Cm HostbasedAuthentication and using a .Cm from="pattern-list" option in a key file. Configuration options that require DNS include using a USER@HOST pattern in .Cm AllowUsers or .Cm DenyUsers . .It Fl V Display the version number and exit. .El .Sh AUTHENTICATION The OpenSSH SSH daemon supports SSH protocol 2 only. Each host has a host-specific key, used to identify the host. Whenever a client connects, the daemon responds with its public host key. The client compares the host key against its own database to verify that it has not changed. Forward secrecy is provided through a Diffie-Hellman key agreement. This key agreement results in a shared session key. The rest of the session is encrypted using a symmetric cipher. The client selects the encryption algorithm to use from those offered by the server. Additionally, session integrity is provided through a cryptographic message authentication code (MAC). .Pp Finally, the server and the client enter an authentication dialog. The client tries to authenticate itself using host-based authentication, public key authentication, challenge-response authentication, or password authentication. .Pp Regardless of the authentication type, the account is checked to ensure that it is accessible. An account is not accessible if it is locked, listed in .Cm DenyUsers or its group is listed in .Cm DenyGroups \&. The definition of a locked account is system dependent. Some platforms have their own account database (eg AIX) and some modify the passwd field ( .Ql \&*LK\&* on Solaris and UnixWare, .Ql \&* on HP-UX, containing .Ql Nologin on Tru64, a leading .Ql \&*LOCKED\&* on FreeBSD and a leading .Ql \&! on most Linuxes). If there is a requirement to disable password authentication for the account while allowing still public-key, then the passwd field should be set to something other than these values (eg .Ql NP or .Ql \&*NP\&* ). .Pp If the client successfully authenticates itself, a dialog for preparing the session is entered. At this time the client may request things like allocating a pseudo-tty, forwarding X11 connections, forwarding TCP connections, or forwarding the authentication agent connection over the secure channel. .Pp After this, the client either requests an interactive shell or execution -or a non-interactive command, which +of a non-interactive command, which .Nm will execute via the user's shell using its .Fl c option. The sides then enter session mode. In this mode, either side may send data at any time, and such data is forwarded to/from the shell or command on the server side, and the user terminal in the client side. .Pp When the user program terminates and all forwarded X11 and other connections have been closed, the server sends command exit status to the client, and both sides exit. .Sh LOGIN PROCESS When a user successfully logs in, .Nm does the following: .Bl -enum -offset indent .It If the login is on a tty, and no command has been specified, prints last login time and .Pa /etc/motd (unless prevented in the configuration file or by .Pa ~/.hushlogin ; see the .Sx FILES section). .It If the login is on a tty, records login time. .It Checks .Pa /etc/nologin and .Pa /var/run/nologin ; if one exists, it prints the contents and quits (unless root). .It Changes to run with normal user privileges. .It Sets up basic environment. .It Reads the file .Pa ~/.ssh/environment , if it exists, and users are allowed to change their environment. See the .Cm PermitUserEnvironment option in .Xr sshd_config 5 . .It Changes to user's home directory. .It If .Pa ~/.ssh/rc exists and the .Xr sshd_config 5 .Cm PermitUserRC option is set, runs it; else if .Pa /etc/ssh/sshrc exists, runs it; otherwise runs .Xr xauth 1 . The .Dq rc files are given the X11 authentication protocol and cookie in standard input. See .Sx SSHRC , below. .It Runs user's shell or command. All commands are run under the user's login shell as specified in the system password database. .El .Sh SSHRC If the file .Pa ~/.ssh/rc exists, .Xr sh 1 runs it after reading the environment files but before starting the user's shell or command. It must not produce any output on stdout; stderr must be used instead. If X11 forwarding is in use, it will receive the "proto cookie" pair in its standard input (and .Ev DISPLAY in its environment). The script must call .Xr xauth 1 because .Nm will not run xauth automatically to add X11 cookies. .Pp The primary purpose of this file is to run any initialization routines which may be needed before the user's home directory becomes accessible; AFS is a particular example of such an environment. .Pp This file will probably contain some initialization code followed by something similar to: .Bd -literal -offset 3n if read proto cookie && [ -n "$DISPLAY" ]; then if [ `echo $DISPLAY | cut -c1-10` = 'localhost:' ]; then # X11UseLocalhost=yes echo add unix:`echo $DISPLAY | cut -c11-` $proto $cookie else # X11UseLocalhost=no echo add $DISPLAY $proto $cookie fi | xauth -q - fi .Ed .Pp If this file does not exist, .Pa /etc/ssh/sshrc is run, and if that does not exist either, xauth is used to add the cookie. .Sh AUTHORIZED_KEYS FILE FORMAT .Cm AuthorizedKeysFile specifies the files containing public keys for public key authentication; if this option is not specified, the default is .Pa ~/.ssh/authorized_keys and .Pa ~/.ssh/authorized_keys2 . Each line of the file contains one key (empty lines and lines starting with a .Ql # are ignored as comments). Public keys consist of the following space-separated fields: options, keytype, base64-encoded key, comment. The options field is optional. The supported key types are: .Pp .Bl -item -compact -offset indent .It sk-ecdsa-sha2-nistp256@openssh.com .It ecdsa-sha2-nistp256 .It ecdsa-sha2-nistp384 .It ecdsa-sha2-nistp521 .It sk-ssh-ed25519@openssh.com .It ssh-ed25519 .It ssh-dss .It ssh-rsa .El .Pp The comment field is not used for anything (but may be convenient for the user to identify the key). .Pp Note that lines in this file can be several hundred bytes long (because of the size of the public key encoding) up to a limit of 8 kilobytes, which permits RSA keys up to 16 kilobits. You don't want to type them in; instead, copy the .Pa id_dsa.pub , .Pa id_ecdsa.pub , .Pa id_ecdsa_sk.pub , .Pa id_ed25519.pub , .Pa id_ed25519_sk.pub , or the .Pa id_rsa.pub file and edit it. .Pp .Nm enforces a minimum RSA key modulus size of 1024 bits. .Pp The options (if present) consist of comma-separated option specifications. No spaces are permitted, except within double quotes. The following option specifications are supported (note that option keywords are case-insensitive): .Bl -tag -width Ds .It Cm agent-forwarding Enable authentication agent forwarding previously disabled by the .Cm restrict option. .It Cm cert-authority Specifies that the listed key is a certification authority (CA) that is trusted to validate signed certificates for user authentication. .Pp Certificates may encode access restrictions similar to these key options. If both certificate restrictions and key options are present, the most restrictive union of the two is applied. .It Cm command="command" Specifies that the command is executed whenever this key is used for authentication. The command supplied by the user (if any) is ignored. The command is run on a pty if the client requests a pty; otherwise it is run without a tty. If an 8-bit clean channel is required, one must not request a pty or should specify .Cm no-pty . A quote may be included in the command by quoting it with a backslash. .Pp This option might be useful to restrict certain public keys to perform just a specific operation. An example might be a key that permits remote backups but nothing else. Note that the client may specify TCP and/or X11 forwarding unless they are explicitly prohibited, e.g. using the .Cm restrict key option. .Pp The command originally supplied by the client is available in the .Ev SSH_ORIGINAL_COMMAND environment variable. Note that this option applies to shell, command or subsystem execution. Also note that this command may be superseded by a .Xr sshd_config 5 .Cm ForceCommand directive. .Pp If a command is specified and a forced-command is embedded in a certificate used for authentication, then the certificate will be accepted only if the two commands are identical. .It Cm environment="NAME=value" Specifies that the string is to be added to the environment when logging in using this key. Environment variables set this way override other default environment values. Multiple options of this type are permitted. Environment processing is disabled by default and is controlled via the .Cm PermitUserEnvironment option. .It Cm expiry-time="timespec" Specifies a time after which the key will not be accepted. The time may be specified as a YYYYMMDD[Z] date or a YYYYMMDDHHMM[SS][Z] time. Dates and times will be interpreted in the system time zone unless suffixed by a Z character, in which case they will be interpreted in the UTC time zone. .It Cm from="pattern-list" Specifies that in addition to public key authentication, either the canonical name of the remote host or its IP address must be present in the comma-separated list of patterns. See PATTERNS in .Xr ssh_config 5 for more information on patterns. .Pp In addition to the wildcard matching that may be applied to hostnames or addresses, a .Cm from stanza may match IP addresses using CIDR address/masklen notation. .Pp The purpose of this option is to optionally increase security: public key authentication by itself does not trust the network or name servers or anything (but the key); however, if somebody somehow steals the key, the key permits an intruder to log in from anywhere in the world. This additional option makes using a stolen key more difficult (name servers and/or routers would have to be compromised in addition to just the key). .It Cm no-agent-forwarding Forbids authentication agent forwarding when this key is used for authentication. .It Cm no-port-forwarding Forbids TCP forwarding when this key is used for authentication. Any port forward requests by the client will return an error. This might be used, e.g. in connection with the .Cm command option. .It Cm no-pty Prevents tty allocation (a request to allocate a pty will fail). .It Cm no-user-rc Disables execution of .Pa ~/.ssh/rc . .It Cm no-X11-forwarding Forbids X11 forwarding when this key is used for authentication. Any X11 forward requests by the client will return an error. .It Cm permitlisten="[host:]port" Limit remote port forwarding with the .Xr ssh 1 .Fl R option such that it may only listen on the specified host (optional) and port. IPv6 addresses can be specified by enclosing the address in square brackets. Multiple .Cm permitlisten options may be applied separated by commas. Hostnames may include wildcards as described in the PATTERNS section in .Xr ssh_config 5 . A port specification of .Cm * matches any port. Note that the setting of .Cm GatewayPorts may further restrict listen addresses. Note that .Xr ssh 1 will send a hostname of .Dq localhost if a listen host was not specified when the forwarding was requested, and that this name is treated differently to the explicit localhost addresses .Dq 127.0.0.1 and .Dq ::1 . .It Cm permitopen="host:port" Limit local port forwarding with the .Xr ssh 1 .Fl L option such that it may only connect to the specified host and port. IPv6 addresses can be specified by enclosing the address in square brackets. Multiple .Cm permitopen options may be applied separated by commas. No pattern matching or name lookup is performed on the specified hostnames, they must be literal host names and/or addresses. A port specification of .Cm * matches any port. .It Cm port-forwarding Enable port forwarding previously disabled by the .Cm restrict option. .It Cm principals="principals" On a .Cm cert-authority line, specifies allowed principals for certificate authentication as a comma-separated list. At least one name from the list must appear in the certificate's list of principals for the certificate to be accepted. This option is ignored for keys that are not marked as trusted certificate signers using the .Cm cert-authority option. .It Cm pty Permits tty allocation previously disabled by the .Cm restrict option. .It Cm no-touch-required Do not require demonstration of user presence for signatures made using this key. This option only makes sense for the FIDO authenticator algorithms .Cm ecdsa-sk and .Cm ed25519-sk . .It Cm verify-required Require that signatures made using this key attest that they verified the user, e.g. via a PIN. This option only makes sense for the FIDO authenticator algorithms .Cm ecdsa-sk and .Cm ed25519-sk . .It Cm restrict Enable all restrictions, i.e. disable port, agent and X11 forwarding, as well as disabling PTY allocation and execution of .Pa ~/.ssh/rc . If any future restriction capabilities are added to authorized_keys files, they will be included in this set. .It Cm tunnel="n" Force a .Xr tun 4 device on the server. Without this option, the next available device will be used if the client requests a tunnel. .It Cm user-rc Enables execution of .Pa ~/.ssh/rc previously disabled by the .Cm restrict option. .It Cm X11-forwarding Permits X11 forwarding previously disabled by the .Cm restrict option. .El .Pp An example authorized_keys file: .Bd -literal -offset 3n # Comments are allowed at start of line. Blank lines are allowed. # Plain key, no restrictions ssh-rsa ... # Forced command, disable PTY and all forwarding restrict,command="dump /home" ssh-rsa ... # Restriction of ssh -L forwarding destinations permitopen="192.0.2.1:80",permitopen="192.0.2.2:25" ssh-rsa ... # Restriction of ssh -R forwarding listeners permitlisten="localhost:8080",permitlisten="[::1]:22000" ssh-rsa ... # Configuration for tunnel forwarding tunnel="0",command="sh /etc/netstart tun0" ssh-rsa ... # Override of restriction to allow PTY allocation restrict,pty,command="nethack" ssh-rsa ... # Allow FIDO key without requiring touch no-touch-required sk-ecdsa-sha2-nistp256@openssh.com ... # Require user-verification (e.g. PIN or biometric) for FIDO key verify-required sk-ecdsa-sha2-nistp256@openssh.com ... # Trust CA key, allow touch-less FIDO if requested in certificate cert-authority,no-touch-required,principals="user_a" ssh-rsa ... .Ed .Sh SSH_KNOWN_HOSTS FILE FORMAT The .Pa /etc/ssh/ssh_known_hosts and .Pa ~/.ssh/known_hosts files contain host public keys for all known hosts. The global file should be prepared by the administrator (optional), and the per-user file is maintained automatically: whenever the user connects to an unknown host, its key is added to the per-user file. .Pp Each line in these files contains the following fields: marker (optional), hostnames, keytype, base64-encoded key, comment. The fields are separated by spaces. .Pp The marker is optional, but if it is present then it must be one of .Dq @cert-authority , to indicate that the line contains a certification authority (CA) key, or .Dq @revoked , to indicate that the key contained on the line is revoked and must not ever be accepted. Only one marker should be used on a key line. .Pp Hostnames is a comma-separated list of patterns .Pf ( Ql * and .Ql \&? act as wildcards); each pattern in turn is matched against the host name. When .Nm sshd is authenticating a client, such as when using .Cm HostbasedAuthentication , this will be the canonical client host name. When .Xr ssh 1 is authenticating a server, this will be the host name given by the user, the value of the .Xr ssh 1 .Cm HostkeyAlias if it was specified, or the canonical server hostname if the .Xr ssh 1 .Cm CanonicalizeHostname option was used. .Pp A pattern may also be preceded by .Ql \&! to indicate negation: if the host name matches a negated pattern, it is not accepted (by that line) even if it matched another pattern on the line. A hostname or address may optionally be enclosed within .Ql \&[ and .Ql \&] brackets then followed by .Ql \&: and a non-standard port number. .Pp Alternately, hostnames may be stored in a hashed form which hides host names and addresses should the file's contents be disclosed. Hashed hostnames start with a .Ql | character. Only one hashed hostname may appear on a single line and none of the above negation or wildcard operators may be applied. .Pp The keytype and base64-encoded key are taken directly from the host key; they can be obtained, for example, from .Pa /etc/ssh/ssh_host_rsa_key.pub . The optional comment field continues to the end of the line, and is not used. .Pp Lines starting with .Ql # and empty lines are ignored as comments. .Pp When performing host authentication, authentication is accepted if any matching line has the proper key; either one that matches exactly or, if the server has presented a certificate for authentication, the key of the certification authority that signed the certificate. For a key to be trusted as a certification authority, it must use the .Dq @cert-authority marker described above. .Pp The known hosts file also provides a facility to mark keys as revoked, for example when it is known that the associated private key has been stolen. Revoked keys are specified by including the .Dq @revoked marker at the beginning of the key line, and are never accepted for authentication or as certification authorities, but instead will produce a warning from .Xr ssh 1 when they are encountered. .Pp It is permissible (but not recommended) to have several lines or different host keys for the same names. This will inevitably happen when short forms of host names from different domains are put in the file. It is possible that the files contain conflicting information; authentication is accepted if valid information can be found from either file. .Pp Note that the lines in these files are typically hundreds of characters long, and you definitely don't want to type in the host keys by hand. Rather, generate them by a script, .Xr ssh-keyscan 1 or by taking, for example, .Pa /etc/ssh/ssh_host_rsa_key.pub and adding the host names at the front. .Xr ssh-keygen 1 also offers some basic automated editing for .Pa ~/.ssh/known_hosts including removing hosts matching a host name and converting all host names to their hashed representations. .Pp An example ssh_known_hosts file: .Bd -literal -offset 3n # Comments allowed at start of line cvs.example.net,192.0.2.10 ssh-rsa AAAA1234.....= # A hashed hostname |1|JfKTdBh7rNbXkVAQCRp4OQoPfmI=|USECr3SWf1JUPsms5AqfD5QfxkM= ssh-rsa AAAA1234.....= # A revoked key @revoked * ssh-rsa AAAAB5W... # A CA key, accepted for any host in *.mydomain.com or *.mydomain.org @cert-authority *.mydomain.org,*.mydomain.com ssh-rsa AAAAB5W... .Ed .Sh FILES .Bl -tag -width Ds -compact .It Pa ~/.hushlogin This file is used to suppress printing the last login time and .Pa /etc/motd , if .Cm PrintLastLog and .Cm PrintMotd , respectively, are enabled. It does not suppress printing of the banner specified by .Cm Banner . .Pp .It Pa ~/.rhosts This file is used for host-based authentication (see .Xr ssh 1 for more information). On some machines this file may need to be world-readable if the user's home directory is on an NFS partition, because .Nm reads it as root. Additionally, this file must be owned by the user, and must not have write permissions for anyone else. The recommended permission for most machines is read/write for the user, and not accessible by others. .Pp .It Pa ~/.shosts This file is used in exactly the same way as .Pa .rhosts , but allows host-based authentication without permitting login with rlogin/rsh. .Pp .It Pa ~/.ssh/ This directory is the default location for all user-specific configuration and authentication information. There is no general requirement to keep the entire contents of this directory secret, but the recommended permissions are read/write/execute for the user, and not accessible by others. .Pp .It Pa ~/.ssh/authorized_keys Lists the public keys (DSA, ECDSA, Ed25519, RSA) that can be used for logging in as this user. The format of this file is described above. The content of the file is not highly sensitive, but the recommended permissions are read/write for the user, and not accessible by others. .Pp If this file, the .Pa ~/.ssh directory, or the user's home directory are writable by other users, then the file could be modified or replaced by unauthorized users. In this case, .Nm will not allow it to be used unless the .Cm StrictModes option has been set to .Dq no . .Pp .It Pa ~/.ssh/environment This file is read into the environment at login (if it exists). It can only contain empty lines, comment lines (that start with .Ql # ) , and assignment lines of the form name=value. The file should be writable only by the user; it need not be readable by anyone else. Environment processing is disabled by default and is controlled via the .Cm PermitUserEnvironment option. .Pp .It Pa ~/.ssh/known_hosts Contains a list of host keys for all hosts the user has logged into that are not already in the systemwide list of known host keys. The format of this file is described above. This file should be writable only by root/the owner and can, but need not be, world-readable. .Pp .It Pa ~/.ssh/rc Contains initialization routines to be run before the user's home directory becomes accessible. This file should be writable only by the user, and need not be readable by anyone else. .Pp .It Pa /etc/hosts.allow .It Pa /etc/hosts.deny Access controls that should be enforced by tcp-wrappers are defined here. Further details are described in .Xr hosts_access 5 . .Pp .It Pa /etc/hosts.equiv This file is for host-based authentication (see .Xr ssh 1 ) . It should only be writable by root. .Pp .It Pa /etc/moduli Contains Diffie-Hellman groups used for the "Diffie-Hellman Group Exchange" key exchange method. The file format is described in .Xr moduli 5 . If no usable groups are found in this file then fixed internal groups will be used. .Pp .It Pa /etc/motd See .Xr motd 5 . .Pp .It Pa /etc/nologin If this file exists, .Nm refuses to let anyone except root log in. The contents of the file are displayed to anyone trying to log in, and non-root connections are refused. The file should be world-readable. .Pp .It Pa /etc/shosts.equiv This file is used in exactly the same way as .Pa hosts.equiv , but allows host-based authentication without permitting login with rlogin/rsh. .Pp .It Pa /etc/ssh/ssh_host_ecdsa_key .It Pa /etc/ssh/ssh_host_ed25519_key .It Pa /etc/ssh/ssh_host_rsa_key These files contain the private parts of the host keys. These files should only be owned by root, readable only by root, and not accessible to others. Note that .Nm does not start if these files are group/world-accessible. .Pp .It Pa /etc/ssh/ssh_host_ecdsa_key.pub .It Pa /etc/ssh/ssh_host_ed25519_key.pub .It Pa /etc/ssh/ssh_host_rsa_key.pub These files contain the public parts of the host keys. These files should be world-readable but writable only by root. Their contents should match the respective private parts. These files are not really used for anything; they are provided for the convenience of the user so their contents can be copied to known hosts files. These files are created using .Xr ssh-keygen 1 . .Pp .It Pa /etc/ssh/ssh_known_hosts Systemwide list of known host keys. This file should be prepared by the system administrator to contain the public host keys of all machines in the organization. The format of this file is described above. This file should be writable only by root/the owner and should be world-readable. .Pp .It Pa /etc/ssh/sshd_config Contains configuration data for .Nm sshd . The file format and configuration options are described in .Xr sshd_config 5 . .Pp .It Pa /etc/ssh/sshrc Similar to .Pa ~/.ssh/rc , it can be used to specify machine-specific login-time initializations globally. This file should be writable only by root, and should be world-readable. .Pp .It Pa /var/empty .Xr chroot 2 directory used by .Nm during privilege separation in the pre-authentication phase. The directory should not contain any files and must be owned by root and not group or world-writable. .Pp .It Pa /var/run/sshd.pid Contains the process ID of the .Nm listening for connections (if there are several daemons running concurrently for different ports, this contains the process ID of the one started last). The content of this file is not sensitive; it can be world-readable. .El .Sh SEE ALSO .Xr scp 1 , .Xr sftp 1 , .Xr ssh 1 , .Xr ssh-add 1 , .Xr ssh-agent 1 , .Xr ssh-keygen 1 , .Xr ssh-keyscan 1 , .Xr chroot 2 , .Xr hosts_access 5 , .Xr login.conf 5 , .Xr moduli 5 , .Xr sshd_config 5 , .Xr inetd 8 , .Xr sftp-server 8 .Sh AUTHORS OpenSSH is a derivative of the original and free ssh 1.2.12 release by Tatu Ylonen. Aaron Campbell, Bob Beck, Markus Friedl, Niels Provos, Theo de Raadt and Dug Song removed many bugs, re-added newer features and created OpenSSH. Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. Niels Provos and Markus Friedl contributed support for privilege separation. diff --git a/crypto/openssh/sshd.c b/crypto/openssh/sshd.c index a82b82d08c14..cfdd61ec5747 100644 --- a/crypto/openssh/sshd.c +++ b/crypto/openssh/sshd.c @@ -1,2564 +1,2564 @@ /* $OpenBSD: sshd.c,v 1.600 2023/03/08 04:43:12 guenther Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This program is the ssh daemon. It listens for connections from clients, * and performs authentication, executes use commands or shell, and forwards * information to/from the application to the user client over an encrypted * connection. This can also handle forwarding of X11, TCP/IP, and * authentication agent connections. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 implementation: * Privilege Separation: * * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 2002 Niels Provos. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include "openbsd-compat/sys-tree.h" #include "openbsd-compat/sys-queue.h" #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #ifdef HAVE_POLL_H #include #endif #include #include #include #include #include #include #include #include #ifdef WITH_OPENSSL #include #include #include #include "openbsd-compat/openssl-compat.h" #endif #ifdef HAVE_SECUREWARE #include #include #endif #ifdef __FreeBSD__ #include #if defined(GSSAPI) && defined(HAVE_GSSAPI_GSSAPI_H) #include #elif defined(GSSAPI) && defined(HAVE_GSSAPI_H) #include #endif #endif #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "sshpty.h" #include "packet.h" #include "log.h" #include "sshbuf.h" #include "misc.h" #include "match.h" #include "servconf.h" #include "uidswap.h" #include "compat.h" #include "cipher.h" #include "digest.h" #include "sshkey.h" #include "kex.h" #include "authfile.h" #include "pathnames.h" #include "atomicio.h" #include "canohost.h" #include "hostfile.h" #include "auth.h" #include "authfd.h" #include "msg.h" #include "dispatch.h" #include "channels.h" #include "session.h" #include "monitor.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "ssh-sandbox.h" #include "auth-options.h" #include "version.h" #include "ssherr.h" #include "sk-api.h" #include "srclimit.h" #include "dh.h" #include "blacklist_client.h" #ifdef LIBWRAP #include #include #endif /* LIBWRAP */ /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) #define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) #define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) extern char *__progname; /* Server configuration options. */ ServerOptions options; /* Name of the server configuration file. */ char *config_file_name = _PATH_SERVER_CONFIG_FILE; /* * Debug mode flag. This can be set on the command line. If debug * mode is enabled, extra debugging output will be sent to the system * log, the daemon will not go to background, and will exit after processing * the first connection. */ int debug_flag = 0; /* * Indicating that the daemon should only test the configuration and keys. * If test_flag > 1 ("-T" flag), then sshd will also dump the effective * configuration, optionally using connection information provided by the * "-C" flag. */ static int test_flag = 0; /* Flag indicating that the daemon is being started from inetd. */ static int inetd_flag = 0; /* Flag indicating that sshd should not detach and become a daemon. */ static int no_daemon_flag = 0; /* debug goes to stderr unless inetd_flag is set */ static int log_stderr = 0; /* Saved arguments to main(). */ static char **saved_argv; static int saved_argc; /* re-exec */ static int rexeced_flag = 0; static int rexec_flag = 1; static int rexec_argc = 0; static char **rexec_argv; /* * The sockets that the server is listening; this is used in the SIGHUP * signal handler. */ #define MAX_LISTEN_SOCKS 16 static int listen_socks[MAX_LISTEN_SOCKS]; static int num_listen_socks = 0; /* Daemon's agent connection */ int auth_sock = -1; static int have_agent = 0; /* * Any really sensitive data in the application is contained in this * structure. The idea is that this structure could be locked into memory so * that the pages do not get written into swap. However, there are some * problems. The private key contains BIGNUMs, and we do not (in principle) * have access to the internals of them, and locking just the structure is * not very useful. Currently, memory locking is not implemented. */ struct { struct sshkey **host_keys; /* all private host keys */ struct sshkey **host_pubkeys; /* all public host keys */ struct sshkey **host_certificates; /* all public host certificates */ int have_ssh2_key; } sensitive_data; /* This is set to true when a signal is received. */ static volatile sig_atomic_t received_sighup = 0; static volatile sig_atomic_t received_sigterm = 0; /* record remote hostname or ip */ u_int utmp_len = HOST_NAME_MAX+1; /* * startup_pipes/flags are used for tracking children of the listening sshd * process early in their lifespans. This tracking is needed for three things: * * 1) Implementing the MaxStartups limit of concurrent unauthenticated * connections. * 2) Avoiding a race condition for SIGHUP processing, where child processes * may have listen_socks open that could collide with main listener process * after it restarts. * 3) Ensuring that rexec'd sshd processes have received their initial state * from the parent listen process before handling SIGHUP. * * Child processes signal that they have completed closure of the listen_socks * and (if applicable) received their rexec state by sending a char over their * sock. Child processes signal that authentication has completed by closing * the sock (or by exiting). */ static int *startup_pipes = NULL; static int *startup_flags = NULL; /* Indicates child closed listener */ static int startup_pipe = -1; /* in child */ /* variables used for privilege separation */ int use_privsep = -1; struct monitor *pmonitor = NULL; int privsep_is_preauth = 1; static int privsep_chroot = 1; /* global connection state and authentication contexts */ Authctxt *the_authctxt = NULL; struct ssh *the_active_state; /* global key/cert auth options. XXX move to permanent ssh->authctxt? */ struct sshauthopt *auth_opts = NULL; /* sshd_config buffer */ struct sshbuf *cfg; /* Included files from the configuration file */ struct include_list includes = TAILQ_HEAD_INITIALIZER(includes); /* message to be displayed after login */ struct sshbuf *loginmsg; /* Unprivileged user */ struct passwd *privsep_pw = NULL; /* Prototypes for various functions defined later in this file. */ void destroy_sensitive_data(void); void demote_sensitive_data(void); static void do_ssh2_kex(struct ssh *); static char *listener_proctitle; /* * Close all listening sockets */ static void close_listen_socks(void) { int i; for (i = 0; i < num_listen_socks; i++) close(listen_socks[i]); num_listen_socks = 0; } static void close_startup_pipes(void) { int i; if (startup_pipes) for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1) close(startup_pipes[i]); } /* * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; * the effect is to reread the configuration file (and to regenerate * the server key). */ static void sighup_handler(int sig) { received_sighup = 1; } /* * Called from the main program after receiving SIGHUP. * Restarts the server. */ static void sighup_restart(void) { logit("Received SIGHUP; restarting."); if (options.pid_file != NULL) unlink(options.pid_file); platform_pre_restart(); close_listen_socks(); close_startup_pipes(); ssh_signal(SIGHUP, SIG_IGN); /* will be restored after exec */ execv(saved_argv[0], saved_argv); logit("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], strerror(errno)); exit(1); } /* * Generic signal handler for terminating signals in the master daemon. */ static void sigterm_handler(int sig) { received_sigterm = sig; } /* * SIGCHLD handler. This is called whenever a child dies. This will then * reap any zombies left by exited children. */ static void main_sigchld_handler(int sig) { int save_errno = errno; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid == -1 && errno == EINTR)) ; errno = save_errno; } /* * Signal handler for the alarm after the login grace period has expired. */ static void grace_alarm_handler(int sig) { /* * Try to kill any processes that we have spawned, E.g. authorized * keys command helpers or privsep children. */ if (getpgid(0) == getpid()) { ssh_signal(SIGTERM, SIG_IGN); kill(0, SIGTERM); } BLACKLIST_NOTIFY(the_active_state, BLACKLIST_AUTH_FAIL, "ssh"); /* Log error and exit. */ sigdie("Timeout before authentication for %s port %d", ssh_remote_ipaddr(the_active_state), ssh_remote_port(the_active_state)); } /* Destroy the host and server keys. They will no longer be needed. */ void destroy_sensitive_data(void) { u_int i; for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { sshkey_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = NULL; } if (sensitive_data.host_certificates[i]) { sshkey_free(sensitive_data.host_certificates[i]); sensitive_data.host_certificates[i] = NULL; } } } /* Demote private to public keys for network child */ void demote_sensitive_data(void) { struct sshkey *tmp; u_int i; int r; for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { if ((r = sshkey_from_private( sensitive_data.host_keys[i], &tmp)) != 0) fatal_r(r, "could not demote host %s key", sshkey_type(sensitive_data.host_keys[i])); sshkey_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = tmp; } /* Certs do not need demotion */ } } static void reseed_prngs(void) { u_int32_t rnd[256]; #ifdef WITH_OPENSSL RAND_poll(); #endif arc4random_stir(); /* noop on recent arc4random() implementations */ arc4random_buf(rnd, sizeof(rnd)); /* let arc4random notice PID change */ #ifdef WITH_OPENSSL RAND_seed(rnd, sizeof(rnd)); /* give libcrypto a chance to notice the PID change */ if ((RAND_bytes((u_char *)rnd, 1)) != 1) fatal("%s: RAND_bytes failed", __func__); #endif explicit_bzero(rnd, sizeof(rnd)); } static void privsep_preauth_child(void) { gid_t gidset[1]; /* Enable challenge-response authentication for privilege separation */ privsep_challenge_enable(); #ifdef GSSAPI /* Cache supported mechanism OIDs for later use */ ssh_gssapi_prepare_supported_oids(); #endif reseed_prngs(); /* Demote the private keys to public keys. */ demote_sensitive_data(); /* Demote the child */ if (privsep_chroot) { /* Change our root directory */ if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, strerror(errno)); if (chdir("/") == -1) fatal("chdir(\"/\"): %s", strerror(errno)); /* Drop our privileges */ debug3("privsep user:group %u:%u", (u_int)privsep_pw->pw_uid, (u_int)privsep_pw->pw_gid); gidset[0] = privsep_pw->pw_gid; if (setgroups(1, gidset) == -1) fatal("setgroups: %.100s", strerror(errno)); permanently_set_uid(privsep_pw); } } static int privsep_preauth(struct ssh *ssh) { int status, r; pid_t pid; struct ssh_sandbox *box = NULL; /* Set up unprivileged child process to deal with network data */ pmonitor = monitor_init(); /* Store a pointer to the kex for later rekeying */ pmonitor->m_pkex = &ssh->kex; if (use_privsep == PRIVSEP_ON) box = ssh_sandbox_init(pmonitor); pid = fork(); if (pid == -1) { fatal("fork of unprivileged child failed"); } else if (pid != 0) { debug2("Network child is on pid %ld", (long)pid); pmonitor->m_pid = pid; if (have_agent) { r = ssh_get_authentication_socket(&auth_sock); if (r != 0) { error_r(r, "Could not get agent socket"); have_agent = 0; } } if (box != NULL) ssh_sandbox_parent_preauth(box, pid); monitor_child_preauth(ssh, pmonitor); /* Wait for the child's exit status */ while (waitpid(pid, &status, 0) == -1) { if (errno == EINTR) continue; pmonitor->m_pid = -1; fatal_f("waitpid: %s", strerror(errno)); } privsep_is_preauth = 0; pmonitor->m_pid = -1; if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) fatal_f("preauth child exited with status %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) fatal_f("preauth child terminated by signal %d", WTERMSIG(status)); if (box != NULL) ssh_sandbox_parent_finish(box); return 1; } else { /* child */ close(pmonitor->m_sendfd); close(pmonitor->m_log_recvfd); /* Arrange for logging to be sent to the monitor */ set_log_handler(mm_log_handler, pmonitor); privsep_preauth_child(); setproctitle("%s", "[net]"); if (box != NULL) ssh_sandbox_child(box); return 0; } } static void privsep_postauth(struct ssh *ssh, Authctxt *authctxt) { #ifdef DISABLE_FD_PASSING if (1) { #else if (authctxt->pw->pw_uid == 0) { #endif /* File descriptor passing is broken or root login */ use_privsep = 0; goto skip; } /* New socket pair */ monitor_reinit(pmonitor); pmonitor->m_pid = fork(); if (pmonitor->m_pid == -1) fatal("fork of unprivileged child failed"); else if (pmonitor->m_pid != 0) { verbose("User child is on pid %ld", (long)pmonitor->m_pid); sshbuf_reset(loginmsg); monitor_clear_keystate(ssh, pmonitor); monitor_child_postauth(ssh, pmonitor); /* NEVERREACHED */ exit(0); } /* child */ close(pmonitor->m_sendfd); pmonitor->m_sendfd = -1; /* Demote the private keys to public keys. */ demote_sensitive_data(); reseed_prngs(); /* Drop privileges */ do_setusercontext(authctxt->pw); skip: /* It is safe now to apply the key state */ monitor_apply_keystate(ssh, pmonitor); /* * Tell the packet layer that authentication was successful, since * this information is not part of the key state. */ ssh_packet_set_authenticated(ssh); } static void append_hostkey_type(struct sshbuf *b, const char *s) { int r; if (match_pattern_list(s, options.hostkeyalgorithms, 0) != 1) { debug3_f("%s key not permitted by HostkeyAlgorithms", s); return; } if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) > 0 ? "," : "", s)) != 0) fatal_fr(r, "sshbuf_putf"); } static char * list_hostkey_types(void) { struct sshbuf *b; struct sshkey *key; char *ret; u_int i; if ((b = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); for (i = 0; i < options.num_host_key_files; i++) { key = sensitive_data.host_keys[i]; if (key == NULL) key = sensitive_data.host_pubkeys[i]; if (key == NULL) continue; switch (key->type) { case KEY_RSA: /* for RSA we also support SHA2 signatures */ append_hostkey_type(b, "rsa-sha2-512"); append_hostkey_type(b, "rsa-sha2-256"); /* FALLTHROUGH */ case KEY_DSA: case KEY_ECDSA: case KEY_ED25519: case KEY_ECDSA_SK: case KEY_ED25519_SK: case KEY_XMSS: append_hostkey_type(b, sshkey_ssh_name(key)); break; } /* If the private key has a cert peer, then list that too */ key = sensitive_data.host_certificates[i]; if (key == NULL) continue; switch (key->type) { case KEY_RSA_CERT: /* for RSA we also support SHA2 signatures */ append_hostkey_type(b, "rsa-sha2-512-cert-v01@openssh.com"); append_hostkey_type(b, "rsa-sha2-256-cert-v01@openssh.com"); /* FALLTHROUGH */ case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: case KEY_ECDSA_SK_CERT: case KEY_ED25519_SK_CERT: case KEY_XMSS_CERT: append_hostkey_type(b, sshkey_ssh_name(key)); break; } } if ((ret = sshbuf_dup_string(b)) == NULL) fatal_f("sshbuf_dup_string failed"); sshbuf_free(b); debug_f("%s", ret); return ret; } static struct sshkey * get_hostkey_by_type(int type, int nid, int need_private, struct ssh *ssh) { u_int i; struct sshkey *key; for (i = 0; i < options.num_host_key_files; i++) { switch (type) { case KEY_RSA_CERT: case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: case KEY_ECDSA_SK_CERT: case KEY_ED25519_SK_CERT: case KEY_XMSS_CERT: key = sensitive_data.host_certificates[i]; break; default: key = sensitive_data.host_keys[i]; if (key == NULL && !need_private) key = sensitive_data.host_pubkeys[i]; break; } if (key == NULL || key->type != type) continue; switch (type) { case KEY_ECDSA: case KEY_ECDSA_SK: case KEY_ECDSA_CERT: case KEY_ECDSA_SK_CERT: if (key->ecdsa_nid != nid) continue; /* FALLTHROUGH */ default: return need_private ? sensitive_data.host_keys[i] : key; } } return NULL; } struct sshkey * get_hostkey_public_by_type(int type, int nid, struct ssh *ssh) { return get_hostkey_by_type(type, nid, 0, ssh); } struct sshkey * get_hostkey_private_by_type(int type, int nid, struct ssh *ssh) { return get_hostkey_by_type(type, nid, 1, ssh); } struct sshkey * get_hostkey_by_index(int ind) { if (ind < 0 || (u_int)ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_keys[ind]); } struct sshkey * get_hostkey_public_by_index(int ind, struct ssh *ssh) { if (ind < 0 || (u_int)ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_pubkeys[ind]); } int get_hostkey_index(struct sshkey *key, int compare, struct ssh *ssh) { u_int i; for (i = 0; i < options.num_host_key_files; i++) { if (sshkey_is_cert(key)) { if (key == sensitive_data.host_certificates[i] || (compare && sensitive_data.host_certificates[i] && sshkey_equal(key, sensitive_data.host_certificates[i]))) return (i); } else { if (key == sensitive_data.host_keys[i] || (compare && sensitive_data.host_keys[i] && sshkey_equal(key, sensitive_data.host_keys[i]))) return (i); if (key == sensitive_data.host_pubkeys[i] || (compare && sensitive_data.host_pubkeys[i] && sshkey_equal(key, sensitive_data.host_pubkeys[i]))) return (i); } } return (-1); } /* Inform the client of all hostkeys */ static void notify_hostkeys(struct ssh *ssh) { struct sshbuf *buf; struct sshkey *key; u_int i, nkeys; int r; char *fp; /* Some clients cannot cope with the hostkeys message, skip those. */ if (ssh->compat & SSH_BUG_HOSTKEYS) return; if ((buf = sshbuf_new()) == NULL) fatal_f("sshbuf_new"); for (i = nkeys = 0; i < options.num_host_key_files; i++) { key = get_hostkey_public_by_index(i, ssh); if (key == NULL || key->type == KEY_UNSPEC || sshkey_is_cert(key)) continue; fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); debug3_f("key %d: %s %s", i, sshkey_ssh_name(key), fp); free(fp); if (nkeys == 0) { /* * Start building the request when we find the * first usable key. */ if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "hostkeys-00@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0) /* want reply */ sshpkt_fatal(ssh, r, "%s: start request", __func__); } /* Append the key to the request */ sshbuf_reset(buf); if ((r = sshkey_putb(key, buf)) != 0) fatal_fr(r, "couldn't put hostkey %d", i); if ((r = sshpkt_put_stringb(ssh, buf)) != 0) sshpkt_fatal(ssh, r, "%s: append key", __func__); nkeys++; } debug3_f("sent %u hostkeys", nkeys); if (nkeys == 0) fatal_f("no hostkeys"); if ((r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send", __func__); sshbuf_free(buf); } /* * returns 1 if connection should be dropped, 0 otherwise. * dropping starts at connection #max_startups_begin with a probability * of (max_startups_rate/100). the probability increases linearly until * all connections are dropped for startups > max_startups */ static int should_drop_connection(int startups) { int p, r; if (startups < options.max_startups_begin) return 0; if (startups >= options.max_startups) return 1; if (options.max_startups_rate == 100) return 1; p = 100 - options.max_startups_rate; p *= startups - options.max_startups_begin; p /= options.max_startups - options.max_startups_begin; p += options.max_startups_rate; r = arc4random_uniform(100); debug_f("p %d, r %d", p, r); return (r < p) ? 1 : 0; } /* * Check whether connection should be accepted by MaxStartups. * Returns 0 if the connection is accepted. If the connection is refused, * returns 1 and attempts to send notification to client. * Logs when the MaxStartups condition is entered or exited, and periodically * while in that state. */ static int drop_connection(int sock, int startups, int notify_pipe) { char *laddr, *raddr; const char msg[] = "Exceeded MaxStartups\r\n"; static time_t last_drop, first_drop; static u_int ndropped; LogLevel drop_level = SYSLOG_LEVEL_VERBOSE; time_t now; now = monotime(); if (!should_drop_connection(startups) && srclimit_check_allow(sock, notify_pipe) == 1) { if (last_drop != 0 && startups < options.max_startups_begin - 1) { /* XXX maybe need better hysteresis here */ logit("exited MaxStartups throttling after %s, " "%u connections dropped", fmt_timeframe(now - first_drop), ndropped); last_drop = 0; } return 0; } #define SSHD_MAXSTARTUPS_LOG_INTERVAL (5 * 60) if (last_drop == 0) { error("beginning MaxStartups throttling"); drop_level = SYSLOG_LEVEL_INFO; first_drop = now; ndropped = 0; } else if (last_drop + SSHD_MAXSTARTUPS_LOG_INTERVAL < now) { /* Periodic logs */ error("in MaxStartups throttling for %s, " "%u connections dropped", fmt_timeframe(now - first_drop), ndropped + 1); drop_level = SYSLOG_LEVEL_INFO; } last_drop = now; ndropped++; laddr = get_local_ipaddr(sock); raddr = get_peer_ipaddr(sock); do_log2(drop_level, "drop connection #%d from [%s]:%d on [%s]:%d " "past MaxStartups", startups, raddr, get_peer_port(sock), laddr, get_local_port(sock)); free(laddr); free(raddr); /* best-effort notification to client */ (void)write(sock, msg, sizeof(msg) - 1); return 1; } static void usage(void) { if (options.version_addendum != NULL && *options.version_addendum != '\0') fprintf(stderr, "%s %s, %s\n", SSH_RELEASE, options.version_addendum, SSH_OPENSSL_VERSION); else fprintf(stderr, "%s, %s\n", SSH_RELEASE, SSH_OPENSSL_VERSION); fprintf(stderr, "usage: sshd [-46DdeGiqTtV] [-C connection_spec] [-c host_cert_file]\n" " [-E log_file] [-f config_file] [-g login_grace_time]\n" " [-h host_key_file] [-o option] [-p port] [-u len]\n" ); exit(1); } static void send_rexec_state(int fd, struct sshbuf *conf) { struct sshbuf *m = NULL, *inc = NULL; struct include_item *item = NULL; int r; debug3_f("entering fd = %d config len %zu", fd, sshbuf_len(conf)); if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* pack includes into a string */ TAILQ_FOREACH(item, &includes, entry) { if ((r = sshbuf_put_cstring(inc, item->selector)) != 0 || (r = sshbuf_put_cstring(inc, item->filename)) != 0 || (r = sshbuf_put_stringb(inc, item->contents)) != 0) fatal_fr(r, "compose includes"); } /* * Protocol from reexec master to child: * string configuration * string included_files[] { * string selector * string filename * string contents * } */ if ((r = sshbuf_put_stringb(m, conf)) != 0 || (r = sshbuf_put_stringb(m, inc)) != 0) fatal_fr(r, "compose config"); if (ssh_msg_send(fd, 0, m) == -1) error_f("ssh_msg_send failed"); sshbuf_free(m); sshbuf_free(inc); debug3_f("done"); } static void recv_rexec_state(int fd, struct sshbuf *conf) { struct sshbuf *m, *inc; u_char *cp, ver; size_t len; int r; struct include_item *item; debug3_f("entering fd = %d", fd); if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if (ssh_msg_recv(fd, m) == -1) fatal_f("ssh_msg_recv failed"); if ((r = sshbuf_get_u8(m, &ver)) != 0) fatal_fr(r, "parse version"); if (ver != 0) fatal_f("rexec version mismatch"); if ((r = sshbuf_get_string(m, &cp, &len)) != 0 || (r = sshbuf_get_stringb(m, inc)) != 0) fatal_fr(r, "parse config"); if (conf != NULL && (r = sshbuf_put(conf, cp, len))) fatal_fr(r, "sshbuf_put"); while (sshbuf_len(inc) != 0) { item = xcalloc(1, sizeof(*item)); if ((item->contents = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_get_cstring(inc, &item->selector, NULL)) != 0 || (r = sshbuf_get_cstring(inc, &item->filename, NULL)) != 0 || (r = sshbuf_get_stringb(inc, item->contents)) != 0) fatal_fr(r, "parse includes"); TAILQ_INSERT_TAIL(&includes, item, entry); } free(cp); sshbuf_free(m); debug3_f("done"); } /* Accept a connection from inetd */ static void server_accept_inetd(int *sock_in, int *sock_out) { if (rexeced_flag) { close(REEXEC_CONFIG_PASS_FD); *sock_in = *sock_out = dup(STDIN_FILENO); } else { *sock_in = dup(STDIN_FILENO); *sock_out = dup(STDOUT_FILENO); } /* * We intentionally do not close the descriptors 0, 1, and 2 * as our code for setting the descriptors won't work if * ttyfd happens to be one of those. */ if (stdfd_devnull(1, 1, !log_stderr) == -1) error_f("stdfd_devnull failed"); debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); } /* * Listen for TCP connections */ static void listen_on_addrs(struct listenaddr *la) { int ret, listen_sock; struct addrinfo *ai; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; for (ai = la->addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; if (num_listen_socks >= MAX_LISTEN_SOCKS) fatal("Too many listen sockets. " "Enlarge MAX_LISTEN_SOCKS"); if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { error("getnameinfo failed: %.100s", ssh_gai_strerror(ret)); continue; } /* Create socket for listening. */ listen_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (listen_sock == -1) { /* kernel may not support ipv6 */ verbose("socket: %.100s", strerror(errno)); continue; } if (set_nonblock(listen_sock) == -1) { close(listen_sock); continue; } if (fcntl(listen_sock, F_SETFD, FD_CLOEXEC) == -1) { verbose("socket: CLOEXEC: %s", strerror(errno)); close(listen_sock); continue; } /* Socket options */ set_reuseaddr(listen_sock); if (la->rdomain != NULL && set_rdomain(listen_sock, la->rdomain) == -1) { close(listen_sock); continue; } /* Only communicate in IPv6 over AF_INET6 sockets. */ if (ai->ai_family == AF_INET6) sock_set_v6only(listen_sock); debug("Bind to port %s on %s.", strport, ntop); /* Bind the socket to the desired port. */ if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) == -1) { error("Bind to port %s on %s failed: %.200s.", strport, ntop, strerror(errno)); close(listen_sock); continue; } listen_socks[num_listen_socks] = listen_sock; num_listen_socks++; /* Start listening on the port. */ if (listen(listen_sock, SSH_LISTEN_BACKLOG) == -1) fatal("listen on [%s]:%s: %.100s", ntop, strport, strerror(errno)); logit("Server listening on %s port %s%s%s.", ntop, strport, la->rdomain == NULL ? "" : " rdomain ", la->rdomain == NULL ? "" : la->rdomain); } } static void server_listen(void) { u_int i; /* Initialise per-source limit tracking. */ srclimit_init(options.max_startups, options.per_source_max_startups, options.per_source_masklen_ipv4, options.per_source_masklen_ipv6); for (i = 0; i < options.num_listen_addrs; i++) { listen_on_addrs(&options.listen_addrs[i]); freeaddrinfo(options.listen_addrs[i].addrs); free(options.listen_addrs[i].rdomain); memset(&options.listen_addrs[i], 0, sizeof(options.listen_addrs[i])); } free(options.listen_addrs); options.listen_addrs = NULL; options.num_listen_addrs = 0; if (!num_listen_socks) fatal("Cannot bind any address."); } /* * The main TCP accept loop. Note that, for the non-debug case, returns * from this function are in a forked subprocess. */ static void server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) { struct pollfd *pfd = NULL; int i, j, ret, npfd; int ostartups = -1, startups = 0, listening = 0, lameduck = 0; int startup_p[2] = { -1 , -1 }, *startup_pollfd; char c = 0; struct sockaddr_storage from; socklen_t fromlen; pid_t pid; u_char rnd[256]; sigset_t nsigset, osigset; #ifdef LIBWRAP struct request_info req; request_init(&req, RQ_DAEMON, __progname, 0); #endif /* pipes connected to unauthenticated child sshd processes */ startup_pipes = xcalloc(options.max_startups, sizeof(int)); startup_flags = xcalloc(options.max_startups, sizeof(int)); startup_pollfd = xcalloc(options.max_startups, sizeof(int)); for (i = 0; i < options.max_startups; i++) startup_pipes[i] = -1; /* * Prepare signal mask that we use to block signals that might set * received_sigterm or received_sighup, so that we are guaranteed * to immediately wake up the ppoll if a signal is received after * the flag is checked. */ sigemptyset(&nsigset); sigaddset(&nsigset, SIGHUP); sigaddset(&nsigset, SIGCHLD); sigaddset(&nsigset, SIGTERM); sigaddset(&nsigset, SIGQUIT); /* sized for worst-case */ pfd = xcalloc(num_listen_socks + options.max_startups, sizeof(struct pollfd)); /* * Stay listening for connections until the system crashes or * the daemon is killed with a signal. */ for (;;) { sigprocmask(SIG_BLOCK, &nsigset, &osigset); if (received_sigterm) { logit("Received signal %d; terminating.", (int) received_sigterm); close_listen_socks(); if (options.pid_file != NULL) unlink(options.pid_file); exit(received_sigterm == SIGTERM ? 0 : 255); } if (ostartups != startups) { setproctitle("%s [listener] %d of %d-%d startups", listener_proctitle, startups, options.max_startups_begin, options.max_startups); ostartups = startups; } if (received_sighup) { if (!lameduck) { debug("Received SIGHUP; waiting for children"); close_listen_socks(); lameduck = 1; } if (listening <= 0) { sigprocmask(SIG_SETMASK, &osigset, NULL); sighup_restart(); } } for (i = 0; i < num_listen_socks; i++) { pfd[i].fd = listen_socks[i]; pfd[i].events = POLLIN; } npfd = num_listen_socks; for (i = 0; i < options.max_startups; i++) { startup_pollfd[i] = -1; if (startup_pipes[i] != -1) { pfd[npfd].fd = startup_pipes[i]; pfd[npfd].events = POLLIN; startup_pollfd[i] = npfd++; } } /* Wait until a connection arrives or a child exits. */ ret = ppoll(pfd, npfd, NULL, &osigset); if (ret == -1 && errno != EINTR) { error("ppoll: %.100s", strerror(errno)); if (errno == EINVAL) cleanup_exit(1); /* can't recover */ } sigprocmask(SIG_SETMASK, &osigset, NULL); if (ret == -1) continue; for (i = 0; i < options.max_startups; i++) { if (startup_pipes[i] == -1 || startup_pollfd[i] == -1 || !(pfd[startup_pollfd[i]].revents & (POLLIN|POLLHUP))) continue; switch (read(startup_pipes[i], &c, sizeof(c))) { case -1: if (errno == EINTR || errno == EAGAIN) continue; if (errno != EPIPE) { error_f("startup pipe %d (fd=%d): " "read %s", i, startup_pipes[i], strerror(errno)); } /* FALLTHROUGH */ case 0: /* child exited or completed auth */ close(startup_pipes[i]); srclimit_done(startup_pipes[i]); startup_pipes[i] = -1; startups--; if (startup_flags[i]) listening--; break; case 1: /* child has finished preliminaries */ if (startup_flags[i]) { listening--; startup_flags[i] = 0; } break; } } for (i = 0; i < num_listen_socks; i++) { if (!(pfd[i].revents & POLLIN)) continue; fromlen = sizeof(from); *newsock = accept(listen_socks[i], (struct sockaddr *)&from, &fromlen); if (*newsock == -1) { if (errno != EINTR && errno != EWOULDBLOCK && errno != ECONNABORTED && errno != EAGAIN) error("accept: %.100s", strerror(errno)); if (errno == EMFILE || errno == ENFILE) usleep(100 * 1000); continue; } #ifdef LIBWRAP /* Check whether logins are denied from this host. */ request_set(&req, RQ_FILE, *newsock, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0); sock_host(&req); if (!hosts_access(&req)) { const struct linger l = { .l_onoff = 1, .l_linger = 0 }; (void )setsockopt(*newsock, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); (void )close(*newsock); /* * Mimic message from libwrap's refuse() as * precisely as we can afford. The authentic * message prints the IP address and the * hostname it resolves to in parentheses. If * the IP address cannot be resolved to a * hostname, the IP address will be repeated * in parentheses. As name resolution in the * main server loop could stall, and logging * resolved names adds little or no value to * incident investigation, this implementation * only repeats the IP address in parentheses. * This should resemble librwap's refuse() * closely enough not to break auditing * software like sshguard or custom scripts. */ syslog(LOG_WARNING, "refused connect from %s (%s)", eval_hostaddr(req.client), eval_hostaddr(req.client)); debug("Connection refused by tcp wrapper"); continue; } #endif /* LIBWRAP */ if (unset_nonblock(*newsock) == -1) { close(*newsock); continue; } if (pipe(startup_p) == -1) { error_f("pipe(startup_p): %s", strerror(errno)); close(*newsock); continue; } if (drop_connection(*newsock, startups, startup_p[0])) { close(*newsock); close(startup_p[0]); close(startup_p[1]); continue; } if (rexec_flag && socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) { error("reexec socketpair: %s", strerror(errno)); close(*newsock); close(startup_p[0]); close(startup_p[1]); continue; } for (j = 0; j < options.max_startups; j++) if (startup_pipes[j] == -1) { startup_pipes[j] = startup_p[0]; startups++; startup_flags[j] = 1; break; } /* * Got connection. Fork a child to handle it, unless * we are in debugging mode. */ if (debug_flag) { /* * In debugging mode. Close the listening * socket, and start processing the * connection without forking. */ debug("Server will not fork when running in debugging mode."); close_listen_socks(); *sock_in = *newsock; *sock_out = *newsock; close(startup_p[0]); close(startup_p[1]); startup_pipe = -1; pid = getpid(); if (rexec_flag) { send_rexec_state(config_s[0], cfg); close(config_s[0]); } free(pfd); return; } /* * Normal production daemon. Fork, and have * the child process the connection. The * parent continues listening. */ platform_pre_fork(); listening++; if ((pid = fork()) == 0) { /* * Child. Close the listening and * max_startup sockets. Start using * the accepted socket. Reinitialize * logging (since our pid has changed). * We return from this function to handle * the connection. */ platform_post_fork_child(); startup_pipe = startup_p[1]; close_startup_pipes(); close_listen_socks(); *sock_in = *newsock; *sock_out = *newsock; log_init(__progname, options.log_level, options.log_facility, log_stderr); if (rexec_flag) close(config_s[0]); else { /* * Signal parent that the preliminaries * for this child are complete. For the * re-exec case, this happens after the * child has received the rexec state * from the server. */ (void)atomicio(vwrite, startup_pipe, "\0", 1); } free(pfd); return; } /* Parent. Stay in the loop. */ platform_post_fork_parent(pid); if (pid == -1) error("fork: %.100s", strerror(errno)); else debug("Forked child %ld.", (long)pid); close(startup_p[1]); if (rexec_flag) { close(config_s[1]); send_rexec_state(config_s[0], cfg); close(config_s[0]); } close(*newsock); /* * Ensure that our random state differs * from that of the child */ arc4random_stir(); arc4random_buf(rnd, sizeof(rnd)); #ifdef WITH_OPENSSL RAND_seed(rnd, sizeof(rnd)); if ((RAND_bytes((u_char *)rnd, 1)) != 1) fatal("%s: RAND_bytes failed", __func__); #endif explicit_bzero(rnd, sizeof(rnd)); } } } /* * If IP options are supported, make sure there are none (log and * return an error if any are found). Basically we are worried about * source routing; it can be used to pretend you are somebody * (ip-address) you are not. That itself may be "almost acceptable" * under certain circumstances, but rhosts authentication is useless * if source routing is accepted. Notice also that if we just dropped * source routing here, the other side could use IP spoofing to do * rest of the interaction and could still bypass security. So we * exit here if we detect any IP options. */ static void check_ip_options(struct ssh *ssh) { #ifdef IP_OPTIONS int sock_in = ssh_packet_get_connection_in(ssh); struct sockaddr_storage from; u_char opts[200]; socklen_t i, option_size = sizeof(opts), fromlen = sizeof(from); char text[sizeof(opts) * 3 + 1]; memset(&from, 0, sizeof(from)); if (getpeername(sock_in, (struct sockaddr *)&from, &fromlen) == -1) return; if (from.ss_family != AF_INET) return; /* XXX IPv6 options? */ if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts, &option_size) >= 0 && option_size != 0) { text[0] = '\0'; for (i = 0; i < option_size; i++) snprintf(text + i*3, sizeof(text) - i*3, " %2.2x", opts[i]); fatal("Connection from %.100s port %d with IP opts: %.800s", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text); } return; #endif /* IP_OPTIONS */ } /* Set the routing domain for this process */ static void set_process_rdomain(struct ssh *ssh, const char *name) { #if defined(HAVE_SYS_SET_PROCESS_RDOMAIN) if (name == NULL) return; /* default */ if (strcmp(name, "%D") == 0) { /* "expands" to routing domain of connection */ if ((name = ssh_packet_rdomain_in(ssh)) == NULL) return; } /* NB. We don't pass 'ssh' to sys_set_process_rdomain() */ return sys_set_process_rdomain(name); #elif defined(__OpenBSD__) int rtable, ortable = getrtable(); const char *errstr; if (name == NULL) return; /* default */ if (strcmp(name, "%D") == 0) { /* "expands" to routing domain of connection */ if ((name = ssh_packet_rdomain_in(ssh)) == NULL) return; } rtable = (int)strtonum(name, 0, 255, &errstr); if (errstr != NULL) /* Shouldn't happen */ fatal("Invalid routing domain \"%s\": %s", name, errstr); if (rtable != ortable && setrtable(rtable) != 0) fatal("Unable to set routing domain %d: %s", rtable, strerror(errno)); debug_f("set routing domain %d (was %d)", rtable, ortable); #else /* defined(__OpenBSD__) */ fatal("Unable to set routing domain: not supported in this platform"); #endif } static void accumulate_host_timing_secret(struct sshbuf *server_cfg, struct sshkey *key) { static struct ssh_digest_ctx *ctx; u_char *hash; size_t len; struct sshbuf *buf; int r; if (ctx == NULL && (ctx = ssh_digest_start(SSH_DIGEST_SHA512)) == NULL) fatal_f("ssh_digest_start"); if (key == NULL) { /* finalize */ /* add server config in case we are using agent for host keys */ if (ssh_digest_update(ctx, sshbuf_ptr(server_cfg), sshbuf_len(server_cfg)) != 0) fatal_f("ssh_digest_update"); len = ssh_digest_bytes(SSH_DIGEST_SHA512); hash = xmalloc(len); if (ssh_digest_final(ctx, hash, len) != 0) fatal_f("ssh_digest_final"); options.timing_secret = PEEK_U64(hash); freezero(hash, len); ssh_digest_free(ctx); ctx = NULL; return; } if ((buf = sshbuf_new()) == NULL) fatal_f("could not allocate buffer"); if ((r = sshkey_private_serialize(key, buf)) != 0) fatal_fr(r, "encode %s key", sshkey_ssh_name(key)); if (ssh_digest_update(ctx, sshbuf_ptr(buf), sshbuf_len(buf)) != 0) fatal_f("ssh_digest_update"); sshbuf_reset(buf); sshbuf_free(buf); } static char * prepare_proctitle(int ac, char **av) { char *ret = NULL; int i; for (i = 0; i < ac; i++) xextendf(&ret, " ", "%s", av[i]); return ret; } static void print_config(struct ssh *ssh, struct connection_info *connection_info) { /* * If no connection info was provided by -C then use * use a blank one that will cause no predicate to match. */ if (connection_info == NULL) connection_info = get_connection_info(ssh, 0, 0); connection_info->test = 1; parse_server_match_config(&options, &includes, connection_info); dump_config(&options); exit(0); } /* * Main program for the daemon. */ int main(int ac, char **av) { struct ssh *ssh = NULL; extern char *optarg; extern int optind; int r, opt, on = 1, do_dump_cfg = 0, already_daemon, remote_port; int sock_in = -1, sock_out = -1, newsock = -1; const char *remote_ip, *rdomain; char *fp, *line, *laddr, *logfile = NULL; int config_s[2] = { -1 , -1 }; u_int i, j; u_int64_t ibytes, obytes; mode_t new_umask; struct sshkey *key; struct sshkey *pubkey; int keytype; Authctxt *authctxt; struct connection_info *connection_info = NULL; sigset_t sigmask; #ifdef HAVE_SECUREWARE (void)set_auth_parameters(ac, av); #endif __progname = ssh_get_progname(av[0]); sigemptyset(&sigmask); sigprocmask(SIG_SETMASK, &sigmask, NULL); /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ saved_argc = ac; rexec_argc = ac; saved_argv = xcalloc(ac + 1, sizeof(*saved_argv)); for (i = 0; (int)i < ac; i++) saved_argv[i] = xstrdup(av[i]); saved_argv[i] = NULL; #ifndef HAVE_SETPROCTITLE /* Prepare for later setproctitle emulation */ compat_init_setproctitle(ac, av); av = saved_argv; #endif if (geteuid() == 0 && setgroups(0, NULL) == -1) debug("setgroups(): %.200s", strerror(errno)); /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); /* Initialize configuration options to their default values. */ initialize_server_options(&options); /* Parse command-line arguments. */ while ((opt = getopt(ac, av, "C:E:b:c:f:g:h:k:o:p:u:46DGQRTdeiqrtV")) != -1) { switch (opt) { case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'f': config_file_name = optarg; break; case 'c': servconf_add_hostcert("[command-line]", 0, &options, optarg); break; case 'd': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else if (options.log_level < SYSLOG_LEVEL_DEBUG3) options.log_level++; break; case 'D': no_daemon_flag = 1; break; case 'G': do_dump_cfg = 1; break; case 'E': logfile = optarg; /* FALLTHROUGH */ case 'e': log_stderr = 1; break; case 'i': inetd_flag = 1; break; case 'r': rexec_flag = 0; break; case 'R': rexeced_flag = 1; inetd_flag = 1; break; case 'Q': /* ignored */ break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'b': /* protocol 1, ignored */ break; case 'p': options.ports_from_cmdline = 1; if (options.num_ports >= MAX_PORTS) { fprintf(stderr, "too many ports.\n"); exit(1); } options.ports[options.num_ports++] = a2port(optarg); if (options.ports[options.num_ports-1] <= 0) { fprintf(stderr, "Bad port number.\n"); exit(1); } break; case 'g': if ((options.login_grace_time = convtime(optarg)) == -1) { fprintf(stderr, "Invalid login grace time.\n"); exit(1); } break; case 'k': /* protocol 1, ignored */ break; case 'h': servconf_add_hostkey("[command-line]", 0, &options, optarg, 1); break; case 't': test_flag = 1; break; case 'T': test_flag = 2; break; case 'C': connection_info = get_connection_info(ssh, 0, 0); if (parse_server_match_testspec(connection_info, optarg) == -1) exit(1); break; case 'u': utmp_len = (u_int)strtonum(optarg, 0, HOST_NAME_MAX+1+1, NULL); if (utmp_len > HOST_NAME_MAX+1) { fprintf(stderr, "Invalid utmp length.\n"); exit(1); } break; case 'o': line = xstrdup(optarg); if (process_server_config_line(&options, line, "command-line", 0, NULL, NULL, &includes) != 0) exit(1); free(line); break; case 'V': fprintf(stderr, "%s, %s\n", - SSH_VERSION, SSH_OPENSSL_VERSION); + SSH_RELEASE, SSH_OPENSSL_VERSION); exit(0); default: usage(); break; } } if (rexeced_flag || inetd_flag) rexec_flag = 0; if (!test_flag && !do_dump_cfg && rexec_flag && !path_absolute(av[0])) fatal("sshd re-exec requires execution with an absolute path"); if (rexeced_flag) closefrom(REEXEC_MIN_FREE_FD); else closefrom(REEXEC_DEVCRYPTO_RESERVED_FD); seed_rng(); /* If requested, redirect the logs to the specified logfile. */ if (logfile != NULL) log_redirect_stderr_to(logfile); /* * Force logging to stderr until we have loaded the private host * key (unless started from inetd) */ log_init(__progname, options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_AUTH : options.log_facility, log_stderr || !inetd_flag || debug_flag); /* * Unset KRB5CCNAME, otherwise the user's session may inherit it from * root's environment */ if (getenv("KRB5CCNAME") != NULL) (void) unsetenv("KRB5CCNAME"); sensitive_data.have_ssh2_key = 0; /* * If we're not doing an extended test do not silently ignore connection * test params. */ if (test_flag < 2 && connection_info != NULL) fatal("Config test connection parameter (-C) provided without " "test mode (-T)"); /* Fetch our configuration */ if ((cfg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if (rexeced_flag) { setproctitle("%s", "[rexeced]"); recv_rexec_state(REEXEC_CONFIG_PASS_FD, cfg); if (!debug_flag) { startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); close(REEXEC_STARTUP_PIPE_FD); /* * Signal parent that this child is at a point where * they can go away if they have a SIGHUP pending. */ (void)atomicio(vwrite, startup_pipe, "\0", 1); } } else if (strcasecmp(config_file_name, "none") != 0) load_server_config(config_file_name, cfg); parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name, cfg, &includes, NULL, rexeced_flag); #ifdef WITH_OPENSSL if (options.moduli_file != NULL) dh_set_moduli_file(options.moduli_file); #endif /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); /* Check that options are sensible */ if (options.authorized_keys_command_user == NULL && (options.authorized_keys_command != NULL && strcasecmp(options.authorized_keys_command, "none") != 0)) fatal("AuthorizedKeysCommand set without " "AuthorizedKeysCommandUser"); if (options.authorized_principals_command_user == NULL && (options.authorized_principals_command != NULL && strcasecmp(options.authorized_principals_command, "none") != 0)) fatal("AuthorizedPrincipalsCommand set without " "AuthorizedPrincipalsCommandUser"); /* * Check whether there is any path through configured auth methods. * Unfortunately it is not possible to verify this generally before * daemonisation in the presence of Match block, but this catches * and warns for trivial misconfigurations that could break login. */ if (options.num_auth_methods != 0) { for (i = 0; i < options.num_auth_methods; i++) { if (auth2_methods_valid(options.auth_methods[i], 1) == 0) break; } if (i >= options.num_auth_methods) fatal("AuthenticationMethods cannot be satisfied by " "enabled authentication methods"); } /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); exit(1); } debug("sshd version %s, %s", SSH_VERSION, SSH_OPENSSL_VERSION); if (do_dump_cfg) print_config(ssh, connection_info); /* Store privilege separation user for later use if required. */ privsep_chroot = use_privsep && (getuid() == 0 || geteuid() == 0); if ((privsep_pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) { if (privsep_chroot || options.kerberos_authentication) fatal("Privilege separation user %s does not exist", SSH_PRIVSEP_USER); } else { privsep_pw = pwcopy(privsep_pw); freezero(privsep_pw->pw_passwd, strlen(privsep_pw->pw_passwd)); privsep_pw->pw_passwd = xstrdup("*"); } endpwent(); /* load host keys */ sensitive_data.host_keys = xcalloc(options.num_host_key_files, sizeof(struct sshkey *)); sensitive_data.host_pubkeys = xcalloc(options.num_host_key_files, sizeof(struct sshkey *)); if (options.host_key_agent) { if (strcmp(options.host_key_agent, SSH_AUTHSOCKET_ENV_NAME)) setenv(SSH_AUTHSOCKET_ENV_NAME, options.host_key_agent, 1); if ((r = ssh_get_authentication_socket(NULL)) == 0) have_agent = 1; else error_r(r, "Could not connect to agent \"%s\"", options.host_key_agent); } for (i = 0; i < options.num_host_key_files; i++) { int ll = options.host_key_file_userprovided[i] ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_DEBUG1; if (options.host_key_files[i] == NULL) continue; if ((r = sshkey_load_private(options.host_key_files[i], "", &key, NULL)) != 0 && r != SSH_ERR_SYSTEM_ERROR) do_log2_r(r, ll, "Unable to load host key \"%s\"", options.host_key_files[i]); if (sshkey_is_sk(key) && key->sk_flags & SSH_SK_USER_PRESENCE_REQD) { debug("host key %s requires user presence, ignoring", options.host_key_files[i]); key->sk_flags &= ~SSH_SK_USER_PRESENCE_REQD; } if (r == 0 && key != NULL && (r = sshkey_shield_private(key)) != 0) { do_log2_r(r, ll, "Unable to shield host key \"%s\"", options.host_key_files[i]); sshkey_free(key); key = NULL; } if ((r = sshkey_load_public(options.host_key_files[i], &pubkey, NULL)) != 0 && r != SSH_ERR_SYSTEM_ERROR) do_log2_r(r, ll, "Unable to load host key \"%s\"", options.host_key_files[i]); if (pubkey != NULL && key != NULL) { if (!sshkey_equal(pubkey, key)) { error("Public key for %s does not match " "private key", options.host_key_files[i]); sshkey_free(pubkey); pubkey = NULL; } } if (pubkey == NULL && key != NULL) { if ((r = sshkey_from_private(key, &pubkey)) != 0) fatal_r(r, "Could not demote key: \"%s\"", options.host_key_files[i]); } if (pubkey != NULL && (r = sshkey_check_rsa_length(pubkey, options.required_rsa_size)) != 0) { error_fr(r, "Host key %s", options.host_key_files[i]); sshkey_free(pubkey); sshkey_free(key); continue; } sensitive_data.host_keys[i] = key; sensitive_data.host_pubkeys[i] = pubkey; if (key == NULL && pubkey != NULL && have_agent) { debug("will rely on agent for hostkey %s", options.host_key_files[i]); keytype = pubkey->type; } else if (key != NULL) { keytype = key->type; accumulate_host_timing_secret(cfg, key); } else { do_log2(ll, "Unable to load host key: %s", options.host_key_files[i]); sensitive_data.host_keys[i] = NULL; sensitive_data.host_pubkeys[i] = NULL; continue; } switch (keytype) { case KEY_RSA: case KEY_DSA: case KEY_ECDSA: case KEY_ED25519: case KEY_ECDSA_SK: case KEY_ED25519_SK: case KEY_XMSS: if (have_agent || key != NULL) sensitive_data.have_ssh2_key = 1; break; } if ((fp = sshkey_fingerprint(pubkey, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal("sshkey_fingerprint failed"); debug("%s host key #%d: %s %s", key ? "private" : "agent", i, sshkey_ssh_name(pubkey), fp); free(fp); } accumulate_host_timing_secret(cfg, NULL); if (!sensitive_data.have_ssh2_key) { logit("sshd: no hostkeys available -- exiting."); exit(1); } /* * Load certificates. They are stored in an array at identical * indices to the public keys that they relate to. */ sensitive_data.host_certificates = xcalloc(options.num_host_key_files, sizeof(struct sshkey *)); for (i = 0; i < options.num_host_key_files; i++) sensitive_data.host_certificates[i] = NULL; for (i = 0; i < options.num_host_cert_files; i++) { if (options.host_cert_files[i] == NULL) continue; if ((r = sshkey_load_public(options.host_cert_files[i], &key, NULL)) != 0) { error_r(r, "Could not load host certificate \"%s\"", options.host_cert_files[i]); continue; } if (!sshkey_is_cert(key)) { error("Certificate file is not a certificate: %s", options.host_cert_files[i]); sshkey_free(key); continue; } /* Find matching private key */ for (j = 0; j < options.num_host_key_files; j++) { if (sshkey_equal_public(key, sensitive_data.host_pubkeys[j])) { sensitive_data.host_certificates[j] = key; break; } } if (j >= options.num_host_key_files) { error("No matching private key for certificate: %s", options.host_cert_files[i]); sshkey_free(key); continue; } sensitive_data.host_certificates[j] = key; debug("host certificate: #%u type %d %s", j, key->type, sshkey_type(key)); } if (privsep_chroot) { struct stat st; if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || (S_ISDIR(st.st_mode) == 0)) fatal("Missing privilege separation directory: %s", _PATH_PRIVSEP_CHROOT_DIR); #ifdef HAVE_CYGWIN if (check_ntsec(_PATH_PRIVSEP_CHROOT_DIR) && (st.st_uid != getuid () || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0)) #else if (st.st_uid != 0 || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0) #endif fatal("%s must be owned by root and not group or " "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); } if (test_flag > 1) print_config(ssh, connection_info); /* Configuration looks good, so exit if in test mode. */ if (test_flag) exit(0); /* * Clear out any supplemental groups we may have inherited. This * prevents inadvertent creation of files with bad modes (in the * portable version at least, it's certainly possible for PAM * to create a file, and we can't control the code in every * module which might be used). */ if (setgroups(0, NULL) < 0) debug("setgroups() failed: %.200s", strerror(errno)); if (rexec_flag) { if (rexec_argc < 0) fatal("rexec_argc %d < 0", rexec_argc); rexec_argv = xcalloc(rexec_argc + 2, sizeof(char *)); for (i = 0; i < (u_int)rexec_argc; i++) { debug("rexec_argv[%d]='%s'", i, saved_argv[i]); rexec_argv[i] = saved_argv[i]; } rexec_argv[rexec_argc] = "-R"; rexec_argv[rexec_argc + 1] = NULL; } listener_proctitle = prepare_proctitle(ac, av); /* Ensure that umask disallows at least group and world write */ new_umask = umask(0077) | 0022; (void) umask(new_umask); /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag && (!inetd_flag || rexeced_flag)) log_stderr = 1; log_init(__progname, options.log_level, options.log_facility, log_stderr); for (i = 0; i < options.num_log_verbose; i++) log_verbose_add(options.log_verbose[i]); /* * If not in debugging mode, not started from inetd and not already * daemonized (eg re-exec via SIGHUP), disconnect from the controlling * terminal, and fork. The original process exits. */ already_daemon = daemonized(); if (!(debug_flag || inetd_flag || no_daemon_flag || already_daemon)) { if (daemon(0, 0) == -1) fatal("daemon() failed: %.200s", strerror(errno)); disconnect_controlling_tty(); } /* Reinitialize the log (because of the fork above). */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Avoid killing the process in high-pressure swapping environments. */ if (!inetd_flag && madvise(NULL, 0, MADV_PROTECT) != 0) debug("madvise(): %.200s", strerror(errno)); /* * Chdir to the root directory so that the current disk can be * unmounted if desired. */ if (chdir("/") == -1) error("chdir(\"/\"): %s", strerror(errno)); /* ignore SIGPIPE */ ssh_signal(SIGPIPE, SIG_IGN); /* Get a connection, either from inetd or a listening TCP socket */ if (inetd_flag) { server_accept_inetd(&sock_in, &sock_out); } else { platform_pre_listen(); server_listen(); ssh_signal(SIGHUP, sighup_handler); ssh_signal(SIGCHLD, main_sigchld_handler); ssh_signal(SIGTERM, sigterm_handler); ssh_signal(SIGQUIT, sigterm_handler); /* * Write out the pid file after the sigterm handler * is setup and the listen sockets are bound */ if (options.pid_file != NULL && !debug_flag) { FILE *f = fopen(options.pid_file, "w"); if (f == NULL) { error("Couldn't create pid file \"%s\": %s", options.pid_file, strerror(errno)); } else { fprintf(f, "%ld\n", (long) getpid()); fclose(f); } } /* Accept a connection and return in a forked child */ server_accept_loop(&sock_in, &sock_out, &newsock, config_s); } /* This is the child processing a new connection. */ setproctitle("%s", "[accepted]"); /* * Create a new session and process group since the 4.4BSD * setlogin() affects the entire process group. We don't * want the child to be able to affect the parent. */ if (!debug_flag && !inetd_flag && setsid() == -1) error("setsid: %.100s", strerror(errno)); if (rexec_flag) { debug("rexec start in %d out %d newsock %d pipe %d sock %d", sock_in, sock_out, newsock, startup_pipe, config_s[0]); if (dup2(newsock, STDIN_FILENO) == -1) debug3_f("dup2 stdin: %s", strerror(errno)); if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) debug3_f("dup2 stdout: %s", strerror(errno)); if (startup_pipe == -1) close(REEXEC_STARTUP_PIPE_FD); else if (startup_pipe != REEXEC_STARTUP_PIPE_FD) { if (dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD) == -1) debug3_f("dup2 startup_p: %s", strerror(errno)); close(startup_pipe); startup_pipe = REEXEC_STARTUP_PIPE_FD; } if (dup2(config_s[1], REEXEC_CONFIG_PASS_FD) == -1) debug3_f("dup2 config_s: %s", strerror(errno)); close(config_s[1]); ssh_signal(SIGHUP, SIG_IGN); /* avoid reset to SIG_DFL */ execv(rexec_argv[0], rexec_argv); /* Reexec has failed, fall back and continue */ error("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); recv_rexec_state(REEXEC_CONFIG_PASS_FD, NULL); log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Clean up fds */ close(REEXEC_CONFIG_PASS_FD); newsock = sock_out = sock_in = dup(STDIN_FILENO); if (stdfd_devnull(1, 1, 0) == -1) error_f("stdfd_devnull failed"); debug("rexec cleanup in %d out %d newsock %d pipe %d sock %d", sock_in, sock_out, newsock, startup_pipe, config_s[0]); } /* Executed child processes don't need these. */ fcntl(sock_out, F_SETFD, FD_CLOEXEC); fcntl(sock_in, F_SETFD, FD_CLOEXEC); /* We will not restart on SIGHUP since it no longer makes sense. */ ssh_signal(SIGALRM, SIG_DFL); ssh_signal(SIGHUP, SIG_DFL); ssh_signal(SIGTERM, SIG_DFL); ssh_signal(SIGQUIT, SIG_DFL); ssh_signal(SIGCHLD, SIG_DFL); ssh_signal(SIGINT, SIG_DFL); #ifdef __FreeBSD__ /* * Initialize the resolver. This may not happen automatically * before privsep chroot(). */ if ((_res.options & RES_INIT) == 0) { debug("res_init()"); res_init(); } #ifdef GSSAPI /* * Force GSS-API to parse its configuration and load any * mechanism plugins. */ { gss_OID_set mechs; OM_uint32 minor_status; gss_indicate_mechs(&minor_status, &mechs); gss_release_oid_set(&minor_status, &mechs); } #endif #endif /* * Register our connection. This turns encryption off because we do * not have a key. */ if ((ssh = ssh_packet_set_connection(NULL, sock_in, sock_out)) == NULL) fatal("Unable to create connection"); the_active_state = ssh; ssh_packet_set_server(ssh); check_ip_options(ssh); /* Prepare the channels layer */ channel_init_channels(ssh); channel_set_af(ssh, options.address_family); process_channel_timeouts(ssh, &options); process_permitopen(ssh, &options); /* Set SO_KEEPALIVE if requested. */ if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); if ((remote_port = ssh_remote_port(ssh)) < 0) { debug("ssh_remote_port failed"); cleanup_exit(255); } if (options.routing_domain != NULL) set_process_rdomain(ssh, options.routing_domain); /* * The rest of the code depends on the fact that * ssh_remote_ipaddr() caches the remote ip, even if * the socket goes away. */ remote_ip = ssh_remote_ipaddr(ssh); #ifdef HAVE_LOGIN_CAP /* Also caches remote hostname for sandboxed child. */ auth_get_canonical_hostname(ssh, options.use_dns); #endif #ifdef SSH_AUDIT_EVENTS audit_connection_from(remote_ip, remote_port); #endif rdomain = ssh_packet_rdomain_in(ssh); /* Log the connection. */ laddr = get_local_ipaddr(sock_in); verbose("Connection from %s port %d on %s port %d%s%s%s", remote_ip, remote_port, laddr, ssh_local_port(ssh), rdomain == NULL ? "" : " rdomain \"", rdomain == NULL ? "" : rdomain, rdomain == NULL ? "" : "\""); free(laddr); /* * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is * cleared after successful authentication. A limit of zero * indicates no limit. Note that we don't set the alarm in debugging * mode; it is just annoying to have the server exit just when you * are about to discover the bug. */ ssh_signal(SIGALRM, grace_alarm_handler); if (!debug_flag) alarm(options.login_grace_time); if ((r = kex_exchange_identification(ssh, -1, options.version_addendum)) != 0) sshpkt_fatal(ssh, r, "banner exchange"); ssh_packet_set_nonblocking(ssh); /* allocate authentication context */ authctxt = xcalloc(1, sizeof(*authctxt)); ssh->authctxt = authctxt; authctxt->loginmsg = loginmsg; /* XXX global for cleanup, access from other modules */ the_authctxt = authctxt; /* Set default key authentication options */ if ((auth_opts = sshauthopt_new_with_keys_defaults()) == NULL) fatal("allocation failed"); /* prepare buffer to collect messages to display to user after login */ if ((loginmsg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); auth_debug_reset(); BLACKLIST_INIT(); if (use_privsep) { if (privsep_preauth(ssh) == 1) goto authenticated; } else if (have_agent) { if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { error_r(r, "Unable to get agent socket"); have_agent = 0; } } /* perform the key exchange */ /* authenticate user and start session */ do_ssh2_kex(ssh); do_authentication2(ssh); /* * If we use privilege separation, the unprivileged child transfers * the current keystate and exits */ if (use_privsep) { mm_send_keystate(ssh, pmonitor); ssh_packet_clear_keys(ssh); exit(0); } authenticated: /* * Cancel the alarm we set to limit the time taken for * authentication. */ alarm(0); ssh_signal(SIGALRM, SIG_DFL); authctxt->authenticated = 1; if (startup_pipe != -1) { close(startup_pipe); startup_pipe = -1; } #ifdef SSH_AUDIT_EVENTS audit_event(ssh, SSH_AUTH_SUCCESS); #endif #ifdef GSSAPI if (options.gss_authentication) { temporarily_use_uid(authctxt->pw); ssh_gssapi_storecreds(); restore_uid(); } #endif #ifdef USE_PAM if (options.use_pam) { do_pam_setcred(1); do_pam_session(ssh); } #endif /* * In privilege separation, we fork another child and prepare * file descriptor passing. */ if (use_privsep) { privsep_postauth(ssh, authctxt); /* the monitor process [priv] will not return */ } ssh_packet_set_timeout(ssh, options.client_alive_interval, options.client_alive_count_max); /* Try to send all our hostkeys to the client */ notify_hostkeys(ssh); /* Start session. */ do_authenticated(ssh, authctxt); /* The connection has been terminated. */ ssh_packet_get_bytes(ssh, &ibytes, &obytes); verbose("Transferred: sent %llu, received %llu bytes", (unsigned long long)obytes, (unsigned long long)ibytes); verbose("Closing connection to %.500s port %d", remote_ip, remote_port); #ifdef USE_PAM if (options.use_pam) finish_pam(); #endif /* USE_PAM */ #ifdef SSH_AUDIT_EVENTS PRIVSEP(audit_event(ssh, SSH_CONNECTION_CLOSE)); #endif ssh_packet_close(ssh); if (use_privsep) mm_terminate(); exit(0); } int sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey, struct sshkey *pubkey, u_char **signature, size_t *slenp, const u_char *data, size_t dlen, const char *alg) { int r; if (use_privsep) { if (privkey) { if (mm_sshkey_sign(ssh, privkey, signature, slenp, data, dlen, alg, options.sk_provider, NULL, ssh->compat) < 0) fatal_f("privkey sign failed"); } else { if (mm_sshkey_sign(ssh, pubkey, signature, slenp, data, dlen, alg, options.sk_provider, NULL, ssh->compat) < 0) fatal_f("pubkey sign failed"); } } else { if (privkey) { if (sshkey_sign(privkey, signature, slenp, data, dlen, alg, options.sk_provider, NULL, ssh->compat) < 0) fatal_f("privkey sign failed"); } else { if ((r = ssh_agent_sign(auth_sock, pubkey, signature, slenp, data, dlen, alg, ssh->compat)) != 0) { fatal_fr(r, "agent sign failed"); } } } return 0; } /* SSH2 key exchange */ static void do_ssh2_kex(struct ssh *ssh) { char *hkalgs = NULL, *myproposal[PROPOSAL_MAX]; const char *compression = NULL; struct kex *kex; int r; if (options.rekey_limit || options.rekey_interval) ssh_packet_set_rekey_limits(ssh, options.rekey_limit, options.rekey_interval); if (options.compression == COMP_NONE) compression = "none"; hkalgs = list_hostkey_types(); kex_proposal_populate_entries(ssh, myproposal, options.kex_algorithms, options.ciphers, options.macs, compression, hkalgs); free(hkalgs); /* start key exchange */ if ((r = kex_setup(ssh, myproposal)) != 0) fatal_r(r, "kex_setup"); kex = ssh->kex; #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; # ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif #endif kex->kex[KEX_C25519_SHA256] = kex_gen_server; kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; kex->sign = sshd_hostkey_sign; ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &kex->done); #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || (r = sshpkt_put_cstring(ssh, "markus")) != 0 || (r = sshpkt_send(ssh)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) fatal_fr(r, "send test"); #endif kex_proposal_free_entries(myproposal); debug("KEX done"); } /* server specific fatal cleanup */ void cleanup_exit(int i) { if (the_active_state != NULL && the_authctxt != NULL) { do_cleanup(the_active_state, the_authctxt); if (use_privsep && privsep_is_preauth && pmonitor != NULL && pmonitor->m_pid > 1) { debug("Killing privsep child %d", pmonitor->m_pid); if (kill(pmonitor->m_pid, SIGKILL) != 0 && errno != ESRCH) { error_f("kill(%d): %s", pmonitor->m_pid, strerror(errno)); } } } #ifdef SSH_AUDIT_EVENTS /* done after do_cleanup so it can cancel the PAM auth 'thread' */ if (the_active_state != NULL && (!use_privsep || mm_is_monitor())) audit_event(the_active_state, SSH_CONNECTION_ABANDON); #endif _exit(i); } diff --git a/crypto/openssh/sshd_config b/crypto/openssh/sshd_config index b73f21e0c9c8..de1b87e17699 100644 --- a/crypto/openssh/sshd_config +++ b/crypto/openssh/sshd_config @@ -1,121 +1,121 @@ # $OpenBSD: sshd_config,v 1.104 2021/07/02 05:11:21 dtucker Exp $ # This is the sshd server system-wide configuration file. See # sshd_config(5) for more information. # This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin # The strategy used for options in the default sshd_config shipped with # OpenSSH is to specify options with their default value where # possible, but leave them commented. Uncommented options override the # default value. # Note that some of FreeBSD's defaults differ from OpenBSD's, and # FreeBSD has a few additional options. #Port 22 #AddressFamily any #ListenAddress 0.0.0.0 #ListenAddress :: #HostKey /etc/ssh/ssh_host_rsa_key #HostKey /etc/ssh/ssh_host_ecdsa_key #HostKey /etc/ssh/ssh_host_ed25519_key # Ciphers and keying #RekeyLimit default none # Logging #SyslogFacility AUTH #LogLevel INFO # Authentication: #LoginGraceTime 2m #PermitRootLogin no #StrictModes yes #MaxAuthTries 6 #MaxSessions 10 #PubkeyAuthentication yes # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 # but this is overridden so installations will only check .ssh/authorized_keys AuthorizedKeysFile .ssh/authorized_keys #AuthorizedPrincipalsFile none #AuthorizedKeysCommand none #AuthorizedKeysCommandUser nobody # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts #HostbasedAuthentication no # Change to yes if you don't trust ~/.ssh/known_hosts for # HostbasedAuthentication #IgnoreUserKnownHosts no # Don't read the user's ~/.rhosts and ~/.shosts files #IgnoreRhosts yes # Change to yes to enable built-in password authentication. # Note that passwords may also be accepted via KbdInteractiveAuthentication. #PasswordAuthentication no #PermitEmptyPasswords no # Change to no to disable PAM authentication #KbdInteractiveAuthentication yes # Kerberos options #KerberosAuthentication no #KerberosOrLocalPasswd yes #KerberosTicketCleanup yes #KerberosGetAFSToken no # GSSAPI options #GSSAPIAuthentication no #GSSAPICleanupCredentials yes # Set this to 'no' to disable PAM authentication, account processing, # and session processing. If this is enabled, PAM authentication will # be allowed through the KbdInteractiveAuthentication and # PasswordAuthentication. Depending on your PAM configuration, # PAM authentication via KbdInteractiveAuthentication may bypass # the setting of "PermitRootLogin prohibit-password". # If you just want the PAM account and session checks to run without # PAM authentication, then enable this but set PasswordAuthentication # and KbdInteractiveAuthentication to 'no'. #UsePAM yes #AllowAgentForwarding yes #AllowTcpForwarding yes #GatewayPorts no #X11Forwarding no #X11DisplayOffset 10 #X11UseLocalhost yes #PermitTTY yes #PrintMotd yes #PrintLastLog yes #TCPKeepAlive yes #PermitUserEnvironment no #Compression delayed #ClientAliveInterval 0 #ClientAliveCountMax 3 #UseDNS yes #PidFile /var/run/sshd.pid #MaxStartups 10:30:100 #PermitTunnel no #ChrootDirectory none #UseBlacklist no -#VersionAddendum FreeBSD-20230810 +#VersionAddendum FreeBSD-20231004 # no default banner path #Banner none # override default of no subsystems Subsystem sftp /usr/libexec/sftp-server # Example of overriding settings on a per-user basis #Match User anoncvs # X11Forwarding no # AllowTcpForwarding no # PermitTTY no # ForceCommand cvs server diff --git a/crypto/openssh/sshd_config.5 b/crypto/openssh/sshd_config.5 index 06385c648f3d..e38e3d662dbb 100644 --- a/crypto/openssh/sshd_config.5 +++ b/crypto/openssh/sshd_config.5 @@ -1,2128 +1,2128 @@ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .\" $OpenBSD: sshd_config.5,v 1.350 2023/07/28 05:42:36 jmc Exp $ .Dd $Mdocdate: July 28 2023 $ .Dt SSHD_CONFIG 5 .Os .Sh NAME .Nm sshd_config .Nd OpenSSH daemon configuration file .Sh DESCRIPTION .Xr sshd 8 reads configuration data from .Pa /etc/ssh/sshd_config (or the file specified with .Fl f on the command line). The file contains keyword-argument pairs, one per line. Unless noted otherwise, for each keyword, the first obtained value will be used. Lines starting with .Ql # and empty lines are interpreted as comments. Arguments may optionally be enclosed in double quotes .Pq \&" in order to represent arguments containing spaces. .Pp The possible keywords and their meanings are as follows (note that keywords are case-insensitive and arguments are case-sensitive): .Bl -tag -width Ds .It Cm AcceptEnv Specifies what environment variables sent by the client will be copied into the session's .Xr environ 7 . See .Cm SendEnv and .Cm SetEnv in .Xr ssh_config 5 for how to configure the client. The .Ev TERM environment variable is always accepted whenever the client requests a pseudo-terminal as it is required by the protocol. Variables are specified by name, which may contain the wildcard characters .Ql * and .Ql \&? . Multiple environment variables may be separated by whitespace or spread across multiple .Cm AcceptEnv directives. Be warned that some environment variables could be used to bypass restricted user environments. For this reason, care should be taken in the use of this directive. The default is not to accept any environment variables. .It Cm AddressFamily Specifies which address family should be used by .Xr sshd 8 . Valid arguments are .Cm any (the default), .Cm inet (use IPv4 only), or .Cm inet6 (use IPv6 only). .It Cm AllowAgentForwarding Specifies whether .Xr ssh-agent 1 forwarding is permitted. The default is .Cm yes . Note that disabling agent forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders. .It Cm AllowGroups This keyword can be followed by a list of group name patterns, separated by spaces. If specified, login is allowed only for users whose primary group or supplementary group list matches one of the patterns. Only group names are valid; a numerical group ID is not recognized. By default, login is allowed for all groups. The allow/deny groups directives are processed in the following order: .Cm DenyGroups , .Cm AllowGroups . .Pp See PATTERNS in .Xr ssh_config 5 for more information on patterns. This keyword may appear multiple times in .Nm with each instance appending to the list. .It Cm AllowStreamLocalForwarding Specifies whether StreamLocal (Unix-domain socket) forwarding is permitted. The available options are .Cm yes (the default) or .Cm all to allow StreamLocal forwarding, .Cm no to prevent all StreamLocal forwarding, .Cm local to allow local (from the perspective of .Xr ssh 1 ) forwarding only or .Cm remote to allow remote forwarding only. Note that disabling StreamLocal forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders. .It Cm AllowTcpForwarding Specifies whether TCP forwarding is permitted. The available options are .Cm yes (the default) or .Cm all to allow TCP forwarding, .Cm no to prevent all TCP forwarding, .Cm local to allow local (from the perspective of .Xr ssh 1 ) forwarding only or .Cm remote to allow remote forwarding only. Note that disabling TCP forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders. .It Cm AllowUsers This keyword can be followed by a list of user name patterns, separated by spaces. If specified, login is allowed only for user names that match one of the patterns. Only user names are valid; a numerical user ID is not recognized. By default, login is allowed for all users. If the pattern takes the form USER@HOST then USER and HOST are separately checked, restricting logins to particular users from particular hosts. HOST criteria may additionally contain addresses to match in CIDR address/masklen format. The allow/deny users directives are processed in the following order: .Cm DenyUsers , .Cm AllowUsers . .Pp See PATTERNS in .Xr ssh_config 5 for more information on patterns. This keyword may appear multiple times in .Nm with each instance appending to the list. .It Cm AuthenticationMethods Specifies the authentication methods that must be successfully completed for a user to be granted access. This option must be followed by one or more lists of comma-separated authentication method names, or by the single string .Cm any to indicate the default behaviour of accepting any single authentication method. If the default is overridden, then successful authentication requires completion of every method in at least one of these lists. .Pp For example, .Qq publickey,password publickey,keyboard-interactive would require the user to complete public key authentication, followed by either password or keyboard interactive authentication. Only methods that are next in one or more lists are offered at each stage, so for this example it would not be possible to attempt password or keyboard-interactive authentication before public key. .Pp For keyboard interactive authentication it is also possible to restrict authentication to a specific device by appending a colon followed by the device identifier .Cm bsdauth or .Cm pam . depending on the server configuration. For example, .Qq keyboard-interactive:bsdauth would restrict keyboard interactive authentication to the .Cm bsdauth device. .Pp If the publickey method is listed more than once, .Xr sshd 8 verifies that keys that have been used successfully are not reused for subsequent authentications. For example, .Qq publickey,publickey requires successful authentication using two different public keys. .Pp Note that each authentication method listed should also be explicitly enabled in the configuration. .Pp The available authentication methods are: .Qq gssapi-with-mic , .Qq hostbased , .Qq keyboard-interactive , .Qq none (used for access to password-less accounts when .Cm PermitEmptyPasswords is enabled), .Qq password and .Qq publickey . .It Cm AuthorizedKeysCommand Specifies a program to be used to look up the user's public keys. The program must be owned by root, not writable by group or others and specified by an absolute path. Arguments to .Cm AuthorizedKeysCommand accept the tokens described in the .Sx TOKENS section. If no arguments are specified then the username of the target user is used. .Pp The program should produce on standard output zero or more lines of authorized_keys output (see .Sx AUTHORIZED_KEYS in .Xr sshd 8 ) . .Cm AuthorizedKeysCommand is tried after the usual .Cm AuthorizedKeysFile files and will not be executed if a matching key is found there. By default, no .Cm AuthorizedKeysCommand is run. .It Cm AuthorizedKeysCommandUser Specifies the user under whose account the .Cm AuthorizedKeysCommand is run. It is recommended to use a dedicated user that has no other role on the host than running authorized keys commands. If .Cm AuthorizedKeysCommand is specified but .Cm AuthorizedKeysCommandUser is not, then .Xr sshd 8 will refuse to start. .It Cm AuthorizedKeysFile Specifies the file that contains the public keys used for user authentication. The format is described in the AUTHORIZED_KEYS FILE FORMAT section of .Xr sshd 8 . Arguments to .Cm AuthorizedKeysFile accept the tokens described in the .Sx TOKENS section. After expansion, .Cm AuthorizedKeysFile is taken to be an absolute path or one relative to the user's home directory. Multiple files may be listed, separated by whitespace. Alternately this option may be set to .Cm none to skip checking for user keys in files. The default is .Qq .ssh/authorized_keys .ssh/authorized_keys2 . .It Cm AuthorizedPrincipalsCommand Specifies a program to be used to generate the list of allowed certificate principals as per .Cm AuthorizedPrincipalsFile . The program must be owned by root, not writable by group or others and specified by an absolute path. Arguments to .Cm AuthorizedPrincipalsCommand accept the tokens described in the .Sx TOKENS section. If no arguments are specified then the username of the target user is used. .Pp The program should produce on standard output zero or more lines of .Cm AuthorizedPrincipalsFile output. If either .Cm AuthorizedPrincipalsCommand or .Cm AuthorizedPrincipalsFile is specified, then certificates offered by the client for authentication must contain a principal that is listed. By default, no .Cm AuthorizedPrincipalsCommand is run. .It Cm AuthorizedPrincipalsCommandUser Specifies the user under whose account the .Cm AuthorizedPrincipalsCommand is run. It is recommended to use a dedicated user that has no other role on the host than running authorized principals commands. If .Cm AuthorizedPrincipalsCommand is specified but .Cm AuthorizedPrincipalsCommandUser is not, then .Xr sshd 8 will refuse to start. .It Cm AuthorizedPrincipalsFile Specifies a file that lists principal names that are accepted for certificate authentication. When using certificates signed by a key listed in .Cm TrustedUserCAKeys , this file lists names, one of which must appear in the certificate for it to be accepted for authentication. Names are listed one per line preceded by key options (as described in .Sx AUTHORIZED_KEYS FILE FORMAT in .Xr sshd 8 ) . Empty lines and comments starting with .Ql # are ignored. .Pp Arguments to .Cm AuthorizedPrincipalsFile accept the tokens described in the .Sx TOKENS section. After expansion, .Cm AuthorizedPrincipalsFile is taken to be an absolute path or one relative to the user's home directory. The default is .Cm none , i.e. not to use a principals file \(en in this case, the username of the user must appear in a certificate's principals list for it to be accepted. .Pp Note that .Cm AuthorizedPrincipalsFile is only used when authentication proceeds using a CA listed in .Cm TrustedUserCAKeys and is not consulted for certification authorities trusted via .Pa ~/.ssh/authorized_keys , though the .Cm principals= key option offers a similar facility (see .Xr sshd 8 for details). .It Cm Banner The contents of the specified file are sent to the remote user before authentication is allowed. If the argument is .Cm none then no banner is displayed. By default, no banner is displayed. .It Cm CASignatureAlgorithms Specifies which algorithms are allowed for signing of certificates by certificate authorities (CAs). The default is: .Bd -literal -offset indent ssh-ed25519,ecdsa-sha2-nistp256, ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. .Pp Certificates signed using other algorithms will not be accepted for public key or host-based authentication. .It Cm ChannelTimeout Specifies whether and how quickly .Xr sshd 8 should close inactive channels. Timeouts are specified as one or more .Dq type=interval pairs separated by whitespace, where the .Dq type must be a channel type name (as described in the table below), optionally containing wildcard characters. .Pp The timeout value .Dq interval is specified in seconds or may use any of the units documented in the .Sx TIME FORMATS section. For example, .Dq session:*=5m would cause all sessions to terminate after five minutes of inactivity. Specifying a zero value disables the inactivity timeout. .Pp The available channel types include: .Bl -tag -width Ds .It Cm agent-connection Open connections to .Xr ssh-agent 1 . .It Cm direct-tcpip , Cm direct-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established from a .Xr ssh 1 local forwarding, i.e.\& .Cm LocalForward or .Cm DynamicForward . .It Cm forwarded-tcpip , Cm forwarded-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established to a .Xr sshd 8 listening on behalf of a .Xr ssh 1 remote forwarding, i.e.\& .Cm RemoteForward . .It Cm session:command Command execution sessions. .It Cm session:shell Interactive shell sessions. .It Cm session:subsystem:... Subsystem sessions, e.g. for .Xr sftp 1 , which could be identified as .Cm session:subsystem:sftp . .It Cm x11-connection Open X11 forwarding sessions. .El .Pp Note that in all the above cases, terminating an inactive session does not guarantee to remove all resources associated with the session, e.g. shell processes or X11 clients relating to the session may continue to execute. .Pp Moreover, terminating an inactive channel or session does not necessarily close the SSH connection, nor does it prevent a client from requesting another channel of the same type. In particular, expiring an inactive forwarding session does not prevent another identical forwarding from being subsequently created. See also .Cm UnusedConnectionTimeout , which may be used in conjunction with this option. .Pp The default is not to expire channels of any type for inactivity. .It Cm ChrootDirectory Specifies the pathname of a directory to .Xr chroot 2 to after authentication. At session startup .Xr sshd 8 checks that all components of the pathname are root-owned directories which are not writable by any other user or group. After the chroot, .Xr sshd 8 changes the working directory to the user's home directory. Arguments to .Cm ChrootDirectory accept the tokens described in the .Sx TOKENS section. .Pp The .Cm ChrootDirectory must contain the necessary files and directories to support the user's session. For an interactive session this requires at least a shell, typically .Xr sh 1 , and basic .Pa /dev nodes such as .Xr null 4 , .Xr zero 4 , .Xr stdin 4 , .Xr stdout 4 , .Xr stderr 4 , and .Xr tty 4 devices. For file transfer sessions using SFTP no additional configuration of the environment is necessary if the in-process sftp-server is used, though sessions which use logging may require .Pa /dev/log inside the chroot directory on some operating systems (see .Xr sftp-server 8 for details). .Pp For safety, it is very important that the directory hierarchy be prevented from modification by other processes on the system (especially those outside the jail). Misconfiguration can lead to unsafe environments which .Xr sshd 8 cannot detect. .Pp The default is .Cm none , indicating not to .Xr chroot 2 . .It Cm Ciphers Specifies the ciphers allowed. Multiple ciphers must be comma-separated. If the specified list begins with a .Sq + character, then the specified ciphers will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified ciphers (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified ciphers will be placed at the head of the default set. .Pp The supported ciphers are: .Pp .Bl -item -compact -offset indent .It 3des-cbc .It aes128-cbc .It aes192-cbc .It aes256-cbc .It aes128-ctr .It aes192-ctr .It aes256-ctr .It aes128-gcm@openssh.com .It aes256-gcm@openssh.com .It chacha20-poly1305@openssh.com .El .Pp The default is: .Bd -literal -offset indent chacha20-poly1305@openssh.com, aes128-ctr,aes192-ctr,aes256-ctr, aes128-gcm@openssh.com,aes256-gcm@openssh.com .Ed .Pp The list of available ciphers may also be obtained using .Qq ssh -Q cipher . .It Cm ClientAliveCountMax Sets the number of client alive messages which may be sent without .Xr sshd 8 receiving any messages back from the client. If this threshold is reached while client alive messages are being sent, sshd will disconnect the client, terminating the session. It is important to note that the use of client alive messages is very different from .Cm TCPKeepAlive . The client alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by .Cm TCPKeepAlive is spoofable. The client alive mechanism is valuable when the client or server depend on knowing when a connection has become unresponsive. .Pp The default value is 3. If .Cm ClientAliveInterval is set to 15, and .Cm ClientAliveCountMax is left at the default, unresponsive SSH clients will be disconnected after approximately 45 seconds. Setting a zero .Cm ClientAliveCountMax disables connection termination. .It Cm ClientAliveInterval Sets a timeout interval in seconds after which if no data has been received from the client, .Xr sshd 8 will send a message through the encrypted channel to request a response from the client. The default is 0, indicating that these messages will not be sent to the client. .It Cm Compression Specifies whether compression is enabled after the user has authenticated successfully. The argument must be .Cm yes , .Cm delayed (a legacy synonym for .Cm yes ) or .Cm no . The default is .Cm yes . .It Cm DenyGroups This keyword can be followed by a list of group name patterns, separated by spaces. Login is disallowed for users whose primary group or supplementary group list matches one of the patterns. Only group names are valid; a numerical group ID is not recognized. By default, login is allowed for all groups. The allow/deny groups directives are processed in the following order: .Cm DenyGroups , .Cm AllowGroups . .Pp See PATTERNS in .Xr ssh_config 5 for more information on patterns. This keyword may appear multiple times in .Nm with each instance appending to the list. .It Cm DenyUsers This keyword can be followed by a list of user name patterns, separated by spaces. Login is disallowed for user names that match one of the patterns. Only user names are valid; a numerical user ID is not recognized. By default, login is allowed for all users. If the pattern takes the form USER@HOST then USER and HOST are separately checked, restricting logins to particular users from particular hosts. HOST criteria may additionally contain addresses to match in CIDR address/masklen format. The allow/deny users directives are processed in the following order: .Cm DenyUsers , .Cm AllowUsers . .Pp See PATTERNS in .Xr ssh_config 5 for more information on patterns. This keyword may appear multiple times in .Nm with each instance appending to the list. .It Cm DisableForwarding Disables all forwarding features, including X11, .Xr ssh-agent 1 , TCP and StreamLocal. This option overrides all other forwarding-related options and may simplify restricted configurations. .It Cm ExposeAuthInfo Writes a temporary file containing a list of authentication methods and public credentials (e.g. keys) used to authenticate the user. The location of the file is exposed to the user session through the .Ev SSH_USER_AUTH environment variable. The default is .Cm no . .It Cm FingerprintHash Specifies the hash algorithm used when logging key fingerprints. Valid options are: .Cm md5 and .Cm sha256 . The default is .Cm sha256 . .It Cm ForceCommand Forces the execution of the command specified by .Cm ForceCommand , ignoring any command supplied by the client and .Pa ~/.ssh/rc if present. The command is invoked by using the user's login shell with the -c option. This applies to shell, command, or subsystem execution. It is most useful inside a .Cm Match block. The command originally supplied by the client is available in the .Ev SSH_ORIGINAL_COMMAND environment variable. Specifying a command of .Cm internal-sftp will force the use of an in-process SFTP server that requires no support files when used with .Cm ChrootDirectory . The default is .Cm none . .It Cm GatewayPorts Specifies whether remote hosts are allowed to connect to ports forwarded for the client. By default, .Xr sshd 8 binds remote port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports. .Cm GatewayPorts can be used to specify that sshd should allow remote port forwardings to bind to non-loopback addresses, thus allowing other hosts to connect. The argument may be .Cm no to force remote port forwardings to be available to the local host only, .Cm yes to force remote port forwardings to bind to the wildcard address, or .Cm clientspecified to allow the client to select the address to which the forwarding is bound. The default is .Cm no . .It Cm GSSAPIAuthentication Specifies whether user authentication based on GSSAPI is allowed. The default is .Cm no . .It Cm GSSAPICleanupCredentials Specifies whether to automatically destroy the user's credentials cache on logout. The default is .Cm yes . .It Cm GSSAPIStrictAcceptorCheck Determines whether to be strict about the identity of the GSSAPI acceptor a client authenticates against. If set to .Cm yes then the client must authenticate against the host service on the current hostname. If set to .Cm no then the client may authenticate against any service key stored in the machine's default store. This facility is provided to assist with operation on multi homed machines. The default is .Cm yes . .It Cm HostbasedAcceptedAlgorithms Specifies the signature algorithms that will be accepted for hostbased authentication as a list of comma-separated patterns. Alternately if the specified list begins with a .Sq + character, then the specified signature algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified signature algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified signature algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q HostbasedAcceptedAlgorithms . This was formerly named HostbasedAcceptedKeyTypes. .It Cm HostbasedAuthentication Specifies whether rhosts or /etc/hosts.equiv authentication together with successful public key client host authentication is allowed (host-based authentication). The default is .Cm no . .It Cm HostbasedUsesNameFromPacketOnly Specifies whether or not the server will attempt to perform a reverse name lookup when matching the name in the .Pa ~/.shosts , .Pa ~/.rhosts , and .Pa /etc/hosts.equiv files during .Cm HostbasedAuthentication . A setting of .Cm yes means that .Xr sshd 8 uses the name supplied by the client rather than attempting to resolve the name from the TCP connection itself. The default is .Cm no . .It Cm HostCertificate Specifies a file containing a public host certificate. The certificate's public key must match a private host key already specified by .Cm HostKey . The default behaviour of .Xr sshd 8 is not to load any certificates. .It Cm HostKey Specifies a file containing a private host key used by SSH. The defaults are .Pa /etc/ssh/ssh_host_ecdsa_key , .Pa /etc/ssh/ssh_host_ed25519_key and .Pa /etc/ssh/ssh_host_rsa_key . .Pp Note that .Xr sshd 8 will refuse to use a file if it is group/world-accessible and that the .Cm HostKeyAlgorithms option restricts which of the keys are actually used by .Xr sshd 8 . .Pp It is possible to have multiple host key files. It is also possible to specify public host key files instead. In this case operations on the private key will be delegated to an .Xr ssh-agent 1 . .It Cm HostKeyAgent Identifies the UNIX-domain socket used to communicate with an agent that has access to the private host keys. If the string .Qq SSH_AUTH_SOCK is specified, the location of the socket will be read from the .Ev SSH_AUTH_SOCK environment variable. .It Cm HostKeyAlgorithms Specifies the host key signature algorithms that the server offers. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q HostKeyAlgorithms . .It Cm IgnoreRhosts Specifies whether to ignore per-user .Pa .rhosts and .Pa .shosts files during .Cm HostbasedAuthentication . The system-wide .Pa /etc/hosts.equiv and .Pa /etc/ssh/shosts.equiv are still used regardless of this setting. .Pp Accepted values are .Cm yes (the default) to ignore all per-user files, .Cm shosts-only to allow the use of .Pa .shosts but to ignore .Pa .rhosts or .Cm no to allow both .Pa .shosts and .Pa rhosts . .It Cm IgnoreUserKnownHosts Specifies whether .Xr sshd 8 should ignore the user's .Pa ~/.ssh/known_hosts during .Cm HostbasedAuthentication and use only the system-wide known hosts file .Pa /etc/ssh/ssh_known_hosts . The default is .Dq no . .It Cm Include Include the specified configuration file(s). Multiple pathnames may be specified and each pathname may contain .Xr glob 7 wildcards that will be expanded and processed in lexical order. Files without absolute paths are assumed to be in .Pa /etc/ssh . An .Cm Include directive may appear inside a .Cm Match block to perform conditional inclusion. .It Cm IPQoS Specifies the IPv4 type-of-service or DSCP class for the connection. Accepted values are .Cm af11 , .Cm af12 , .Cm af13 , .Cm af21 , .Cm af22 , .Cm af23 , .Cm af31 , .Cm af32 , .Cm af33 , .Cm af41 , .Cm af42 , .Cm af43 , .Cm cs0 , .Cm cs1 , .Cm cs2 , .Cm cs3 , .Cm cs4 , .Cm cs5 , .Cm cs6 , .Cm cs7 , .Cm ef , .Cm le , .Cm lowdelay , .Cm throughput , .Cm reliability , a numeric value, or .Cm none to use the operating system default. This option may take one or two arguments, separated by whitespace. If one argument is specified, it is used as the packet class unconditionally. If two values are specified, the first is automatically selected for interactive sessions and the second for non-interactive sessions. The default is .Cm af21 (Low-Latency Data) for interactive sessions and .Cm cs1 (Lower Effort) for non-interactive sessions. .It Cm KbdInteractiveAuthentication Specifies whether to allow keyboard-interactive authentication. All authentication styles from .Xr login.conf 5 are supported. The default is .Cm yes . The argument to this keyword must be .Cm yes or .Cm no . .Cm ChallengeResponseAuthentication is a deprecated alias for this. .It Cm KerberosAuthentication Specifies whether the password provided by the user for .Cm PasswordAuthentication will be validated through the Kerberos KDC. To use this option, the server needs a Kerberos servtab which allows the verification of the KDC's identity. The default is .Cm no . .It Cm KerberosGetAFSToken If AFS is active and the user has a Kerberos 5 TGT, attempt to acquire an AFS token before accessing the user's home directory. The default is .Cm no . .It Cm KerberosOrLocalPasswd If password authentication through Kerberos fails then the password will be validated via any additional local mechanism such as .Pa /etc/passwd . The default is .Cm yes . .It Cm KerberosTicketCleanup Specifies whether to automatically destroy the user's ticket cache file on logout. The default is .Cm yes . .It Cm KexAlgorithms Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must be comma-separated. Alternately if the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. The supported algorithms are: .Pp .Bl -item -compact -offset indent .It curve25519-sha256 .It curve25519-sha256@libssh.org .It diffie-hellman-group1-sha1 .It diffie-hellman-group14-sha1 .It diffie-hellman-group14-sha256 .It diffie-hellman-group16-sha512 .It diffie-hellman-group18-sha512 .It diffie-hellman-group-exchange-sha1 .It diffie-hellman-group-exchange-sha256 .It ecdh-sha2-nistp256 .It ecdh-sha2-nistp384 .It ecdh-sha2-nistp521 .It sntrup761x25519-sha512@openssh.com .El .Pp The default is: .Bd -literal -offset indent sntrup761x25519-sha512@openssh.com, curve25519-sha256,curve25519-sha256@libssh.org, ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512,diffie-hellman-group18-sha512, diffie-hellman-group14-sha256 .Ed .Pp The list of available key exchange algorithms may also be obtained using .Qq ssh -Q KexAlgorithms . .It Cm ListenAddress Specifies the local addresses .Xr sshd 8 should listen on. The following forms may be used: .Pp .Bl -item -offset indent -compact .It .Cm ListenAddress .Sm off .Ar hostname | address .Sm on .Op Cm rdomain Ar domain .It .Cm ListenAddress .Sm off .Ar hostname : port .Sm on .Op Cm rdomain Ar domain .It .Cm ListenAddress .Sm off .Ar IPv4_address : port .Sm on .Op Cm rdomain Ar domain .It .Cm ListenAddress .Sm off .Oo Ar hostname | address Oc : Ar port .Sm on .Op Cm rdomain Ar domain .El .Pp The optional .Cm rdomain qualifier requests .Xr sshd 8 listen in an explicit routing domain. If .Ar port is not specified, sshd will listen on the address and all .Cm Port options specified. The default is to listen on all local addresses on the current default routing domain. Multiple .Cm ListenAddress options are permitted. For more information on routing domains, see .Xr rdomain 4 . .It Cm LoginGraceTime The server disconnects after this time if the user has not successfully logged in. If the value is 0, there is no time limit. The default is 120 seconds. .It Cm LogLevel Gives the verbosity level that is used when logging messages from .Xr sshd 8 . The possible values are: QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is INFO. DEBUG and DEBUG1 are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level violates the privacy of users and is not recommended. .It Cm LogVerbose Specify one or more overrides to LogLevel. An override consists of a pattern lists that matches the source file, function and line number to force detailed logging for. For example, an override pattern of: .Bd -literal -offset indent kex.c:*:1000,*:kex_exchange_identification():*,packet.c:* .Ed .Pp would enable detailed logging for line 1000 of .Pa kex.c , everything in the .Fn kex_exchange_identification function, and all code in the .Pa packet.c file. This option is intended for debugging and no overrides are enabled by default. .It Cm MACs Specifies the available MAC (message authentication code) algorithms. The MAC algorithm is used for data integrity protection. Multiple algorithms must be comma-separated. If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. .Pp The algorithms that contain .Qq -etm calculate the MAC after encryption (encrypt-then-mac). These are considered safer and their use recommended. The supported MACs are: .Pp .Bl -item -compact -offset indent .It hmac-md5 .It hmac-md5-96 .It hmac-sha1 .It hmac-sha1-96 .It hmac-sha2-256 .It hmac-sha2-512 .It umac-64@openssh.com .It umac-128@openssh.com .It hmac-md5-etm@openssh.com .It hmac-md5-96-etm@openssh.com .It hmac-sha1-etm@openssh.com .It hmac-sha1-96-etm@openssh.com .It hmac-sha2-256-etm@openssh.com .It hmac-sha2-512-etm@openssh.com .It umac-64-etm@openssh.com .It umac-128-etm@openssh.com .El .Pp The default is: .Bd -literal -offset indent umac-64-etm@openssh.com,umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com,umac-128@openssh.com, hmac-sha2-256,hmac-sha2-512,hmac-sha1 .Ed .Pp The list of available MAC algorithms may also be obtained using .Qq ssh -Q mac . .It Cm Match Introduces a conditional block. If all of the criteria on the .Cm Match line are satisfied, the keywords on the following lines override those set in the global section of the config file, until either another .Cm Match line or the end of the file. If a keyword appears in multiple .Cm Match blocks that are satisfied, only the first instance of the keyword is applied. .Pp The arguments to .Cm Match are one or more criteria-pattern pairs or the single token .Cm All which matches all criteria. The available criteria are .Cm User , .Cm Group , .Cm Host , .Cm LocalAddress , .Cm LocalPort , .Cm RDomain , and .Cm Address (with .Cm RDomain representing the .Xr rdomain 4 on which the connection was received). .Pp The match patterns may consist of single entries or comma-separated lists and may use the wildcard and negation operators described in the .Sx PATTERNS section of .Xr ssh_config 5 . .Pp The patterns in an .Cm Address criteria may additionally contain addresses to match in CIDR address/masklen format, such as 192.0.2.0/24 or 2001:db8::/32. Note that the mask length provided must be consistent with the address - it is an error to specify a mask length that is too long for the address or one with bits set in this host portion of the address. For example, 192.0.2.0/33 and 192.0.2.0/8, respectively. .Pp Only a subset of keywords may be used on the lines following a .Cm Match keyword. Available keywords are .Cm AcceptEnv , .Cm AllowAgentForwarding , .Cm AllowGroups , .Cm AllowStreamLocalForwarding , .Cm AllowTcpForwarding , .Cm AllowUsers , .Cm AuthenticationMethods , .Cm AuthorizedKeysCommand , .Cm AuthorizedKeysCommandUser , .Cm AuthorizedKeysFile , .Cm AuthorizedPrincipalsCommand , .Cm AuthorizedPrincipalsCommandUser , .Cm AuthorizedPrincipalsFile , .Cm Banner , .Cm CASignatureAlgorithms , .Cm ChannelTimeout , .Cm ChrootDirectory , .Cm ClientAliveCountMax , .Cm ClientAliveInterval , .Cm DenyGroups , .Cm DenyUsers , .Cm DisableForwarding , .Cm ExposeAuthInfo , .Cm ForceCommand , .Cm GatewayPorts , .Cm GSSAPIAuthentication , .Cm HostbasedAcceptedAlgorithms , .Cm HostbasedAuthentication , .Cm HostbasedUsesNameFromPacketOnly , .Cm IgnoreRhosts , .Cm Include , .Cm IPQoS , .Cm KbdInteractiveAuthentication , .Cm KerberosAuthentication , .Cm LogLevel , .Cm MaxAuthTries , .Cm MaxSessions , .Cm PasswordAuthentication , .Cm PermitEmptyPasswords , .Cm PermitListen , .Cm PermitOpen , .Cm PermitRootLogin , .Cm PermitTTY , .Cm PermitTunnel , .Cm PermitUserRC , .Cm PubkeyAcceptedAlgorithms , .Cm PubkeyAuthentication , .Cm PubkeyAuthOptions , .Cm RekeyLimit , .Cm RevokedKeys , .Cm RDomain , .Cm SetEnv , .Cm StreamLocalBindMask , .Cm StreamLocalBindUnlink , .Cm TrustedUserCAKeys , .Cm UnusedConnectionTimeout , .Cm X11DisplayOffset , .Cm X11Forwarding and .Cm X11UseLocalhost . .It Cm MaxAuthTries Specifies the maximum number of authentication attempts permitted per connection. Once the number of failures reaches half this value, additional failures are logged. The default is 6. .It Cm MaxSessions Specifies the maximum number of open shell, login or subsystem (e.g. sftp) sessions permitted per network connection. Multiple sessions may be established by clients that support connection multiplexing. Setting .Cm MaxSessions to 1 will effectively disable session multiplexing, whereas setting it to 0 will prevent all shell, login and subsystem sessions while still permitting forwarding. The default is 10. .It Cm MaxStartups Specifies the maximum number of concurrent unauthenticated connections to the SSH daemon. Additional connections will be dropped until authentication succeeds or the .Cm LoginGraceTime expires for a connection. The default is 10:30:100. .Pp Alternatively, random early drop can be enabled by specifying the three colon separated values start:rate:full (e.g. "10:30:60"). .Xr sshd 8 will refuse connection attempts with a probability of rate/100 (30%) if there are currently start (10) unauthenticated connections. The probability increases linearly and all connection attempts are refused if the number of unauthenticated connections reaches full (60). .It Cm ModuliFile Specifies the .Xr moduli 5 file that contains the Diffie-Hellman groups used for the .Dq diffie-hellman-group-exchange-sha1 and .Dq diffie-hellman-group-exchange-sha256 key exchange methods. The default is .Pa /etc/moduli . .It Cm PasswordAuthentication Specifies whether password authentication is allowed. Note that passwords may also be accepted via .Cm KbdInteractiveAuthentication . See also .Cm UsePAM . The default is .Cm no . .It Cm PermitEmptyPasswords When password authentication is allowed, it specifies whether the server allows login to accounts with empty password strings. The default is .Cm no . .It Cm PermitListen Specifies the addresses/ports on which a remote TCP port forwarding may listen. The listen specification must be one of the following forms: .Pp .Bl -item -offset indent -compact .It .Cm PermitListen .Sm off .Ar port .Sm on .It .Cm PermitListen .Sm off .Ar host : port .Sm on .El .Pp Multiple permissions may be specified by separating them with whitespace. An argument of .Cm any can be used to remove all restrictions and permit any listen requests. An argument of .Cm none can be used to prohibit all listen requests. The host name may contain wildcards as described in the PATTERNS section in .Xr ssh_config 5 . The wildcard .Sq * can also be used in place of a port number to allow all ports. By default all port forwarding listen requests are permitted. Note that the .Cm GatewayPorts option may further restrict which addresses may be listened on. Note also that .Xr ssh 1 will request a listen host of .Dq localhost if no listen host was specifically requested, and this name is treated differently to explicit localhost addresses of .Dq 127.0.0.1 and .Dq ::1 . .It Cm PermitOpen Specifies the destinations to which TCP port forwarding is permitted. The forwarding specification must be one of the following forms: .Pp .Bl -item -offset indent -compact .It .Cm PermitOpen .Sm off .Ar host : port .Sm on .It .Cm PermitOpen .Sm off .Ar IPv4_addr : port .Sm on .It .Cm PermitOpen .Sm off .Ar \&[ IPv6_addr \&] : port .Sm on .El .Pp Multiple forwards may be specified by separating them with whitespace. An argument of .Cm any can be used to remove all restrictions and permit any forwarding requests. An argument of .Cm none can be used to prohibit all forwarding requests. The wildcard .Sq * can be used for host or port to allow all hosts or ports respectively. Otherwise, no pattern matching or address lookups are performed on supplied names. By default all port forwarding requests are permitted. .It Cm PermitRootLogin Specifies whether root can log in using .Xr ssh 1 . The argument must be .Cm yes , .Cm prohibit-password , .Cm forced-commands-only , or .Cm no . The default is .Cm no . Note that if .Cm ChallengeResponseAuthentication and .Cm UsePAM are both .Cm yes , this setting may be overridden by the PAM policy. .Pp If this option is set to .Cm prohibit-password (or its deprecated alias, .Cm without-password ) , password and keyboard-interactive authentication are disabled for root. .Pp If this option is set to .Cm forced-commands-only , root login with public key authentication will be allowed, but only if the .Ar command option has been specified (which may be useful for taking remote backups even if root login is normally not allowed). All other authentication methods are disabled for root. .Pp If this option is set to .Cm no , root is not allowed to log in. .It Cm PermitTTY Specifies whether .Xr pty 4 allocation is permitted. The default is .Cm yes . .It Cm PermitTunnel Specifies whether .Xr tun 4 device forwarding is allowed. The argument must be .Cm yes , .Cm point-to-point (layer 3), .Cm ethernet (layer 2), or .Cm no . Specifying .Cm yes permits both .Cm point-to-point and .Cm ethernet . The default is .Cm no . .Pp Independent of this setting, the permissions of the selected .Xr tun 4 device must allow access to the user. .It Cm PermitUserEnvironment Specifies whether .Pa ~/.ssh/environment and .Cm environment= options in .Pa ~/.ssh/authorized_keys are processed by .Xr sshd 8 . Valid options are .Cm yes , .Cm no or a pattern-list specifying which environment variable names to accept (for example .Qq LANG,LC_* ) . The default is .Cm no . Enabling environment processing may enable users to bypass access restrictions in some configurations using mechanisms such as .Ev LD_PRELOAD . .It Cm PermitUserRC Specifies whether any .Pa ~/.ssh/rc file is executed. The default is .Cm yes . .It Cm PerSourceMaxStartups Specifies the number of unauthenticated connections allowed from a given source address, or .Dq none if there is no limit. This limit is applied in addition to .Cm MaxStartups , whichever is lower. The default is .Cm none . .It Cm PerSourceNetBlockSize Specifies the number of bits of source address that are grouped together for the purposes of applying PerSourceMaxStartups limits. Values for IPv4 and optionally IPv6 may be specified, separated by a colon. The default is .Cm 32:128 , which means each address is considered individually. .It Cm PidFile Specifies the file that contains the process ID of the SSH daemon, or .Cm none to not write one. The default is .Pa /var/run/sshd.pid . .It Cm Port Specifies the port number that .Xr sshd 8 listens on. The default is 22. Multiple options of this type are permitted. See also .Cm ListenAddress . .It Cm PrintLastLog Specifies whether .Xr sshd 8 should print the date and time of the last user login when a user logs in interactively. The default is .Cm yes . .It Cm PrintMotd Specifies whether .Xr sshd 8 should print .Pa /etc/motd when a user logs in interactively. (On some systems it is also printed by the shell, .Pa /etc/profile , or equivalent.) The default is .Cm yes . .It Cm PubkeyAcceptedAlgorithms Specifies the signature algorithms that will be accepted for public key authentication as a list of comma-separated patterns. Alternately if the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q PubkeyAcceptedAlgorithms . .It Cm PubkeyAuthOptions Sets one or more public key authentication options. The supported keywords are: .Cm none (the default; indicating no additional options are enabled), .Cm touch-required and .Cm verify-required . .Pp The .Cm touch-required option causes public key authentication using a FIDO authenticator algorithm (i.e.\& .Cm ecdsa-sk or .Cm ed25519-sk ) to always require the signature to attest that a physically present user explicitly confirmed the authentication (usually by touching the authenticator). By default, .Xr sshd 8 requires user presence unless overridden with an authorized_keys option. The .Cm touch-required flag disables this override. .Pp The .Cm verify-required option requires a FIDO key signature attest that the user was verified, e.g. via a PIN. .Pp Neither the .Cm touch-required or .Cm verify-required options have any effect for other, non-FIDO, public key types. .It Cm PubkeyAuthentication Specifies whether public key authentication is allowed. The default is .Cm yes . .It Cm RekeyLimit Specifies the maximum amount of data that may be transmitted or received before the session key is renegotiated, optionally followed by a maximum amount of time that may pass before the session key is renegotiated. The first argument is specified in bytes and may have a suffix of .Sq K , .Sq M , or .Sq G to indicate Kilobytes, Megabytes, or Gigabytes, respectively. The default is between .Sq 1G and .Sq 4G , depending on the cipher. The optional second value is specified in seconds and may use any of the units documented in the .Sx TIME FORMATS section. The default value for .Cm RekeyLimit is .Cm default none , which means that rekeying is performed after the cipher's default amount of data has been sent or received and no time based rekeying is done. .It Cm RequiredRSASize Specifies the minimum RSA key size (in bits) that .Xr sshd 8 will accept. User and host-based authentication keys smaller than this limit will be refused. The default is .Cm 1024 bits. Note that this limit may only be raised from the default. .It Cm RevokedKeys Specifies revoked public keys file, or .Cm none to not use one. Keys listed in this file will be refused for public key authentication. Note that if this file is not readable, then public key authentication will be refused for all users. Keys may be specified as a text file, listing one public key per line, or as an OpenSSH Key Revocation List (KRL) as generated by .Xr ssh-keygen 1 . For more information on KRLs, see the KEY REVOCATION LISTS section in .Xr ssh-keygen 1 . .It Cm RDomain Specifies an explicit routing domain that is applied after authentication has completed. The user session, as well as any forwarded or listening IP sockets, will be bound to this .Xr rdomain 4 . If the routing domain is set to .Cm \&%D , then the domain in which the incoming connection was received will be applied. .It Cm SecurityKeyProvider Specifies a path to a library that will be used when loading FIDO authenticator-hosted keys, overriding the default of using the built-in USB HID support. .It Cm SetEnv Specifies one or more environment variables to set in child sessions started by .Xr sshd 8 as .Dq NAME=VALUE . The environment value may be quoted (e.g. if it contains whitespace characters). Environment variables set by .Cm SetEnv override the default environment and any variables specified by the user via .Cm AcceptEnv or .Cm PermitUserEnvironment . .It Cm StreamLocalBindMask Sets the octal file creation mode mask .Pq umask used when creating a Unix-domain socket file for local or remote port forwarding. This option is only used for port forwarding to a Unix-domain socket file. .Pp The default value is 0177, which creates a Unix-domain socket file that is readable and writable only by the owner. Note that not all operating systems honor the file mode on Unix-domain socket files. .It Cm StreamLocalBindUnlink Specifies whether to remove an existing Unix-domain socket file for local or remote port forwarding before creating a new one. If the socket file already exists and .Cm StreamLocalBindUnlink is not enabled, .Nm sshd will be unable to forward the port to the Unix-domain socket file. This option is only used for port forwarding to a Unix-domain socket file. .Pp The argument must be .Cm yes or .Cm no . The default is .Cm no . .It Cm StrictModes Specifies whether .Xr sshd 8 should check file modes and ownership of the user's files and home directory before accepting login. This is normally desirable because novices sometimes accidentally leave their directory or files world-writable. The default is .Cm yes . Note that this does not apply to .Cm ChrootDirectory , whose permissions and ownership are checked unconditionally. .It Cm Subsystem Configures an external subsystem (e.g. file transfer daemon). Arguments should be a subsystem name and a command (with optional arguments) to execute upon subsystem request. .Pp The command .Cm sftp-server implements the SFTP file transfer subsystem. .Pp Alternately the name .Cm internal-sftp implements an in-process SFTP server. This may simplify configurations using .Cm ChrootDirectory to force a different filesystem root on clients. .Pp By default no subsystems are defined. .It Cm SyslogFacility Gives the facility code that is used when logging messages from .Xr sshd 8 . The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The default is AUTH. .It Cm TCPKeepAlive Specifies whether the system should send TCP keepalive messages to the other side. If they are sent, death of the connection or crash of one of the machines will be properly noticed. However, this means that connections will die if the route is down temporarily, and some people find it annoying. On the other hand, if TCP keepalives are not sent, sessions may hang indefinitely on the server, leaving .Qq ghost users and consuming server resources. .Pp The default is .Cm yes (to send TCP keepalive messages), and the server will notice if the network goes down or the client host crashes. This avoids infinitely hanging sessions. .Pp To disable TCP keepalive messages, the value should be set to .Cm no . .It Cm TrustedUserCAKeys Specifies a file containing public keys of certificate authorities that are trusted to sign user certificates for authentication, or .Cm none to not use one. Keys are listed one per line; empty lines and comments starting with .Ql # are allowed. If a certificate is presented for authentication and has its signing CA key listed in this file, then it may be used for authentication for any user listed in the certificate's principals list. Note that certificates that lack a list of principals will not be permitted for authentication using .Cm TrustedUserCAKeys . For more details on certificates, see the CERTIFICATES section in .Xr ssh-keygen 1 . .It Cm UnusedConnectionTimeout Specifies whether and how quickly .Xr sshd 8 should close client connections with no open channels. Open channels include active shell, command execution or subsystem sessions, connected network, socket, agent or X11 forwardings. Forwarding listeners, such as those from the .Xr ssh 1 .Fl R flag, are not considered as open channels and do not prevent the timeout. The timeout value is specified in seconds or may use any of the units documented in the .Sx TIME FORMATS section. .Pp Note that this timeout starts when the client connection completes user authentication but before the client has an opportunity to open any channels. Caution should be used when using short timeout values, as they may not provide sufficient time for the client to request and open its channels before terminating the connection. .Pp The default .Cm none is to never expire connections for having no open channels. This option may be useful in conjunction with .Cm ChannelTimeout . .It Cm UseBlacklist Specifies whether .Xr sshd 8 attempts to send authentication success and failure messages to the .Xr blacklistd 8 daemon. The default is .Cm no . For forward compatibility with an upcoming .Xr blacklistd rename, the .Cm UseBlocklist alias can be used instead. .It Cm UseDNS Specifies whether .Xr sshd 8 should look up the remote host name, and to check that the resolved host name for the remote IP address maps back to the very same IP address. .Pp If this option is set to .Cm no , then only addresses and not host names may be used in .Pa ~/.ssh/authorized_keys .Cm from and .Nm .Cm Match .Cm Host directives. The default is .Dq yes . .It Cm UsePAM Enables the Pluggable Authentication Module interface. If set to .Cm yes this will enable PAM authentication using .Cm KbdInteractiveAuthentication and .Cm PasswordAuthentication in addition to PAM account and session module processing for all authentication types. .Pp Because PAM keyboard-interactive authentication usually serves an equivalent role to password authentication, you should disable either .Cm PasswordAuthentication or .Cm KbdInteractiveAuthentication . .Pp If .Cm UsePAM is enabled, you will not be able to run .Xr sshd 8 as a non-root user. The default is .Cm yes . .It Cm VersionAddendum Optionally specifies additional text to append to the SSH protocol banner sent by the server upon connection. The default is -.Qq FreeBSD-20230810 . +.Qq FreeBSD-20231004 . The value .Cm none may be used to disable this. .It Cm X11DisplayOffset Specifies the first display number available for .Xr sshd 8 Ns 's X11 forwarding. This prevents sshd from interfering with real X11 servers. The default is 10. .It Cm X11Forwarding Specifies whether X11 forwarding is permitted. The argument must be .Cm yes or .Cm no . The default is .Cm no . .Pp When X11 forwarding is enabled, there may be additional exposure to the server and to client displays if the .Xr sshd 8 proxy display is configured to listen on the wildcard address (see .Cm X11UseLocalhost ) , though this is not the default. Additionally, the authentication spoofing and authentication data verification and substitution occur on the client side. The security risk of using X11 forwarding is that the client's X11 display server may be exposed to attack when the SSH client requests forwarding (see the warnings for .Cm ForwardX11 in .Xr ssh_config 5 ) . A system administrator may have a stance in which they want to protect clients that may expose themselves to attack by unwittingly requesting X11 forwarding, which can warrant a .Cm no setting. .Pp Note that disabling X11 forwarding does not prevent users from forwarding X11 traffic, as users can always install their own forwarders. .It Cm X11UseLocalhost Specifies whether .Xr sshd 8 should bind the X11 forwarding server to the loopback address or to the wildcard address. By default, sshd binds the forwarding server to the loopback address and sets the hostname part of the .Ev DISPLAY environment variable to .Cm localhost . This prevents remote hosts from connecting to the proxy display. However, some older X11 clients may not function with this configuration. .Cm X11UseLocalhost may be set to .Cm no to specify that the forwarding server should be bound to the wildcard address. The argument must be .Cm yes or .Cm no . The default is .Cm yes . .It Cm XAuthLocation Specifies the full pathname of the .Xr xauth 1 program, or .Cm none to not use one. The default is .Pa /usr/local/bin/xauth . .El .Sh TIME FORMATS .Xr sshd 8 command-line arguments and configuration file options that specify time may be expressed using a sequence of the form: .Sm off .Ar time Op Ar qualifier , .Sm on where .Ar time is a positive integer value and .Ar qualifier is one of the following: .Pp .Bl -tag -width Ds -compact -offset indent .It Aq Cm none seconds .It Cm s | Cm S seconds .It Cm m | Cm M minutes .It Cm h | Cm H hours .It Cm d | Cm D days .It Cm w | Cm W weeks .El .Pp Each member of the sequence is added together to calculate the total time value. .Pp Time format examples: .Pp .Bl -tag -width Ds -compact -offset indent .It 600 600 seconds (10 minutes) .It 10m 10 minutes .It 1h30m 1 hour 30 minutes (90 minutes) .El .Sh TOKENS Arguments to some keywords can make use of tokens, which are expanded at runtime: .Pp .Bl -tag -width XXXX -offset indent -compact .It %% A literal .Sq % . .It \&%C Identifies the connection endpoints, containing four space-separated values: client address, client port number, server address, and server port number. .It \&%D The routing domain in which the incoming connection was received. .It %F The fingerprint of the CA key. .It %f The fingerprint of the key or certificate. .It %h The home directory of the user. .It %i The key ID in the certificate. .It %K The base64-encoded CA key. .It %k The base64-encoded key or certificate for authentication. .It %s The serial number of the certificate. .It \&%T The type of the CA key. .It %t The key or certificate type. .It \&%U The numeric user ID of the target user. .It %u The username. .El .Pp .Cm AuthorizedKeysCommand accepts the tokens %%, %C, %D, %f, %h, %k, %t, %U, and %u. .Pp .Cm AuthorizedKeysFile accepts the tokens %%, %h, %U, and %u. .Pp .Cm AuthorizedPrincipalsCommand accepts the tokens %%, %C, %D, %F, %f, %h, %i, %K, %k, %s, %T, %t, %U, and %u. .Pp .Cm AuthorizedPrincipalsFile accepts the tokens %%, %h, %U, and %u. .Pp .Cm ChrootDirectory accepts the tokens %%, %h, %U, and %u. .Pp .Cm RoutingDomain accepts the token %D. .Sh FILES .Bl -tag -width Ds .It Pa /etc/ssh/sshd_config Contains configuration data for .Xr sshd 8 . This file should be writable by root only, but it is recommended (though not necessary) that it be world-readable. .El .Sh SEE ALSO .Xr sftp-server 8 , .Xr sshd 8 .Sh AUTHORS .An -nosplit OpenSSH is a derivative of the original and free ssh 1.2.12 release by .An Tatu Ylonen . .An Aaron Campbell , Bob Beck , Markus Friedl , Niels Provos , .An Theo de Raadt and .An Dug Song removed many bugs, re-added newer features and created OpenSSH. .An Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. .An Niels Provos and .An Markus Friedl contributed support for privilege separation. diff --git a/crypto/openssh/sshkey.c b/crypto/openssh/sshkey.c index 727728536b34..2d3906ad8475 100644 --- a/crypto/openssh/sshkey.c +++ b/crypto/openssh/sshkey.c @@ -1,3669 +1,3670 @@ -/* $OpenBSD: sshkey.c,v 1.137 2023/07/27 22:23:05 djm Exp $ */ +/* $OpenBSD: sshkey.c,v 1.138 2023/08/21 04:36:46 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Alexander von Gernler. All rights reserved. * Copyright (c) 2010,2011 Damien Miller. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #ifdef WITH_OPENSSL #include #include #include #endif #include "crypto_api.h" #include #include #include +#include #include #include #include #ifdef HAVE_UTIL_H #include #endif /* HAVE_UTIL_H */ #include "ssh2.h" #include "ssherr.h" #include "misc.h" #include "sshbuf.h" #include "cipher.h" #include "digest.h" #define SSHKEY_INTERNAL #include "sshkey.h" #include "match.h" #include "ssh-sk.h" #ifdef WITH_XMSS #include "sshkey-xmss.h" #include "xmss_fast.h" #endif #include "openbsd-compat/openssl-compat.h" /* openssh private key file format */ #define MARK_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n" #define MARK_END "-----END OPENSSH PRIVATE KEY-----\n" #define MARK_BEGIN_LEN (sizeof(MARK_BEGIN) - 1) #define MARK_END_LEN (sizeof(MARK_END) - 1) #define KDFNAME "bcrypt" #define AUTH_MAGIC "openssh-key-v1" #define SALT_LEN 16 #define DEFAULT_CIPHERNAME "aes256-ctr" #define DEFAULT_ROUNDS 24 /* Version identification string for SSH v1 identity files. */ #define LEGACY_BEGIN "SSH PRIVATE KEY FILE FORMAT 1.1\n" /* * Constants relating to "shielding" support; protection of keys expected * to remain in memory for long durations */ #define SSHKEY_SHIELD_PREKEY_LEN (16 * 1024) #define SSHKEY_SHIELD_CIPHER "aes256-ctr" /* XXX want AES-EME* */ #define SSHKEY_SHIELD_PREKEY_HASH SSH_DIGEST_SHA512 int sshkey_private_serialize_opt(struct sshkey *key, struct sshbuf *buf, enum sshkey_serialize_rep); static int sshkey_from_blob_internal(struct sshbuf *buf, struct sshkey **keyp, int allow_cert); /* Supported key types */ extern const struct sshkey_impl sshkey_ed25519_impl; extern const struct sshkey_impl sshkey_ed25519_cert_impl; extern const struct sshkey_impl sshkey_ed25519_sk_impl; extern const struct sshkey_impl sshkey_ed25519_sk_cert_impl; #ifdef WITH_OPENSSL # ifdef OPENSSL_HAS_ECC # ifdef ENABLE_SK extern const struct sshkey_impl sshkey_ecdsa_sk_impl; extern const struct sshkey_impl sshkey_ecdsa_sk_cert_impl; extern const struct sshkey_impl sshkey_ecdsa_sk_webauthn_impl; # endif /* ENABLE_SK */ extern const struct sshkey_impl sshkey_ecdsa_nistp256_impl; extern const struct sshkey_impl sshkey_ecdsa_nistp256_cert_impl; extern const struct sshkey_impl sshkey_ecdsa_nistp384_impl; extern const struct sshkey_impl sshkey_ecdsa_nistp384_cert_impl; # ifdef OPENSSL_HAS_NISTP521 extern const struct sshkey_impl sshkey_ecdsa_nistp521_impl; extern const struct sshkey_impl sshkey_ecdsa_nistp521_cert_impl; # endif /* OPENSSL_HAS_NISTP521 */ # endif /* OPENSSL_HAS_ECC */ extern const struct sshkey_impl sshkey_rsa_impl; extern const struct sshkey_impl sshkey_rsa_cert_impl; extern const struct sshkey_impl sshkey_rsa_sha256_impl; extern const struct sshkey_impl sshkey_rsa_sha256_cert_impl; extern const struct sshkey_impl sshkey_rsa_sha512_impl; extern const struct sshkey_impl sshkey_rsa_sha512_cert_impl; extern const struct sshkey_impl sshkey_dss_impl; extern const struct sshkey_impl sshkey_dsa_cert_impl; #endif /* WITH_OPENSSL */ #ifdef WITH_XMSS extern const struct sshkey_impl sshkey_xmss_impl; extern const struct sshkey_impl sshkey_xmss_cert_impl; #endif const struct sshkey_impl * const keyimpls[] = { &sshkey_ed25519_impl, &sshkey_ed25519_cert_impl, #ifdef ENABLE_SK &sshkey_ed25519_sk_impl, &sshkey_ed25519_sk_cert_impl, #endif #ifdef WITH_OPENSSL # ifdef OPENSSL_HAS_ECC &sshkey_ecdsa_nistp256_impl, &sshkey_ecdsa_nistp256_cert_impl, &sshkey_ecdsa_nistp384_impl, &sshkey_ecdsa_nistp384_cert_impl, # ifdef OPENSSL_HAS_NISTP521 &sshkey_ecdsa_nistp521_impl, &sshkey_ecdsa_nistp521_cert_impl, # endif /* OPENSSL_HAS_NISTP521 */ # ifdef ENABLE_SK &sshkey_ecdsa_sk_impl, &sshkey_ecdsa_sk_cert_impl, &sshkey_ecdsa_sk_webauthn_impl, # endif /* ENABLE_SK */ # endif /* OPENSSL_HAS_ECC */ &sshkey_dss_impl, &sshkey_dsa_cert_impl, &sshkey_rsa_impl, &sshkey_rsa_cert_impl, &sshkey_rsa_sha256_impl, &sshkey_rsa_sha256_cert_impl, &sshkey_rsa_sha512_impl, &sshkey_rsa_sha512_cert_impl, #endif /* WITH_OPENSSL */ #ifdef WITH_XMSS &sshkey_xmss_impl, &sshkey_xmss_cert_impl, #endif NULL }; static const struct sshkey_impl * sshkey_impl_from_type(int type) { int i; for (i = 0; keyimpls[i] != NULL; i++) { if (keyimpls[i]->type == type) return keyimpls[i]; } return NULL; } static const struct sshkey_impl * sshkey_impl_from_type_nid(int type, int nid) { int i; for (i = 0; keyimpls[i] != NULL; i++) { if (keyimpls[i]->type == type && (keyimpls[i]->nid == 0 || keyimpls[i]->nid == nid)) return keyimpls[i]; } return NULL; } static const struct sshkey_impl * sshkey_impl_from_key(const struct sshkey *k) { if (k == NULL) return NULL; return sshkey_impl_from_type_nid(k->type, k->ecdsa_nid); } const char * sshkey_type(const struct sshkey *k) { const struct sshkey_impl *impl; if ((impl = sshkey_impl_from_key(k)) == NULL) return "unknown"; return impl->shortname; } static const char * sshkey_ssh_name_from_type_nid(int type, int nid) { const struct sshkey_impl *impl; if ((impl = sshkey_impl_from_type_nid(type, nid)) == NULL) return "ssh-unknown"; return impl->name; } int sshkey_type_is_cert(int type) { const struct sshkey_impl *impl; if ((impl = sshkey_impl_from_type(type)) == NULL) return 0; return impl->cert; } const char * sshkey_ssh_name(const struct sshkey *k) { return sshkey_ssh_name_from_type_nid(k->type, k->ecdsa_nid); } const char * sshkey_ssh_name_plain(const struct sshkey *k) { return sshkey_ssh_name_from_type_nid(sshkey_type_plain(k->type), k->ecdsa_nid); } int sshkey_type_from_name(const char *name) { int i; const struct sshkey_impl *impl; for (i = 0; keyimpls[i] != NULL; i++) { impl = keyimpls[i]; /* Only allow shortname matches for plain key types */ if ((impl->name != NULL && strcmp(name, impl->name) == 0) || (!impl->cert && strcasecmp(impl->shortname, name) == 0)) return impl->type; } return KEY_UNSPEC; } static int key_type_is_ecdsa_variant(int type) { switch (type) { case KEY_ECDSA: case KEY_ECDSA_CERT: case KEY_ECDSA_SK: case KEY_ECDSA_SK_CERT: return 1; } return 0; } int sshkey_ecdsa_nid_from_name(const char *name) { int i; for (i = 0; keyimpls[i] != NULL; i++) { if (!key_type_is_ecdsa_variant(keyimpls[i]->type)) continue; if (keyimpls[i]->name != NULL && strcmp(name, keyimpls[i]->name) == 0) return keyimpls[i]->nid; } return -1; } int sshkey_match_keyname_to_sigalgs(const char *keyname, const char *sigalgs) { int ktype; if (sigalgs == NULL || *sigalgs == '\0' || (ktype = sshkey_type_from_name(keyname)) == KEY_UNSPEC) return 0; else if (ktype == KEY_RSA) { return match_pattern_list("ssh-rsa", sigalgs, 0) == 1 || match_pattern_list("rsa-sha2-256", sigalgs, 0) == 1 || match_pattern_list("rsa-sha2-512", sigalgs, 0) == 1; } else if (ktype == KEY_RSA_CERT) { return match_pattern_list("ssh-rsa-cert-v01@openssh.com", sigalgs, 0) == 1 || match_pattern_list("rsa-sha2-256-cert-v01@openssh.com", sigalgs, 0) == 1 || match_pattern_list("rsa-sha2-512-cert-v01@openssh.com", sigalgs, 0) == 1; } else return match_pattern_list(keyname, sigalgs, 0) == 1; } char * sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep) { char *tmp, *ret = NULL; size_t i, nlen, rlen = 0; const struct sshkey_impl *impl; for (i = 0; keyimpls[i] != NULL; i++) { impl = keyimpls[i]; if (impl->name == NULL) continue; if (!include_sigonly && impl->sigonly) continue; if ((certs_only && !impl->cert) || (plain_only && impl->cert)) continue; if (ret != NULL) ret[rlen++] = sep; nlen = strlen(impl->name); if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { free(ret); return NULL; } ret = tmp; memcpy(ret + rlen, impl->name, nlen + 1); rlen += nlen; } return ret; } int sshkey_names_valid2(const char *names, int allow_wildcard, int plain_only) { char *s, *cp, *p; const struct sshkey_impl *impl; int i, type; if (names == NULL || strcmp(names, "") == 0) return 0; if ((s = cp = strdup(names)) == NULL) return 0; for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { type = sshkey_type_from_name(p); if (type == KEY_UNSPEC) { if (allow_wildcard) { /* * Try matching key types against the string. * If any has a positive or negative match then * the component is accepted. */ impl = NULL; for (i = 0; keyimpls[i] != NULL; i++) { if (match_pattern_list( keyimpls[i]->name, p, 0) != 0) { impl = keyimpls[i]; break; } } if (impl != NULL) continue; } free(s); return 0; } else if (plain_only && sshkey_type_is_cert(type)) { free(s); return 0; } } free(s); return 1; } u_int sshkey_size(const struct sshkey *k) { const struct sshkey_impl *impl; if ((impl = sshkey_impl_from_key(k)) == NULL) return 0; if (impl->funcs->size != NULL) return impl->funcs->size(k); return impl->keybits; } static int sshkey_type_is_valid_ca(int type) { const struct sshkey_impl *impl; if ((impl = sshkey_impl_from_type(type)) == NULL) return 0; /* All non-certificate types may act as CAs */ return !impl->cert; } int sshkey_is_cert(const struct sshkey *k) { if (k == NULL) return 0; return sshkey_type_is_cert(k->type); } int sshkey_is_sk(const struct sshkey *k) { if (k == NULL) return 0; switch (sshkey_type_plain(k->type)) { case KEY_ECDSA_SK: case KEY_ED25519_SK: return 1; default: return 0; } } /* Return the cert-less equivalent to a certified key type */ int sshkey_type_plain(int type) { switch (type) { case KEY_RSA_CERT: return KEY_RSA; case KEY_DSA_CERT: return KEY_DSA; case KEY_ECDSA_CERT: return KEY_ECDSA; case KEY_ECDSA_SK_CERT: return KEY_ECDSA_SK; case KEY_ED25519_CERT: return KEY_ED25519; case KEY_ED25519_SK_CERT: return KEY_ED25519_SK; case KEY_XMSS_CERT: return KEY_XMSS; default: return type; } } /* Return the cert equivalent to a plain key type */ static int sshkey_type_certified(int type) { switch (type) { case KEY_RSA: return KEY_RSA_CERT; case KEY_DSA: return KEY_DSA_CERT; case KEY_ECDSA: return KEY_ECDSA_CERT; case KEY_ECDSA_SK: return KEY_ECDSA_SK_CERT; case KEY_ED25519: return KEY_ED25519_CERT; case KEY_ED25519_SK: return KEY_ED25519_SK_CERT; case KEY_XMSS: return KEY_XMSS_CERT; default: return -1; } } #ifdef WITH_OPENSSL /* XXX: these are really begging for a table-driven approach */ int sshkey_curve_name_to_nid(const char *name) { if (strcmp(name, "nistp256") == 0) return NID_X9_62_prime256v1; else if (strcmp(name, "nistp384") == 0) return NID_secp384r1; # ifdef OPENSSL_HAS_NISTP521 else if (strcmp(name, "nistp521") == 0) return NID_secp521r1; # endif /* OPENSSL_HAS_NISTP521 */ else return -1; } u_int sshkey_curve_nid_to_bits(int nid) { switch (nid) { case NID_X9_62_prime256v1: return 256; case NID_secp384r1: return 384; # ifdef OPENSSL_HAS_NISTP521 case NID_secp521r1: return 521; # endif /* OPENSSL_HAS_NISTP521 */ default: return 0; } } int sshkey_ecdsa_bits_to_nid(int bits) { switch (bits) { case 256: return NID_X9_62_prime256v1; case 384: return NID_secp384r1; # ifdef OPENSSL_HAS_NISTP521 case 521: return NID_secp521r1; # endif /* OPENSSL_HAS_NISTP521 */ default: return -1; } } const char * sshkey_curve_nid_to_name(int nid) { switch (nid) { case NID_X9_62_prime256v1: return "nistp256"; case NID_secp384r1: return "nistp384"; # ifdef OPENSSL_HAS_NISTP521 case NID_secp521r1: return "nistp521"; # endif /* OPENSSL_HAS_NISTP521 */ default: return NULL; } } int sshkey_ec_nid_to_hash_alg(int nid) { int kbits = sshkey_curve_nid_to_bits(nid); if (kbits <= 0) return -1; /* RFC5656 section 6.2.1 */ if (kbits <= 256) return SSH_DIGEST_SHA256; else if (kbits <= 384) return SSH_DIGEST_SHA384; else return SSH_DIGEST_SHA512; } #endif /* WITH_OPENSSL */ static void cert_free(struct sshkey_cert *cert) { u_int i; if (cert == NULL) return; sshbuf_free(cert->certblob); sshbuf_free(cert->critical); sshbuf_free(cert->extensions); free(cert->key_id); for (i = 0; i < cert->nprincipals; i++) free(cert->principals[i]); free(cert->principals); sshkey_free(cert->signature_key); free(cert->signature_type); freezero(cert, sizeof(*cert)); } static struct sshkey_cert * cert_new(void) { struct sshkey_cert *cert; if ((cert = calloc(1, sizeof(*cert))) == NULL) return NULL; if ((cert->certblob = sshbuf_new()) == NULL || (cert->critical = sshbuf_new()) == NULL || (cert->extensions = sshbuf_new()) == NULL) { cert_free(cert); return NULL; } cert->key_id = NULL; cert->principals = NULL; cert->signature_key = NULL; cert->signature_type = NULL; return cert; } struct sshkey * sshkey_new(int type) { struct sshkey *k; const struct sshkey_impl *impl = NULL; if (type != KEY_UNSPEC && (impl = sshkey_impl_from_type(type)) == NULL) return NULL; /* All non-certificate types may act as CAs */ if ((k = calloc(1, sizeof(*k))) == NULL) return NULL; k->type = type; k->ecdsa_nid = -1; if (impl != NULL && impl->funcs->alloc != NULL) { if (impl->funcs->alloc(k) != 0) { free(k); return NULL; } } if (sshkey_is_cert(k)) { if ((k->cert = cert_new()) == NULL) { sshkey_free(k); return NULL; } } return k; } /* Frees common FIDO fields */ void sshkey_sk_cleanup(struct sshkey *k) { free(k->sk_application); sshbuf_free(k->sk_key_handle); sshbuf_free(k->sk_reserved); k->sk_application = NULL; k->sk_key_handle = k->sk_reserved = NULL; } static void sshkey_free_contents(struct sshkey *k) { const struct sshkey_impl *impl; if (k == NULL) return; if ((impl = sshkey_impl_from_type(k->type)) != NULL && impl->funcs->cleanup != NULL) impl->funcs->cleanup(k); if (sshkey_is_cert(k)) cert_free(k->cert); freezero(k->shielded_private, k->shielded_len); freezero(k->shield_prekey, k->shield_prekey_len); } void sshkey_free(struct sshkey *k) { sshkey_free_contents(k); freezero(k, sizeof(*k)); } static int cert_compare(struct sshkey_cert *a, struct sshkey_cert *b) { if (a == NULL && b == NULL) return 1; if (a == NULL || b == NULL) return 0; if (sshbuf_len(a->certblob) != sshbuf_len(b->certblob)) return 0; if (timingsafe_bcmp(sshbuf_ptr(a->certblob), sshbuf_ptr(b->certblob), sshbuf_len(a->certblob)) != 0) return 0; return 1; } /* Compares FIDO-specific pubkey fields only */ int sshkey_sk_fields_equal(const struct sshkey *a, const struct sshkey *b) { if (a->sk_application == NULL || b->sk_application == NULL) return 0; if (strcmp(a->sk_application, b->sk_application) != 0) return 0; return 1; } /* * Compare public portions of key only, allowing comparisons between * certificates and plain keys too. */ int sshkey_equal_public(const struct sshkey *a, const struct sshkey *b) { const struct sshkey_impl *impl; if (a == NULL || b == NULL || sshkey_type_plain(a->type) != sshkey_type_plain(b->type)) return 0; if ((impl = sshkey_impl_from_type(a->type)) == NULL) return 0; return impl->funcs->equal(a, b); } int sshkey_equal(const struct sshkey *a, const struct sshkey *b) { if (a == NULL || b == NULL || a->type != b->type) return 0; if (sshkey_is_cert(a)) { if (!cert_compare(a->cert, b->cert)) return 0; } return sshkey_equal_public(a, b); } /* Serialise common FIDO key parts */ int sshkey_serialize_sk(const struct sshkey *key, struct sshbuf *b) { int r; if ((r = sshbuf_put_cstring(b, key->sk_application)) != 0) return r; return 0; } static int to_blob_buf(const struct sshkey *key, struct sshbuf *b, int force_plain, enum sshkey_serialize_rep opts) { int type, ret = SSH_ERR_INTERNAL_ERROR; const char *typename; const struct sshkey_impl *impl; if (key == NULL) return SSH_ERR_INVALID_ARGUMENT; type = force_plain ? sshkey_type_plain(key->type) : key->type; if (sshkey_type_is_cert(type)) { if (key->cert == NULL) return SSH_ERR_EXPECTED_CERT; if (sshbuf_len(key->cert->certblob) == 0) return SSH_ERR_KEY_LACKS_CERTBLOB; /* Use the existing blob */ if ((ret = sshbuf_putb(b, key->cert->certblob)) != 0) return ret; return 0; } if ((impl = sshkey_impl_from_type(type)) == NULL) return SSH_ERR_KEY_TYPE_UNKNOWN; typename = sshkey_ssh_name_from_type_nid(type, key->ecdsa_nid); if ((ret = sshbuf_put_cstring(b, typename)) != 0) return ret; return impl->funcs->serialize_public(key, b, opts); } int sshkey_putb(const struct sshkey *key, struct sshbuf *b) { return to_blob_buf(key, b, 0, SSHKEY_SERIALIZE_DEFAULT); } int sshkey_puts_opts(const struct sshkey *key, struct sshbuf *b, enum sshkey_serialize_rep opts) { struct sshbuf *tmp; int r; if ((tmp = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; r = to_blob_buf(key, tmp, 0, opts); if (r == 0) r = sshbuf_put_stringb(b, tmp); sshbuf_free(tmp); return r; } int sshkey_puts(const struct sshkey *key, struct sshbuf *b) { return sshkey_puts_opts(key, b, SSHKEY_SERIALIZE_DEFAULT); } int sshkey_putb_plain(const struct sshkey *key, struct sshbuf *b) { return to_blob_buf(key, b, 1, SSHKEY_SERIALIZE_DEFAULT); } static int to_blob(const struct sshkey *key, u_char **blobp, size_t *lenp, int force_plain, enum sshkey_serialize_rep opts) { int ret = SSH_ERR_INTERNAL_ERROR; size_t len; struct sshbuf *b = NULL; if (lenp != NULL) *lenp = 0; if (blobp != NULL) *blobp = NULL; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((ret = to_blob_buf(key, b, force_plain, opts)) != 0) goto out; len = sshbuf_len(b); if (lenp != NULL) *lenp = len; if (blobp != NULL) { if ((*blobp = malloc(len)) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } memcpy(*blobp, sshbuf_ptr(b), len); } ret = 0; out: sshbuf_free(b); return ret; } int sshkey_to_blob(const struct sshkey *key, u_char **blobp, size_t *lenp) { return to_blob(key, blobp, lenp, 0, SSHKEY_SERIALIZE_DEFAULT); } int sshkey_plain_to_blob(const struct sshkey *key, u_char **blobp, size_t *lenp) { return to_blob(key, blobp, lenp, 1, SSHKEY_SERIALIZE_DEFAULT); } int sshkey_fingerprint_raw(const struct sshkey *k, int dgst_alg, u_char **retp, size_t *lenp) { u_char *blob = NULL, *ret = NULL; size_t blob_len = 0; int r = SSH_ERR_INTERNAL_ERROR; if (retp != NULL) *retp = NULL; if (lenp != NULL) *lenp = 0; if (ssh_digest_bytes(dgst_alg) == 0) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if ((r = to_blob(k, &blob, &blob_len, 1, SSHKEY_SERIALIZE_DEFAULT)) != 0) goto out; if ((ret = calloc(1, SSH_DIGEST_MAX_LENGTH)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = ssh_digest_memory(dgst_alg, blob, blob_len, ret, SSH_DIGEST_MAX_LENGTH)) != 0) goto out; /* success */ if (retp != NULL) { *retp = ret; ret = NULL; } if (lenp != NULL) *lenp = ssh_digest_bytes(dgst_alg); r = 0; out: free(ret); if (blob != NULL) freezero(blob, blob_len); return r; } static char * fingerprint_b64(const char *alg, u_char *dgst_raw, size_t dgst_raw_len) { char *ret; size_t plen = strlen(alg) + 1; size_t rlen = ((dgst_raw_len + 2) / 3) * 4 + plen + 1; if (dgst_raw_len > 65536 || (ret = calloc(1, rlen)) == NULL) return NULL; strlcpy(ret, alg, rlen); strlcat(ret, ":", rlen); if (dgst_raw_len == 0) return ret; if (b64_ntop(dgst_raw, dgst_raw_len, ret + plen, rlen - plen) == -1) { freezero(ret, rlen); return NULL; } /* Trim padding characters from end */ ret[strcspn(ret, "=")] = '\0'; return ret; } static char * fingerprint_hex(const char *alg, u_char *dgst_raw, size_t dgst_raw_len) { char *retval, hex[5]; size_t i, rlen = dgst_raw_len * 3 + strlen(alg) + 2; if (dgst_raw_len > 65536 || (retval = calloc(1, rlen)) == NULL) return NULL; strlcpy(retval, alg, rlen); strlcat(retval, ":", rlen); for (i = 0; i < dgst_raw_len; i++) { snprintf(hex, sizeof(hex), "%s%02x", i > 0 ? ":" : "", dgst_raw[i]); strlcat(retval, hex, rlen); } return retval; } static char * fingerprint_bubblebabble(u_char *dgst_raw, size_t dgst_raw_len) { char vowels[] = { 'a', 'e', 'i', 'o', 'u', 'y' }; char consonants[] = { 'b', 'c', 'd', 'f', 'g', 'h', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'z', 'x' }; u_int i, j = 0, rounds, seed = 1; char *retval; rounds = (dgst_raw_len / 2) + 1; if ((retval = calloc(rounds, 6)) == NULL) return NULL; retval[j++] = 'x'; for (i = 0; i < rounds; i++) { u_int idx0, idx1, idx2, idx3, idx4; if ((i + 1 < rounds) || (dgst_raw_len % 2 != 0)) { idx0 = (((((u_int)(dgst_raw[2 * i])) >> 6) & 3) + seed) % 6; idx1 = (((u_int)(dgst_raw[2 * i])) >> 2) & 15; idx2 = ((((u_int)(dgst_raw[2 * i])) & 3) + (seed / 6)) % 6; retval[j++] = vowels[idx0]; retval[j++] = consonants[idx1]; retval[j++] = vowels[idx2]; if ((i + 1) < rounds) { idx3 = (((u_int)(dgst_raw[(2 * i) + 1])) >> 4) & 15; idx4 = (((u_int)(dgst_raw[(2 * i) + 1]))) & 15; retval[j++] = consonants[idx3]; retval[j++] = '-'; retval[j++] = consonants[idx4]; seed = ((seed * 5) + ((((u_int)(dgst_raw[2 * i])) * 7) + ((u_int)(dgst_raw[(2 * i) + 1])))) % 36; } } else { idx0 = seed % 6; idx1 = 16; idx2 = seed / 6; retval[j++] = vowels[idx0]; retval[j++] = consonants[idx1]; retval[j++] = vowels[idx2]; } } retval[j++] = 'x'; retval[j++] = '\0'; return retval; } /* * Draw an ASCII-Art representing the fingerprint so human brain can * profit from its built-in pattern recognition ability. * This technique is called "random art" and can be found in some * scientific publications like this original paper: * * "Hash Visualization: a New Technique to improve Real-World Security", * Perrig A. and Song D., 1999, International Workshop on Cryptographic * Techniques and E-Commerce (CrypTEC '99) * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf * * The subject came up in a talk by Dan Kaminsky, too. * * If you see the picture is different, the key is different. * If the picture looks the same, you still know nothing. * * The algorithm used here is a worm crawling over a discrete plane, * leaving a trace (augmenting the field) everywhere it goes. * Movement is taken from dgst_raw 2bit-wise. Bumping into walls * makes the respective movement vector be ignored for this turn. * Graphs are not unambiguous, because circles in graphs can be * walked in either direction. */ /* * Field sizes for the random art. Have to be odd, so the starting point * can be in the exact middle of the picture, and FLDBASE should be >=8 . * Else pictures would be too dense, and drawing the frame would * fail, too, because the key type would not fit in anymore. */ #define FLDBASE 8 #define FLDSIZE_Y (FLDBASE + 1) #define FLDSIZE_X (FLDBASE * 2 + 1) static char * fingerprint_randomart(const char *alg, u_char *dgst_raw, size_t dgst_raw_len, const struct sshkey *k) { /* * Chars to be used after each other every time the worm * intersects with itself. Matter of taste. */ char *augmentation_string = " .o+=*BOX@%&#/^SE"; char *retval, *p, title[FLDSIZE_X], hash[FLDSIZE_X]; u_char field[FLDSIZE_X][FLDSIZE_Y]; size_t i, tlen, hlen; u_int b; int x, y, r; size_t len = strlen(augmentation_string) - 1; if ((retval = calloc((FLDSIZE_X + 3), (FLDSIZE_Y + 2))) == NULL) return NULL; /* initialize field */ memset(field, 0, FLDSIZE_X * FLDSIZE_Y * sizeof(char)); x = FLDSIZE_X / 2; y = FLDSIZE_Y / 2; /* process raw key */ for (i = 0; i < dgst_raw_len; i++) { int input; /* each byte conveys four 2-bit move commands */ input = dgst_raw[i]; for (b = 0; b < 4; b++) { /* evaluate 2 bit, rest is shifted later */ x += (input & 0x1) ? 1 : -1; y += (input & 0x2) ? 1 : -1; /* assure we are still in bounds */ x = MAXIMUM(x, 0); y = MAXIMUM(y, 0); x = MINIMUM(x, FLDSIZE_X - 1); y = MINIMUM(y, FLDSIZE_Y - 1); /* augment the field */ if (field[x][y] < len - 2) field[x][y]++; input = input >> 2; } } /* mark starting point and end point*/ field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = len - 1; field[x][y] = len; /* assemble title */ r = snprintf(title, sizeof(title), "[%s %u]", sshkey_type(k), sshkey_size(k)); /* If [type size] won't fit, then try [type]; fits "[ED25519-CERT]" */ if (r < 0 || r > (int)sizeof(title)) r = snprintf(title, sizeof(title), "[%s]", sshkey_type(k)); tlen = (r <= 0) ? 0 : strlen(title); /* assemble hash ID. */ r = snprintf(hash, sizeof(hash), "[%s]", alg); hlen = (r <= 0) ? 0 : strlen(hash); /* output upper border */ p = retval; *p++ = '+'; for (i = 0; i < (FLDSIZE_X - tlen) / 2; i++) *p++ = '-'; memcpy(p, title, tlen); p += tlen; for (i += tlen; i < FLDSIZE_X; i++) *p++ = '-'; *p++ = '+'; *p++ = '\n'; /* output content */ for (y = 0; y < FLDSIZE_Y; y++) { *p++ = '|'; for (x = 0; x < FLDSIZE_X; x++) *p++ = augmentation_string[MINIMUM(field[x][y], len)]; *p++ = '|'; *p++ = '\n'; } /* output lower border */ *p++ = '+'; for (i = 0; i < (FLDSIZE_X - hlen) / 2; i++) *p++ = '-'; memcpy(p, hash, hlen); p += hlen; for (i += hlen; i < FLDSIZE_X; i++) *p++ = '-'; *p++ = '+'; return retval; } char * sshkey_fingerprint(const struct sshkey *k, int dgst_alg, enum sshkey_fp_rep dgst_rep) { char *retval = NULL; u_char *dgst_raw; size_t dgst_raw_len; if (sshkey_fingerprint_raw(k, dgst_alg, &dgst_raw, &dgst_raw_len) != 0) return NULL; switch (dgst_rep) { case SSH_FP_DEFAULT: if (dgst_alg == SSH_DIGEST_MD5) { retval = fingerprint_hex(ssh_digest_alg_name(dgst_alg), dgst_raw, dgst_raw_len); } else { retval = fingerprint_b64(ssh_digest_alg_name(dgst_alg), dgst_raw, dgst_raw_len); } break; case SSH_FP_HEX: retval = fingerprint_hex(ssh_digest_alg_name(dgst_alg), dgst_raw, dgst_raw_len); break; case SSH_FP_BASE64: retval = fingerprint_b64(ssh_digest_alg_name(dgst_alg), dgst_raw, dgst_raw_len); break; case SSH_FP_BUBBLEBABBLE: retval = fingerprint_bubblebabble(dgst_raw, dgst_raw_len); break; case SSH_FP_RANDOMART: retval = fingerprint_randomart(ssh_digest_alg_name(dgst_alg), dgst_raw, dgst_raw_len, k); break; default: freezero(dgst_raw, dgst_raw_len); return NULL; } freezero(dgst_raw, dgst_raw_len); return retval; } static int peek_type_nid(const char *s, size_t l, int *nid) { const struct sshkey_impl *impl; int i; for (i = 0; keyimpls[i] != NULL; i++) { impl = keyimpls[i]; if (impl->name == NULL || strlen(impl->name) != l) continue; if (memcmp(s, impl->name, l) == 0) { *nid = -1; if (key_type_is_ecdsa_variant(impl->type)) *nid = impl->nid; return impl->type; } } return KEY_UNSPEC; } /* XXX this can now be made const char * */ int sshkey_read(struct sshkey *ret, char **cpp) { struct sshkey *k; char *cp, *blobcopy; size_t space; int r, type, curve_nid = -1; struct sshbuf *blob; if (ret == NULL) return SSH_ERR_INVALID_ARGUMENT; if (ret->type != KEY_UNSPEC && sshkey_impl_from_type(ret->type) == NULL) return SSH_ERR_INVALID_ARGUMENT; /* Decode type */ cp = *cpp; space = strcspn(cp, " \t"); if (space == strlen(cp)) return SSH_ERR_INVALID_FORMAT; if ((type = peek_type_nid(cp, space, &curve_nid)) == KEY_UNSPEC) return SSH_ERR_INVALID_FORMAT; /* skip whitespace */ for (cp += space; *cp == ' ' || *cp == '\t'; cp++) ; if (*cp == '\0') return SSH_ERR_INVALID_FORMAT; if (ret->type != KEY_UNSPEC && ret->type != type) return SSH_ERR_KEY_TYPE_MISMATCH; if ((blob = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; /* find end of keyblob and decode */ space = strcspn(cp, " \t"); if ((blobcopy = strndup(cp, space)) == NULL) { sshbuf_free(blob); return SSH_ERR_ALLOC_FAIL; } if ((r = sshbuf_b64tod(blob, blobcopy)) != 0) { free(blobcopy); sshbuf_free(blob); return r; } free(blobcopy); if ((r = sshkey_fromb(blob, &k)) != 0) { sshbuf_free(blob); return r; } sshbuf_free(blob); /* skip whitespace and leave cp at start of comment */ for (cp += space; *cp == ' ' || *cp == '\t'; cp++) ; /* ensure type of blob matches type at start of line */ if (k->type != type) { sshkey_free(k); return SSH_ERR_KEY_TYPE_MISMATCH; } if (key_type_is_ecdsa_variant(type) && curve_nid != k->ecdsa_nid) { sshkey_free(k); return SSH_ERR_EC_CURVE_MISMATCH; } /* Fill in ret from parsed key */ sshkey_free_contents(ret); *ret = *k; freezero(k, sizeof(*k)); /* success */ *cpp = cp; return 0; } int sshkey_to_base64(const struct sshkey *key, char **b64p) { int r = SSH_ERR_INTERNAL_ERROR; struct sshbuf *b = NULL; char *uu = NULL; if (b64p != NULL) *b64p = NULL; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshkey_putb(key, b)) != 0) goto out; if ((uu = sshbuf_dtob64_string(b, 0)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } /* Success */ if (b64p != NULL) { *b64p = uu; uu = NULL; } r = 0; out: sshbuf_free(b); free(uu); return r; } int sshkey_format_text(const struct sshkey *key, struct sshbuf *b) { int r = SSH_ERR_INTERNAL_ERROR; char *uu = NULL; if ((r = sshkey_to_base64(key, &uu)) != 0) goto out; if ((r = sshbuf_putf(b, "%s %s", sshkey_ssh_name(key), uu)) != 0) goto out; r = 0; out: free(uu); return r; } int sshkey_write(const struct sshkey *key, FILE *f) { struct sshbuf *b = NULL; int r = SSH_ERR_INTERNAL_ERROR; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshkey_format_text(key, b)) != 0) goto out; if (fwrite(sshbuf_ptr(b), sshbuf_len(b), 1, f) != 1) { if (feof(f)) errno = EPIPE; r = SSH_ERR_SYSTEM_ERROR; goto out; } /* Success */ r = 0; out: sshbuf_free(b); return r; } const char * sshkey_cert_type(const struct sshkey *k) { switch (k->cert->type) { case SSH2_CERT_TYPE_USER: return "user"; case SSH2_CERT_TYPE_HOST: return "host"; default: return "unknown"; } } int sshkey_check_rsa_length(const struct sshkey *k, int min_size) { #ifdef WITH_OPENSSL const BIGNUM *rsa_n; int nbits; if (k == NULL || k->rsa == NULL || (k->type != KEY_RSA && k->type != KEY_RSA_CERT)) return 0; RSA_get0_key(k->rsa, &rsa_n, NULL, NULL); nbits = BN_num_bits(rsa_n); if (nbits < SSH_RSA_MINIMUM_MODULUS_SIZE || (min_size > 0 && nbits < min_size)) return SSH_ERR_KEY_LENGTH; #endif /* WITH_OPENSSL */ return 0; } #ifdef WITH_OPENSSL # ifdef OPENSSL_HAS_ECC int sshkey_ecdsa_key_to_nid(EC_KEY *k) { EC_GROUP *eg; int nids[] = { NID_X9_62_prime256v1, NID_secp384r1, # ifdef OPENSSL_HAS_NISTP521 NID_secp521r1, # endif /* OPENSSL_HAS_NISTP521 */ -1 }; int nid; u_int i; const EC_GROUP *g = EC_KEY_get0_group(k); /* * The group may be stored in a ASN.1 encoded private key in one of two * ways: as a "named group", which is reconstituted by ASN.1 object ID * or explicit group parameters encoded into the key blob. Only the * "named group" case sets the group NID for us, but we can figure * it out for the other case by comparing against all the groups that * are supported. */ if ((nid = EC_GROUP_get_curve_name(g)) > 0) return nid; for (i = 0; nids[i] != -1; i++) { if ((eg = EC_GROUP_new_by_curve_name(nids[i])) == NULL) return -1; if (EC_GROUP_cmp(g, eg, NULL) == 0) break; EC_GROUP_free(eg); } if (nids[i] != -1) { /* Use the group with the NID attached */ EC_GROUP_set_asn1_flag(eg, OPENSSL_EC_NAMED_CURVE); if (EC_KEY_set_group(k, eg) != 1) { EC_GROUP_free(eg); return -1; } } return nids[i]; } # endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ int sshkey_generate(int type, u_int bits, struct sshkey **keyp) { struct sshkey *k; int ret = SSH_ERR_INTERNAL_ERROR; const struct sshkey_impl *impl; if (keyp == NULL || sshkey_type_is_cert(type)) return SSH_ERR_INVALID_ARGUMENT; *keyp = NULL; if ((impl = sshkey_impl_from_type(type)) == NULL) return SSH_ERR_KEY_TYPE_UNKNOWN; if (impl->funcs->generate == NULL) return SSH_ERR_FEATURE_UNSUPPORTED; if ((k = sshkey_new(KEY_UNSPEC)) == NULL) return SSH_ERR_ALLOC_FAIL; k->type = type; if ((ret = impl->funcs->generate(k, bits)) != 0) { sshkey_free(k); return ret; } /* success */ *keyp = k; return 0; } int sshkey_cert_copy(const struct sshkey *from_key, struct sshkey *to_key) { u_int i; const struct sshkey_cert *from; struct sshkey_cert *to; int r = SSH_ERR_INTERNAL_ERROR; if (to_key == NULL || (from = from_key->cert) == NULL) return SSH_ERR_INVALID_ARGUMENT; if ((to = cert_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_putb(to->certblob, from->certblob)) != 0 || (r = sshbuf_putb(to->critical, from->critical)) != 0 || (r = sshbuf_putb(to->extensions, from->extensions)) != 0) goto out; to->serial = from->serial; to->type = from->type; if (from->key_id == NULL) to->key_id = NULL; else if ((to->key_id = strdup(from->key_id)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } to->valid_after = from->valid_after; to->valid_before = from->valid_before; if (from->signature_key == NULL) to->signature_key = NULL; else if ((r = sshkey_from_private(from->signature_key, &to->signature_key)) != 0) goto out; if (from->signature_type != NULL && (to->signature_type = strdup(from->signature_type)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if (from->nprincipals > SSHKEY_CERT_MAX_PRINCIPALS) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if (from->nprincipals > 0) { if ((to->principals = calloc(from->nprincipals, sizeof(*to->principals))) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } for (i = 0; i < from->nprincipals; i++) { to->principals[i] = strdup(from->principals[i]); if (to->principals[i] == NULL) { to->nprincipals = i; r = SSH_ERR_ALLOC_FAIL; goto out; } } } to->nprincipals = from->nprincipals; /* success */ cert_free(to_key->cert); to_key->cert = to; to = NULL; r = 0; out: cert_free(to); return r; } int sshkey_copy_public_sk(const struct sshkey *from, struct sshkey *to) { /* Append security-key application string */ if ((to->sk_application = strdup(from->sk_application)) == NULL) return SSH_ERR_ALLOC_FAIL; return 0; } int sshkey_from_private(const struct sshkey *k, struct sshkey **pkp) { struct sshkey *n = NULL; int r = SSH_ERR_INTERNAL_ERROR; const struct sshkey_impl *impl; *pkp = NULL; if ((impl = sshkey_impl_from_key(k)) == NULL) return SSH_ERR_KEY_TYPE_UNKNOWN; if ((n = sshkey_new(k->type)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = impl->funcs->copy_public(k, n)) != 0) goto out; if (sshkey_is_cert(k) && (r = sshkey_cert_copy(k, n)) != 0) goto out; /* success */ *pkp = n; n = NULL; r = 0; out: sshkey_free(n); return r; } int sshkey_is_shielded(struct sshkey *k) { return k != NULL && k->shielded_private != NULL; } int sshkey_shield_private(struct sshkey *k) { struct sshbuf *prvbuf = NULL; u_char *prekey = NULL, *enc = NULL, keyiv[SSH_DIGEST_MAX_LENGTH]; struct sshcipher_ctx *cctx = NULL; const struct sshcipher *cipher; size_t i, enclen = 0; struct sshkey *kswap = NULL, tmp; int r = SSH_ERR_INTERNAL_ERROR; #ifdef DEBUG_PK fprintf(stderr, "%s: entering for %s\n", __func__, sshkey_ssh_name(k)); #endif if ((cipher = cipher_by_name(SSHKEY_SHIELD_CIPHER)) == NULL) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if (cipher_keylen(cipher) + cipher_ivlen(cipher) > ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH)) { r = SSH_ERR_INTERNAL_ERROR; goto out; } /* Prepare a random pre-key, and from it an ephemeral key */ if ((prekey = malloc(SSHKEY_SHIELD_PREKEY_LEN)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } arc4random_buf(prekey, SSHKEY_SHIELD_PREKEY_LEN); if ((r = ssh_digest_memory(SSHKEY_SHIELD_PREKEY_HASH, prekey, SSHKEY_SHIELD_PREKEY_LEN, keyiv, SSH_DIGEST_MAX_LENGTH)) != 0) goto out; #ifdef DEBUG_PK fprintf(stderr, "%s: key+iv\n", __func__); sshbuf_dump_data(keyiv, ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH), stderr); #endif if ((r = cipher_init(&cctx, cipher, keyiv, cipher_keylen(cipher), keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 1)) != 0) goto out; /* Serialise and encrypt the private key using the ephemeral key */ if ((prvbuf = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if (sshkey_is_shielded(k) && (r = sshkey_unshield_private(k)) != 0) goto out; if ((r = sshkey_private_serialize_opt(k, prvbuf, SSHKEY_SERIALIZE_SHIELD)) != 0) goto out; /* pad to cipher blocksize */ i = 0; while (sshbuf_len(prvbuf) % cipher_blocksize(cipher)) { if ((r = sshbuf_put_u8(prvbuf, ++i & 0xff)) != 0) goto out; } #ifdef DEBUG_PK fprintf(stderr, "%s: serialised\n", __func__); sshbuf_dump(prvbuf, stderr); #endif /* encrypt */ enclen = sshbuf_len(prvbuf); if ((enc = malloc(enclen)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = cipher_crypt(cctx, 0, enc, sshbuf_ptr(prvbuf), sshbuf_len(prvbuf), 0, 0)) != 0) goto out; #ifdef DEBUG_PK fprintf(stderr, "%s: encrypted\n", __func__); sshbuf_dump_data(enc, enclen, stderr); #endif /* Make a scrubbed, public-only copy of our private key argument */ if ((r = sshkey_from_private(k, &kswap)) != 0) goto out; /* Swap the private key out (it will be destroyed below) */ tmp = *kswap; *kswap = *k; *k = tmp; /* Insert the shielded key into our argument */ k->shielded_private = enc; k->shielded_len = enclen; k->shield_prekey = prekey; k->shield_prekey_len = SSHKEY_SHIELD_PREKEY_LEN; enc = prekey = NULL; /* transferred */ enclen = 0; /* preserve key fields that are required for correct operation */ k->sk_flags = kswap->sk_flags; /* success */ r = 0; out: /* XXX behaviour on error - invalidate original private key? */ cipher_free(cctx); explicit_bzero(keyiv, sizeof(keyiv)); explicit_bzero(&tmp, sizeof(tmp)); freezero(enc, enclen); freezero(prekey, SSHKEY_SHIELD_PREKEY_LEN); sshkey_free(kswap); sshbuf_free(prvbuf); return r; } /* Check deterministic padding after private key */ static int private2_check_padding(struct sshbuf *decrypted) { u_char pad; size_t i; int r; i = 0; while (sshbuf_len(decrypted)) { if ((r = sshbuf_get_u8(decrypted, &pad)) != 0) goto out; if (pad != (++i & 0xff)) { r = SSH_ERR_INVALID_FORMAT; goto out; } } /* success */ r = 0; out: explicit_bzero(&pad, sizeof(pad)); explicit_bzero(&i, sizeof(i)); return r; } int sshkey_unshield_private(struct sshkey *k) { struct sshbuf *prvbuf = NULL; u_char *cp, keyiv[SSH_DIGEST_MAX_LENGTH]; struct sshcipher_ctx *cctx = NULL; const struct sshcipher *cipher; struct sshkey *kswap = NULL, tmp; int r = SSH_ERR_INTERNAL_ERROR; #ifdef DEBUG_PK fprintf(stderr, "%s: entering for %s\n", __func__, sshkey_ssh_name(k)); #endif if (!sshkey_is_shielded(k)) return 0; /* nothing to do */ if ((cipher = cipher_by_name(SSHKEY_SHIELD_CIPHER)) == NULL) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if (cipher_keylen(cipher) + cipher_ivlen(cipher) > ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH)) { r = SSH_ERR_INTERNAL_ERROR; goto out; } /* check size of shielded key blob */ if (k->shielded_len < cipher_blocksize(cipher) || (k->shielded_len % cipher_blocksize(cipher)) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* Calculate the ephemeral key from the prekey */ if ((r = ssh_digest_memory(SSHKEY_SHIELD_PREKEY_HASH, k->shield_prekey, k->shield_prekey_len, keyiv, SSH_DIGEST_MAX_LENGTH)) != 0) goto out; if ((r = cipher_init(&cctx, cipher, keyiv, cipher_keylen(cipher), keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 0)) != 0) goto out; #ifdef DEBUG_PK fprintf(stderr, "%s: key+iv\n", __func__); sshbuf_dump_data(keyiv, ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH), stderr); #endif /* Decrypt and parse the shielded private key using the ephemeral key */ if ((prvbuf = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_reserve(prvbuf, k->shielded_len, &cp)) != 0) goto out; /* decrypt */ #ifdef DEBUG_PK fprintf(stderr, "%s: encrypted\n", __func__); sshbuf_dump_data(k->shielded_private, k->shielded_len, stderr); #endif if ((r = cipher_crypt(cctx, 0, cp, k->shielded_private, k->shielded_len, 0, 0)) != 0) goto out; #ifdef DEBUG_PK fprintf(stderr, "%s: serialised\n", __func__); sshbuf_dump(prvbuf, stderr); #endif /* Parse private key */ if ((r = sshkey_private_deserialize(prvbuf, &kswap)) != 0) goto out; if ((r = private2_check_padding(prvbuf)) != 0) goto out; /* Swap the parsed key back into place */ tmp = *kswap; *kswap = *k; *k = tmp; /* success */ r = 0; out: cipher_free(cctx); explicit_bzero(keyiv, sizeof(keyiv)); explicit_bzero(&tmp, sizeof(tmp)); sshkey_free(kswap); sshbuf_free(prvbuf); return r; } static int cert_parse(struct sshbuf *b, struct sshkey *key, struct sshbuf *certbuf) { struct sshbuf *principals = NULL, *crit = NULL; struct sshbuf *exts = NULL, *ca = NULL; u_char *sig = NULL; size_t signed_len = 0, slen = 0, kidlen = 0; int ret = SSH_ERR_INTERNAL_ERROR; /* Copy the entire key blob for verification and later serialisation */ if ((ret = sshbuf_putb(key->cert->certblob, certbuf)) != 0) return ret; /* Parse body of certificate up to signature */ if ((ret = sshbuf_get_u64(b, &key->cert->serial)) != 0 || (ret = sshbuf_get_u32(b, &key->cert->type)) != 0 || (ret = sshbuf_get_cstring(b, &key->cert->key_id, &kidlen)) != 0 || (ret = sshbuf_froms(b, &principals)) != 0 || (ret = sshbuf_get_u64(b, &key->cert->valid_after)) != 0 || (ret = sshbuf_get_u64(b, &key->cert->valid_before)) != 0 || (ret = sshbuf_froms(b, &crit)) != 0 || (ret = sshbuf_froms(b, &exts)) != 0 || (ret = sshbuf_get_string_direct(b, NULL, NULL)) != 0 || (ret = sshbuf_froms(b, &ca)) != 0) { /* XXX debug print error for ret */ ret = SSH_ERR_INVALID_FORMAT; goto out; } /* Signature is left in the buffer so we can calculate this length */ signed_len = sshbuf_len(key->cert->certblob) - sshbuf_len(b); if ((ret = sshbuf_get_string(b, &sig, &slen)) != 0) { ret = SSH_ERR_INVALID_FORMAT; goto out; } if (key->cert->type != SSH2_CERT_TYPE_USER && key->cert->type != SSH2_CERT_TYPE_HOST) { ret = SSH_ERR_KEY_CERT_UNKNOWN_TYPE; goto out; } /* Parse principals section */ while (sshbuf_len(principals) > 0) { char *principal = NULL; char **oprincipals = NULL; if (key->cert->nprincipals >= SSHKEY_CERT_MAX_PRINCIPALS) { ret = SSH_ERR_INVALID_FORMAT; goto out; } if ((ret = sshbuf_get_cstring(principals, &principal, NULL)) != 0) { ret = SSH_ERR_INVALID_FORMAT; goto out; } oprincipals = key->cert->principals; key->cert->principals = recallocarray(key->cert->principals, key->cert->nprincipals, key->cert->nprincipals + 1, sizeof(*key->cert->principals)); if (key->cert->principals == NULL) { free(principal); key->cert->principals = oprincipals; ret = SSH_ERR_ALLOC_FAIL; goto out; } key->cert->principals[key->cert->nprincipals++] = principal; } /* * Stash a copies of the critical options and extensions sections * for later use. */ if ((ret = sshbuf_putb(key->cert->critical, crit)) != 0 || (exts != NULL && (ret = sshbuf_putb(key->cert->extensions, exts)) != 0)) goto out; /* * Validate critical options and extensions sections format. */ while (sshbuf_len(crit) != 0) { if ((ret = sshbuf_get_string_direct(crit, NULL, NULL)) != 0 || (ret = sshbuf_get_string_direct(crit, NULL, NULL)) != 0) { sshbuf_reset(key->cert->critical); ret = SSH_ERR_INVALID_FORMAT; goto out; } } while (exts != NULL && sshbuf_len(exts) != 0) { if ((ret = sshbuf_get_string_direct(exts, NULL, NULL)) != 0 || (ret = sshbuf_get_string_direct(exts, NULL, NULL)) != 0) { sshbuf_reset(key->cert->extensions); ret = SSH_ERR_INVALID_FORMAT; goto out; } } /* Parse CA key and check signature */ if (sshkey_from_blob_internal(ca, &key->cert->signature_key, 0) != 0) { ret = SSH_ERR_KEY_CERT_INVALID_SIGN_KEY; goto out; } if (!sshkey_type_is_valid_ca(key->cert->signature_key->type)) { ret = SSH_ERR_KEY_CERT_INVALID_SIGN_KEY; goto out; } if ((ret = sshkey_verify(key->cert->signature_key, sig, slen, sshbuf_ptr(key->cert->certblob), signed_len, NULL, 0, NULL)) != 0) goto out; if ((ret = sshkey_get_sigtype(sig, slen, &key->cert->signature_type)) != 0) goto out; /* Success */ ret = 0; out: sshbuf_free(ca); sshbuf_free(crit); sshbuf_free(exts); sshbuf_free(principals); free(sig); return ret; } int sshkey_deserialize_sk(struct sshbuf *b, struct sshkey *key) { /* Parse additional security-key application string */ if (sshbuf_get_cstring(b, &key->sk_application, NULL) != 0) return SSH_ERR_INVALID_FORMAT; return 0; } static int sshkey_from_blob_internal(struct sshbuf *b, struct sshkey **keyp, int allow_cert) { int type, ret = SSH_ERR_INTERNAL_ERROR; char *ktype = NULL; struct sshkey *key = NULL; struct sshbuf *copy; const struct sshkey_impl *impl; #ifdef DEBUG_PK /* XXX */ sshbuf_dump(b, stderr); #endif if (keyp != NULL) *keyp = NULL; if ((copy = sshbuf_fromb(b)) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } if (sshbuf_get_cstring(b, &ktype, NULL) != 0) { ret = SSH_ERR_INVALID_FORMAT; goto out; } type = sshkey_type_from_name(ktype); if (!allow_cert && sshkey_type_is_cert(type)) { ret = SSH_ERR_KEY_CERT_INVALID_SIGN_KEY; goto out; } if ((impl = sshkey_impl_from_type(type)) == NULL) { ret = SSH_ERR_KEY_TYPE_UNKNOWN; goto out; } if ((key = sshkey_new(type)) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } if (sshkey_type_is_cert(type)) { /* Skip nonce that preceeds all certificates */ if (sshbuf_get_string_direct(b, NULL, NULL) != 0) { ret = SSH_ERR_INVALID_FORMAT; goto out; } } if ((ret = impl->funcs->deserialize_public(ktype, b, key)) != 0) goto out; /* Parse certificate potion */ if (sshkey_is_cert(key) && (ret = cert_parse(b, key, copy)) != 0) goto out; if (key != NULL && sshbuf_len(b) != 0) { ret = SSH_ERR_INVALID_FORMAT; goto out; } ret = 0; if (keyp != NULL) { *keyp = key; key = NULL; } out: sshbuf_free(copy); sshkey_free(key); free(ktype); return ret; } int sshkey_from_blob(const u_char *blob, size_t blen, struct sshkey **keyp) { struct sshbuf *b; int r; if ((b = sshbuf_from(blob, blen)) == NULL) return SSH_ERR_ALLOC_FAIL; r = sshkey_from_blob_internal(b, keyp, 1); sshbuf_free(b); return r; } int sshkey_fromb(struct sshbuf *b, struct sshkey **keyp) { return sshkey_from_blob_internal(b, keyp, 1); } int sshkey_froms(struct sshbuf *buf, struct sshkey **keyp) { struct sshbuf *b; int r; if ((r = sshbuf_froms(buf, &b)) != 0) return r; r = sshkey_from_blob_internal(b, keyp, 1); sshbuf_free(b); return r; } int sshkey_get_sigtype(const u_char *sig, size_t siglen, char **sigtypep) { int r; struct sshbuf *b = NULL; char *sigtype = NULL; if (sigtypep != NULL) *sigtypep = NULL; if ((b = sshbuf_from(sig, siglen)) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_cstring(b, &sigtype, NULL)) != 0) goto out; /* success */ if (sigtypep != NULL) { *sigtypep = sigtype; sigtype = NULL; } r = 0; out: free(sigtype); sshbuf_free(b); return r; } /* * * Checks whether a certificate's signature type is allowed. * Returns 0 (success) if the certificate signature type appears in the * "allowed" pattern-list, or the key is not a certificate to begin with. * Otherwise returns a ssherr.h code. */ int sshkey_check_cert_sigtype(const struct sshkey *key, const char *allowed) { if (key == NULL || allowed == NULL) return SSH_ERR_INVALID_ARGUMENT; if (!sshkey_type_is_cert(key->type)) return 0; if (key->cert == NULL || key->cert->signature_type == NULL) return SSH_ERR_INVALID_ARGUMENT; if (match_pattern_list(key->cert->signature_type, allowed, 0) != 1) return SSH_ERR_SIGN_ALG_UNSUPPORTED; return 0; } /* * Returns the expected signature algorithm for a given public key algorithm. */ const char * sshkey_sigalg_by_name(const char *name) { const struct sshkey_impl *impl; int i; for (i = 0; keyimpls[i] != NULL; i++) { impl = keyimpls[i]; if (strcmp(impl->name, name) != 0) continue; if (impl->sigalg != NULL) return impl->sigalg; if (!impl->cert) return impl->name; return sshkey_ssh_name_from_type_nid( sshkey_type_plain(impl->type), impl->nid); } return NULL; } /* * Verifies that the signature algorithm appearing inside the signature blob * matches that which was requested. */ int sshkey_check_sigtype(const u_char *sig, size_t siglen, const char *requested_alg) { const char *expected_alg; char *sigtype = NULL; int r; if (requested_alg == NULL) return 0; if ((expected_alg = sshkey_sigalg_by_name(requested_alg)) == NULL) return SSH_ERR_INVALID_ARGUMENT; if ((r = sshkey_get_sigtype(sig, siglen, &sigtype)) != 0) return r; r = strcmp(expected_alg, sigtype) == 0; free(sigtype); return r ? 0 : SSH_ERR_SIGN_ALG_UNSUPPORTED; } int sshkey_sign(struct sshkey *key, u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) { int was_shielded = sshkey_is_shielded(key); int r2, r = SSH_ERR_INTERNAL_ERROR; const struct sshkey_impl *impl; if (sigp != NULL) *sigp = NULL; if (lenp != NULL) *lenp = 0; if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE) return SSH_ERR_INVALID_ARGUMENT; if ((impl = sshkey_impl_from_key(key)) == NULL) return SSH_ERR_KEY_TYPE_UNKNOWN; if ((r = sshkey_unshield_private(key)) != 0) return r; if (sshkey_is_sk(key)) { r = sshsk_sign(sk_provider, key, sigp, lenp, data, datalen, compat, sk_pin); } else { if (impl->funcs->sign == NULL) r = SSH_ERR_SIGN_ALG_UNSUPPORTED; else { r = impl->funcs->sign(key, sigp, lenp, data, datalen, alg, sk_provider, sk_pin, compat); } } if (was_shielded && (r2 = sshkey_shield_private(key)) != 0) return r2; return r; } /* * ssh_key_verify returns 0 for a correct signature and < 0 on error. * If "alg" specified, then the signature must use that algorithm. */ int sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen, const u_char *data, size_t dlen, const char *alg, u_int compat, struct sshkey_sig_details **detailsp) { const struct sshkey_impl *impl; if (detailsp != NULL) *detailsp = NULL; if (siglen == 0 || dlen > SSH_KEY_MAX_SIGN_DATA_SIZE) return SSH_ERR_INVALID_ARGUMENT; if ((impl = sshkey_impl_from_key(key)) == NULL) return SSH_ERR_KEY_TYPE_UNKNOWN; return impl->funcs->verify(key, sig, siglen, data, dlen, alg, compat, detailsp); } /* Convert a plain key to their _CERT equivalent */ int sshkey_to_certified(struct sshkey *k) { int newtype; if ((newtype = sshkey_type_certified(k->type)) == -1) return SSH_ERR_INVALID_ARGUMENT; if ((k->cert = cert_new()) == NULL) return SSH_ERR_ALLOC_FAIL; k->type = newtype; return 0; } /* Convert a certificate to its raw key equivalent */ int sshkey_drop_cert(struct sshkey *k) { if (!sshkey_type_is_cert(k->type)) return SSH_ERR_KEY_TYPE_UNKNOWN; cert_free(k->cert); k->cert = NULL; k->type = sshkey_type_plain(k->type); return 0; } /* Sign a certified key, (re-)generating the signed certblob. */ int sshkey_certify_custom(struct sshkey *k, struct sshkey *ca, const char *alg, const char *sk_provider, const char *sk_pin, sshkey_certify_signer *signer, void *signer_ctx) { const struct sshkey_impl *impl; struct sshbuf *principals = NULL; u_char *ca_blob = NULL, *sig_blob = NULL, nonce[32]; size_t i, ca_len, sig_len; int ret = SSH_ERR_INTERNAL_ERROR; struct sshbuf *cert = NULL; char *sigtype = NULL; if (k == NULL || k->cert == NULL || k->cert->certblob == NULL || ca == NULL) return SSH_ERR_INVALID_ARGUMENT; if (!sshkey_is_cert(k)) return SSH_ERR_KEY_TYPE_UNKNOWN; if (!sshkey_type_is_valid_ca(ca->type)) return SSH_ERR_KEY_CERT_INVALID_SIGN_KEY; if ((impl = sshkey_impl_from_key(k)) == NULL) return SSH_ERR_INTERNAL_ERROR; /* * If no alg specified as argument but a signature_type was set, * then prefer that. If both were specified, then they must match. */ if (alg == NULL) alg = k->cert->signature_type; else if (k->cert->signature_type != NULL && strcmp(alg, k->cert->signature_type) != 0) return SSH_ERR_INVALID_ARGUMENT; /* * If no signing algorithm or signature_type was specified and we're * using a RSA key, then default to a good signature algorithm. */ if (alg == NULL && ca->type == KEY_RSA) alg = "rsa-sha2-512"; if ((ret = sshkey_to_blob(ca, &ca_blob, &ca_len)) != 0) return SSH_ERR_KEY_CERT_INVALID_SIGN_KEY; cert = k->cert->certblob; /* for readability */ sshbuf_reset(cert); if ((ret = sshbuf_put_cstring(cert, sshkey_ssh_name(k))) != 0) goto out; /* -v01 certs put nonce first */ arc4random_buf(&nonce, sizeof(nonce)); if ((ret = sshbuf_put_string(cert, nonce, sizeof(nonce))) != 0) goto out; /* Public key next */ if ((ret = impl->funcs->serialize_public(k, cert, SSHKEY_SERIALIZE_DEFAULT)) != 0) goto out; /* Then remaining cert fields */ if ((ret = sshbuf_put_u64(cert, k->cert->serial)) != 0 || (ret = sshbuf_put_u32(cert, k->cert->type)) != 0 || (ret = sshbuf_put_cstring(cert, k->cert->key_id)) != 0) goto out; if ((principals = sshbuf_new()) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } for (i = 0; i < k->cert->nprincipals; i++) { if ((ret = sshbuf_put_cstring(principals, k->cert->principals[i])) != 0) goto out; } if ((ret = sshbuf_put_stringb(cert, principals)) != 0 || (ret = sshbuf_put_u64(cert, k->cert->valid_after)) != 0 || (ret = sshbuf_put_u64(cert, k->cert->valid_before)) != 0 || (ret = sshbuf_put_stringb(cert, k->cert->critical)) != 0 || (ret = sshbuf_put_stringb(cert, k->cert->extensions)) != 0 || (ret = sshbuf_put_string(cert, NULL, 0)) != 0 || /* Reserved */ (ret = sshbuf_put_string(cert, ca_blob, ca_len)) != 0) goto out; /* Sign the whole mess */ if ((ret = signer(ca, &sig_blob, &sig_len, sshbuf_ptr(cert), sshbuf_len(cert), alg, sk_provider, sk_pin, 0, signer_ctx)) != 0) goto out; /* Check and update signature_type against what was actually used */ if ((ret = sshkey_get_sigtype(sig_blob, sig_len, &sigtype)) != 0) goto out; if (alg != NULL && strcmp(alg, sigtype) != 0) { ret = SSH_ERR_SIGN_ALG_UNSUPPORTED; goto out; } if (k->cert->signature_type == NULL) { k->cert->signature_type = sigtype; sigtype = NULL; } /* Append signature and we are done */ if ((ret = sshbuf_put_string(cert, sig_blob, sig_len)) != 0) goto out; ret = 0; out: if (ret != 0) sshbuf_reset(cert); free(sig_blob); free(ca_blob); free(sigtype); sshbuf_free(principals); return ret; } static int default_key_sign(struct sshkey *key, u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, const char *alg, const char *sk_provider, const char *sk_pin, u_int compat, void *ctx) { if (ctx != NULL) return SSH_ERR_INVALID_ARGUMENT; return sshkey_sign(key, sigp, lenp, data, datalen, alg, sk_provider, sk_pin, compat); } int sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg, const char *sk_provider, const char *sk_pin) { return sshkey_certify_custom(k, ca, alg, sk_provider, sk_pin, default_key_sign, NULL); } int sshkey_cert_check_authority(const struct sshkey *k, int want_host, int require_principal, int wildcard_pattern, uint64_t verify_time, const char *name, const char **reason) { u_int i, principal_matches; if (reason == NULL) return SSH_ERR_INVALID_ARGUMENT; if (!sshkey_is_cert(k)) { *reason = "Key is not a certificate"; return SSH_ERR_KEY_CERT_INVALID; } if (want_host) { if (k->cert->type != SSH2_CERT_TYPE_HOST) { *reason = "Certificate invalid: not a host certificate"; return SSH_ERR_KEY_CERT_INVALID; } } else { if (k->cert->type != SSH2_CERT_TYPE_USER) { *reason = "Certificate invalid: not a user certificate"; return SSH_ERR_KEY_CERT_INVALID; } } if (verify_time < k->cert->valid_after) { *reason = "Certificate invalid: not yet valid"; return SSH_ERR_KEY_CERT_INVALID; } if (verify_time >= k->cert->valid_before) { *reason = "Certificate invalid: expired"; return SSH_ERR_KEY_CERT_INVALID; } if (k->cert->nprincipals == 0) { if (require_principal) { *reason = "Certificate lacks principal list"; return SSH_ERR_KEY_CERT_INVALID; } } else if (name != NULL) { principal_matches = 0; for (i = 0; i < k->cert->nprincipals; i++) { if (wildcard_pattern) { if (match_pattern(k->cert->principals[i], name)) { principal_matches = 1; break; } } else if (strcmp(name, k->cert->principals[i]) == 0) { principal_matches = 1; break; } } if (!principal_matches) { *reason = "Certificate invalid: name is not a listed " "principal"; return SSH_ERR_KEY_CERT_INVALID; } } return 0; } int sshkey_cert_check_authority_now(const struct sshkey *k, int want_host, int require_principal, int wildcard_pattern, const char *name, const char **reason) { time_t now; if ((now = time(NULL)) < 0) { /* yikes - system clock before epoch! */ *reason = "Certificate invalid: not yet valid"; return SSH_ERR_KEY_CERT_INVALID; } return sshkey_cert_check_authority(k, want_host, require_principal, wildcard_pattern, (uint64_t)now, name, reason); } int sshkey_cert_check_host(const struct sshkey *key, const char *host, int wildcard_principals, const char *ca_sign_algorithms, const char **reason) { int r; if ((r = sshkey_cert_check_authority_now(key, 1, 0, wildcard_principals, host, reason)) != 0) return r; if (sshbuf_len(key->cert->critical) != 0) { *reason = "Certificate contains unsupported critical options"; return SSH_ERR_KEY_CERT_INVALID; } if (ca_sign_algorithms != NULL && (r = sshkey_check_cert_sigtype(key, ca_sign_algorithms)) != 0) { *reason = "Certificate signed with disallowed algorithm"; return SSH_ERR_KEY_CERT_INVALID; } return 0; } size_t sshkey_format_cert_validity(const struct sshkey_cert *cert, char *s, size_t l) { char from[32], to[32], ret[128]; *from = *to = '\0'; if (cert->valid_after == 0 && cert->valid_before == 0xffffffffffffffffULL) return strlcpy(s, "forever", l); if (cert->valid_after != 0) format_absolute_time(cert->valid_after, from, sizeof(from)); if (cert->valid_before != 0xffffffffffffffffULL) format_absolute_time(cert->valid_before, to, sizeof(to)); if (cert->valid_after == 0) snprintf(ret, sizeof(ret), "before %s", to); else if (cert->valid_before == 0xffffffffffffffffULL) snprintf(ret, sizeof(ret), "after %s", from); else snprintf(ret, sizeof(ret), "from %s to %s", from, to); return strlcpy(s, ret, l); } /* Common serialization for FIDO private keys */ int sshkey_serialize_private_sk(const struct sshkey *key, struct sshbuf *b) { int r; if ((r = sshbuf_put_cstring(b, key->sk_application)) != 0 || (r = sshbuf_put_u8(b, key->sk_flags)) != 0 || (r = sshbuf_put_stringb(b, key->sk_key_handle)) != 0 || (r = sshbuf_put_stringb(b, key->sk_reserved)) != 0) return r; return 0; } int sshkey_private_serialize_opt(struct sshkey *key, struct sshbuf *buf, enum sshkey_serialize_rep opts) { int r = SSH_ERR_INTERNAL_ERROR; int was_shielded = sshkey_is_shielded(key); struct sshbuf *b = NULL; const struct sshkey_impl *impl; if ((impl = sshkey_impl_from_key(key)) == NULL) return SSH_ERR_INTERNAL_ERROR; if ((r = sshkey_unshield_private(key)) != 0) return r; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_put_cstring(b, sshkey_ssh_name(key))) != 0) goto out; if (sshkey_is_cert(key)) { if (key->cert == NULL || sshbuf_len(key->cert->certblob) == 0) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if ((r = sshbuf_put_stringb(b, key->cert->certblob)) != 0) goto out; } if ((r = impl->funcs->serialize_private(key, b, opts)) != 0) goto out; /* * success (but we still need to append the output to buf after * possibly re-shielding the private key) */ r = 0; out: if (was_shielded) r = sshkey_shield_private(key); if (r == 0) r = sshbuf_putb(buf, b); sshbuf_free(b); return r; } int sshkey_private_serialize(struct sshkey *key, struct sshbuf *b) { return sshkey_private_serialize_opt(key, b, SSHKEY_SERIALIZE_DEFAULT); } /* Shared deserialization of FIDO private key components */ int sshkey_private_deserialize_sk(struct sshbuf *buf, struct sshkey *k) { int r; if ((k->sk_key_handle = sshbuf_new()) == NULL || (k->sk_reserved = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_cstring(buf, &k->sk_application, NULL)) != 0 || (r = sshbuf_get_u8(buf, &k->sk_flags)) != 0 || (r = sshbuf_get_stringb(buf, k->sk_key_handle)) != 0 || (r = sshbuf_get_stringb(buf, k->sk_reserved)) != 0) return r; return 0; } int sshkey_private_deserialize(struct sshbuf *buf, struct sshkey **kp) { const struct sshkey_impl *impl; char *tname = NULL; char *expect_sk_application = NULL; u_char *expect_ed25519_pk = NULL; struct sshkey *k = NULL; int type, r = SSH_ERR_INTERNAL_ERROR; if (kp != NULL) *kp = NULL; if ((r = sshbuf_get_cstring(buf, &tname, NULL)) != 0) goto out; type = sshkey_type_from_name(tname); if (sshkey_type_is_cert(type)) { /* * Certificate key private keys begin with the certificate * itself. Make sure this matches the type of the enclosing * private key. */ if ((r = sshkey_froms(buf, &k)) != 0) goto out; if (k->type != type) { r = SSH_ERR_KEY_CERT_MISMATCH; goto out; } /* For ECDSA keys, the group must match too */ if (k->type == KEY_ECDSA && k->ecdsa_nid != sshkey_ecdsa_nid_from_name(tname)) { r = SSH_ERR_KEY_CERT_MISMATCH; goto out; } /* * Several fields are redundant between certificate and * private key body, we require these to match. */ expect_sk_application = k->sk_application; expect_ed25519_pk = k->ed25519_pk; k->sk_application = NULL; k->ed25519_pk = NULL; /* XXX xmss too or refactor */ } else { if ((k = sshkey_new(type)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } } if ((impl = sshkey_impl_from_type(type)) == NULL) { r = SSH_ERR_INTERNAL_ERROR; goto out; } if ((r = impl->funcs->deserialize_private(tname, buf, k)) != 0) goto out; /* XXX xmss too or refactor */ if ((expect_sk_application != NULL && (k->sk_application == NULL || strcmp(expect_sk_application, k->sk_application) != 0)) || (expect_ed25519_pk != NULL && (k->ed25519_pk == NULL || memcmp(expect_ed25519_pk, k->ed25519_pk, ED25519_PK_SZ) != 0))) { r = SSH_ERR_KEY_CERT_MISMATCH; goto out; } /* success */ r = 0; if (kp != NULL) { *kp = k; k = NULL; } out: free(tname); sshkey_free(k); free(expect_sk_application); free(expect_ed25519_pk); return r; } #if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) int sshkey_ec_validate_public(const EC_GROUP *group, const EC_POINT *public) { EC_POINT *nq = NULL; BIGNUM *order = NULL, *x = NULL, *y = NULL, *tmp = NULL; int ret = SSH_ERR_KEY_INVALID_EC_VALUE; /* * NB. This assumes OpenSSL has already verified that the public * point lies on the curve. This is done by EC_POINT_oct2point() * implicitly calling EC_POINT_is_on_curve(). If this code is ever * reachable with public points not unmarshalled using * EC_POINT_oct2point then the caller will need to explicitly check. */ /* * We shouldn't ever hit this case because bignum_get_ecpoint() * refuses to load GF2m points. */ if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) != NID_X9_62_prime_field) goto out; /* Q != infinity */ if (EC_POINT_is_at_infinity(group, public)) goto out; if ((x = BN_new()) == NULL || (y = BN_new()) == NULL || (order = BN_new()) == NULL || (tmp = BN_new()) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } /* log2(x) > log2(order)/2, log2(y) > log2(order)/2 */ if (EC_GROUP_get_order(group, order, NULL) != 1 || EC_POINT_get_affine_coordinates_GFp(group, public, x, y, NULL) != 1) { ret = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if (BN_num_bits(x) <= BN_num_bits(order) / 2 || BN_num_bits(y) <= BN_num_bits(order) / 2) goto out; /* nQ == infinity (n == order of subgroup) */ if ((nq = EC_POINT_new(group)) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } if (EC_POINT_mul(group, nq, NULL, public, order, NULL) != 1) { ret = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if (EC_POINT_is_at_infinity(group, nq) != 1) goto out; /* x < order - 1, y < order - 1 */ if (!BN_sub(tmp, order, BN_value_one())) { ret = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if (BN_cmp(x, tmp) >= 0 || BN_cmp(y, tmp) >= 0) goto out; ret = 0; out: BN_clear_free(x); BN_clear_free(y); BN_clear_free(order); BN_clear_free(tmp); EC_POINT_free(nq); return ret; } int sshkey_ec_validate_private(const EC_KEY *key) { BIGNUM *order = NULL, *tmp = NULL; int ret = SSH_ERR_KEY_INVALID_EC_VALUE; if ((order = BN_new()) == NULL || (tmp = BN_new()) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto out; } /* log2(private) > log2(order)/2 */ if (EC_GROUP_get_order(EC_KEY_get0_group(key), order, NULL) != 1) { ret = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if (BN_num_bits(EC_KEY_get0_private_key(key)) <= BN_num_bits(order) / 2) goto out; /* private < order - 1 */ if (!BN_sub(tmp, order, BN_value_one())) { ret = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if (BN_cmp(EC_KEY_get0_private_key(key), tmp) >= 0) goto out; ret = 0; out: BN_clear_free(order); BN_clear_free(tmp); return ret; } void sshkey_dump_ec_point(const EC_GROUP *group, const EC_POINT *point) { BIGNUM *x = NULL, *y = NULL; if (point == NULL) { fputs("point=(NULL)\n", stderr); return; } if ((x = BN_new()) == NULL || (y = BN_new()) == NULL) { fprintf(stderr, "%s: BN_new failed\n", __func__); goto out; } if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) != NID_X9_62_prime_field) { fprintf(stderr, "%s: group is not a prime field\n", __func__); goto out; } if (EC_POINT_get_affine_coordinates_GFp(group, point, x, y, NULL) != 1) { fprintf(stderr, "%s: EC_POINT_get_affine_coordinates_GFp\n", __func__); goto out; } fputs("x=", stderr); BN_print_fp(stderr, x); fputs("\ny=", stderr); BN_print_fp(stderr, y); fputs("\n", stderr); out: BN_clear_free(x); BN_clear_free(y); } void sshkey_dump_ec_key(const EC_KEY *key) { const BIGNUM *exponent; sshkey_dump_ec_point(EC_KEY_get0_group(key), EC_KEY_get0_public_key(key)); fputs("exponent=", stderr); if ((exponent = EC_KEY_get0_private_key(key)) == NULL) fputs("(NULL)", stderr); else BN_print_fp(stderr, EC_KEY_get0_private_key(key)); fputs("\n", stderr); } #endif /* WITH_OPENSSL && OPENSSL_HAS_ECC */ static int sshkey_private_to_blob2(struct sshkey *prv, struct sshbuf *blob, const char *passphrase, const char *comment, const char *ciphername, int rounds) { u_char *cp, *key = NULL, *pubkeyblob = NULL; u_char salt[SALT_LEN]; size_t i, pubkeylen, keylen, ivlen, blocksize, authlen; u_int check; int r = SSH_ERR_INTERNAL_ERROR; struct sshcipher_ctx *ciphercontext = NULL; const struct sshcipher *cipher; const char *kdfname = KDFNAME; struct sshbuf *encoded = NULL, *encrypted = NULL, *kdf = NULL; if (rounds <= 0) rounds = DEFAULT_ROUNDS; if (passphrase == NULL || !strlen(passphrase)) { ciphername = "none"; kdfname = "none"; } else if (ciphername == NULL) ciphername = DEFAULT_CIPHERNAME; if ((cipher = cipher_by_name(ciphername)) == NULL) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if ((kdf = sshbuf_new()) == NULL || (encoded = sshbuf_new()) == NULL || (encrypted = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } blocksize = cipher_blocksize(cipher); keylen = cipher_keylen(cipher); ivlen = cipher_ivlen(cipher); authlen = cipher_authlen(cipher); if ((key = calloc(1, keylen + ivlen)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if (strcmp(kdfname, "bcrypt") == 0) { arc4random_buf(salt, SALT_LEN); if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, SALT_LEN, key, keylen + ivlen, rounds) < 0) { r = SSH_ERR_INVALID_ARGUMENT; goto out; } if ((r = sshbuf_put_string(kdf, salt, SALT_LEN)) != 0 || (r = sshbuf_put_u32(kdf, rounds)) != 0) goto out; } else if (strcmp(kdfname, "none") != 0) { /* Unsupported KDF type */ r = SSH_ERR_KEY_UNKNOWN_CIPHER; goto out; } if ((r = cipher_init(&ciphercontext, cipher, key, keylen, key + keylen, ivlen, 1)) != 0) goto out; if ((r = sshbuf_put(encoded, AUTH_MAGIC, sizeof(AUTH_MAGIC))) != 0 || (r = sshbuf_put_cstring(encoded, ciphername)) != 0 || (r = sshbuf_put_cstring(encoded, kdfname)) != 0 || (r = sshbuf_put_stringb(encoded, kdf)) != 0 || (r = sshbuf_put_u32(encoded, 1)) != 0 || /* number of keys */ (r = sshkey_to_blob(prv, &pubkeyblob, &pubkeylen)) != 0 || (r = sshbuf_put_string(encoded, pubkeyblob, pubkeylen)) != 0) goto out; /* set up the buffer that will be encrypted */ /* Random check bytes */ check = arc4random(); if ((r = sshbuf_put_u32(encrypted, check)) != 0 || (r = sshbuf_put_u32(encrypted, check)) != 0) goto out; /* append private key and comment*/ if ((r = sshkey_private_serialize_opt(prv, encrypted, SSHKEY_SERIALIZE_FULL)) != 0 || (r = sshbuf_put_cstring(encrypted, comment)) != 0) goto out; /* padding */ i = 0; while (sshbuf_len(encrypted) % blocksize) { if ((r = sshbuf_put_u8(encrypted, ++i & 0xff)) != 0) goto out; } /* length in destination buffer */ if ((r = sshbuf_put_u32(encoded, sshbuf_len(encrypted))) != 0) goto out; /* encrypt */ if ((r = sshbuf_reserve(encoded, sshbuf_len(encrypted) + authlen, &cp)) != 0) goto out; if ((r = cipher_crypt(ciphercontext, 0, cp, sshbuf_ptr(encrypted), sshbuf_len(encrypted), 0, authlen)) != 0) goto out; sshbuf_reset(blob); /* assemble uuencoded key */ if ((r = sshbuf_put(blob, MARK_BEGIN, MARK_BEGIN_LEN)) != 0 || (r = sshbuf_dtob64(encoded, blob, 1)) != 0 || (r = sshbuf_put(blob, MARK_END, MARK_END_LEN)) != 0) goto out; /* success */ r = 0; out: sshbuf_free(kdf); sshbuf_free(encoded); sshbuf_free(encrypted); cipher_free(ciphercontext); explicit_bzero(salt, sizeof(salt)); if (key != NULL) freezero(key, keylen + ivlen); if (pubkeyblob != NULL) freezero(pubkeyblob, pubkeylen); return r; } static int private2_uudecode(struct sshbuf *blob, struct sshbuf **decodedp) { const u_char *cp; size_t encoded_len; int r; u_char last; struct sshbuf *encoded = NULL, *decoded = NULL; if (blob == NULL || decodedp == NULL) return SSH_ERR_INVALID_ARGUMENT; *decodedp = NULL; if ((encoded = sshbuf_new()) == NULL || (decoded = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } /* check preamble */ cp = sshbuf_ptr(blob); encoded_len = sshbuf_len(blob); if (encoded_len < (MARK_BEGIN_LEN + MARK_END_LEN) || memcmp(cp, MARK_BEGIN, MARK_BEGIN_LEN) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } cp += MARK_BEGIN_LEN; encoded_len -= MARK_BEGIN_LEN; /* Look for end marker, removing whitespace as we go */ while (encoded_len > 0) { if (*cp != '\n' && *cp != '\r') { if ((r = sshbuf_put_u8(encoded, *cp)) != 0) goto out; } last = *cp; encoded_len--; cp++; if (last == '\n') { if (encoded_len >= MARK_END_LEN && memcmp(cp, MARK_END, MARK_END_LEN) == 0) { /* \0 terminate */ if ((r = sshbuf_put_u8(encoded, 0)) != 0) goto out; break; } } } if (encoded_len == 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* decode base64 */ if ((r = sshbuf_b64tod(decoded, (char *)sshbuf_ptr(encoded))) != 0) goto out; /* check magic */ if (sshbuf_len(decoded) < sizeof(AUTH_MAGIC) || memcmp(sshbuf_ptr(decoded), AUTH_MAGIC, sizeof(AUTH_MAGIC))) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* success */ *decodedp = decoded; decoded = NULL; r = 0; out: sshbuf_free(encoded); sshbuf_free(decoded); return r; } static int private2_decrypt(struct sshbuf *decoded, const char *passphrase, struct sshbuf **decryptedp, struct sshkey **pubkeyp) { char *ciphername = NULL, *kdfname = NULL; const struct sshcipher *cipher = NULL; int r = SSH_ERR_INTERNAL_ERROR; size_t keylen = 0, ivlen = 0, authlen = 0, slen = 0; struct sshbuf *kdf = NULL, *decrypted = NULL; struct sshcipher_ctx *ciphercontext = NULL; struct sshkey *pubkey = NULL; u_char *key = NULL, *salt = NULL, *dp; u_int blocksize, rounds, nkeys, encrypted_len, check1, check2; if (decoded == NULL || decryptedp == NULL || pubkeyp == NULL) return SSH_ERR_INVALID_ARGUMENT; *decryptedp = NULL; *pubkeyp = NULL; if ((decrypted = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } /* parse public portion of key */ if ((r = sshbuf_consume(decoded, sizeof(AUTH_MAGIC))) != 0 || (r = sshbuf_get_cstring(decoded, &ciphername, NULL)) != 0 || (r = sshbuf_get_cstring(decoded, &kdfname, NULL)) != 0 || (r = sshbuf_froms(decoded, &kdf)) != 0 || (r = sshbuf_get_u32(decoded, &nkeys)) != 0) goto out; if (nkeys != 1) { /* XXX only one key supported at present */ r = SSH_ERR_INVALID_FORMAT; goto out; } if ((r = sshkey_froms(decoded, &pubkey)) != 0 || (r = sshbuf_get_u32(decoded, &encrypted_len)) != 0) goto out; if ((cipher = cipher_by_name(ciphername)) == NULL) { r = SSH_ERR_KEY_UNKNOWN_CIPHER; goto out; } if (strcmp(kdfname, "none") != 0 && strcmp(kdfname, "bcrypt") != 0) { r = SSH_ERR_KEY_UNKNOWN_CIPHER; goto out; } if (strcmp(kdfname, "none") == 0 && strcmp(ciphername, "none") != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } if ((passphrase == NULL || strlen(passphrase) == 0) && strcmp(kdfname, "none") != 0) { /* passphrase required */ r = SSH_ERR_KEY_WRONG_PASSPHRASE; goto out; } /* check size of encrypted key blob */ blocksize = cipher_blocksize(cipher); if (encrypted_len < blocksize || (encrypted_len % blocksize) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* setup key */ keylen = cipher_keylen(cipher); ivlen = cipher_ivlen(cipher); authlen = cipher_authlen(cipher); if ((key = calloc(1, keylen + ivlen)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if (strcmp(kdfname, "bcrypt") == 0) { if ((r = sshbuf_get_string(kdf, &salt, &slen)) != 0 || (r = sshbuf_get_u32(kdf, &rounds)) != 0) goto out; if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, slen, key, keylen + ivlen, rounds) < 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } } /* check that an appropriate amount of auth data is present */ if (sshbuf_len(decoded) < authlen || sshbuf_len(decoded) - authlen < encrypted_len) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* decrypt private portion of key */ if ((r = sshbuf_reserve(decrypted, encrypted_len, &dp)) != 0 || (r = cipher_init(&ciphercontext, cipher, key, keylen, key + keylen, ivlen, 0)) != 0) goto out; if ((r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(decoded), encrypted_len, 0, authlen)) != 0) { /* an integrity error here indicates an incorrect passphrase */ if (r == SSH_ERR_MAC_INVALID) r = SSH_ERR_KEY_WRONG_PASSPHRASE; goto out; } if ((r = sshbuf_consume(decoded, encrypted_len + authlen)) != 0) goto out; /* there should be no trailing data */ if (sshbuf_len(decoded) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* check check bytes */ if ((r = sshbuf_get_u32(decrypted, &check1)) != 0 || (r = sshbuf_get_u32(decrypted, &check2)) != 0) goto out; if (check1 != check2) { r = SSH_ERR_KEY_WRONG_PASSPHRASE; goto out; } /* success */ *decryptedp = decrypted; decrypted = NULL; *pubkeyp = pubkey; pubkey = NULL; r = 0; out: cipher_free(ciphercontext); free(ciphername); free(kdfname); sshkey_free(pubkey); if (salt != NULL) { explicit_bzero(salt, slen); free(salt); } if (key != NULL) { explicit_bzero(key, keylen + ivlen); free(key); } sshbuf_free(kdf); sshbuf_free(decrypted); return r; } static int sshkey_parse_private2(struct sshbuf *blob, int type, const char *passphrase, struct sshkey **keyp, char **commentp) { char *comment = NULL; int r = SSH_ERR_INTERNAL_ERROR; struct sshbuf *decoded = NULL, *decrypted = NULL; struct sshkey *k = NULL, *pubkey = NULL; if (keyp != NULL) *keyp = NULL; if (commentp != NULL) *commentp = NULL; /* Undo base64 encoding and decrypt the private section */ if ((r = private2_uudecode(blob, &decoded)) != 0 || (r = private2_decrypt(decoded, passphrase, &decrypted, &pubkey)) != 0) goto out; if (type != KEY_UNSPEC && sshkey_type_plain(type) != sshkey_type_plain(pubkey->type)) { r = SSH_ERR_KEY_TYPE_MISMATCH; goto out; } /* Load the private key and comment */ if ((r = sshkey_private_deserialize(decrypted, &k)) != 0 || (r = sshbuf_get_cstring(decrypted, &comment, NULL)) != 0) goto out; /* Check deterministic padding after private section */ if ((r = private2_check_padding(decrypted)) != 0) goto out; /* Check that the public key in the envelope matches the private key */ if (!sshkey_equal(pubkey, k)) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* success */ r = 0; if (keyp != NULL) { *keyp = k; k = NULL; } if (commentp != NULL) { *commentp = comment; comment = NULL; } out: free(comment); sshbuf_free(decoded); sshbuf_free(decrypted); sshkey_free(k); sshkey_free(pubkey); return r; } static int sshkey_parse_private2_pubkey(struct sshbuf *blob, int type, struct sshkey **keyp) { int r = SSH_ERR_INTERNAL_ERROR; struct sshbuf *decoded = NULL; struct sshkey *pubkey = NULL; u_int nkeys = 0; if (keyp != NULL) *keyp = NULL; if ((r = private2_uudecode(blob, &decoded)) != 0) goto out; /* parse public key from unencrypted envelope */ if ((r = sshbuf_consume(decoded, sizeof(AUTH_MAGIC))) != 0 || (r = sshbuf_skip_string(decoded)) != 0 || /* cipher */ (r = sshbuf_skip_string(decoded)) != 0 || /* KDF alg */ (r = sshbuf_skip_string(decoded)) != 0 || /* KDF hint */ (r = sshbuf_get_u32(decoded, &nkeys)) != 0) goto out; if (nkeys != 1) { /* XXX only one key supported at present */ r = SSH_ERR_INVALID_FORMAT; goto out; } /* Parse the public key */ if ((r = sshkey_froms(decoded, &pubkey)) != 0) goto out; if (type != KEY_UNSPEC && sshkey_type_plain(type) != sshkey_type_plain(pubkey->type)) { r = SSH_ERR_KEY_TYPE_MISMATCH; goto out; } /* success */ r = 0; if (keyp != NULL) { *keyp = pubkey; pubkey = NULL; } out: sshbuf_free(decoded); sshkey_free(pubkey); return r; } #ifdef WITH_OPENSSL /* convert SSH v2 key to PEM or PKCS#8 format */ static int sshkey_private_to_blob_pem_pkcs8(struct sshkey *key, struct sshbuf *buf, int format, const char *_passphrase, const char *comment) { int was_shielded = sshkey_is_shielded(key); int success, r; int blen, len = strlen(_passphrase); u_char *passphrase = (len > 0) ? (u_char *)_passphrase : NULL; const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL; char *bptr; BIO *bio = NULL; struct sshbuf *blob; EVP_PKEY *pkey = NULL; if (len > 0 && len <= 4) return SSH_ERR_PASSPHRASE_TOO_SHORT; if ((blob = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; if ((bio = BIO_new(BIO_s_mem())) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if (format == SSHKEY_PRIVATE_PKCS8 && (pkey = EVP_PKEY_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshkey_unshield_private(key)) != 0) goto out; switch (key->type) { case KEY_DSA: if (format == SSHKEY_PRIVATE_PEM) { success = PEM_write_bio_DSAPrivateKey(bio, key->dsa, cipher, passphrase, len, NULL, NULL); } else { success = EVP_PKEY_set1_DSA(pkey, key->dsa); } break; #ifdef OPENSSL_HAS_ECC case KEY_ECDSA: if (format == SSHKEY_PRIVATE_PEM) { success = PEM_write_bio_ECPrivateKey(bio, key->ecdsa, cipher, passphrase, len, NULL, NULL); } else { success = EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); } break; #endif case KEY_RSA: if (format == SSHKEY_PRIVATE_PEM) { success = PEM_write_bio_RSAPrivateKey(bio, key->rsa, cipher, passphrase, len, NULL, NULL); } else { success = EVP_PKEY_set1_RSA(pkey, key->rsa); } break; default: success = 0; break; } if (success == 0) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if (format == SSHKEY_PRIVATE_PKCS8) { if ((success = PEM_write_bio_PrivateKey(bio, pkey, cipher, passphrase, len, NULL, NULL)) == 0) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } } if ((blen = BIO_get_mem_data(bio, &bptr)) <= 0) { r = SSH_ERR_INTERNAL_ERROR; goto out; } if ((r = sshbuf_put(blob, bptr, blen)) != 0) goto out; r = 0; out: if (was_shielded) r = sshkey_shield_private(key); if (r == 0) r = sshbuf_putb(buf, blob); EVP_PKEY_free(pkey); sshbuf_free(blob); BIO_free(bio); return r; } #endif /* WITH_OPENSSL */ /* Serialise "key" to buffer "blob" */ int sshkey_private_to_fileblob(struct sshkey *key, struct sshbuf *blob, const char *passphrase, const char *comment, int format, const char *openssh_format_cipher, int openssh_format_rounds) { switch (key->type) { #ifdef WITH_OPENSSL case KEY_DSA: case KEY_ECDSA: case KEY_RSA: break; /* see below */ #endif /* WITH_OPENSSL */ case KEY_ED25519: case KEY_ED25519_SK: #ifdef WITH_XMSS case KEY_XMSS: #endif /* WITH_XMSS */ #ifdef WITH_OPENSSL case KEY_ECDSA_SK: #endif /* WITH_OPENSSL */ return sshkey_private_to_blob2(key, blob, passphrase, comment, openssh_format_cipher, openssh_format_rounds); default: return SSH_ERR_KEY_TYPE_UNKNOWN; } #ifdef WITH_OPENSSL switch (format) { case SSHKEY_PRIVATE_OPENSSH: return sshkey_private_to_blob2(key, blob, passphrase, comment, openssh_format_cipher, openssh_format_rounds); case SSHKEY_PRIVATE_PEM: case SSHKEY_PRIVATE_PKCS8: return sshkey_private_to_blob_pem_pkcs8(key, blob, format, passphrase, comment); default: return SSH_ERR_INVALID_ARGUMENT; } #endif /* WITH_OPENSSL */ } #ifdef WITH_OPENSSL static int translate_libcrypto_error(unsigned long pem_err) { int pem_reason = ERR_GET_REASON(pem_err); switch (ERR_GET_LIB(pem_err)) { case ERR_LIB_PEM: switch (pem_reason) { case PEM_R_BAD_PASSWORD_READ: #ifdef PEM_R_PROBLEMS_GETTING_PASSWORD case PEM_R_PROBLEMS_GETTING_PASSWORD: #endif #ifdef PEM_R_BAD_DECRYPT case PEM_R_BAD_DECRYPT: #endif return SSH_ERR_KEY_WRONG_PASSPHRASE; default: return SSH_ERR_INVALID_FORMAT; } case ERR_LIB_EVP: switch (pem_reason) { #ifdef EVP_R_BAD_DECRYPT case EVP_R_BAD_DECRYPT: return SSH_ERR_KEY_WRONG_PASSPHRASE; #endif #ifdef EVP_R_BN_DECODE_ERROR case EVP_R_BN_DECODE_ERROR: #endif case EVP_R_DECODE_ERROR: #ifdef EVP_R_PRIVATE_KEY_DECODE_ERROR case EVP_R_PRIVATE_KEY_DECODE_ERROR: #endif return SSH_ERR_INVALID_FORMAT; default: return SSH_ERR_LIBCRYPTO_ERROR; } case ERR_LIB_ASN1: return SSH_ERR_INVALID_FORMAT; } return SSH_ERR_LIBCRYPTO_ERROR; } static void clear_libcrypto_errors(void) { while (ERR_get_error() != 0) ; } /* * Translate OpenSSL error codes to determine whether * passphrase is required/incorrect. */ static int convert_libcrypto_error(void) { /* * Some password errors are reported at the beginning * of the error queue. */ if (translate_libcrypto_error(ERR_peek_error()) == SSH_ERR_KEY_WRONG_PASSPHRASE) return SSH_ERR_KEY_WRONG_PASSPHRASE; return translate_libcrypto_error(ERR_peek_last_error()); } static int pem_passphrase_cb(char *buf, int size, int rwflag, void *u) { char *p = (char *)u; size_t len; if (p == NULL || (len = strlen(p)) == 0) return -1; if (size < 0 || len > (size_t)size) return -1; memcpy(buf, p, len); return (int)len; } static int sshkey_parse_private_pem_fileblob(struct sshbuf *blob, int type, const char *passphrase, struct sshkey **keyp) { EVP_PKEY *pk = NULL; struct sshkey *prv = NULL; BIO *bio = NULL; int r; if (keyp != NULL) *keyp = NULL; if ((bio = BIO_new(BIO_s_mem())) == NULL || sshbuf_len(blob) > INT_MAX) return SSH_ERR_ALLOC_FAIL; if (BIO_write(bio, sshbuf_ptr(blob), sshbuf_len(blob)) != (int)sshbuf_len(blob)) { r = SSH_ERR_ALLOC_FAIL; goto out; } clear_libcrypto_errors(); if ((pk = PEM_read_bio_PrivateKey(bio, NULL, pem_passphrase_cb, (char *)passphrase)) == NULL) { /* * libcrypto may return various ASN.1 errors when attempting * to parse a key with an incorrect passphrase. * Treat all format errors as "incorrect passphrase" if a * passphrase was supplied. */ if (passphrase != NULL && *passphrase != '\0') r = SSH_ERR_KEY_WRONG_PASSPHRASE; else r = convert_libcrypto_error(); goto out; } if (EVP_PKEY_base_id(pk) == EVP_PKEY_RSA && (type == KEY_UNSPEC || type == KEY_RSA)) { if ((prv = sshkey_new(KEY_UNSPEC)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } prv->rsa = EVP_PKEY_get1_RSA(pk); prv->type = KEY_RSA; #ifdef DEBUG_PK RSA_print_fp(stderr, prv->rsa, 8); #endif if (RSA_blinding_on(prv->rsa, NULL) != 1) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } if ((r = sshkey_check_rsa_length(prv, 0)) != 0) goto out; } else if (EVP_PKEY_base_id(pk) == EVP_PKEY_DSA && (type == KEY_UNSPEC || type == KEY_DSA)) { if ((prv = sshkey_new(KEY_UNSPEC)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } prv->dsa = EVP_PKEY_get1_DSA(pk); prv->type = KEY_DSA; #ifdef DEBUG_PK DSA_print_fp(stderr, prv->dsa, 8); #endif #ifdef OPENSSL_HAS_ECC } else if (EVP_PKEY_base_id(pk) == EVP_PKEY_EC && (type == KEY_UNSPEC || type == KEY_ECDSA)) { if ((prv = sshkey_new(KEY_UNSPEC)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } prv->ecdsa = EVP_PKEY_get1_EC_KEY(pk); prv->type = KEY_ECDSA; prv->ecdsa_nid = sshkey_ecdsa_key_to_nid(prv->ecdsa); if (prv->ecdsa_nid == -1 || sshkey_curve_nid_to_name(prv->ecdsa_nid) == NULL || sshkey_ec_validate_public(EC_KEY_get0_group(prv->ecdsa), EC_KEY_get0_public_key(prv->ecdsa)) != 0 || sshkey_ec_validate_private(prv->ecdsa) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } # ifdef DEBUG_PK if (prv != NULL && prv->ecdsa != NULL) sshkey_dump_ec_key(prv->ecdsa); # endif #endif /* OPENSSL_HAS_ECC */ } else { r = SSH_ERR_INVALID_FORMAT; goto out; } r = 0; if (keyp != NULL) { *keyp = prv; prv = NULL; } out: BIO_free(bio); EVP_PKEY_free(pk); sshkey_free(prv); return r; } #endif /* WITH_OPENSSL */ int sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, const char *passphrase, struct sshkey **keyp, char **commentp) { int r = SSH_ERR_INTERNAL_ERROR; if (keyp != NULL) *keyp = NULL; if (commentp != NULL) *commentp = NULL; switch (type) { case KEY_ED25519: case KEY_XMSS: /* No fallback for new-format-only keys */ return sshkey_parse_private2(blob, type, passphrase, keyp, commentp); default: r = sshkey_parse_private2(blob, type, passphrase, keyp, commentp); /* Only fallback to PEM parser if a format error occurred. */ if (r != SSH_ERR_INVALID_FORMAT) return r; #ifdef WITH_OPENSSL return sshkey_parse_private_pem_fileblob(blob, type, passphrase, keyp); #else return SSH_ERR_INVALID_FORMAT; #endif /* WITH_OPENSSL */ } } int sshkey_parse_private_fileblob(struct sshbuf *buffer, const char *passphrase, struct sshkey **keyp, char **commentp) { if (keyp != NULL) *keyp = NULL; if (commentp != NULL) *commentp = NULL; return sshkey_parse_private_fileblob_type(buffer, KEY_UNSPEC, passphrase, keyp, commentp); } void sshkey_sig_details_free(struct sshkey_sig_details *details) { freezero(details, sizeof(*details)); } int sshkey_parse_pubkey_from_private_fileblob_type(struct sshbuf *blob, int type, struct sshkey **pubkeyp) { int r = SSH_ERR_INTERNAL_ERROR; if (pubkeyp != NULL) *pubkeyp = NULL; /* only new-format private keys bundle a public key inside */ if ((r = sshkey_parse_private2_pubkey(blob, type, pubkeyp)) != 0) return r; return 0; } #ifdef WITH_XMSS /* * serialize the key with the current state and forward the state * maxsign times. */ int sshkey_private_serialize_maxsign(struct sshkey *k, struct sshbuf *b, u_int32_t maxsign, int printerror) { int r, rupdate; if (maxsign == 0 || sshkey_type_plain(k->type) != KEY_XMSS) return sshkey_private_serialize_opt(k, b, SSHKEY_SERIALIZE_DEFAULT); if ((r = sshkey_xmss_get_state(k, printerror)) != 0 || (r = sshkey_private_serialize_opt(k, b, SSHKEY_SERIALIZE_STATE)) != 0 || (r = sshkey_xmss_forward_state(k, maxsign)) != 0) goto out; r = 0; out: if ((rupdate = sshkey_xmss_update_state(k, printerror)) != 0) { if (r == 0) r = rupdate; } return r; } u_int32_t sshkey_signatures_left(const struct sshkey *k) { if (sshkey_type_plain(k->type) == KEY_XMSS) return sshkey_xmss_signatures_left(k); return 0; } int sshkey_enable_maxsign(struct sshkey *k, u_int32_t maxsign) { if (sshkey_type_plain(k->type) != KEY_XMSS) return SSH_ERR_INVALID_ARGUMENT; return sshkey_xmss_enable_maxsign(k, maxsign); } int sshkey_set_filename(struct sshkey *k, const char *filename) { if (k == NULL) return SSH_ERR_INVALID_ARGUMENT; if (sshkey_type_plain(k->type) != KEY_XMSS) return 0; if (filename == NULL) return SSH_ERR_INVALID_ARGUMENT; if ((k->xmss_filename = strdup(filename)) == NULL) return SSH_ERR_ALLOC_FAIL; return 0; } #else int sshkey_private_serialize_maxsign(struct sshkey *k, struct sshbuf *b, u_int32_t maxsign, int printerror) { return sshkey_private_serialize_opt(k, b, SSHKEY_SERIALIZE_DEFAULT); } u_int32_t sshkey_signatures_left(const struct sshkey *k) { return 0; } int sshkey_enable_maxsign(struct sshkey *k, u_int32_t maxsign) { return SSH_ERR_INVALID_ARGUMENT; } int sshkey_set_filename(struct sshkey *k, const char *filename) { if (k == NULL) return SSH_ERR_INVALID_ARGUMENT; return 0; } #endif /* WITH_XMSS */ diff --git a/crypto/openssh/sshsig.c b/crypto/openssh/sshsig.c index 854d67322409..d219db90e9a3 100644 --- a/crypto/openssh/sshsig.c +++ b/crypto/openssh/sshsig.c @@ -1,1147 +1,1158 @@ -/* $OpenBSD: sshsig.c,v 1.32 2023/04/06 03:56:02 djm Exp $ */ +/* $OpenBSD: sshsig.c,v 1.33 2023/09/06 23:18:15 djm Exp $ */ /* * Copyright (c) 2019 Google LLC * * 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 "includes.h" #include #include #include #include #include #include #include "authfd.h" #include "authfile.h" #include "log.h" #include "misc.h" #include "sshbuf.h" #include "sshsig.h" #include "ssherr.h" #include "sshkey.h" #include "match.h" #include "digest.h" #define SIG_VERSION 0x01 #define MAGIC_PREAMBLE "SSHSIG" #define MAGIC_PREAMBLE_LEN (sizeof(MAGIC_PREAMBLE) - 1) -#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----\n" +#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----" #define END_SIGNATURE "-----END SSH SIGNATURE-----" #define RSA_SIGN_ALG "rsa-sha2-512" /* XXX maybe make configurable */ #define RSA_SIGN_ALLOWED "rsa-sha2-512,rsa-sha2-256" #define HASHALG_DEFAULT "sha512" /* XXX maybe make configurable */ #define HASHALG_ALLOWED "sha256,sha512" int sshsig_armor(const struct sshbuf *blob, struct sshbuf **out) { struct sshbuf *buf = NULL; int r = SSH_ERR_INTERNAL_ERROR; *out = NULL; if ((buf = sshbuf_new()) == NULL) { error_f("sshbuf_new failed"); r = SSH_ERR_ALLOC_FAIL; goto out; } - if ((r = sshbuf_put(buf, BEGIN_SIGNATURE, - sizeof(BEGIN_SIGNATURE)-1)) != 0) { + if ((r = sshbuf_putf(buf, "%s\n", BEGIN_SIGNATURE)) != 0) { error_fr(r, "sshbuf_putf"); goto out; } if ((r = sshbuf_dtob64(blob, buf, 1)) != 0) { error_fr(r, "base64 encode signature"); goto out; } if ((r = sshbuf_put(buf, END_SIGNATURE, sizeof(END_SIGNATURE)-1)) != 0 || (r = sshbuf_put_u8(buf, '\n')) != 0) { error_fr(r, "sshbuf_put"); goto out; } /* success */ *out = buf; buf = NULL; /* transferred */ r = 0; out: sshbuf_free(buf); return r; } int sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out) { int r; size_t eoffset = 0; struct sshbuf *buf = NULL; struct sshbuf *sbuf = NULL; char *b64 = NULL; if ((sbuf = sshbuf_fromb(sig)) == NULL) { error_f("sshbuf_fromb failed"); return SSH_ERR_ALLOC_FAIL; } + /* Expect and consume preamble + lf/crlf */ if ((r = sshbuf_cmp(sbuf, 0, BEGIN_SIGNATURE, sizeof(BEGIN_SIGNATURE)-1)) != 0) { error("Couldn't parse signature: missing header"); goto done; } - if ((r = sshbuf_consume(sbuf, sizeof(BEGIN_SIGNATURE)-1)) != 0) { error_fr(r, "consume"); goto done; } - + if ((r = sshbuf_cmp(sbuf, 0, "\r\n", 2)) == 0) + eoffset = 2; + else if ((r = sshbuf_cmp(sbuf, 0, "\n", 1)) == 0) + eoffset = 1; + else { + r = SSH_ERR_INVALID_FORMAT; + error_f("no header eol"); + goto done; + } + if ((r = sshbuf_consume(sbuf, eoffset)) != 0) { + error_fr(r, "consume eol"); + goto done; + } + /* Find and consume lf + suffix (any prior cr would be ignored) */ if ((r = sshbuf_find(sbuf, 0, "\n" END_SIGNATURE, - sizeof("\n" END_SIGNATURE)-1, &eoffset)) != 0) { + sizeof(END_SIGNATURE), &eoffset)) != 0) { error("Couldn't parse signature: missing footer"); goto done; } - if ((r = sshbuf_consume_end(sbuf, sshbuf_len(sbuf)-eoffset)) != 0) { error_fr(r, "consume"); goto done; } if ((b64 = sshbuf_dup_string(sbuf)) == NULL) { error_f("sshbuf_dup_string failed"); r = SSH_ERR_ALLOC_FAIL; goto done; } if ((buf = sshbuf_new()) == NULL) { error_f("sshbuf_new() failed"); r = SSH_ERR_ALLOC_FAIL; goto done; } if ((r = sshbuf_b64tod(buf, b64)) != 0) { error_fr(r, "decode base64"); goto done; } /* success */ *out = buf; r = 0; buf = NULL; /* transferred */ done: sshbuf_free(buf); sshbuf_free(sbuf); free(b64); return r; } static int sshsig_wrap_sign(struct sshkey *key, const char *hashalg, const char *sk_provider, const char *sk_pin, const struct sshbuf *h_message, const char *sig_namespace, struct sshbuf **out, sshsig_signer *signer, void *signer_ctx) { int r; size_t slen = 0; u_char *sig = NULL; struct sshbuf *blob = NULL; struct sshbuf *tosign = NULL; const char *sign_alg = NULL; if ((tosign = sshbuf_new()) == NULL || (blob = sshbuf_new()) == NULL) { error_f("sshbuf_new failed"); r = SSH_ERR_ALLOC_FAIL; goto done; } if ((r = sshbuf_put(tosign, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 || (r = sshbuf_put_cstring(tosign, sig_namespace)) != 0 || (r = sshbuf_put_string(tosign, NULL, 0)) != 0 || /* reserved */ (r = sshbuf_put_cstring(tosign, hashalg)) != 0 || (r = sshbuf_put_stringb(tosign, h_message)) != 0) { error_fr(r, "assemble message to sign"); goto done; } /* If using RSA keys then default to a good signature algorithm */ if (sshkey_type_plain(key->type) == KEY_RSA) sign_alg = RSA_SIGN_ALG; if (signer != NULL) { if ((r = signer(key, &sig, &slen, sshbuf_ptr(tosign), sshbuf_len(tosign), sign_alg, sk_provider, sk_pin, 0, signer_ctx)) != 0) { error_r(r, "Couldn't sign message (signer)"); goto done; } } else { if ((r = sshkey_sign(key, &sig, &slen, sshbuf_ptr(tosign), sshbuf_len(tosign), sign_alg, sk_provider, sk_pin, 0)) != 0) { error_r(r, "Couldn't sign message"); goto done; } } if ((r = sshbuf_put(blob, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 || (r = sshbuf_put_u32(blob, SIG_VERSION)) != 0 || (r = sshkey_puts(key, blob)) != 0 || (r = sshbuf_put_cstring(blob, sig_namespace)) != 0 || (r = sshbuf_put_string(blob, NULL, 0)) != 0 || /* reserved */ (r = sshbuf_put_cstring(blob, hashalg)) != 0 || (r = sshbuf_put_string(blob, sig, slen)) != 0) { error_fr(r, "assemble signature object"); goto done; } if (out != NULL) { *out = blob; blob = NULL; } r = 0; done: free(sig); sshbuf_free(blob); sshbuf_free(tosign); return r; } /* Check preamble and version. */ static int sshsig_parse_preamble(struct sshbuf *buf) { int r = SSH_ERR_INTERNAL_ERROR; uint32_t sversion; if ((r = sshbuf_cmp(buf, 0, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 || (r = sshbuf_consume(buf, (sizeof(MAGIC_PREAMBLE)-1))) != 0 || (r = sshbuf_get_u32(buf, &sversion)) != 0) { error("Couldn't verify signature: invalid format"); return r; } if (sversion > SIG_VERSION) { error("Signature version %lu is larger than supported " "version %u", (unsigned long)sversion, SIG_VERSION); return SSH_ERR_INVALID_FORMAT; } return 0; } static int sshsig_check_hashalg(const char *hashalg) { if (hashalg == NULL || match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1) return 0; error_f("unsupported hash algorithm \"%.100s\"", hashalg); return SSH_ERR_SIGN_ALG_UNSUPPORTED; } static int sshsig_peek_hashalg(struct sshbuf *signature, char **hashalgp) { struct sshbuf *buf = NULL; char *hashalg = NULL; int r = SSH_ERR_INTERNAL_ERROR; if (hashalgp != NULL) *hashalgp = NULL; if ((buf = sshbuf_fromb(signature)) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshsig_parse_preamble(buf)) != 0) goto done; if ((r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 || (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 || (r = sshbuf_get_string(buf, NULL, NULL)) != 0 || (r = sshbuf_get_cstring(buf, &hashalg, NULL)) != 0 || (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0) { error_fr(r, "parse signature object"); goto done; } /* success */ r = 0; *hashalgp = hashalg; hashalg = NULL; done: free(hashalg); sshbuf_free(buf); return r; } static int sshsig_wrap_verify(struct sshbuf *signature, const char *hashalg, const struct sshbuf *h_message, const char *expect_namespace, struct sshkey **sign_keyp, struct sshkey_sig_details **sig_details) { int r = SSH_ERR_INTERNAL_ERROR; struct sshbuf *buf = NULL, *toverify = NULL; struct sshkey *key = NULL; const u_char *sig; char *got_namespace = NULL, *sigtype = NULL, *sig_hashalg = NULL; size_t siglen; debug_f("verify message length %zu", sshbuf_len(h_message)); if (sig_details != NULL) *sig_details = NULL; if (sign_keyp != NULL) *sign_keyp = NULL; if ((toverify = sshbuf_new()) == NULL) { error_f("sshbuf_new failed"); r = SSH_ERR_ALLOC_FAIL; goto done; } if ((r = sshbuf_put(toverify, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 || (r = sshbuf_put_cstring(toverify, expect_namespace)) != 0 || (r = sshbuf_put_string(toverify, NULL, 0)) != 0 || /* reserved */ (r = sshbuf_put_cstring(toverify, hashalg)) != 0 || (r = sshbuf_put_stringb(toverify, h_message)) != 0) { error_fr(r, "assemble message to verify"); goto done; } if ((r = sshsig_parse_preamble(signature)) != 0) goto done; if ((r = sshkey_froms(signature, &key)) != 0 || (r = sshbuf_get_cstring(signature, &got_namespace, NULL)) != 0 || (r = sshbuf_get_string(signature, NULL, NULL)) != 0 || (r = sshbuf_get_cstring(signature, &sig_hashalg, NULL)) != 0 || (r = sshbuf_get_string_direct(signature, &sig, &siglen)) != 0) { error_fr(r, "parse signature object"); goto done; } if (sshbuf_len(signature) != 0) { error("Signature contains trailing data"); r = SSH_ERR_INVALID_FORMAT; goto done; } if (strcmp(expect_namespace, got_namespace) != 0) { error("Couldn't verify signature: namespace does not match"); debug_f("expected namespace \"%s\" received \"%s\"", expect_namespace, got_namespace); r = SSH_ERR_SIGNATURE_INVALID; goto done; } if (strcmp(hashalg, sig_hashalg) != 0) { error("Couldn't verify signature: hash algorithm mismatch"); debug_f("expected algorithm \"%s\" received \"%s\"", hashalg, sig_hashalg); r = SSH_ERR_SIGNATURE_INVALID; goto done; } /* Ensure that RSA keys use an acceptable signature algorithm */ if (sshkey_type_plain(key->type) == KEY_RSA) { if ((r = sshkey_get_sigtype(sig, siglen, &sigtype)) != 0) { error_r(r, "Couldn't verify signature: unable to get " "signature type"); goto done; } if (match_pattern_list(sigtype, RSA_SIGN_ALLOWED, 0) != 1) { error("Couldn't verify signature: unsupported RSA " "signature algorithm %s", sigtype); r = SSH_ERR_SIGN_ALG_UNSUPPORTED; goto done; } } if ((r = sshkey_verify(key, sig, siglen, sshbuf_ptr(toverify), sshbuf_len(toverify), NULL, 0, sig_details)) != 0) { error_r(r, "Signature verification failed"); goto done; } /* success */ r = 0; if (sign_keyp != NULL) { *sign_keyp = key; key = NULL; /* transferred */ } done: free(got_namespace); free(sigtype); free(sig_hashalg); sshbuf_free(buf); sshbuf_free(toverify); sshkey_free(key); return r; } static int hash_buffer(const struct sshbuf *m, const char *hashalg, struct sshbuf **bp) { char *hex, hash[SSH_DIGEST_MAX_LENGTH]; int alg, r = SSH_ERR_INTERNAL_ERROR; struct sshbuf *b = NULL; *bp = NULL; memset(hash, 0, sizeof(hash)); if ((r = sshsig_check_hashalg(hashalg)) != 0) return r; if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) { error_f("can't look up hash algorithm %s", hashalg); return SSH_ERR_INTERNAL_ERROR; } if ((r = ssh_digest_buffer(alg, m, hash, sizeof(hash))) != 0) { error_fr(r, "ssh_digest_buffer"); return r; } if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) { debug3_f("final hash: %s", hex); freezero(hex, strlen(hex)); } if ((b = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) { error_fr(r, "sshbuf_put"); goto out; } *bp = b; b = NULL; /* transferred */ /* success */ r = 0; out: sshbuf_free(b); explicit_bzero(hash, sizeof(hash)); return r; } int sshsig_signb(struct sshkey *key, const char *hashalg, const char *sk_provider, const char *sk_pin, const struct sshbuf *message, const char *sig_namespace, struct sshbuf **out, sshsig_signer *signer, void *signer_ctx) { struct sshbuf *b = NULL; int r = SSH_ERR_INTERNAL_ERROR; if (hashalg == NULL) hashalg = HASHALG_DEFAULT; if (out != NULL) *out = NULL; if ((r = hash_buffer(message, hashalg, &b)) != 0) { error_fr(r, "hash buffer"); goto out; } if ((r = sshsig_wrap_sign(key, hashalg, sk_provider, sk_pin, b, sig_namespace, out, signer, signer_ctx)) != 0) goto out; /* success */ r = 0; out: sshbuf_free(b); return r; } int sshsig_verifyb(struct sshbuf *signature, const struct sshbuf *message, const char *expect_namespace, struct sshkey **sign_keyp, struct sshkey_sig_details **sig_details) { struct sshbuf *b = NULL; int r = SSH_ERR_INTERNAL_ERROR; char *hashalg = NULL; if (sig_details != NULL) *sig_details = NULL; if (sign_keyp != NULL) *sign_keyp = NULL; if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0) return r; debug_f("signature made with hash \"%s\"", hashalg); if ((r = hash_buffer(message, hashalg, &b)) != 0) { error_fr(r, "hash buffer"); goto out; } if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace, sign_keyp, sig_details)) != 0) goto out; /* success */ r = 0; out: sshbuf_free(b); free(hashalg); return r; } static int hash_file(int fd, const char *hashalg, struct sshbuf **bp) { char *hex, rbuf[8192], hash[SSH_DIGEST_MAX_LENGTH]; ssize_t n, total = 0; struct ssh_digest_ctx *ctx = NULL; int alg, oerrno, r = SSH_ERR_INTERNAL_ERROR; struct sshbuf *b = NULL; *bp = NULL; memset(hash, 0, sizeof(hash)); if ((r = sshsig_check_hashalg(hashalg)) != 0) return r; if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) { error_f("can't look up hash algorithm %s", hashalg); return SSH_ERR_INTERNAL_ERROR; } if ((ctx = ssh_digest_start(alg)) == NULL) { error_f("ssh_digest_start failed"); return SSH_ERR_INTERNAL_ERROR; } for (;;) { if ((n = read(fd, rbuf, sizeof(rbuf))) == -1) { if (errno == EINTR || errno == EAGAIN) continue; oerrno = errno; error_f("read: %s", strerror(errno)); errno = oerrno; r = SSH_ERR_SYSTEM_ERROR; goto out; } else if (n == 0) { debug2_f("hashed %zu bytes", total); break; /* EOF */ } total += (size_t)n; if ((r = ssh_digest_update(ctx, rbuf, (size_t)n)) != 0) { error_fr(r, "ssh_digest_update"); goto out; } } if ((r = ssh_digest_final(ctx, hash, sizeof(hash))) != 0) { error_fr(r, "ssh_digest_final"); goto out; } if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) { debug3_f("final hash: %s", hex); freezero(hex, strlen(hex)); } if ((b = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) { error_fr(r, "sshbuf_put"); goto out; } *bp = b; b = NULL; /* transferred */ /* success */ r = 0; out: oerrno = errno; sshbuf_free(b); ssh_digest_free(ctx); explicit_bzero(hash, sizeof(hash)); errno = oerrno; return r; } int sshsig_sign_fd(struct sshkey *key, const char *hashalg, const char *sk_provider, const char *sk_pin, int fd, const char *sig_namespace, struct sshbuf **out, sshsig_signer *signer, void *signer_ctx) { struct sshbuf *b = NULL; int r = SSH_ERR_INTERNAL_ERROR; if (hashalg == NULL) hashalg = HASHALG_DEFAULT; if (out != NULL) *out = NULL; if ((r = hash_file(fd, hashalg, &b)) != 0) { error_fr(r, "hash_file"); return r; } if ((r = sshsig_wrap_sign(key, hashalg, sk_provider, sk_pin, b, sig_namespace, out, signer, signer_ctx)) != 0) goto out; /* success */ r = 0; out: sshbuf_free(b); return r; } int sshsig_verify_fd(struct sshbuf *signature, int fd, const char *expect_namespace, struct sshkey **sign_keyp, struct sshkey_sig_details **sig_details) { struct sshbuf *b = NULL; int r = SSH_ERR_INTERNAL_ERROR; char *hashalg = NULL; if (sig_details != NULL) *sig_details = NULL; if (sign_keyp != NULL) *sign_keyp = NULL; if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0) return r; debug_f("signature made with hash \"%s\"", hashalg); if ((r = hash_file(fd, hashalg, &b)) != 0) { error_fr(r, "hash_file"); goto out; } if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace, sign_keyp, sig_details)) != 0) goto out; /* success */ r = 0; out: sshbuf_free(b); free(hashalg); return r; } struct sshsigopt { int ca; char *namespaces; uint64_t valid_after, valid_before; }; struct sshsigopt * sshsigopt_parse(const char *opts, const char *path, u_long linenum, const char **errstrp) { struct sshsigopt *ret; int r; char *opt; const char *errstr = NULL; if ((ret = calloc(1, sizeof(*ret))) == NULL) return NULL; if (opts == NULL || *opts == '\0') return ret; /* Empty options yields empty options :) */ while (*opts && *opts != ' ' && *opts != '\t') { /* flag options */ if ((r = opt_flag("cert-authority", 0, &opts)) != -1) { ret->ca = 1; } else if (opt_match(&opts, "namespaces")) { if (ret->namespaces != NULL) { errstr = "multiple \"namespaces\" clauses"; goto fail; } ret->namespaces = opt_dequote(&opts, &errstr); if (ret->namespaces == NULL) goto fail; } else if (opt_match(&opts, "valid-after")) { if (ret->valid_after != 0) { errstr = "multiple \"valid-after\" clauses"; goto fail; } if ((opt = opt_dequote(&opts, &errstr)) == NULL) goto fail; if (parse_absolute_time(opt, &ret->valid_after) != 0 || ret->valid_after == 0) { free(opt); errstr = "invalid \"valid-after\" time"; goto fail; } free(opt); } else if (opt_match(&opts, "valid-before")) { if (ret->valid_before != 0) { errstr = "multiple \"valid-before\" clauses"; goto fail; } if ((opt = opt_dequote(&opts, &errstr)) == NULL) goto fail; if (parse_absolute_time(opt, &ret->valid_before) != 0 || ret->valid_before == 0) { free(opt); errstr = "invalid \"valid-before\" time"; goto fail; } free(opt); } /* * Skip the comma, and move to the next option * (or break out if there are no more). */ if (*opts == '\0' || *opts == ' ' || *opts == '\t') break; /* End of options. */ /* Anything other than a comma is an unknown option */ if (*opts != ',') { errstr = "unknown key option"; goto fail; } opts++; if (*opts == '\0') { errstr = "unexpected end-of-options"; goto fail; } } /* final consistency check */ if (ret->valid_after != 0 && ret->valid_before != 0 && ret->valid_before <= ret->valid_after) { errstr = "\"valid-before\" time is before \"valid-after\""; goto fail; } /* success */ return ret; fail: if (errstrp != NULL) *errstrp = errstr; sshsigopt_free(ret); return NULL; } void sshsigopt_free(struct sshsigopt *opts) { if (opts == NULL) return; free(opts->namespaces); free(opts); } static int parse_principals_key_and_options(const char *path, u_long linenum, char *line, const char *required_principal, char **principalsp, struct sshkey **keyp, struct sshsigopt **sigoptsp) { char *opts = NULL, *tmp, *cp, *principals = NULL; const char *reason = NULL; struct sshsigopt *sigopts = NULL; struct sshkey *key = NULL; int r = SSH_ERR_INTERNAL_ERROR; if (principalsp != NULL) *principalsp = NULL; if (sigoptsp != NULL) *sigoptsp = NULL; if (keyp != NULL) *keyp = NULL; cp = line; cp = cp + strspn(cp, " \t"); /* skip leading whitespace */ if (*cp == '#' || *cp == '\0') return SSH_ERR_KEY_NOT_FOUND; /* blank or all-comment line */ /* format: identity[,identity...] [option[,option...]] key */ if ((tmp = strdelimw(&cp)) == NULL || cp == NULL) { error("%s:%lu: invalid line", path, linenum); r = SSH_ERR_INVALID_FORMAT; goto out; } if ((principals = strdup(tmp)) == NULL) { error_f("strdup failed"); r = SSH_ERR_ALLOC_FAIL; goto out; } /* * Bail out early if we're looking for a particular principal and this * line does not list it. */ if (required_principal != NULL) { if (match_pattern_list(required_principal, principals, 0) != 1) { /* principal didn't match */ r = SSH_ERR_KEY_NOT_FOUND; goto out; } debug_f("%s:%lu: matched principal \"%s\"", path, linenum, required_principal); } if ((key = sshkey_new(KEY_UNSPEC)) == NULL) { error_f("sshkey_new failed"); r = SSH_ERR_ALLOC_FAIL; goto out; } if (sshkey_read(key, &cp) != 0) { /* no key? Check for options */ opts = cp; if (sshkey_advance_past_options(&cp) != 0) { error("%s:%lu: invalid options", path, linenum); r = SSH_ERR_INVALID_FORMAT; goto out; } if (cp == NULL || *cp == '\0') { error("%s:%lu: missing key", path, linenum); r = SSH_ERR_INVALID_FORMAT; goto out; } *cp++ = '\0'; skip_space(&cp); if (sshkey_read(key, &cp) != 0) { error("%s:%lu: invalid key", path, linenum); r = SSH_ERR_INVALID_FORMAT; goto out; } } debug3("%s:%lu: options %s", path, linenum, opts == NULL ? "" : opts); if ((sigopts = sshsigopt_parse(opts, path, linenum, &reason)) == NULL) { error("%s:%lu: bad options: %s", path, linenum, reason); r = SSH_ERR_INVALID_FORMAT; goto out; } /* success */ if (principalsp != NULL) { *principalsp = principals; principals = NULL; /* transferred */ } if (sigoptsp != NULL) { *sigoptsp = sigopts; sigopts = NULL; /* transferred */ } if (keyp != NULL) { *keyp = key; key = NULL; /* transferred */ } r = 0; out: free(principals); sshsigopt_free(sigopts); sshkey_free(key); return r; } static int cert_filter_principals(const char *path, u_long linenum, char **principalsp, const struct sshkey *cert, uint64_t verify_time) { char *cp, *oprincipals, *principals; const char *reason; struct sshbuf *nprincipals; int r = SSH_ERR_INTERNAL_ERROR, success = 0; u_int i; oprincipals = principals = *principalsp; *principalsp = NULL; if ((nprincipals = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } while ((cp = strsep(&principals, ",")) != NULL && *cp != '\0') { /* Check certificate validity */ if ((r = sshkey_cert_check_authority(cert, 0, 1, 0, verify_time, NULL, &reason)) != 0) { debug("%s:%lu: principal \"%s\" not authorized: %s", path, linenum, cp, reason); continue; } /* Return all matching principal names from the cert */ for (i = 0; i < cert->cert->nprincipals; i++) { if (match_pattern(cert->cert->principals[i], cp)) { if ((r = sshbuf_putf(nprincipals, "%s%s", sshbuf_len(nprincipals) != 0 ? "," : "", cert->cert->principals[i])) != 0) { error_f("buffer error"); goto out; } } } } if (sshbuf_len(nprincipals) == 0) { error("%s:%lu: no valid principals found", path, linenum); r = SSH_ERR_KEY_CERT_INVALID; goto out; } if ((principals = sshbuf_dup_string(nprincipals)) == NULL) { error_f("buffer error"); goto out; } /* success */ success = 1; *principalsp = principals; out: sshbuf_free(nprincipals); free(oprincipals); return success ? 0 : r; } static int check_allowed_keys_line(const char *path, u_long linenum, char *line, const struct sshkey *sign_key, const char *principal, const char *sig_namespace, uint64_t verify_time, char **principalsp) { struct sshkey *found_key = NULL; char *principals = NULL; int r, success = 0; const char *reason = NULL; struct sshsigopt *sigopts = NULL; char tvalid[64], tverify[64]; if (principalsp != NULL) *principalsp = NULL; /* Parse the line */ if ((r = parse_principals_key_and_options(path, linenum, line, principal, &principals, &found_key, &sigopts)) != 0) { /* error already logged */ goto done; } if (!sigopts->ca && sshkey_equal(found_key, sign_key)) { /* Exact match of key */ debug("%s:%lu: matched key", path, linenum); } else if (sigopts->ca && sshkey_is_cert(sign_key) && sshkey_equal_public(sign_key->cert->signature_key, found_key)) { if (principal) { /* Match certificate CA key with specified principal */ if ((r = sshkey_cert_check_authority(sign_key, 0, 1, 0, verify_time, principal, &reason)) != 0) { error("%s:%lu: certificate not authorized: %s", path, linenum, reason); goto done; } debug("%s:%lu: matched certificate CA key", path, linenum); } else { /* No principal specified - find all matching ones */ if ((r = cert_filter_principals(path, linenum, &principals, sign_key, verify_time)) != 0) { /* error already displayed */ debug_r(r, "%s:%lu: cert_filter_principals", path, linenum); goto done; } debug("%s:%lu: matched certificate CA key", path, linenum); } } else { /* Didn't match key */ goto done; } /* Check whether options preclude the use of this key */ if (sigopts->namespaces != NULL && sig_namespace != NULL && match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) { error("%s:%lu: key is not permitted for use in signature " "namespace \"%s\"", path, linenum, sig_namespace); goto done; } /* check key time validity */ format_absolute_time((uint64_t)verify_time, tverify, sizeof(tverify)); if (sigopts->valid_after != 0 && (uint64_t)verify_time < sigopts->valid_after) { format_absolute_time(sigopts->valid_after, tvalid, sizeof(tvalid)); error("%s:%lu: key is not yet valid: " "verify time %s < valid-after %s", path, linenum, tverify, tvalid); goto done; } if (sigopts->valid_before != 0 && (uint64_t)verify_time > sigopts->valid_before) { format_absolute_time(sigopts->valid_before, tvalid, sizeof(tvalid)); error("%s:%lu: key has expired: " "verify time %s > valid-before %s", path, linenum, tverify, tvalid); goto done; } success = 1; done: if (success && principalsp != NULL) { *principalsp = principals; principals = NULL; /* transferred */ } free(principals); sshkey_free(found_key); sshsigopt_free(sigopts); return success ? 0 : SSH_ERR_KEY_NOT_FOUND; } int sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key, const char *principal, const char *sig_namespace, uint64_t verify_time) { FILE *f = NULL; char *line = NULL; size_t linesize = 0; u_long linenum = 0; int r = SSH_ERR_KEY_NOT_FOUND, oerrno; /* Check key and principal against file */ if ((f = fopen(path, "r")) == NULL) { oerrno = errno; error("Unable to open allowed keys file \"%s\": %s", path, strerror(errno)); errno = oerrno; return SSH_ERR_SYSTEM_ERROR; } while (getline(&line, &linesize, f) != -1) { linenum++; r = check_allowed_keys_line(path, linenum, line, sign_key, principal, sig_namespace, verify_time, NULL); free(line); line = NULL; linesize = 0; if (r == SSH_ERR_KEY_NOT_FOUND) continue; else if (r == 0) { /* success */ fclose(f); return 0; } else break; } /* Either we hit an error parsing or we simply didn't find the key */ fclose(f); free(line); return r; } int sshsig_find_principals(const char *path, const struct sshkey *sign_key, uint64_t verify_time, char **principals) { FILE *f = NULL; char *line = NULL; size_t linesize = 0; u_long linenum = 0; int r = SSH_ERR_KEY_NOT_FOUND, oerrno; if ((f = fopen(path, "r")) == NULL) { oerrno = errno; error("Unable to open allowed keys file \"%s\": %s", path, strerror(errno)); errno = oerrno; return SSH_ERR_SYSTEM_ERROR; } while (getline(&line, &linesize, f) != -1) { linenum++; r = check_allowed_keys_line(path, linenum, line, sign_key, NULL, NULL, verify_time, principals); free(line); line = NULL; linesize = 0; if (r == SSH_ERR_KEY_NOT_FOUND) continue; else if (r == 0) { /* success */ fclose(f); return 0; } else break; } free(line); /* Either we hit an error parsing or we simply didn't find the key */ if (ferror(f) != 0) { oerrno = errno; fclose(f); error("Unable to read allowed keys file \"%s\": %s", path, strerror(errno)); errno = oerrno; return SSH_ERR_SYSTEM_ERROR; } fclose(f); return r; } int sshsig_match_principals(const char *path, const char *principal, char ***principalsp, size_t *nprincipalsp) { FILE *f = NULL; char *found, *line = NULL, **principals = NULL, **tmp; size_t i, nprincipals = 0, linesize = 0; u_long linenum = 0; int oerrno = 0, r, ret = 0; if (principalsp != NULL) *principalsp = NULL; if (nprincipalsp != NULL) *nprincipalsp = 0; /* Check key and principal against file */ if ((f = fopen(path, "r")) == NULL) { oerrno = errno; error("Unable to open allowed keys file \"%s\": %s", path, strerror(errno)); errno = oerrno; return SSH_ERR_SYSTEM_ERROR; } while (getline(&line, &linesize, f) != -1) { linenum++; /* Parse the line */ if ((r = parse_principals_key_and_options(path, linenum, line, principal, &found, NULL, NULL)) != 0) { if (r == SSH_ERR_KEY_NOT_FOUND) continue; ret = r; oerrno = errno; break; /* unexpected error */ } if ((tmp = recallocarray(principals, nprincipals, nprincipals + 1, sizeof(*principals))) == NULL) { ret = SSH_ERR_ALLOC_FAIL; free(found); break; } principals = tmp; principals[nprincipals++] = found; /* transferred */ free(line); line = NULL; linesize = 0; } fclose(f); if (ret == 0) { if (nprincipals == 0) ret = SSH_ERR_KEY_NOT_FOUND; if (principalsp != NULL) { *principalsp = principals; principals = NULL; /* transferred */ } if (nprincipalsp != 0) { *nprincipalsp = nprincipals; nprincipals = 0; } } for (i = 0; i < nprincipals; i++) free(principals[i]); free(principals); errno = oerrno; return ret; } int sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey) { struct sshkey *pk = NULL; int r = SSH_ERR_SIGNATURE_INVALID; if (pubkey == NULL) return SSH_ERR_INTERNAL_ERROR; if ((r = sshsig_parse_preamble(signature)) != 0) return r; if ((r = sshkey_froms(signature, &pk)) != 0) return r; *pubkey = pk; pk = NULL; return 0; } diff --git a/crypto/openssh/version.h b/crypto/openssh/version.h index 44109aba3c80..ba3690668818 100644 --- a/crypto/openssh/version.h +++ b/crypto/openssh/version.h @@ -1,8 +1,8 @@ -/* $OpenBSD: version.h,v 1.98 2023/08/10 01:01:07 djm Exp $ */ +/* $OpenBSD: version.h,v 1.99 2023/10/04 04:04:09 djm Exp $ */ -#define SSH_VERSION "OpenSSH_9.4" +#define SSH_VERSION "OpenSSH_9.5" #define SSH_PORTABLE "p1" #define SSH_RELEASE SSH_VERSION SSH_PORTABLE -#define SSH_VERSION_FREEBSD "FreeBSD-20230810" +#define SSH_VERSION_FREEBSD "FreeBSD-20231004"