12 Commits

Author SHA1 Message Date
Ryo Nakamura
16d3b5b772 fix github actions to obtain libssh version dynamically 2025-08-11 15:44:23 +09:00
Ryo Nakamura
27646fc71d bump libssh version to 0.11.2 2025-08-11 15:10:33 +09:00
Ryo Nakamura
deda8ca74c set cmake_minimum_required to 3.13 for libssh (#32) 2025-04-16 12:19:13 +09:00
Ryo Nakamura
5fad665c39 add test_quiet_mode 2025-02-27 14:53:27 +09:00
Ryo Nakamura
1b655b61c9 fix -q to redirect stdout to /dev/null (#30) 2025-02-27 14:36:06 +09:00
Ryo Nakamura
c16b981d5d use openssl@3 on macos 2025-01-19 15:29:44 +09:00
Ryo Nakamura
1c787e562f parallelize test on GitHub Actions uing matrix 2025-01-07 16:46:06 +09:00
Ryo Nakamura
248f932a99 add archlinux:base support and test 2025-01-07 16:12:02 +09:00
Ryo Nakamura
1636f2a965 test: set -E log_file option for sshd for debug 2025-01-07 16:04:56 +09:00
Ryo Nakamura
31e011f85c macos: install openssl@3.0 from homebrew
because openssl@1.1 has been disabled since 2024/10/24.
2025-01-07 14:41:49 +09:00
Ryo Nakamura
006bd30424 add install instruction for MacPorts
mscp is available on MacPorts by @lasmarois #23
2024-07-04 13:45:15 +09:00
Ryo Nakamura
60f442689e adjust mscp.rst for v0.2.1 2024-05-11 21:46:18 +09:00
18 changed files with 732 additions and 38 deletions

View File

@@ -15,13 +15,17 @@ jobs:
with:
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
uses: vmactions/freebsd-vm@v1
with:
prepare: |
pkg install -y git cmake
run: |
patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
build/mscp -h

View File

@@ -22,6 +22,11 @@ jobs:
with:
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
run: ./scripts/install-build-deps.sh
@@ -29,13 +34,10 @@ jobs:
id: brew-prefix
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
# 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
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENSSL_ROOT_DIR=${{steps.brew-prefix.outputs.HOMEBREW_PREFIX}}/opt/openssl@1.1
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENSSL_ROOT_DIR=${{steps.brew-prefix.outputs.HOMEBREW_PREFIX}}/opt/openssl@3
- name: Build
# Build your program with the given configuration

View File

@@ -22,14 +22,16 @@ jobs:
with:
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
run: |
sudo apt-get update
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
# 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

View File

@@ -42,14 +42,16 @@ jobs:
with:
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
run: |
sudo apt-get update
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.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3

View File

@@ -17,8 +17,10 @@ jobs:
with:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
# TODO: just building docker does not require packages. fix CMakeLists
- name: install build dependency
@@ -43,8 +45,10 @@ jobs:
with:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- 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: Set variables
run: |

View File

@@ -12,15 +12,29 @@ env:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
index: # see DIST_IDS and DIST_VERS lists in CMakeLists.txt
- ubuntu-20.04
- ubuntu-22.04
- ubuntu-24.04
- rocky-8.9
- rocky-9.3
- almalinux-9.3
- alpine-3.19
- arch-base
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
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
# TODO: just building docker does not require libssh. fix CMakeLists
# TODO: just building docker images does not require libssh. fix CMakeLists
- name: install build dependency
run: |
sudo apt-get update
@@ -30,7 +44,7 @@ jobs:
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build Containers
run: make -C ${{github.workspace}}/build docker-build-all
run: make -C ${{github.workspace}}/build docker-build-${{ matrix.index }}
- name: Run Test
run: make -C ${{github.workspace}}/build docker-test-all
run: make -C ${{github.workspace}}/build docker-test-${{ matrix.index }}

View File

@@ -165,8 +165,10 @@ enable_testing()
# Custom targets to build and test mscp in docker containers.
# foreach(IN ZIP_LISTS) (cmake >= 3.17) can shorten the following lists.
# However, ubuntu 20.04 has cmake 3.16.3. So this is a roundabout trick.
list(APPEND DIST_IDS ubuntu ubuntu ubuntu rocky rocky almalinux alpine)
list(APPEND DIST_VERS 20.04 22.04 24.04 8.9 9.3 9.3 3.19)
#
# 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_VERS 20.04 22.04 24.04 8.9 9.3 9.3 3.19 base)
list(LENGTH DIST_IDS _DIST_LISTLEN)
math(EXPR DIST_LISTLEN "${_DIST_LISTLEN} - 1")

View File

@@ -0,0 +1,36 @@
FROM archlinux:base
ARG REQUIREDPKGS
# install pyest and openssh for test
RUN set -ex && pacman -Syy && pacman --noconfirm -S ${REQUIREDPKGS} openssh python-pytest
RUN mkdir /var/run/sshd \
&& ssh-keygen -A \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& 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
RUN useradd -m -d /home/test test \
&& echo "test:userpassword" | chpasswd \
&& mkdir -p /home/test/.ssh \
&& ssh-keygen -f /home/test/.ssh/id_rsa_test -N "keypassphrase" \
&& cat /home/test/.ssh/id_rsa_test.pub >> /home/test/.ssh/authorized_keys \
&& chown -R test:test /home/test \
&& chown -R test:test /home/test/.ssh
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make -j 2 \
&& make install

View File

@@ -42,7 +42,11 @@ Paper:
- macOS
```console
# Homebrew
brew install upa/tap/mscp
# MacPorts
sudo port install mscp
```
- Ubuntu
@@ -92,7 +96,7 @@ mkdir build && cd build
cmake ..
# in macOS, you may need OPENSSL_ROOT_DIR for cmake:
# cmake .. -DOPENSSL_ROOT_DIR=$(brew --prefix)/opt/openssl@1.1
# cmake .. -DOPENSSL_ROOT_DIR=$(brew --prefix)/opt/openssl@3
# build
make

View File

@@ -2,7 +2,7 @@
MSCP
====
:Date: v0.2.0-9-g675126a
:Date: v0.2.1
NAME
====

2
libssh

Submodule libssh updated: 6f1b1e76bb...dff6c0821e

View File

@@ -1,5 +1,6 @@
Patches in this directory introduces `sftp_async_write()` and
`sftp_async_write_end()` to libssh. Those implementations are derived
from https://github.com/limes-datentechnik-gmbh/libssh. See [Re: SFTP
Write async](https://archive.libssh.org/libssh/2020-06/0000004.html).
Patches in this directory introduce enhancements for libssh including
`sftp_async_write()` and `sftp_async_write_end()`, derived from
https://github.com/limes-datentechnik-gmbh/libssh. See [Re: SFTP Write
async](https://archive.libssh.org/libssh/2020-06/0000004.html).

View File

@@ -1,3 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a64b7708..c6344a5a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3.0)
+cmake_minimum_required(VERSION 3.13.0)
cmake_policy(SET CMP0048 NEW)
# Specify search path for CMake modules to be loaded by include()
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 9de10225..0f3d20ed 100644
--- a/ConfigureChecks.cmake

573
patch/libssh-0.11.2.patch Normal file
View File

@@ -0,0 +1,573 @@
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..c6b01c1c 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,11 @@ 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);
+
#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..ae62ddfe 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.
*
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) {

View File

@@ -45,7 +45,7 @@ done
case $platform in
Darwin)
cmd="brew install"
pkgs="openssl@1.1"
pkgs="openssl@3"
;;
Linux-ubuntu*)
cmd="apt-get install --no-install-recommends -y"
@@ -55,6 +55,10 @@ case $platform in
cmd="yum install -y"
pkgs="gcc make cmake zlib-devel openssl-devel rpm-build"
;;
Linux-arch*)
cmd="pacman --no-confirm -S"
pkgs="gcc make cmake"
;;
FreeBSD-freebsd)
cmd="pkg install"
pkgs="cmake"

View File

@@ -17,7 +17,7 @@ sed -i -e 's/AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config
# Run sshd
if [ ! -e /var/run/sshd.pid ]; then
/usr/sbin/sshd
/usr/sbin/sshd -E /tmp/sshd.log
sleep 1
fi

View File

@@ -2,6 +2,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <math.h>
@@ -311,6 +312,24 @@ long atol_with_unit(char *value, bool i)
return v * factor;
}
int to_dev_null(int fd)
{
int nfd = open("/dev/null", O_WRONLY);
if (nfd < 0) {
pr_err("open /dev/null: %s", strerrno());
return -1;
}
if (dup2(nfd, fd) < 0) {
pr_err("dup2: %s", strerrno());
return -1;
}
close(nfd);
return 0;
}
int main(int argc, char **argv)
{
struct mscp_ssh_opts s;
@@ -320,7 +339,7 @@ int main(int argc, char **argv)
int ch, n, i, ret;
int direction = 0;
char *remote = NULL, *checkpoint_save = NULL, *checkpoint_load = NULL;
bool dryrun = false, resume = false;
bool quiet = false, dryrun = false, resume = false;
int nr_options = 0;
memset(&s, 0, sizeof(s));
@@ -378,7 +397,7 @@ int main(int argc, char **argv)
o.severity++;
break;
case 'q':
o.severity = MSCP_SEVERITY_NONE;
quiet = true;
break;
case 'D':
dryrun = true;
@@ -441,6 +460,9 @@ int main(int argc, char **argv)
}
}
if (quiet)
to_dev_null(STDOUT_FILENO);
s.password = getenv(ENV_SSH_AUTH_PASSWORD);
s.passphrase = getenv(ENV_SSH_AUTH_PASSPHRASE);

View File

@@ -15,17 +15,19 @@ from subprocess import check_call, CalledProcessError
from util import File, check_same_md5sum
def run2ok(args, env = None):
def run2ok(args, env = None, quiet = False):
cmd = list(map(str, args))
print("cmd: {}".format(" ".join(cmd)))
if not quiet:
print("cmd: {}".format(" ".join(cmd)))
check_call(cmd, env = env)
def run2ng(args, env = None, timeout = None):
def run2ng(args, env = None, timeout = None, quiet = False):
if timeout:
args = ["timeout", "-s", "INT", timeout] + args
cmd = list(map(str, args))
print("cmd: {}".format(" ".join(cmd)))
with pytest.raises(CalledProcessError) as e:
if not quiet:
print("cmd: {}".format(" ".join(cmd)))
with pytest.raises(CalledProcessError):
check_call(cmd, env = env)
@@ -418,6 +420,18 @@ def test_v6_to_v4_should_fail(mscp):
run2ng([mscp, "-vvv", "-6", src.path, dst_prefix + dst.path])
src.cleanup()
def test_quiet_mode(capsys, mscp):
src = File("src", size = 1024).make()
dst = File("dst")
dst_prefix = "127.0.0.1:{}/".format(os.getcwd())
run2ok([mscp, "-vvv", "-q", src.path, dst_prefix + dst.path], quiet=True)
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
captured = capsys.readouterr()
assert not captured.out
assert not captured.err
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_set_conn_interval(mscp, src_prefix, dst_prefix):
srcs = []