1
0
mirror of https://github.com/libssh2/libssh2.git synced 2025-11-21 14:00:51 +03:00
Files
libssh2/tests/openssh_fixture.c
Viktor Szakats 2addafb77b build fixes and improvements (mostly for Windows)
- in `hostkey.c` check the result of `libssh2_sha256_init()` and
  `libssh2_sha512_init()` calls. This avoid the warning that we're
  ignoring the return values.

- fix code using `int` (or `SOCKET`) for sockets. Use libssh2's
  dedicated `libssh2_socket_t` and `LIBSSH2_INVALID_SOCKET` instead.

- fix compiler warnings due to `STATUS_*` macro redefinitions between
  `ntstatus.h` / `winnt.h`. Solve it by manually defining the single
  `STATUS` value we need from `ntstatus.h` and stop including the whole
  header.
  Fixes #733

- improve Windows UWP/WinRT builds by detecting it with code copied
  from the curl project. Then excluding problematic libssh2 parts
  according to PR by Dmitry Kostjučenko.
  Fixes #734

- always use `SecureZeroMemory()` on Windows.

  We can tweak this if not found or not inlined by a C compiler which
  we otherwise support. Same if it causes issues with UWP apps.

  Ref: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa366877(v=vs.85)
  Ref: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlsecurezeromemory

- always enable `LIBSSH2_CLEAR_MEMORY` on Windows. CMake and
  curl-for-win builds already did that. Delete `SecureZeroMemory()`
  detection from autotools' WinCNG backend logic, that this
  setting used to depend on.

  TODO: Enable it for all platforms in a separate PR.
  TODO: For clearing buffers in WinCNG, call `_libssh2_explicit_zero()`,
        insead of a local function or explicit `SecureZeroMemory()`.

- Makefile.inc: move `os400qc3.h` to `HEADERS`. This fixes
  compilation on non-unixy platforms. Recent regression.

- `libssh2.rc`: replace copyright with plain ASCII, as in curl.

  Ref: curl/curl@1ca62bb
  Ref: curl/curl#7765
  Ref: curl/curl#7776

- CMake fixes and improvements:

  - enable warnings with llvm/clang.
  - enable more comprehensive warnings with gcc and llvm/clang.
    Logic copied from curl:
    233810bb5f/CMakeLists.txt (L131-L148)
  - fix `Policy CMP0080` CMake warning by deleting that reference.
  - add `ENABLE_WERROR` (default: `OFF`) option. Ported from curl.
  - add `PICKY_COMPILER` (default: `ON`) option, as known from curl.

    It controls both the newly added picky warnings for llvm/clang and
    gcc, and also the pre-existing ones for MSVC.

- `win32/GNUmakefile` fixes and improvements:

  - delete `_AMD64_` and add missing `-m64` for x64 builds under test.
  - add support for `ARCH=custom`.
    It disables hardcoded Intel 64-bit and Intel 32-bit options,
    allowing ARM64 builds.
  - add support for `LIBSSH2_RCFLAG_EXTRAS`.
    To pass custom options to windres, e.g. in ARM64 builds.
  - add support for `LIBSSH2_RC`. To override `windres`.
  - delete support for Metrowerks C. Last released in 2004.

- `win32/libssh2_config.h`: delete unnecessary socket #includes

  `src/libssh2_priv.h` includes `winsock2.h` and `ws2tcpip.h` further
   down the line, triggered by `HAVE_WINSOCK2_H`.

  `mswsock.h` does not seem to be necessary anymore.

  Double-including these (before `windows.h`) caused compiler failures
  when building against BoringSSL and warnings with LibreSSL. We could
  work this around by passing `-DNOCRYPT`. Deleting the duplicates
  fixes these issues.

  Timeline:
  2013: c910cd382d deleted `mswsock.h` from `src/libssh2_priv.h`
  2008: 8c43bc52b1 added `winsock2.h` and `ws2tcpip.h` to `src/libssh2_priv.h`
  2005: dc4bb1af96 added the now deleted #includes

- delete or replace `LIBSSH2_WIN32` with `WIN32`.

- replace hand-rolled `HAVE_WINDOWS_H` macro with `WIN32`. Also delete
  its detections/definitions.

- delete unused `LIBSSH2_DARWIN` macro.

- delete unused `writev()` Windows implementation

  There is no reference to `writev()` since 2007-02-02, commit
  9d55db6501.

- fix a bunch of MSVC / llvm/clang / gcc compiler warnings:

  - `warning C4100: '...': unreferenced formal parameter`
  - using value of undefined PP macro `LIBSSH2DEBUG`
  - missing void from function definition
  - `if()` block missing in non-debug builds
  - unreferenced variable in non-debug builds
  - `warning: must specify at least one argument for '...' parameter of variadic macro [-Wgnu-zero-variadic-macro-arguments]`
    in `_libssh2_debug()`
  - `warning C4295: 'ciphertext' : array is too small to include a terminating null character`
  - `warning C4706: assignment within conditional expression`
  - `warning C4996: 'inet_addr': Use inet_pton() or InetPton() instead or
      define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings`
    By suppressning it. Would be best to use inet_pton() as suggested.
    On Windows this needs Vista though.
  - `warning C4152: nonstandard extension, function/data pointer conversion in expression`
    (silenced locally)
  - `warning C4068: unknown pragma`

  Ref: https://ci.appveyor.com/project/libssh2org/libssh2/builds/46354480/job/j7d0m34qgq8rag5w

Closes #808
2023-03-03 13:30:03 +00:00

467 lines
13 KiB
C

/* Copyright (C) 2016 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:
*
* Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* 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.
*
* Neither the name of the copyright holder nor the names
* of any other 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 OWNER 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.
*/
#ifdef WIN32
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif
#endif
#include "openssh_fixture.h"
#include "session_fixture.h"
#include "libssh2_config.h"
#ifdef HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
static int have_docker = 0;
static int run_command_varg(char **output, const char *command, va_list args)
{
FILE *pipe;
char redirect_stderr[] = "%s 2>&1";
char command_buf[BUFSIZ];
char buf[BUFSIZ];
int ret;
size_t buf_len;
if(output) {
*output = NULL;
}
/* Format the command string */
ret = vsnprintf(command_buf, sizeof(command_buf), command, args);
if(ret < 0 || ret >= BUFSIZ) {
fprintf(stderr, "Unable to format command (%s)\n", command);
return -1;
}
/* Rewrite the command to redirect stderr to stdout to we can output it */
if(strlen(command_buf) + strlen(redirect_stderr) >= sizeof(buf)) {
fprintf(stderr, "Unable to rewrite command (%s)\n", command);
return -1;
}
ret = snprintf(buf, sizeof(buf), redirect_stderr, command_buf);
if(ret < 0 || ret >= BUFSIZ) {
fprintf(stderr, "Unable to rewrite command (%s)\n", command);
return -1;
}
fprintf(stdout, "Command: %s\n", command_buf);
#ifdef WIN32
pipe = _popen(buf, "r");
#else
pipe = popen(buf, "r");
#endif
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], sizeof(buf) - buf_len, pipe) != NULL) {
buf_len = strlen(buf);
}
#ifdef WIN32
ret = _pclose(pipe);
#else
ret = pclose(pipe);
#endif
if(ret != 0) {
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(buf[end - 1])) {
buf[end - 1] = '\0';
}
*output = strdup(buf);
}
return ret;
}
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_docker_image(void)
{
if(have_docker) {
char buildcmd[1024];
const char *container_image_name = openssh_server_image();
if(container_image_name != NULL) {
int ret = run_command(NULL, "docker pull --quiet %s",
container_image_name);
if(ret == 0) {
ret = run_command(NULL, "docker tag %s libssh2/openssh_server",
container_image_name);
if(ret == 0) {
return ret;
}
}
}
buildcmd[sizeof(buildcmd)-1] = 0;
snprintf(buildcmd, sizeof(buildcmd)-1,
"docker build --quiet -t libssh2/openssh_server %s",
srcdir_path("openssh_server"));
return run_command(NULL, buildcmd);
}
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(have_docker) {
const char *container_host_port = openssh_server_port();
if(container_host_port != NULL) {
return run_command(container_id_out,
"docker run --rm -d -p %s:22 "
"libssh2/openssh_server",
container_host_port);
}
return run_command(container_id_out,
"docker run --rm -d -p 22 "
"libssh2/openssh_server");
}
else {
*container_id_out = strdup("");
return 0;
}
}
static int stop_openssh_server(char *container_id)
{
if(have_docker) {
return run_command(NULL, "docker stop %s", container_id);
}
else {
return 0;
}
}
static const char *docker_machine_name(void)
{
return getenv("DOCKER_MACHINE_NAME");
}
static int is_running_inside_a_container(void)
{
#ifdef WIN32
return 0;
#else
const char *cgroup_filename = "/proc/self/cgroup";
FILE *f = NULL;
char *line = NULL;
size_t len = 0;
ssize_t read = 0;
int found = 0;
f = fopen(cgroup_filename, "r");
if(f == NULL) {
/* Don't go further, we are not in a container */
return 0;
}
while((read = getline(&line, &len, f)) != -1) {
if(strstr(line, "docker") != NULL) {
found = 1;
break;
}
}
fclose(f);
free(line);
return found;
#endif
}
static void portable_sleep(unsigned int seconds)
{
#ifdef WIN32
Sleep(seconds);
#else
sleep(seconds);
#endif
}
static int ip_address_from_container(char *container_id, char **ip_address_out)
{
const char *active_docker_machine = docker_machine_name();
if(active_docker_machine != NULL) {
/* This can be flaky when tests run in parallel (see
https://github.com/docker/machine/issues/2612), so we retry a few
times with exponential backoff if it fails */
int attempt_no = 0;
int wait_time = 500;
for(;;) {
int ret = run_command(ip_address_out, "docker-machine ip %s",
active_docker_machine);
if(ret == 0) {
return 0;
}
else if(attempt_no > 5) {
fprintf(
stderr,
"Unable to get IP from docker-machine after %d attempts\n",
attempt_no);
return -1;
}
else {
portable_sleep(wait_time);
++attempt_no;
wait_time *= 2;
}
}
}
else {
if(is_running_inside_a_container()) {
return run_command(ip_address_out,
"docker inspect --format "
"\"{{ .NetworkSettings.IPAddress }}\""
" %s",
container_id);
}
else {
return run_command(ip_address_out,
"docker inspect --format "
"\"{{ index (index (index "
".NetworkSettings.Ports "
"\\\"22/tcp\\\") 0) \\\"HostIp\\\" }}\" %s",
container_id);
}
}
}
static int port_from_container(char *container_id, char **port_out)
{
if(is_running_inside_a_container()) {
*port_out = strdup("22");
return 0;
}
else {
return run_command(port_out,
"docker inspect --format "
"\"{{ index (index (index .NetworkSettings.Ports "
"\\\"22/tcp\\\") 0) \\\"HostPort\\\" }}\" %s",
container_id);
}
}
static int open_socket_to_container(char *container_id)
{
char *ip_address = NULL;
char *port_string = NULL;
unsigned long hostaddr;
libssh2_socket_t sock;
struct sockaddr_in sin;
int counter = 0;
int ret;
if(have_docker) {
ret = ip_address_from_container(container_id, &ip_address);
if(ret != 0) {
fprintf(stderr, "Failed to get IP address for container %s\n",
container_id);
ret = -1;
goto cleanup;
}
ret = port_from_container(container_id, &port_string);
if(ret != 0) {
fprintf(stderr, "Failed to get port for container %s\n",
container_id);
ret = -1;
}
}
else {
const char *env;
env = getenv("OPENSSH_SERVER_HOST");
if(!env) {
env = "127.0.0.1";
}
ip_address = strdup(env);
env = openssh_server_port();
if(!env) {
env = "4711";
}
port_string = strdup(env);
ret = 0;
}
/* 0.0.0.0 is returned by Docker for Windows, because the container
is reachable from anywhere. But 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") == 0) {
free(ip_address);
ip_address = strdup("127.0.0.1");
}
hostaddr = inet_addr(ip_address);
if(hostaddr == (unsigned long)(-1)) {
fprintf(stderr, "Failed to convert %s host address\n", ip_address);
ret = -1;
goto cleanup;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock <= 0) {
fprintf(stderr, "Failed to open socket (%d)\n", sock);
ret = -1;
goto cleanup;
}
sin.sin_family = AF_INET;
sin.sin_port = htons((short)strtol(port_string, NULL, 0));
sin.sin_addr.s_addr = hostaddr;
for(counter = 0; counter < 3; ++counter) {
if(connect(sock, (struct sockaddr *)(&sin),
sizeof(struct sockaddr_in)) != 0) {
ret = -1;
fprintf(stderr,
"Connection to %s:%s attempt #%d failed: retrying...\n",
ip_address, port_string, counter);
portable_sleep(1 + 2*counter);
}
else {
ret = sock;
break;
}
}
if(ret == -1) {
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 HAVE_WINSOCK2_H
WSADATA wsadata;
ret = WSAStartup(MAKEWORD(2, 0), &wsadata);
if(ret != 0) {
fprintf(stderr, "WSAStartup failed with error: %d\n", ret);
return 1;
}
#endif
have_docker = (getenv("OPENSSH_NO_DOCKER") == NULL);
ret = build_openssh_server_docker_image();
if(ret == 0) {
return start_openssh_server(&running_container_id);
}
else {
fprintf(stderr, "Failed to build docker image\n");
return ret;
}
}
void stop_openssh_fixture(void)
{
if(running_container_id) {
stop_openssh_server(running_container_id);
free(running_container_id);
running_container_id = NULL;
}
else if(have_docker) {
fprintf(stderr, "Cannot stop container - none started");
}
}
int open_socket_to_openssh_server(void)
{
return open_socket_to_container(running_container_id);
}