mirror of
https://github.com/upa/mscp.git
synced 2026-02-14 00:54:43 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf9672f933 | ||
|
|
27ef4a127a | ||
|
|
1eea34ff39 | ||
|
|
11106d64fe | ||
|
|
f5f3323695 | ||
|
|
7ac34ccdde | ||
|
|
18bc88e17c | ||
|
|
f4db06b6bc | ||
|
|
504818909f | ||
|
|
763b47bb47 | ||
|
|
9ab0046b6c | ||
|
|
ecee66d03f | ||
|
|
a0153414f6 | ||
|
|
3f8b107d77 | ||
|
|
6716a71575 | ||
|
|
7fdb4a534e | ||
|
|
7095c45fc7 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build
|
||||||
6
.github/workflows/build-freebsd.yml
vendored
6
.github/workflows/build-freebsd.yml
vendored
@@ -15,13 +15,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: apply the patch to libssh
|
||||||
|
run: |
|
||||||
|
git -C libssh fetch --all --tags --prune
|
||||||
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
- name: Build in FreeBSD
|
- name: Build in FreeBSD
|
||||||
uses: vmactions/freebsd-vm@v1
|
uses: vmactions/freebsd-vm@v1
|
||||||
with:
|
with:
|
||||||
prepare: |
|
prepare: |
|
||||||
pkg install -y git cmake
|
pkg install -y git cmake
|
||||||
run: |
|
run: |
|
||||||
patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
|
||||||
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build build
|
cmake --build build
|
||||||
build/mscp -h
|
build/mscp -h
|
||||||
|
|||||||
8
.github/workflows/build-macos.yml
vendored
8
.github/workflows/build-macos.yml
vendored
@@ -22,6 +22,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: apply the patch to libssh
|
||||||
|
run: |
|
||||||
|
git -C libssh fetch --all --tags --prune
|
||||||
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
- name: install build dependency
|
- name: install build dependency
|
||||||
run: ./scripts/install-build-deps.sh
|
run: ./scripts/install-build-deps.sh
|
||||||
|
|
||||||
@@ -29,9 +34,6 @@ jobs:
|
|||||||
id: brew-prefix
|
id: brew-prefix
|
||||||
run: echo "HOMEBREW_PREFIX=$(brew --prefix)" >> $GITHUB_OUTPUT
|
run: echo "HOMEBREW_PREFIX=$(brew --prefix)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: patch to libssh
|
|
||||||
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
|||||||
8
.github/workflows/build-ubuntu.yml
vendored
8
.github/workflows/build-ubuntu.yml
vendored
@@ -22,14 +22,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: apply the patch to libssh
|
||||||
|
run: |
|
||||||
|
git -C libssh fetch --all --tags --prune
|
||||||
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
- name: install build dependency
|
- name: install build dependency
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo ./scripts/install-build-deps.sh
|
sudo ./scripts/install-build-deps.sh
|
||||||
|
|
||||||
- name: patch to libssh
|
|
||||||
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
|||||||
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@@ -42,14 +42,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: apply the patch to libssh
|
||||||
|
run: |
|
||||||
|
git -C libssh fetch --all --tags --prune
|
||||||
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
- name: install build dependency
|
- name: install build dependency
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo ./scripts/install-build-deps.sh
|
sudo ./scripts/install-build-deps.sh
|
||||||
|
|
||||||
- name: patch to libssh
|
|
||||||
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v3
|
||||||
|
|||||||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
@@ -10,32 +10,6 @@ env:
|
|||||||
BUILD_TYPE: Release
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: patch to libssh
|
|
||||||
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
|
||||||
|
|
||||||
# TODO: just building docker does not require packages. fix CMakeLists
|
|
||||||
- name: install build dependency
|
|
||||||
run: sudo ./scripts/install-build-deps.sh
|
|
||||||
|
|
||||||
- name: Configure Cmake
|
|
||||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
|
||||||
|
|
||||||
- name: Build single binary mscp
|
|
||||||
run: make -C ${{github.workspace}}/build build-single-binary
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
${{github.workspace}}/build/mscp.linux.x86_64.static
|
|
||||||
|
|
||||||
source-release:
|
source-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -43,8 +17,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: patch to libssh
|
- name: apply the patch to libssh
|
||||||
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
run: |
|
||||||
|
git -C libssh fetch --all --tags --prune
|
||||||
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
- name: Set variables
|
- name: Set variables
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -22,15 +22,17 @@ jobs:
|
|||||||
- rocky-8.9
|
- rocky-8.9
|
||||||
- rocky-9.3
|
- rocky-9.3
|
||||||
- almalinux-9.3
|
- almalinux-9.3
|
||||||
- alpine-3.19
|
- alpine-3.22
|
||||||
- arch-base
|
- arch-base
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: patch to libssh
|
- name: apply the patch to libssh
|
||||||
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
|
run: |
|
||||||
|
git -C libssh fetch --all --tags --prune
|
||||||
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
# TODO: just building docker images does not require libssh. fix CMakeLists
|
# TODO: just building docker images does not require libssh. fix CMakeLists
|
||||||
- name: install build dependency
|
- name: install build dependency
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ enable_testing()
|
|||||||
#
|
#
|
||||||
# When edit DIST_IDS and DIST_VERS, also edit .github/workflows/test.yaml
|
# When edit DIST_IDS and DIST_VERS, also edit .github/workflows/test.yaml
|
||||||
list(APPEND DIST_IDS ubuntu ubuntu ubuntu rocky rocky almalinux alpine arch)
|
list(APPEND DIST_IDS ubuntu ubuntu ubuntu rocky rocky almalinux alpine arch)
|
||||||
list(APPEND DIST_VERS 20.04 22.04 24.04 8.9 9.3 9.3 3.19 base)
|
list(APPEND DIST_VERS 20.04 22.04 24.04 8.9 9.3 9.3 3.22 base)
|
||||||
|
|
||||||
list(LENGTH DIST_IDS _DIST_LISTLEN)
|
list(LENGTH DIST_IDS _DIST_LISTLEN)
|
||||||
math(EXPR DIST_LISTLEN "${_DIST_LISTLEN} - 1")
|
math(EXPR DIST_LISTLEN "${_DIST_LISTLEN} - 1")
|
||||||
@@ -208,6 +208,16 @@ foreach(x RANGE ${DIST_LISTLEN})
|
|||||||
--add-host=ip6-localhost:::1
|
--add-host=ip6-localhost:::1
|
||||||
${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh)
|
${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh)
|
||||||
|
|
||||||
|
add_custom_target(docker-run-${DOCKER_INDEX}
|
||||||
|
COMMENT "Start ${DOCKER_IMAGE} container"
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
COMMAND
|
||||||
|
${CE} run --init --rm --privileged
|
||||||
|
--sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
--add-host=ip6-localhost:::1
|
||||||
|
-it
|
||||||
|
${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh bash)
|
||||||
|
|
||||||
list(APPEND DOCKER_BUILDS docker-build-${DOCKER_INDEX})
|
list(APPEND DOCKER_BUILDS docker-build-${DOCKER_INDEX})
|
||||||
list(APPEND DOCKER_BUILDS_NO_CACHE docker-build-${DOCKER_INDEX}-no-cache)
|
list(APPEND DOCKER_BUILDS_NO_CACHE docker-build-${DOCKER_INDEX}-no-cache)
|
||||||
list(APPEND DOCKER_TESTS docker-test-${DOCKER_INDEX})
|
list(APPEND DOCKER_TESTS docker-test-${DOCKER_INDEX})
|
||||||
@@ -278,9 +288,9 @@ add_custom_target(build-single-binary
|
|||||||
COMMENT "Build mscp as a single binary in alpine conatiner"
|
COMMENT "Build mscp as a single binary in alpine conatiner"
|
||||||
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
|
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
|
||||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/${SINGLEBINARYFILE}
|
BYPRODUCTS ${CMAKE_BINARY_DIR}/${SINGLEBINARYFILE}
|
||||||
DEPENDS docker-build-alpine-3.19
|
DEPENDS docker-build-alpine-3.22
|
||||||
COMMAND
|
COMMAND
|
||||||
${CE} run --rm -v ${CMAKE_BINARY_DIR}:/out mscp-alpine:3.19
|
${CE} run --rm -v ${CMAKE_BINARY_DIR}:/out mscp-alpine:3.22
|
||||||
cp /mscp/build/mscp /out/${SINGLEBINARYFILE})
|
cp /mscp/build/mscp /out/${SINGLEBINARYFILE})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.19
|
FROM alpine:3.22
|
||||||
|
|
||||||
# do not use REQUIREDPKGS build argument because
|
# do not use REQUIREDPKGS build argument because
|
||||||
# this Dockerfile compiles mscp with conan,so we do not need
|
# this Dockerfile compiles mscp with conan,so we do not need
|
||||||
@@ -19,6 +19,10 @@ RUN ssh-keygen -A \
|
|||||||
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
|
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
|
||||||
&& cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
|
&& cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
|
||||||
|
|
||||||
|
# disable PerSourcePenaltie, which would distrub test:
|
||||||
|
# https://undeadly.org/cgi?action=article;sid=20240607042157
|
||||||
|
RUN echo "PerSourcePenalties=no" > /etc/ssh/sshd_config.d/90-mscp-test.conf
|
||||||
|
|
||||||
# create test user
|
# create test user
|
||||||
RUN addgroup -S test \
|
RUN addgroup -S test \
|
||||||
&& adduser -S test -G test \
|
&& adduser -S test -G test \
|
||||||
@@ -61,12 +61,6 @@ sudo dnf copr enable upaaa/mscp
|
|||||||
sudo dnf install mscp
|
sudo dnf install mscp
|
||||||
```
|
```
|
||||||
|
|
||||||
- Single binary `mscp` for x86_64 (not optimal performance)
|
|
||||||
```console
|
|
||||||
wget https://github.com/upa/mscp/releases/latest/download/mscp.linux.x86_64.static -O /usr/local/bin/mscp
|
|
||||||
chmod 755 /usr/local/bin/mscp
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -86,7 +80,7 @@ cd mscp
|
|||||||
|
|
||||||
# prepare patched libssh
|
# prepare patched libssh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
patch -d libssh -p1 < patch/$(git --git-dir=./libssh/.git describe).patch
|
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
|
||||||
|
|
||||||
# install build dependency
|
# install build dependency
|
||||||
bash ./scripts/install-build-deps.sh
|
bash ./scripts/install-build-deps.sh
|
||||||
|
|||||||
13
debian/changelog
vendored
13
debian/changelog
vendored
@@ -1,4 +1,15 @@
|
|||||||
mscp (0.2.2) UNRELEASED; urgency=medium
|
mscp (0.2.3) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* bump libssh version to 0.11.2 (#25)
|
||||||
|
* adopt new AIO read API of libssh
|
||||||
|
* fix path handling when remote dst path has suffix '/' (#24)
|
||||||
|
* fix remote path handling including '~' (partially)
|
||||||
|
* try pubkey auth first to avoid PerSourcePenalties
|
||||||
|
* remove the single-binary version of mscp from release
|
||||||
|
|
||||||
|
-- Ryo Nakamura <upa@haeena.net> Tue, 12 Aug 2025 18:11:47 +0900
|
||||||
|
|
||||||
|
mscp (0.2.2) unstable; urgency=medium
|
||||||
|
|
||||||
* bump cmake_minimum_version on libssh (#32)
|
* bump cmake_minimum_version on libssh (#32)
|
||||||
* fix quiet mode (#30)
|
* fix quiet mode (#30)
|
||||||
|
|||||||
2
libssh
2
libssh
Submodule libssh updated: 6f1b1e76bb...dff6c0821e
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
Patches in this directory introduces `sftp_async_write()` and
|
Patches in this directory introduce enhancements for libssh including
|
||||||
`sftp_async_write_end()` to libssh. Those implementations are derived
|
`sftp_async_write()` and `sftp_async_write_end()`, derived from
|
||||||
from https://github.com/limes-datentechnik-gmbh/libssh. See [Re: SFTP
|
https://github.com/limes-datentechnik-gmbh/libssh. See [Re: SFTP Write
|
||||||
Write async](https://archive.libssh.org/libssh/2020-06/0000004.html).
|
async](https://archive.libssh.org/libssh/2020-06/0000004.html).
|
||||||
|
|
||||||
|
|||||||
605
patch/libssh-0.11.2.patch
Normal file
605
patch/libssh-0.11.2.patch
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
|
||||||
|
index 8765dc6e..766e7d16 100644
|
||||||
|
--- a/ConfigureChecks.cmake
|
||||||
|
+++ b/ConfigureChecks.cmake
|
||||||
|
@@ -209,6 +209,7 @@ if (UNIX)
|
||||||
|
check_library_exists(util forkpty "" HAVE_LIBUTIL)
|
||||||
|
check_function_exists(cfmakeraw HAVE_CFMAKERAW)
|
||||||
|
check_function_exists(__strtoull HAVE___STRTOULL)
|
||||||
|
+ check_symbol_exists(TCP_CONGESTION "netinet/tcp.h" HAVE_TCP_CONGESTION)
|
||||||
|
endif (UNIX)
|
||||||
|
|
||||||
|
set(LIBSSH_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} CACHE INTERNAL "libssh required system libraries")
|
||||||
|
diff --git a/config.h.cmake b/config.h.cmake
|
||||||
|
index 8dce5273..ef534762 100644
|
||||||
|
--- a/config.h.cmake
|
||||||
|
+++ b/config.h.cmake
|
||||||
|
@@ -219,6 +219,8 @@
|
||||||
|
|
||||||
|
#cmakedefine HAVE_GCC_BOUNDED_ATTRIBUTE 1
|
||||||
|
|
||||||
|
+#cmakedefine HAVE_TCP_CONGESTION 1
|
||||||
|
+
|
||||||
|
/* Define to 1 if you want to enable GSSAPI */
|
||||||
|
#cmakedefine WITH_GSSAPI 1
|
||||||
|
|
||||||
|
diff --git a/include/libssh/buffer.h b/include/libssh/buffer.h
|
||||||
|
index d22178e7..2d6aa0a7 100644
|
||||||
|
--- a/include/libssh/buffer.h
|
||||||
|
+++ b/include/libssh/buffer.h
|
||||||
|
@@ -37,6 +37,8 @@ int ssh_buffer_add_u8(ssh_buffer buffer, uint8_t data);
|
||||||
|
int ssh_buffer_add_u16(ssh_buffer buffer, uint16_t data);
|
||||||
|
int ssh_buffer_add_u32(ssh_buffer buffer, uint32_t data);
|
||||||
|
int ssh_buffer_add_u64(ssh_buffer buffer, uint64_t data);
|
||||||
|
+ssize_t ssh_buffer_add_func(ssh_buffer buffer, ssh_add_func f, size_t max_bytes,
|
||||||
|
+ void *userdata);
|
||||||
|
|
||||||
|
int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len);
|
||||||
|
|
||||||
|
diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h
|
||||||
|
index 3bddb019..1d5d7761 100644
|
||||||
|
--- a/include/libssh/libssh.h
|
||||||
|
+++ b/include/libssh/libssh.h
|
||||||
|
@@ -373,6 +373,7 @@ enum ssh_options_e {
|
||||||
|
SSH_OPTIONS_HOST,
|
||||||
|
SSH_OPTIONS_PORT,
|
||||||
|
SSH_OPTIONS_PORT_STR,
|
||||||
|
+ SSH_OPTIONS_AI_FAMILY,
|
||||||
|
SSH_OPTIONS_FD,
|
||||||
|
SSH_OPTIONS_USER,
|
||||||
|
SSH_OPTIONS_SSH_DIR,
|
||||||
|
@@ -407,6 +408,7 @@ enum ssh_options_e {
|
||||||
|
SSH_OPTIONS_GSSAPI_AUTH,
|
||||||
|
SSH_OPTIONS_GLOBAL_KNOWNHOSTS,
|
||||||
|
SSH_OPTIONS_NODELAY,
|
||||||
|
+ SSH_OPTIONS_CCALGO,
|
||||||
|
SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
|
||||||
|
SSH_OPTIONS_PROCESS_CONFIG,
|
||||||
|
SSH_OPTIONS_REKEY_DATA,
|
||||||
|
@@ -876,6 +878,7 @@ LIBSSH_API const char* ssh_get_hmac_in(ssh_session session);
|
||||||
|
LIBSSH_API const char* ssh_get_hmac_out(ssh_session session);
|
||||||
|
|
||||||
|
LIBSSH_API ssh_buffer ssh_buffer_new(void);
|
||||||
|
+LIBSSH_API ssh_buffer ssh_buffer_new_size(uint32_t size, uint32_t headroom);
|
||||||
|
LIBSSH_API void ssh_buffer_free(ssh_buffer buffer);
|
||||||
|
#define SSH_BUFFER_FREE(x) \
|
||||||
|
do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0)
|
||||||
|
@@ -886,6 +889,12 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer);
|
||||||
|
LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer);
|
||||||
|
LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message);
|
||||||
|
|
||||||
|
+typedef ssize_t (*ssh_add_func) (void *ptr, size_t max_bytes, void *userdata);
|
||||||
|
+
|
||||||
|
+LIBSSH_API const char **ssh_ciphers(void);
|
||||||
|
+LIBSSH_API const char **ssh_hmacs(void);
|
||||||
|
+LIBSSH_API void ssh_use_openssh_proxy_jumps(int);
|
||||||
|
+
|
||||||
|
#ifndef LIBSSH_LEGACY_0_4
|
||||||
|
#include "libssh/legacy.h"
|
||||||
|
#endif
|
||||||
|
diff --git a/include/libssh/session.h b/include/libssh/session.h
|
||||||
|
index aed94072..327cf4fe 100644
|
||||||
|
--- a/include/libssh/session.h
|
||||||
|
+++ b/include/libssh/session.h
|
||||||
|
@@ -255,6 +255,7 @@ struct ssh_session_struct {
|
||||||
|
unsigned long timeout; /* seconds */
|
||||||
|
unsigned long timeout_usec;
|
||||||
|
uint16_t port;
|
||||||
|
+ int ai_family;
|
||||||
|
socket_t fd;
|
||||||
|
int StrictHostKeyChecking;
|
||||||
|
char compressionlevel;
|
||||||
|
@@ -264,6 +265,7 @@ struct ssh_session_struct {
|
||||||
|
int flags;
|
||||||
|
int exp_flags;
|
||||||
|
int nodelay;
|
||||||
|
+ char *ccalgo;
|
||||||
|
bool config_processed;
|
||||||
|
uint8_t options_seen[SOC_MAX];
|
||||||
|
uint64_t rekey_data;
|
||||||
|
diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h
|
||||||
|
index cf4458c3..1a864795 100644
|
||||||
|
--- a/include/libssh/sftp.h
|
||||||
|
+++ b/include/libssh/sftp.h
|
||||||
|
@@ -569,6 +569,10 @@ SSH_DEPRECATED LIBSSH_API int sftp_async_read(sftp_file file,
|
||||||
|
uint32_t len,
|
||||||
|
uint32_t id);
|
||||||
|
|
||||||
|
+LIBSSH_API ssize_t sftp_async_write(sftp_file file, ssh_add_func f, size_t count,
|
||||||
|
+ void *userdata, uint32_t* id);
|
||||||
|
+LIBSSH_API int sftp_async_write_end(sftp_file file, uint32_t id, int blocking);
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @brief Write to a file using an opened sftp file handle.
|
||||||
|
*
|
||||||
|
diff --git a/src/buffer.c b/src/buffer.c
|
||||||
|
index 449fa941..f49e8af6 100644
|
||||||
|
--- a/src/buffer.c
|
||||||
|
+++ b/src/buffer.c
|
||||||
|
@@ -142,6 +142,40 @@ struct ssh_buffer_struct *ssh_buffer_new(void)
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * @brief Create a new SSH buffer with a specified size and headroom.
|
||||||
|
+ *
|
||||||
|
+ * @param[in] len length for newly initialized SSH buffer.
|
||||||
|
+ * @param[in] headroom length for headroom
|
||||||
|
+ * @return A newly initialized SSH buffer, NULL on error.
|
||||||
|
+ */
|
||||||
|
+struct ssh_buffer_struct *ssh_buffer_new_size(uint32_t len, uint32_t headroom)
|
||||||
|
+{
|
||||||
|
+ struct ssh_buffer_struct *buf = NULL;
|
||||||
|
+ int rc;
|
||||||
|
+
|
||||||
|
+ if (len < headroom)
|
||||||
|
+ return NULL;
|
||||||
|
+
|
||||||
|
+ buf = calloc(1, sizeof(struct ssh_buffer_struct));
|
||||||
|
+ if (buf == NULL) {
|
||||||
|
+ return NULL;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ rc = ssh_buffer_allocate_size(buf, len);
|
||||||
|
+ if (rc != 0) {
|
||||||
|
+ SAFE_FREE(buf);
|
||||||
|
+ return NULL;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ buf->pos += headroom;
|
||||||
|
+ buf->used += headroom;
|
||||||
|
+
|
||||||
|
+ buffer_verify(buf);
|
||||||
|
+
|
||||||
|
+ return buf;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @brief Deallocate a SSH buffer.
|
||||||
|
*
|
||||||
|
@@ -329,6 +363,49 @@ int ssh_buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * @brief Add data at the tail of a buffer by an external function
|
||||||
|
+ *
|
||||||
|
+ * @param[in] buffer The buffer to add data.
|
||||||
|
+ *
|
||||||
|
+ * @param[in] f function that adds data to the buffer.
|
||||||
|
+ *
|
||||||
|
+ * @param[in] max_bytes The maximum length of the data to add.
|
||||||
|
+ *
|
||||||
|
+ * @return actual bytes added on success, < 0 on error.
|
||||||
|
+ */
|
||||||
|
+ssize_t ssh_buffer_add_func(struct ssh_buffer_struct *buffer, ssh_add_func f,
|
||||||
|
+ size_t max_bytes, void *userdata)
|
||||||
|
+{
|
||||||
|
+ ssize_t actual;
|
||||||
|
+
|
||||||
|
+ if (buffer == NULL) {
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ buffer_verify(buffer);
|
||||||
|
+
|
||||||
|
+ if (buffer->used + max_bytes < max_bytes) {
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (buffer->allocated < (buffer->used + max_bytes)) {
|
||||||
|
+ if (buffer->pos > 0) {
|
||||||
|
+ buffer_shift(buffer);
|
||||||
|
+ }
|
||||||
|
+ if (realloc_buffer(buffer, buffer->used + max_bytes) < 0) {
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if ((actual = f(buffer->data + buffer->used, max_bytes, userdata)) < 0)
|
||||||
|
+ return -1;
|
||||||
|
+
|
||||||
|
+ buffer->used += actual;
|
||||||
|
+ buffer_verify(buffer);
|
||||||
|
+ return actual;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @brief Ensure the buffer has at least a certain preallocated size.
|
||||||
|
*
|
||||||
|
diff --git a/src/connect.c b/src/connect.c
|
||||||
|
index 2cb64037..51f4c87e 100644
|
||||||
|
--- a/src/connect.c
|
||||||
|
+++ b/src/connect.c
|
||||||
|
@@ -109,7 +109,7 @@ static int ssh_connect_socket_close(socket_t s)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
-static int getai(const char *host, int port, struct addrinfo **ai)
|
||||||
|
+static int getai(const char *host, int port, int ai_family, struct addrinfo **ai)
|
||||||
|
{
|
||||||
|
const char *service = NULL;
|
||||||
|
struct addrinfo hints;
|
||||||
|
@@ -118,7 +118,7 @@ static int getai(const char *host, int port, struct addrinfo **ai)
|
||||||
|
ZERO_STRUCT(hints);
|
||||||
|
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
- hints.ai_family = PF_UNSPEC;
|
||||||
|
+ hints.ai_family = ai_family > 0 ? ai_family : PF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
if (port == 0) {
|
||||||
|
@@ -151,6 +151,20 @@ static int set_tcp_nodelay(socket_t socket)
|
||||||
|
sizeof(opt));
|
||||||
|
}
|
||||||
|
|
||||||
|
+static int set_tcp_ccalgo(socket_t socket, const char *ccalgo)
|
||||||
|
+{
|
||||||
|
+#ifdef HAVE_TCP_CONGESTION
|
||||||
|
+ return setsockopt(socket,
|
||||||
|
+ IPPROTO_TCP,
|
||||||
|
+ TCP_CONGESTION,
|
||||||
|
+ (void *)ccalgo,
|
||||||
|
+ strlen(ccalgo));
|
||||||
|
+#else
|
||||||
|
+ errno = ENOTSUP;
|
||||||
|
+ return -1;
|
||||||
|
+#endif
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
@@ -168,7 +182,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
|
||||||
|
struct addrinfo *ai = NULL;
|
||||||
|
struct addrinfo *itr = NULL;
|
||||||
|
|
||||||
|
- rc = getai(host, port, &ai);
|
||||||
|
+ rc = getai(host, port, session->opts.ai_family, &ai);
|
||||||
|
if (rc != 0) {
|
||||||
|
ssh_set_error(session, SSH_FATAL,
|
||||||
|
"Failed to resolve hostname %s (%s)",
|
||||||
|
@@ -194,7 +208,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
|
||||||
|
|
||||||
|
SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr);
|
||||||
|
|
||||||
|
- rc = getai(bind_addr, 0, &bind_ai);
|
||||||
|
+ rc = getai(bind_addr, 0, session->opts.ai_family, &bind_ai);
|
||||||
|
if (rc != 0) {
|
||||||
|
ssh_set_error(session, SSH_FATAL,
|
||||||
|
"Failed to resolve bind address %s (%s)",
|
||||||
|
@@ -251,6 +265,18 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (session->opts.ccalgo) {
|
||||||
|
+ rc = set_tcp_ccalgo(s, session->opts.ccalgo);
|
||||||
|
+ if (rc < 0) {
|
||||||
|
+ ssh_set_error(session, SSH_FATAL,
|
||||||
|
+ "Failed to set TCP_CONGESTION on socket: %s",
|
||||||
|
+ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||||
|
+ ssh_connect_socket_close(s);
|
||||||
|
+ s = -1;
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
errno = 0;
|
||||||
|
rc = connect(s, itr->ai_addr, itr->ai_addrlen);
|
||||||
|
if (rc == -1) {
|
||||||
|
diff --git a/src/misc.c b/src/misc.c
|
||||||
|
index 774211fb..74e57959 100644
|
||||||
|
--- a/src/misc.c
|
||||||
|
+++ b/src/misc.c
|
||||||
|
@@ -71,6 +71,8 @@
|
||||||
|
#include "libssh/priv.h"
|
||||||
|
#include "libssh/misc.h"
|
||||||
|
#include "libssh/session.h"
|
||||||
|
+#include "libssh/wrapper.h"
|
||||||
|
+#include "libssh/crypto.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBGCRYPT
|
||||||
|
#define GCRYPT_STRING "/gcrypt"
|
||||||
|
@@ -2054,6 +2056,42 @@ ssize_t ssh_readn(int fd, void *buf, size_t nbytes)
|
||||||
|
return total_bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * @brief Return supported cipher names
|
||||||
|
+ * @return The list of cipher names.
|
||||||
|
+ */
|
||||||
|
+const char **ssh_ciphers(void)
|
||||||
|
+{
|
||||||
|
+ struct ssh_cipher_struct *tab=ssh_get_ciphertab();
|
||||||
|
+ static const char *ciphers[32];
|
||||||
|
+ int n;
|
||||||
|
+
|
||||||
|
+ memset(ciphers, 0, sizeof(*ciphers));
|
||||||
|
+
|
||||||
|
+ for (n = 0; tab[n].name != NULL; n++) {
|
||||||
|
+ ciphers[n] = tab[n].name;
|
||||||
|
+ }
|
||||||
|
+ return ciphers;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/**
|
||||||
|
+ * @brief Return supported hmac names
|
||||||
|
+ * @return The list of hmac names.
|
||||||
|
+ */
|
||||||
|
+const char **ssh_hmacs(void)
|
||||||
|
+{
|
||||||
|
+ struct ssh_hmac_struct *tab=ssh_get_hmactab();
|
||||||
|
+ static const char *hmacs[32];
|
||||||
|
+ int n;
|
||||||
|
+
|
||||||
|
+ memset(hmacs, 0, sizeof(*hmacs));
|
||||||
|
+
|
||||||
|
+ for (n = 0; tab[n].name != NULL; n++) {
|
||||||
|
+ hmacs[n] = tab[n].name;
|
||||||
|
+ }
|
||||||
|
+ return hmacs;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @brief Write the requested number of bytes to a local file.
|
||||||
|
*
|
||||||
|
@@ -2227,6 +2265,17 @@ ssh_proxyjumps_free(struct ssh_list *proxy_jump_list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+static bool force_openssh_proxy_jumps;
|
||||||
|
+
|
||||||
|
+/**
|
||||||
|
+ * @breif set use openssh proxy jumps without the OPENSSH_PROXYJUMP env var
|
||||||
|
+ */
|
||||||
|
+void
|
||||||
|
+ssh_use_openssh_proxy_jumps(int v)
|
||||||
|
+{
|
||||||
|
+ force_openssh_proxy_jumps = (v > 0);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @brief Check if libssh proxy jumps is enabled
|
||||||
|
*
|
||||||
|
@@ -2241,7 +2290,12 @@ ssh_libssh_proxy_jumps(void)
|
||||||
|
{
|
||||||
|
const char *t = getenv("OPENSSH_PROXYJUMP");
|
||||||
|
|
||||||
|
+ if (force_openssh_proxy_jumps)
|
||||||
|
+ return false;
|
||||||
|
+
|
||||||
|
return !(t != NULL && t[0] == '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
+
|
||||||
|
+
|
||||||
|
/** @} */
|
||||||
|
diff --git a/src/options.c b/src/options.c
|
||||||
|
index 785296dd..a82d4d81 100644
|
||||||
|
--- a/src/options.c
|
||||||
|
+++ b/src/options.c
|
||||||
|
@@ -251,6 +251,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
|
||||||
|
new->opts.gss_delegate_creds = src->opts.gss_delegate_creds;
|
||||||
|
new->opts.flags = src->opts.flags;
|
||||||
|
new->opts.nodelay = src->opts.nodelay;
|
||||||
|
+ new->opts.ccalgo = src->opts.ccalgo;
|
||||||
|
new->opts.config_processed = src->opts.config_processed;
|
||||||
|
new->opts.control_master = src->opts.control_master;
|
||||||
|
new->common.log_verbosity = src->common.log_verbosity;
|
||||||
|
@@ -326,6 +327,9 @@ int ssh_options_set_algo(ssh_session session,
|
||||||
|
* - SSH_OPTIONS_PORT_STR:
|
||||||
|
* The port to connect to (const char *).
|
||||||
|
*
|
||||||
|
+ * - SSH_OPTIONS_AI_FAMILY:
|
||||||
|
+ * The address family for connecting (int *).
|
||||||
|
+ *
|
||||||
|
* - SSH_OPTIONS_FD:
|
||||||
|
* The file descriptor to use (socket_t).\n
|
||||||
|
* \n
|
||||||
|
@@ -571,6 +575,10 @@ int ssh_options_set_algo(ssh_session session,
|
||||||
|
* Set it to disable Nagle's Algorithm (TCP_NODELAY) on the
|
||||||
|
* session socket. (int, 0=false)
|
||||||
|
*
|
||||||
|
+ * - SSH_OPTIONS_CCALGO
|
||||||
|
+ * Set it to specify TCP congestion control algorithm on the
|
||||||
|
+ * session socket (Linux only). (int, 0=false)
|
||||||
|
+ *
|
||||||
|
* - SSH_OPTIONS_PROCESS_CONFIG
|
||||||
|
* Set it to false to disable automatic processing of per-user
|
||||||
|
* and system-wide OpenSSH configuration files. LibSSH
|
||||||
|
@@ -727,6 +735,21 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
|
||||||
|
session->opts.port = i & 0xffffU;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
+ case SSH_OPTIONS_AI_FAMILY:
|
||||||
|
+ if (value == NULL) {
|
||||||
|
+ session->opts.ai_family = 0;
|
||||||
|
+ ssh_set_error_invalid(session);
|
||||||
|
+ return -1;
|
||||||
|
+ } else {
|
||||||
|
+ int *x = (int *) value;
|
||||||
|
+ if (*x < 0) {
|
||||||
|
+ session->opts.ai_family = 0;
|
||||||
|
+ ssh_set_error_invalid(session);
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+ session->opts.ai_family = *x;
|
||||||
|
+ }
|
||||||
|
+ break;
|
||||||
|
case SSH_OPTIONS_FD:
|
||||||
|
if (value == NULL) {
|
||||||
|
session->opts.fd = SSH_INVALID_SOCKET;
|
||||||
|
@@ -1241,6 +1264,20 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
|
||||||
|
session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
+ case SSH_OPTIONS_CCALGO:
|
||||||
|
+ v = value;
|
||||||
|
+ if (v == NULL || v[0] == '\0') {
|
||||||
|
+ ssh_set_error_invalid(session);
|
||||||
|
+ return -1;
|
||||||
|
+ } else {
|
||||||
|
+ SAFE_FREE(session->opts.ccalgo);
|
||||||
|
+ session->opts.ccalgo = strdup(v);
|
||||||
|
+ if (session->opts.ccalgo == NULL) {
|
||||||
|
+ ssh_set_error_oom(session);
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ break;
|
||||||
|
case SSH_OPTIONS_PROCESS_CONFIG:
|
||||||
|
if (value == NULL) {
|
||||||
|
ssh_set_error_invalid(session);
|
||||||
|
diff --git a/src/session.c b/src/session.c
|
||||||
|
index 9fd5d946..ed9f908e 100644
|
||||||
|
--- a/src/session.c
|
||||||
|
+++ b/src/session.c
|
||||||
|
@@ -107,9 +107,11 @@ ssh_session ssh_new(void)
|
||||||
|
/* OPTIONS */
|
||||||
|
session->opts.StrictHostKeyChecking = 1;
|
||||||
|
session->opts.port = 22;
|
||||||
|
+ session->opts.ai_family = 0;
|
||||||
|
session->opts.fd = -1;
|
||||||
|
session->opts.compressionlevel = 7;
|
||||||
|
session->opts.nodelay = 0;
|
||||||
|
+ session->opts.ccalgo = NULL;
|
||||||
|
session->opts.identities_only = false;
|
||||||
|
session->opts.control_master = SSH_CONTROL_MASTER_NO;
|
||||||
|
|
||||||
|
diff --git a/src/sftp.c b/src/sftp.c
|
||||||
|
index 37b4133b..12b6d296 100644
|
||||||
|
--- a/src/sftp.c
|
||||||
|
+++ b/src/sftp.c
|
||||||
|
@@ -1488,6 +1488,132 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
|
||||||
|
return -1; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
+/*
|
||||||
|
+ * sftp_async_write is based on and sftp_async_write_end is copied from
|
||||||
|
+ * https://github.com/limes-datentechnik-gmbh/libssh
|
||||||
|
+ *
|
||||||
|
+ * sftp_async_write has some optimizations:
|
||||||
|
+ * - use ssh_buffer_new_size() to reduce realoc_buffer.
|
||||||
|
+ * - use ssh_buffer_add_func() to avoid memcpy from read buffer to ssh buffer.
|
||||||
|
+ */
|
||||||
|
+ssize_t sftp_async_write(sftp_file file, ssh_add_func f, size_t count, void *userdata,
|
||||||
|
+ uint32_t* id) {
|
||||||
|
+ sftp_session sftp = file->sftp;
|
||||||
|
+ ssh_buffer buffer;
|
||||||
|
+ uint32_t buf_sz;
|
||||||
|
+ ssize_t actual;
|
||||||
|
+ int len;
|
||||||
|
+ int packetlen;
|
||||||
|
+ int rc;
|
||||||
|
+
|
||||||
|
+#define HEADROOM 16
|
||||||
|
+ /* sftp_packet_write() prepends a 5-bytes (uint32_t length and
|
||||||
|
+ * 1-byte type) header to the head of the payload by
|
||||||
|
+ * ssh_buffer_prepend_data(). Inserting headroom by
|
||||||
|
+ * ssh_buffer_new_size() eliminates memcpy for prepending the
|
||||||
|
+ * header.
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+ buf_sz = (HEADROOM + /* for header */
|
||||||
|
+ sizeof(uint32_t) + /* id */
|
||||||
|
+ ssh_string_len(file->handle) + 4 + /* file->handle */
|
||||||
|
+ sizeof(uint64_t) + /* file->offset */
|
||||||
|
+ sizeof(uint32_t) + /* count */
|
||||||
|
+ count); /* datastring */
|
||||||
|
+
|
||||||
|
+ buffer = ssh_buffer_new_size(buf_sz, HEADROOM);
|
||||||
|
+ if (buffer == NULL) {
|
||||||
|
+ ssh_set_error_oom(sftp->session);
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ *id = sftp_get_new_id(file->sftp);
|
||||||
|
+
|
||||||
|
+ rc = ssh_buffer_pack(buffer,
|
||||||
|
+ "dSqd",
|
||||||
|
+ *id,
|
||||||
|
+ file->handle,
|
||||||
|
+ file->offset,
|
||||||
|
+ count); /* len of datastring */
|
||||||
|
+
|
||||||
|
+ if (rc != SSH_OK){
|
||||||
|
+ ssh_set_error_oom(sftp->session);
|
||||||
|
+ ssh_buffer_free(buffer);
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ actual = ssh_buffer_add_func(buffer, f, count, userdata);
|
||||||
|
+ if (actual < 0){
|
||||||
|
+ ssh_set_error_oom(sftp->session);
|
||||||
|
+ ssh_buffer_free(buffer);
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ packetlen=ssh_buffer_get_len(buffer)+5;
|
||||||
|
+ len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer);
|
||||||
|
+ ssh_buffer_free(buffer);
|
||||||
|
+ if (len < 0) {
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ } else if (len != packetlen) {
|
||||||
|
+ ssh_set_error(sftp->session, SSH_FATAL,
|
||||||
|
+ "Could only send %d of %d bytes to remote host!", len, packetlen);
|
||||||
|
+ SSH_LOG(SSH_LOG_PACKET,
|
||||||
|
+ "Could not write as much data as expected");
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ file->offset += actual;
|
||||||
|
+
|
||||||
|
+ return actual;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+int sftp_async_write_end(sftp_file file, uint32_t id, int blocking) {
|
||||||
|
+ sftp_session sftp = file->sftp;
|
||||||
|
+ sftp_message msg = NULL;
|
||||||
|
+ sftp_status_message status;
|
||||||
|
+
|
||||||
|
+ msg = sftp_dequeue(sftp, id);
|
||||||
|
+ while (msg == NULL) {
|
||||||
|
+ if (!blocking && ssh_channel_poll(sftp->channel, 0) == 0) {
|
||||||
|
+ /* we cannot block */
|
||||||
|
+ return SSH_AGAIN;
|
||||||
|
+ }
|
||||||
|
+ if (sftp_read_and_dispatch(sftp) < 0) {
|
||||||
|
+ /* something nasty has happened */
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ }
|
||||||
|
+ msg = sftp_dequeue(sftp, id);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ switch (msg->packet_type) {
|
||||||
|
+ case SSH_FXP_STATUS:
|
||||||
|
+ status = parse_status_msg(msg);
|
||||||
|
+ sftp_message_free(msg);
|
||||||
|
+ if (status == NULL) {
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ }
|
||||||
|
+ sftp_set_error(sftp, status->status);
|
||||||
|
+ switch (status->status) {
|
||||||
|
+ case SSH_FX_OK:
|
||||||
|
+ status_msg_free(status);
|
||||||
|
+ return SSH_OK;
|
||||||
|
+ default:
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
|
||||||
|
+ "SFTP server: %s", status->errormsg);
|
||||||
|
+ status_msg_free(status);
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ default:
|
||||||
|
+ ssh_set_error(sftp->session, SSH_FATAL,
|
||||||
|
+ "Received message %d during write!", msg->packet_type);
|
||||||
|
+ sftp_message_free(msg);
|
||||||
|
+ return SSH_ERROR;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return SSH_ERROR; /* not reached */
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/* Seek to a specific location in a file. */
|
||||||
|
int sftp_seek(sftp_file file, uint32_t new_offset) {
|
||||||
|
if (file == NULL) {
|
||||||
@@ -38,6 +38,9 @@ make -C build install DESTDIR=%{buildroot}
|
|||||||
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Aug 12 2025 Ryo Nakamura <upa@haeena.net> - 0.2.3-1
|
||||||
|
- RPM release for v0.2.3
|
||||||
|
|
||||||
* Wed Apr 16 2025 Ryo Nakamura <upa@haeena.net> - 0.2.2-1
|
* Wed Apr 16 2025 Ryo Nakamura <upa@haeena.net> - 0.2.2-1
|
||||||
- RPM release for v0.2.2
|
- RPM release for v0.2.2
|
||||||
|
|
||||||
|
|||||||
@@ -28,5 +28,11 @@ for port in 22 8022; do
|
|||||||
ssh-keyscan -p $port ::1 >> ${HOME}/.ssh/known_hosts
|
ssh-keyscan -p $port ::1 >> ${HOME}/.ssh/known_hosts
|
||||||
done
|
done
|
||||||
|
|
||||||
# Run test
|
|
||||||
python3 -m pytest -v ../test
|
if [ $# -gt 0 ]; then
|
||||||
|
# command arguments are passed, exec them
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
# no arguments passed, run the test
|
||||||
|
python3 -m pytest -v ../test
|
||||||
|
fi
|
||||||
|
|||||||
32
src/main.c
32
src/main.c
@@ -205,7 +205,7 @@ struct target *validate_targets(char **arg, int len)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
struct target *t, *t0;
|
struct target *t, *t0;
|
||||||
int n;
|
int n, nslash;
|
||||||
|
|
||||||
if ((t = calloc(len, sizeof(struct target))) == NULL) {
|
if ((t = calloc(len, sizeof(struct target))) == NULL) {
|
||||||
pr_err("calloc: %s", strerrno());
|
pr_err("calloc: %s", strerrno());
|
||||||
@@ -223,9 +223,33 @@ struct target *validate_targets(char **arg, int len)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check all user@host are identical. t[len - 1] is destination,
|
/* expand remote path, e.g., empty dst path and '~' */
|
||||||
* so we need to check t[0] to t[len - 2] having the identical
|
for (n = 0; n < len; n++) {
|
||||||
* remote notation */
|
if (!t[n].host)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* this target is a remote path. check the path and
|
||||||
|
* expand it. this part is derived from
|
||||||
|
* openssh-portal prepare_remote_path() function.
|
||||||
|
*/
|
||||||
|
char *path = t[n].path;
|
||||||
|
if (*path == '\0' || strcmp(path, "~") == 0)
|
||||||
|
t[n].path = strdup(".");
|
||||||
|
else if (strncmp(path, "~/", 2) == 0) {
|
||||||
|
if ((nslash = strspn(path + 2, "/")) == strlen(path + 2))
|
||||||
|
t[n].path = strdup(".");
|
||||||
|
else
|
||||||
|
t[n].path = strdup(path + 2 + nslash);
|
||||||
|
}
|
||||||
|
if (!t[n].path) {
|
||||||
|
pr_err("strdup failed: %s", strerrno());
|
||||||
|
goto free_target_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check all user@host are identical. t[len - 1] is the
|
||||||
|
* destination, so we need to check t[0] to t[len - 2] having
|
||||||
|
* the identical remote notation */
|
||||||
t0 = &t[0];
|
t0 = &t[0];
|
||||||
for (n = 1; n < len - 1; n++) {
|
for (n = 1; n < len - 1; n++) {
|
||||||
if (compare_remote(t0, &t[n]) != 0)
|
if (compare_remote(t0, &t[n]) != 0)
|
||||||
|
|||||||
20
src/mscp.c
20
src/mscp.c
@@ -275,6 +275,13 @@ struct mscp *mscp_init(struct mscp_opts *o, struct mscp_ssh_opts *s)
|
|||||||
}
|
}
|
||||||
pr_notice("bitrate limit: %lu bps", o->bitrate);
|
pr_notice("bitrate limit: %lu bps", o->bitrate);
|
||||||
|
|
||||||
|
/* workaround: set libssh using openssh proxyjump
|
||||||
|
* https://gitlab.com/libssh/libssh-mirror/-/issues/319 */
|
||||||
|
ssh_use_openssh_proxy_jumps(1);
|
||||||
|
|
||||||
|
/* call ssh_init() because libssh is statically linked */
|
||||||
|
ssh_init();
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
|
|
||||||
free_out:
|
free_out:
|
||||||
@@ -322,10 +329,12 @@ int mscp_set_dst_path(struct mscp *m, const char *dst_path)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!non_null_string(dst_path))
|
if (!non_null_string(dst_path)) {
|
||||||
strncpy(m->dst_path, ".", 1);
|
priv_set_errv("empty dst path");
|
||||||
else
|
return -1;
|
||||||
strncpy(m->dst_path, dst_path, PATH_MAX);
|
}
|
||||||
|
|
||||||
|
strncpy(m->dst_path, dst_path, PATH_MAX);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -393,6 +402,9 @@ void *mscp_scan_thread(void *arg)
|
|||||||
if (pool_size(m->src_pool) > 1)
|
if (pool_size(m->src_pool) > 1)
|
||||||
a.dst_path_should_dir = true;
|
a.dst_path_should_dir = true;
|
||||||
|
|
||||||
|
if (m->dst_path[strlen(m->dst_path) - 1] == '/')
|
||||||
|
a.dst_path_should_dir = true;
|
||||||
|
|
||||||
if (mscp_stat(m->dst_path, &ds, dst_sftp) == 0) {
|
if (mscp_stat(m->dst_path, &ds, dst_sftp) == 0) {
|
||||||
if (S_ISDIR(ds.st_mode))
|
if (S_ISDIR(ds.st_mode))
|
||||||
a.dst_path_is_dir = true;
|
a.dst_path_is_dir = true;
|
||||||
|
|||||||
53
src/path.c
53
src/path.c
@@ -410,46 +410,45 @@ static int copy_chunk_l2r(struct chunk *c, int fd, sftp_file sf, int nr_ahead, i
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd, int nr_ahead, int buf_sz,
|
static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd,
|
||||||
struct bwlimit *bw, size_t *counter)
|
int nr_ahead, int buf_sz,
|
||||||
|
struct bwlimit *bw, size_t *counter)
|
||||||
{
|
{
|
||||||
ssize_t read_bytes, write_bytes, remaind, thrown;
|
ssize_t read_bytes, write_bytes, remain, thrown, len, requested;
|
||||||
|
sftp_aio reqs[nr_ahead];
|
||||||
char buf[buf_sz];
|
char buf[buf_sz];
|
||||||
int idx;
|
int i;
|
||||||
struct {
|
|
||||||
int id;
|
|
||||||
ssize_t len;
|
|
||||||
} reqs[nr_ahead];
|
|
||||||
|
|
||||||
if (c->len == 0)
|
if (c->len == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
remaind = thrown = c->len;
|
remain = thrown = c->len;
|
||||||
|
|
||||||
for (idx = 0; idx < nr_ahead && thrown > 0; idx++) {
|
for (i = 0; i < nr_ahead && thrown > 0; i++) {
|
||||||
reqs[idx].len = min(thrown, sizeof(buf));
|
len = min(thrown, sizeof(buf));
|
||||||
reqs[idx].id = sftp_async_read_begin(sf, reqs[idx].len);
|
requested = sftp_aio_begin_read(sf, len, &reqs[i]);
|
||||||
if (reqs[idx].id < 0) {
|
if (requested == SSH_ERROR) {
|
||||||
priv_set_errv("sftp_async_read_begin: %d",
|
priv_set_errv("sftp_aio_begin_read: %d",
|
||||||
sftp_get_error(sf->sftp));
|
sftp_get_error(sf->sftp));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
thrown -= reqs[idx].len;
|
thrown -= requested;
|
||||||
bwlimit_wait(bw, reqs[idx].len);
|
bwlimit_wait(bw, requested);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx = 0; remaind > 0; idx = (idx + 1) % nr_ahead) {
|
for (i = 0; remain > 0; i = (i + 1) % nr_ahead) {
|
||||||
read_bytes = sftp_async_read(sf, buf, reqs[idx].len, reqs[idx].id);
|
read_bytes = sftp_aio_wait_read(&reqs[i], buf, sizeof(buf));
|
||||||
if (read_bytes == SSH_ERROR) {
|
if (read_bytes == SSH_ERROR) {
|
||||||
priv_set_errv("sftp_async_read: %d", sftp_get_error(sf->sftp));
|
priv_set_errv("sftp_aio_wait_read: %d",
|
||||||
|
sftp_get_error(sf->sftp));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thrown > 0) {
|
if (thrown > 0) {
|
||||||
reqs[idx].len = min(thrown, sizeof(buf));
|
len = min(thrown, sizeof(buf));
|
||||||
reqs[idx].id = sftp_async_read_begin(sf, reqs[idx].len);
|
requested = sftp_aio_begin_read(sf, len, &reqs[i]);
|
||||||
thrown -= reqs[idx].len;
|
thrown -= requested;
|
||||||
bwlimit_wait(bw, reqs[idx].len);
|
bwlimit_wait(bw, requested);
|
||||||
}
|
}
|
||||||
|
|
||||||
write_bytes = write(fd, buf, read_bytes);
|
write_bytes = write(fd, buf, read_bytes);
|
||||||
@@ -464,13 +463,13 @@ static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd, int nr_ahead, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
*counter += write_bytes;
|
*counter += write_bytes;
|
||||||
remaind -= read_bytes;
|
remain -= write_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remaind < 0) {
|
if (remain < 0) {
|
||||||
priv_set_errv("invalid remaind bytes %ld. last async_read bytes %ld. "
|
priv_set_errv("invalid remain bytes %ld. last async_read bytes %ld. "
|
||||||
"last write bytes %ld",
|
"last write bytes %ld",
|
||||||
remaind, read_bytes, write_bytes);
|
remain, read_bytes, write_bytes);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
src/ssh.c
18
src/ssh.c
@@ -117,23 +117,11 @@ static int ssh_authenticate(ssh_session ssh, struct mscp_ssh_opts *opts)
|
|||||||
int auth_bit_mask;
|
int auth_bit_mask;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* none method */
|
/* try publickey auth first */
|
||||||
ret = ssh_userauth_none(ssh, NULL);
|
char *p = opts->passphrase ? opts->passphrase : NULL;
|
||||||
if (ret == SSH_AUTH_SUCCESS)
|
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
|
||||||
if (auth_bit_mask & SSH_AUTH_METHOD_NONE &&
|
|
||||||
ssh_userauth_none(ssh, NULL) == SSH_AUTH_SUCCESS)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
|
||||||
if (auth_bit_mask & SSH_AUTH_METHOD_PUBLICKEY) {
|
|
||||||
char *p = opts->passphrase ? opts->passphrase : NULL;
|
|
||||||
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
||||||
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
|
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
|
||||||
if (!opts->password) {
|
if (!opts->password) {
|
||||||
|
|||||||
@@ -226,6 +226,62 @@ def test_copy_file_under_root_to_dir(mscp, src_prefix, dst_prefix):
|
|||||||
src.cleanup()
|
src.cleanup()
|
||||||
dst.cleanup(preserve_dir = True)
|
dst.cleanup(preserve_dir = True)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
||||||
|
def test_dst_has_suffix_slash(mscp, src_prefix, dst_prefix):
|
||||||
|
"""
|
||||||
|
if dst path has suffix '/' like "dir/" and does not exist,
|
||||||
|
mscp should create dir/ and put dir/src-file-name.
|
||||||
|
"""
|
||||||
|
dstdir = "non_existent_dstdir/"
|
||||||
|
shutil.rmtree(dstdir, ignore_errors=True)
|
||||||
|
|
||||||
|
src = File("src", size = 1024 * 1024).make()
|
||||||
|
dst = File(f"{dstdir}/src")
|
||||||
|
|
||||||
|
run2ok([mscp, "-vvv", src_prefix + src.path,
|
||||||
|
dst_prefix + dstdir])
|
||||||
|
|
||||||
|
assert check_same_md5sum(src, dst)
|
||||||
|
src.cleanup()
|
||||||
|
dst.cleanup()
|
||||||
|
|
||||||
|
param_tilde_paths = [
|
||||||
|
("src", "localhost:~/dst"),
|
||||||
|
("localhost:~/src", "dst"),
|
||||||
|
]
|
||||||
|
@pytest.mark.parametrize("src_path, dst_path", param_tilde_paths)
|
||||||
|
def test_remote_path_contains_tilde(mscp, src_path, dst_path):
|
||||||
|
"""
|
||||||
|
if remote path contains '~' as prefix, it should be expanded as '.'.
|
||||||
|
Note that `~user` notation is not supported yet.
|
||||||
|
"""
|
||||||
|
def extract_and_expand(path):
|
||||||
|
path = path if not ':' in path else path[path.index(':')+1:]
|
||||||
|
return path.replace('~', os.environ["HOME"])
|
||||||
|
|
||||||
|
src_f_path = extract_and_expand(src_path)
|
||||||
|
dst_f_path = extract_and_expand(dst_path)
|
||||||
|
|
||||||
|
src = File(src_f_path, size = 1024 * 1024).make()
|
||||||
|
dst = File(dst_f_path)
|
||||||
|
|
||||||
|
run2ok([mscp, "-vvv", src_path, dst_path])
|
||||||
|
assert check_same_md5sum(src, dst)
|
||||||
|
|
||||||
|
src.cleanup(preserve_dir=True)
|
||||||
|
dst.cleanup(preserve_dir=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_path_contains_tilde2(mscp):
|
||||||
|
src = File("src", size = 1024 * 1024).make()
|
||||||
|
dst = File(f"{os.environ['HOME']}/src")
|
||||||
|
|
||||||
|
run2ok([mscp, "-vvv", src.path, f"localhost:~"])
|
||||||
|
assert check_same_md5sum(src, dst)
|
||||||
|
|
||||||
|
src.cleanup(preserve_dir=True)
|
||||||
|
dst.cleanup(preserve_dir=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
||||||
def test_min_chunk(mscp, src_prefix, dst_prefix):
|
def test_min_chunk(mscp, src_prefix, dst_prefix):
|
||||||
|
|||||||
Reference in New Issue
Block a user