mirror of
https://github.com/libssh2/libssh2.git
synced 2026-10-24 03:48:58 +03:00
To help debugging issues. Also: - bump sshd LogLevel to DEBUG1, for essential details. - bump command output buffer in `run_command_varg()` - drop redundant condition in `run_command_varg()` - replace `BUFSIZ` with literal sizes in `run_command_varg()`. After this patch it's showing this in the CI log for the libgcrypt fallout that previously needed a long time to diagnose: ``` 61: ---- sshd log ---- 61: No matching private key for certificate: /etc/ssh/ssh_host_mldsa44-ed25519_key-cert.pub 61: Server listening on 0.0.0.0 port 22. 61: Server listening on :: port 22. 61: debug1: sshd-session version OpenSSH_10.5, OpenSSL 3.6.3 9 Jun 2026 61: Connection from 172.17.0.1 port 51878 on 172.17.0.2 port 22 rdomain "" 61: debug1: sshd-auth version OpenSSH_10.5, OpenSSL 3.6.3 9 Jun 2026 [preauth] 61: debug1: list_hostkey_types: rsa-sha2-512,rsa-sha2-256,ssh-rsa,rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256, ecdsa-sha2-nistp256-cert-v01@openssh.com,ssh-ed25519,ssh-ed25519-cert-v01@openssh.com [preauth] 61: debug1: Local version string SSH-2.0-OpenSSH_10.5p1 Debian-1 [preauth] 61: debug1: Remote protocol version 2.0, remote software version libssh2_1.11.2_DEV [preauth] 61: debug1: compat_banner: no match: libssh2_1.11.2_DEV [preauth] 61: debug1: SSH2_MSG_KEXINIT sent [preauth] 61: debug1: SSH2_MSG_KEXINIT received [preauth] 61: debug1: kex: algorithm: (no match) [preauth] 61: Unable to negotiate with 172.17.0.1 port 51878: no matching key exchange method found. Their offer: diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512, diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1, diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,ext-info-c, kex-strict-c-v00@openssh.com [preauth] [...] 61: ------------------ ``` Follow-up to1d0b86ac84#2484 Follow-up to8be5716437#2174 Follow-up to51f6259d18#1720 Closes #2621
518 lines
15 KiB
C
518 lines
15 KiB
C
/* Copyright (C) Alexander Lamaison
|
|
* 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 copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include "session_fixture.h"
|
|
#include "openssh_fixture.h"
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h> /* for atoi() */
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef _WIN64
|
|
#define LIBSSH2_SOCKET_MASK "llu"
|
|
#elif defined(_WIN32)
|
|
#define LIBSSH2_SOCKET_MASK "u"
|
|
#else
|
|
#define LIBSSH2_SOCKET_MASK "d"
|
|
#endif
|
|
|
|
#ifdef LIBSSH2_WINDOWS_UWP
|
|
#define popen(x, y) NULL
|
|
#define pclose(x) (-1)
|
|
#elif defined(_WIN32)
|
|
#define popen _popen
|
|
#define pclose _pclose
|
|
#endif
|
|
|
|
static const char *container_cmd = NULL;
|
|
|
|
int openssh_fixture_have_container(void)
|
|
{
|
|
return !!container_cmd;
|
|
}
|
|
|
|
static int run_command_varg(char **output, const char *command, va_list args)
|
|
SSH2_PRINTF(2, 0);
|
|
|
|
static int run_command_varg(char **output, const char *command, va_list args)
|
|
{
|
|
static const char redirect_stderr[] = "%s 2>&1";
|
|
|
|
FILE *pipe;
|
|
char command_buf[8192];
|
|
char buf[64 * 1024]; /* sizeof(command_buf + " 2>&1") or larger */
|
|
int ret;
|
|
size_t buf_len;
|
|
|
|
if(output)
|
|
*output = NULL;
|
|
|
|
/* Format the command string */
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
#endif
|
|
ret = vsnprintf(command_buf, sizeof(command_buf), command, args);
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
if(ret < 0 || (size_t)ret >= sizeof(command_buf)) {
|
|
fprintf(stderr, "Unable to format command (%s)\n", command);
|
|
return -1;
|
|
}
|
|
|
|
ret = snprintf(buf, sizeof(buf), redirect_stderr, command_buf);
|
|
if(ret < 0 || (size_t)ret >= sizeof(buf)) {
|
|
fprintf(stderr, "Unable to rewrite command (%s)\n", command);
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
fprintf(stderr, "Command: %s\n", command_buf);
|
|
#endif
|
|
fprintf(stdout, "Command: %s\n", command_buf);
|
|
pipe = popen(buf, "r");
|
|
if(!pipe) {
|
|
fprintf(stderr, "Unable to execute command '%s'\n", command);
|
|
return -1;
|
|
}
|
|
buf[0] = '\0';
|
|
buf_len = 0;
|
|
while(buf_len < (sizeof(buf) - 1) &&
|
|
fgets(&buf[buf_len], (int)(sizeof(buf) - buf_len), pipe))
|
|
buf_len = strlen(buf);
|
|
|
|
ret = pclose(pipe);
|
|
if(ret)
|
|
fprintf(stderr, "Error running command '%s' (exit %d): %s\n",
|
|
command, ret, buf);
|
|
|
|
if(output) {
|
|
/* command output may contain a trailing newline, so we trim
|
|
* whitespace here */
|
|
size_t end = strlen(buf);
|
|
while(end > 0 && isspace((int)buf[end - 1])) {
|
|
buf[end - 1] = '\0';
|
|
--end;
|
|
}
|
|
|
|
*output = libssh2_strdup(buf);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int run_command(char **output, const char *command, ...)
|
|
SSH2_PRINTF(2, 3);
|
|
|
|
static int run_command(char **output, const char *command, ...)
|
|
{
|
|
va_list args;
|
|
int ret;
|
|
|
|
va_start(args, command);
|
|
ret = run_command_varg(output, command, args);
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char *openssh_server_image(void)
|
|
{
|
|
return getenv("OPENSSH_SERVER_IMAGE");
|
|
}
|
|
|
|
static int build_openssh_server_container_image(void)
|
|
{
|
|
if(container_cmd) {
|
|
const char *container_image_name = openssh_server_image();
|
|
if(container_image_name) {
|
|
int ret = run_command(NULL, "%s image pull %s%s", container_cmd,
|
|
/* Requires Apple container 0.7.0+ */
|
|
strstr(container_cmd, "container") ?
|
|
"--progress none " : "",
|
|
container_image_name);
|
|
if(!ret) {
|
|
ret = run_command(NULL, "%s image tag %s "
|
|
"libssh2/openssh_server", container_cmd,
|
|
container_image_name);
|
|
if(!ret)
|
|
return ret;
|
|
}
|
|
}
|
|
return run_command(NULL,
|
|
"%s build --quiet -t libssh2/openssh_server %s",
|
|
container_cmd, srcdir_path("openssh_server"));
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static const char *openssh_server_port(void)
|
|
{
|
|
return getenv("OPENSSH_SERVER_PORT");
|
|
}
|
|
|
|
static int start_openssh_server(char **container_id_out)
|
|
{
|
|
if(container_cmd) {
|
|
const char *container_host_port = openssh_server_port();
|
|
if(strstr(container_cmd, "container") && !container_host_port) {
|
|
fprintf(stderr, "OPENSSH_SERVER_PORT must be set\n");
|
|
return 1;
|
|
}
|
|
if(container_host_port)
|
|
return run_command(container_id_out, "%s run %s"
|
|
"--rm -d -p %s:22 libssh2/openssh_server",
|
|
container_cmd,
|
|
/* Requires Apple container 0.7.0+ */
|
|
strstr(container_cmd, "container") ?
|
|
"--progress none " : "",
|
|
container_host_port);
|
|
return run_command(container_id_out, "%s run --rm -d -p 22 "
|
|
"libssh2/openssh_server", container_cmd);
|
|
}
|
|
else {
|
|
*container_id_out = libssh2_strdup("");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void openssh_server_dump_logs(char *container_id)
|
|
{
|
|
if(container_cmd) {
|
|
char *logs = NULL;
|
|
int ret;
|
|
ret = run_command(&logs, "%s logs %s", container_cmd, container_id);
|
|
if(ret)
|
|
fprintf(stderr, "Failed to query server logs: %d\n", ret);
|
|
else
|
|
fprintf(stderr,
|
|
"---- sshd log ----\n%s\n"
|
|
"------------------\n", logs);
|
|
free(logs);
|
|
}
|
|
}
|
|
|
|
static int stop_openssh_server(char *container_id)
|
|
{
|
|
if(container_cmd)
|
|
return run_command(NULL, "%s stop %s", container_cmd, container_id);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int is_running_inside_a_container(void)
|
|
{
|
|
int found = 0;
|
|
#ifndef _WIN32
|
|
const char *env = getenv("container");
|
|
/* Value may be 'podman', 'oci' */
|
|
if(env && *env)
|
|
found = 1;
|
|
else {
|
|
FILE *fp = fopen("/proc/self/cgroup", "r");
|
|
if(fp) {
|
|
char line[256];
|
|
while(fgets(line, sizeof(line), fp)) {
|
|
if(strstr(line, "docker")) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
}
|
|
#endif
|
|
return found;
|
|
}
|
|
|
|
static void portable_sleep(unsigned int seconds)
|
|
{
|
|
#ifdef _WIN32
|
|
Sleep(seconds * 1000);
|
|
#else
|
|
sleep(seconds);
|
|
#endif
|
|
}
|
|
|
|
static int ip_address_from_container(char *container_id, char **ip_address_out)
|
|
{
|
|
if(is_running_inside_a_container())
|
|
return run_command(ip_address_out, "%s inspect --format "
|
|
"\"{{ .NetworkSettings.IPAddress }}\""
|
|
" %s", container_cmd, container_id);
|
|
else if(strstr(container_cmd, "container")) {
|
|
/* Requires jq and Apple container 0.8.0+ */
|
|
int ret = run_command(ip_address_out, "%s inspect %s | "
|
|
"jq --raw-output "
|
|
"'.[0].status.networks[0].ipv4Gateway'",
|
|
container_cmd, container_id);
|
|
if(!ret && *ip_address_out &&
|
|
(!*ip_address_out[0] || !strcmp(*ip_address_out, "null"))) {
|
|
free(*ip_address_out);
|
|
*ip_address_out = NULL;
|
|
ret = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
else {
|
|
/* Requires podman 6.1.0+
|
|
https://github.com/podman-container-tools/podman/issues/29164 */
|
|
int ret = run_command(ip_address_out, "%s inspect --format "
|
|
"\"{{ (index (index .NetworkSettings.Ports "
|
|
"\\\"22/tcp\\\") 0).HostIp }}\" %s",
|
|
container_cmd, container_id);
|
|
if(ret && strstr(container_cmd, "podman")) {
|
|
free(*ip_address_out);
|
|
/* Also works with 'docker'. */
|
|
ret = run_command(ip_address_out, "%s port %s \"22/tcp\"",
|
|
container_cmd, container_id);
|
|
if(!ret) {
|
|
char *hit;
|
|
hit = strchr(*ip_address_out, '\r'); /* ignore CR */
|
|
if(hit)
|
|
*hit = '\0';
|
|
hit = strchr(*ip_address_out, '\n'); /* pick first line */
|
|
if(hit)
|
|
*hit = '\0';
|
|
hit = strrchr(*ip_address_out, ':'); /* pick port part */
|
|
if(hit)
|
|
*hit = '\0';
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static int port_from_container(char *container_id, char **port_out)
|
|
{
|
|
if(is_running_inside_a_container()) {
|
|
*port_out = libssh2_strdup("22");
|
|
return 0;
|
|
}
|
|
else if(openssh_server_port()) {
|
|
*port_out = libssh2_strdup(openssh_server_port());
|
|
return 0;
|
|
}
|
|
else
|
|
return run_command(port_out, "%s inspect --format "
|
|
"\"{{ (index (index .NetworkSettings.Ports "
|
|
"\\\"22/tcp\\\") 0).HostPort }}\" %s",
|
|
container_cmd, container_id);
|
|
}
|
|
|
|
static void close_socket_to_container(libssh2_socket_t sock)
|
|
{
|
|
if(sock != LIBSSH2_INVALID_SOCKET) {
|
|
shutdown(sock, 2 /* SHUT_RDWR */);
|
|
LIBSSH2_SOCKET_CLOSE(sock);
|
|
}
|
|
}
|
|
|
|
static libssh2_socket_t open_socket_to_container(char *container_id)
|
|
{
|
|
char *ip_address = NULL;
|
|
char *port_string = NULL;
|
|
uint32_t hostaddr;
|
|
libssh2_socket_t sock;
|
|
struct sockaddr_in sin;
|
|
unsigned int counter;
|
|
libssh2_socket_t ret = LIBSSH2_INVALID_SOCKET;
|
|
|
|
if(container_cmd) {
|
|
int res;
|
|
res = ip_address_from_container(container_id, &ip_address);
|
|
if(res) {
|
|
fprintf(stderr, "Failed to get IP address for container %s\n",
|
|
container_id);
|
|
goto cleanup;
|
|
}
|
|
|
|
res = port_from_container(container_id, &port_string);
|
|
if(res) {
|
|
fprintf(stderr, "Failed to get port for container %s\n",
|
|
container_id);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else {
|
|
const char *env;
|
|
env = getenv("OPENSSH_SERVER_HOST");
|
|
if(!env)
|
|
env = "127.0.0.1";
|
|
ip_address = libssh2_strdup(env);
|
|
env = openssh_server_port();
|
|
if(!env)
|
|
env = "4711";
|
|
port_string = libssh2_strdup(env);
|
|
}
|
|
|
|
/* 0.0.0.0 is returned by Docker for Windows, because the container
|
|
is reachable from anywhere. We cannot connect to 0.0.0.0,
|
|
instead we assume localhost and try to connect to 127.0.0.1. */
|
|
if(ip_address && !strcmp(ip_address, "0.0.0.0")) {
|
|
free(ip_address);
|
|
ip_address = libssh2_strdup("127.0.0.1");
|
|
}
|
|
|
|
hostaddr = inet_addr(ip_address);
|
|
if(hostaddr == (uint32_t)(-1)) {
|
|
fprintf(stderr, "Failed to convert %s host address\n", ip_address);
|
|
goto cleanup;
|
|
}
|
|
|
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if(sock == LIBSSH2_INVALID_SOCKET) {
|
|
fprintf(stderr,
|
|
"Failed to open socket (%" LIBSSH2_SOCKET_MASK ")\n", sock);
|
|
goto cleanup;
|
|
}
|
|
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons((unsigned short)atoi(port_string));
|
|
sin.sin_addr.s_addr = hostaddr;
|
|
|
|
for(counter = 0; counter < 3; ++counter) {
|
|
if(connect(sock, (struct sockaddr *)(&sin),
|
|
sizeof(struct sockaddr_in))) {
|
|
fprintf(stderr,
|
|
"Connection to %s:%s attempt #%u failed: retrying...\n",
|
|
ip_address, port_string, counter);
|
|
portable_sleep(1 + 2 * counter);
|
|
}
|
|
else {
|
|
ret = sock;
|
|
break;
|
|
}
|
|
}
|
|
if(ret == LIBSSH2_INVALID_SOCKET) {
|
|
close_socket_to_container(sock);
|
|
fprintf(stderr, "Failed to connect to %s:%s\n",
|
|
ip_address, port_string);
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
free(ip_address);
|
|
free(port_string);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *running_container_id = NULL;
|
|
|
|
int start_openssh_fixture(void)
|
|
{
|
|
int ret;
|
|
#ifdef _WIN32
|
|
WSADATA wsadata;
|
|
|
|
ret = WSAStartup(MAKEWORD(2, 0), &wsadata);
|
|
if(ret) {
|
|
fprintf(stderr, "WSAStartup failed with error: %d\n", ret);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
if(!getenv("OPENSSH_NO_DOCKER")) { /* for compatibility */
|
|
container_cmd = getenv("FIXTURE_CONTAINER_CMD");
|
|
if(!container_cmd)
|
|
container_cmd = "docker";
|
|
else if(!*container_cmd)
|
|
container_cmd = NULL;
|
|
}
|
|
|
|
ret = build_openssh_server_container_image();
|
|
if(!ret)
|
|
return start_openssh_server(&running_container_id);
|
|
else {
|
|
fprintf(stderr, "Failed to build container image\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
void stop_openssh_fixture(int exit_code)
|
|
{
|
|
if(running_container_id) {
|
|
if(exit_code)
|
|
openssh_server_dump_logs(running_container_id);
|
|
stop_openssh_server(running_container_id);
|
|
free(running_container_id);
|
|
running_container_id = NULL;
|
|
}
|
|
else if(container_cmd)
|
|
fprintf(stderr, "Cannot stop container - none started\n");
|
|
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
libssh2_socket_t open_socket_to_openssh_server(void)
|
|
{
|
|
return open_socket_to_container(running_container_id);
|
|
}
|
|
|
|
void close_socket_to_openssh_server(libssh2_socket_t sock)
|
|
{
|
|
close_socket_to_container(sock);
|
|
}
|
|
|
|
char *libssh2_strdup(const char *str)
|
|
{
|
|
size_t len;
|
|
char *newstr;
|
|
|
|
if(!str)
|
|
return (char *)NULL;
|
|
|
|
len = strlen(str) + 1;
|
|
|
|
newstr = malloc(len);
|
|
if(!newstr)
|
|
return (char *)NULL;
|
|
|
|
memcpy(newstr, str, len);
|
|
return newstr;
|
|
}
|