libssh2

Форк
0
/
openssh_fixture.c 
490 строк · 13.1 Кб
1
/* Copyright (C) Alexander Lamaison
2
 * All rights reserved.
3
 *
4
 * Redistribution and use in source and binary forms,
5
 * with or without modification, are permitted provided
6
 * that the following conditions are met:
7
 *
8
 *   Redistributions of source code must retain the above
9
 *   copyright notice, this list of conditions and the
10
 *   following disclaimer.
11
 *
12
 *   Redistributions in binary form must reproduce the above
13
 *   copyright notice, this list of conditions and the following
14
 *   disclaimer in the documentation and/or other materials
15
 *   provided with the distribution.
16
 *
17
 *   Neither the name of the copyright holder nor the names
18
 *   of any other contributors may be used to endorse or
19
 *   promote products derived from this software without
20
 *   specific prior written permission.
21
 *
22
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
24
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
34
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
35
 * OF SUCH DAMAGE.
36
 *
37
 * SPDX-License-Identifier: BSD-3-Clause
38
 */
39

40
#include "session_fixture.h"
41
#include "openssh_fixture.h"
42

43
#ifdef HAVE_SYS_SOCKET_H
44
#include <sys/socket.h>
45
#endif
46
#ifdef HAVE_UNISTD_H
47
#include <unistd.h>
48
#endif
49
#ifdef HAVE_ARPA_INET_H
50
#include <arpa/inet.h>
51
#endif
52
#ifdef HAVE_NETINET_IN_H
53
#include <netinet/in.h>
54
#endif
55

56
#include <stdio.h>
57
#include <stdlib.h>
58
#include <stdarg.h>
59
#include <ctype.h>
60

61
#if defined(_WIN32) && defined(_WIN64)
62
#define LIBSSH2_SOCKET_MASK "%lld"
63
#else
64
#define LIBSSH2_SOCKET_MASK "%d"
65
#endif
66

67
#ifdef LIBSSH2_WINDOWS_UWP
68
#define popen(x, y) (NULL)
69
#define pclose(x) (-1)
70
#elif defined(_WIN32)
71
#define popen _popen
72
#define pclose _pclose
73
#endif
74

75
static int have_docker = 0;
76

77
int openssh_fixture_have_docker(void)
78
{
79
    return have_docker;
80
}
81

82
static int run_command_varg(char **output, const char *command, va_list args)
83
    LIBSSH2_PRINTF(2, 0);
84

85
static int run_command_varg(char **output, const char *command, va_list args)
86
{
87
    static const char redirect_stderr[] = "%s 2>&1";
88

89
    FILE *pipe;
90
    char command_buf[BUFSIZ];
91
    char buf[BUFSIZ + sizeof(redirect_stderr)];
92
    int ret;
93
    size_t buf_len;
94

95
    if(output) {
96
        *output = NULL;
97
    }
98

99
    /* Format the command string */
100
    ret = vsnprintf(command_buf, sizeof(command_buf), command, args);
101
    if(ret < 0 || ret >= BUFSIZ) {
102
        fprintf(stderr, "Unable to format command (%s)\n", command);
103
        return -1;
104
    }
105

106
    /* Rewrite the command to redirect stderr to stdout to we can output it */
107
    if(strlen(command_buf) + strlen(redirect_stderr) >= sizeof(buf)) {
108
        fprintf(stderr, "Unable to rewrite command (%s)\n", command);
109
        return -1;
110
    }
111

112
    ret = snprintf(buf, sizeof(buf), redirect_stderr, command_buf);
113
    if(ret < 0 || ret >= BUFSIZ) {
114
        fprintf(stderr, "Unable to rewrite command (%s)\n", command);
115
        return -1;
116
    }
117

118
    fprintf(stdout, "Command: %s\n", command_buf);
119
    pipe = popen(buf, "r");
120
    if(!pipe) {
121
        fprintf(stderr, "Unable to execute command '%s'\n", command);
122
        return -1;
123
    }
124
    buf[0] = 0;
125
    buf_len = 0;
126
    while(buf_len < (sizeof(buf) - 1) &&
127
        fgets(&buf[buf_len], (int)(sizeof(buf) - buf_len), pipe)) {
128
        buf_len = strlen(buf);
129
    }
130

131
    ret = pclose(pipe);
132
    if(ret) {
133
        fprintf(stderr, "Error running command '%s' (exit %d): %s\n",
134
                command, ret, buf);
135
    }
136

137
    if(output) {
138
        /* command output may contain a trailing newline, so we trim
139
         * whitespace here */
140
        size_t end = strlen(buf);
141
        while(end > 0 && isspace((int)buf[end - 1])) {
142
            buf[end - 1] = '\0';
143
        }
144

145
        *output = strdup(buf);
146
    }
147
    return ret;
148
}
149

150
static int run_command(char **output, const char *command, ...)
151
    LIBSSH2_PRINTF(2, 3);
152

153
static int run_command(char **output, const char *command, ...)
154
{
155
    va_list args;
156
    int ret;
157

158
    va_start(args, command);
159
    ret = run_command_varg(output, command, args);
160
    va_end(args);
161

162
    return ret;
163
}
164

165
static const char *openssh_server_image(void)
166
{
167
    return getenv("OPENSSH_SERVER_IMAGE");
168
}
169

170
static int build_openssh_server_docker_image(void)
171
{
172
    if(have_docker) {
173
        const char *container_image_name = openssh_server_image();
174
        if(container_image_name) {
175
            int ret = run_command(NULL, "docker pull %s",
176
                                  container_image_name);
177
            if(ret == 0) {
178
                ret = run_command(NULL, "docker tag %s libssh2/openssh_server",
179
                                  container_image_name);
180
                if(ret == 0) {
181
                    return ret;
182
                }
183
            }
184
        }
185
        return run_command(NULL,
186
                           "docker build --quiet -t libssh2/openssh_server %s",
187
                           srcdir_path("openssh_server"));
188
    }
189
    else {
190
        return 0;
191
    }
192
}
193

194
static const char *openssh_server_port(void)
195
{
196
    return getenv("OPENSSH_SERVER_PORT");
197
}
198

199
static int start_openssh_server(char **container_id_out)
200
{
201
    if(have_docker) {
202
        const char *container_host_port = openssh_server_port();
203
        if(container_host_port) {
204
            return run_command(container_id_out,
205
                               "docker run --rm -d -p %s:22 "
206
                               "libssh2/openssh_server",
207
                               container_host_port);
208
        }
209

210
        return run_command(container_id_out,
211
                           "docker run --rm -d -p 22 "
212
                           "libssh2/openssh_server");
213
    }
214
    else {
215
        *container_id_out = strdup("");
216
        return 0;
217
    }
218
}
219

220
static int stop_openssh_server(char *container_id)
221
{
222
    if(have_docker) {
223
        return run_command(NULL, "docker stop %s", container_id);
224
    }
225
    else {
226
        return 0;
227
    }
228
}
229

230
static const char *docker_machine_name(void)
231
{
232
    return getenv("DOCKER_MACHINE_NAME");
233
}
234

235
static int is_running_inside_a_container(void)
236
{
237
#ifdef _WIN32
238
    return 0;
239
#else
240
    const char *cgroup_filename = "/proc/self/cgroup";
241
    FILE *f;
242
    char *line = NULL;
243
    size_t len = 0;
244
    ssize_t read;
245
    int found = 0;
246
    f = fopen(cgroup_filename, "r");
247
    if(!f) {
248
        /* Don't go further, we are not in a container */
249
        return 0;
250
    }
251
    while((read = getline(&line, &len, f)) != -1) {
252
        if(strstr(line, "docker")) {
253
            found = 1;
254
            break;
255
        }
256
    }
257
    fclose(f);
258
    free(line);
259
    return found;
260
#endif
261
}
262

263
static void portable_sleep(unsigned int seconds)
264
{
265
#ifdef _WIN32
266
    Sleep(seconds);
267
#else
268
    sleep(seconds);
269
#endif
270
}
271

272
static int ip_address_from_container(char *container_id, char **ip_address_out)
273
{
274
    const char *active_docker_machine = docker_machine_name();
275
    if(active_docker_machine) {
276

277
        /* This can be flaky when tests run in parallel (see
278
           https://github.com/docker/machine/issues/2612), so we retry a few
279
           times with exponential backoff if it fails */
280
        int attempt_no = 0;
281
        unsigned int wait_time = 500;
282
        for(;;) {
283
            int ret = run_command(ip_address_out, "docker-machine ip %s",
284
                                  active_docker_machine);
285
            if(ret == 0) {
286
                return 0;
287
            }
288
            else if(attempt_no > 5) {
289
                fprintf(
290
                    stderr,
291
                    "Unable to get IP from docker-machine after %d attempts\n",
292
                    attempt_no);
293
                return -1;
294
            }
295
            else {
296
                portable_sleep(wait_time);
297
                ++attempt_no;
298
                wait_time *= 2;
299
            }
300
        }
301
    }
302
    else {
303
        if(is_running_inside_a_container()) {
304
            return run_command(ip_address_out,
305
                               "docker inspect --format "
306
                               "\"{{ .NetworkSettings.IPAddress }}\""
307
                               " %s",
308
                               container_id);
309
        }
310
        else {
311
            return run_command(ip_address_out,
312
                               "docker inspect --format "
313
                               "\"{{ index (index (index "
314
                               ".NetworkSettings.Ports "
315
                               "\\\"22/tcp\\\") 0) \\\"HostIp\\\" }}\" %s",
316
                               container_id);
317
        }
318
    }
319
}
320

321
static int port_from_container(char *container_id, char **port_out)
322
{
323
    if(is_running_inside_a_container()) {
324
        *port_out = strdup("22");
325
        return 0;
326
    }
327
    else {
328
        return run_command(port_out,
329
                           "docker inspect --format "
330
                           "\"{{ index (index (index .NetworkSettings.Ports "
331
                           "\\\"22/tcp\\\") 0) \\\"HostPort\\\" }}\" %s",
332
                           container_id);
333
    }
334
}
335

336
static libssh2_socket_t open_socket_to_container(char *container_id)
337
{
338
    char *ip_address = NULL;
339
    char *port_string = NULL;
340
    uint32_t hostaddr;
341
    libssh2_socket_t sock;
342
    struct sockaddr_in sin;
343
    unsigned int counter;
344
    libssh2_socket_t ret = LIBSSH2_INVALID_SOCKET;
345

346
    if(have_docker) {
347
        int res;
348
        res = ip_address_from_container(container_id, &ip_address);
349
        if(res) {
350
            fprintf(stderr, "Failed to get IP address for container %s\n",
351
                    container_id);
352
            goto cleanup;
353
        }
354

355
        res = port_from_container(container_id, &port_string);
356
        if(res) {
357
            fprintf(stderr, "Failed to get port for container %s\n",
358
                    container_id);
359
            goto cleanup;
360
        }
361
    }
362
    else {
363
        const char *env;
364
        env = getenv("OPENSSH_SERVER_HOST");
365
        if(!env) {
366
            env = "127.0.0.1";
367
        }
368
        ip_address = strdup(env);
369
        env = openssh_server_port();
370
        if(!env) {
371
            env = "4711";
372
        }
373
        port_string = strdup(env);
374
    }
375

376
    /* 0.0.0.0 is returned by Docker for Windows, because the container
377
       is reachable from anywhere. But we cannot connect to 0.0.0.0,
378
       instead we assume localhost and try to connect to 127.0.0.1. */
379
    if(ip_address && strcmp(ip_address, "0.0.0.0") == 0) {
380
        free(ip_address);
381
        ip_address = strdup("127.0.0.1");
382
    }
383

384
    hostaddr = inet_addr(ip_address);
385
    if(hostaddr == (uint32_t)(-1)) {
386
        fprintf(stderr, "Failed to convert %s host address\n", ip_address);
387
        goto cleanup;
388
    }
389

390
    sock = socket(AF_INET, SOCK_STREAM, 0);
391
    if(sock == LIBSSH2_INVALID_SOCKET) {
392
        fprintf(stderr,
393
                "Failed to open socket (" LIBSSH2_SOCKET_MASK ")\n", sock);
394
        goto cleanup;
395
    }
396

397
    sin.sin_family = AF_INET;
398
    sin.sin_port = htons((unsigned short)strtol(port_string, NULL, 0));
399
    sin.sin_addr.s_addr = hostaddr;
400

401
    for(counter = 0; counter < 3; ++counter) {
402
        if(connect(sock, (struct sockaddr*)(&sin),
403
                   sizeof(struct sockaddr_in))) {
404
            fprintf(stderr,
405
                    "Connection to %s:%s attempt #%d failed: retrying...\n",
406
                    ip_address, port_string, counter);
407
            portable_sleep(1 + 2*counter);
408
        }
409
        else {
410
            ret = sock;
411
            break;
412
        }
413
    }
414
    if(ret == LIBSSH2_INVALID_SOCKET) {
415
        fprintf(stderr, "Failed to connect to %s:%s\n",
416
                ip_address, port_string);
417
        goto cleanup;
418
    }
419

420
cleanup:
421
    free(ip_address);
422
    free(port_string);
423

424
    return ret;
425
}
426

427
static void close_socket_to_container(libssh2_socket_t sock)
428
{
429
    if(sock != LIBSSH2_INVALID_SOCKET) {
430
        shutdown(sock, 2 /* SHUT_RDWR */);
431
#ifdef _WIN32
432
        closesocket(sock);
433
#else
434
        close(sock);
435
#endif
436
    }
437
}
438

439
static char *running_container_id = NULL;
440

441
int start_openssh_fixture(void)
442
{
443
    int ret;
444
#ifdef _WIN32
445
    WSADATA wsadata;
446

447
    ret = WSAStartup(MAKEWORD(2, 0), &wsadata);
448
    if(ret) {
449
        fprintf(stderr, "WSAStartup failed with error: %d\n", ret);
450
        return 1;
451
    }
452
#endif
453

454
    have_docker = !getenv("OPENSSH_NO_DOCKER");
455

456
    ret = build_openssh_server_docker_image();
457
    if(!ret) {
458
        return start_openssh_server(&running_container_id);
459
    }
460
    else {
461
        fprintf(stderr, "Failed to build docker image\n");
462
        return ret;
463
    }
464
}
465

466
void stop_openssh_fixture(void)
467
{
468
    if(running_container_id) {
469
        stop_openssh_server(running_container_id);
470
        free(running_container_id);
471
        running_container_id = NULL;
472
    }
473
    else if(have_docker) {
474
        fprintf(stderr, "Cannot stop container - none started\n");
475
    }
476

477
#ifdef _WIN32
478
    WSACleanup();
479
#endif
480
}
481

482
libssh2_socket_t open_socket_to_openssh_server(void)
483
{
484
    return open_socket_to_container(running_container_id);
485
}
486

487
void close_socket_to_openssh_server(libssh2_socket_t sock)
488
{
489
    close_socket_to_container(sock);
490
}
491

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.