1 /* Copyright (C) 2016 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
38 #include "openssh_fixture.h"
39 #include "libssh2_config.h"
40
41 #ifdef HAVE_WINSOCK2_H
42 #include <winsock2.h>
43 #endif
44 #ifdef HAVE_SYS_SOCKET_H
45 #include <sys/socket.h>
46 #endif
47 #ifdef HAVE_ARPA_INET_H
48 #include <arpa/inet.h>
49 #endif
50 #ifdef HAVE_NETINET_IN_H
51 #include <netinet/in.h>
52 #endif
53 #ifdef HAVE_UNISTD_H
54 #include <unistd.h>
55 #endif
56 #include <ctype.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <stdarg.h>
61
run_command_varg(char ** output,const char * command,va_list args)62 static int run_command_varg(char **output, const char *command, va_list args)
63 {
64 FILE *pipe;
65 char redirect_stderr[] = "%s 2>&1";
66 char command_buf[BUFSIZ];
67 char buf[BUFSIZ];
68 int ret;
69 size_t buf_len;
70
71 if(output) {
72 *output = NULL;
73 }
74
75 /* Format the command string */
76 ret = vsnprintf(command_buf, sizeof(command_buf), command, args);
77 if(ret < 0 || ret >= BUFSIZ) {
78 fprintf(stderr, "Unable to format command (%s)\n", command);
79 return -1;
80 }
81
82 /* Rewrite the command to redirect stderr to stdout to we can output it */
83 if(strlen(command_buf) + strlen(redirect_stderr) >= sizeof(buf)) {
84 fprintf(stderr, "Unable to rewrite command (%s)\n", command);
85 return -1;
86 }
87
88 ret = snprintf(buf, sizeof(buf), redirect_stderr, command_buf);
89 if(ret < 0 || ret >= BUFSIZ) {
90 fprintf(stderr, "Unable to rewrite command (%s)\n", command);
91 return -1;
92 }
93
94 fprintf(stdout, "Command: %s\n", command);
95 #ifdef WIN32
96 pipe = _popen(buf, "r");
97 #else
98 pipe = popen(buf, "r");
99 #endif
100 if(!pipe) {
101 fprintf(stderr, "Unable to execute command '%s'\n", command);
102 return -1;
103 }
104 buf[0] = 0;
105 buf_len = 0;
106 while(buf_len < (sizeof(buf) - 1) &&
107 fgets(&buf[buf_len], sizeof(buf) - buf_len, pipe) != NULL) {
108 buf_len = strlen(buf);
109 }
110
111 #ifdef WIN32
112 ret = _pclose(pipe);
113 #else
114 ret = pclose(pipe);
115 #endif
116 if(ret != 0) {
117 fprintf(stderr, "Error running command '%s' (exit %d): %s\n",
118 command, ret, buf);
119 }
120
121 if(output) {
122 /* command output may contain a trailing newline, so we trim
123 * whitespace here */
124 size_t end = strlen(buf);
125 while(end > 0 && isspace(buf[end - 1])) {
126 buf[end - 1] = '\0';
127 }
128
129 *output = strdup(buf);
130 }
131 return ret;
132 }
133
run_command(char ** output,const char * command,...)134 static int run_command(char **output, const char *command, ...)
135 {
136 va_list args;
137 int ret;
138
139 va_start(args, command);
140 ret = run_command_varg(output, command, args);
141 va_end(args);
142
143 return ret;
144 }
145
build_openssh_server_docker_image(void)146 static int build_openssh_server_docker_image(void)
147 {
148 return run_command(NULL, "docker build -t libssh2/openssh_server "
149 "openssh_server");
150 }
151
openssh_server_port(void)152 static const char *openssh_server_port(void)
153 {
154 return getenv("OPENSSH_SERVER_PORT");
155 }
156
start_openssh_server(char ** container_id_out)157 static int start_openssh_server(char **container_id_out)
158 {
159 const char *container_host_port = openssh_server_port();
160 if(container_host_port != NULL) {
161 return run_command(container_id_out,
162 "docker run --rm -d -p %s:22 "
163 "libssh2/openssh_server",
164 container_host_port);
165 }
166 else {
167 return run_command(container_id_out,
168 "docker run --rm -d -p 22 "
169 "libssh2/openssh_server");
170 }
171 }
172
stop_openssh_server(char * container_id)173 static int stop_openssh_server(char *container_id)
174 {
175 return run_command(NULL, "docker stop %s", container_id);
176 }
177
docker_machine_name(void)178 static const char *docker_machine_name(void)
179 {
180 return getenv("DOCKER_MACHINE_NAME");
181 }
182
is_running_inside_a_container()183 static int is_running_inside_a_container()
184 {
185 #ifdef WIN32
186 return 0;
187 #else
188 const char *cgroup_filename = "/proc/self/cgroup";
189 FILE *f = NULL;
190 char *line = NULL;
191 size_t len = 0;
192 ssize_t read = 0;
193 int found = 0;
194 f = fopen(cgroup_filename, "r");
195 if(f == NULL) {
196 /* Don't go further, we are not in a container */
197 return 0;
198 }
199 while((read = getline(&line, &len, f)) != -1) {
200 if(strstr(line, "docker") != NULL) {
201 found = 1;
202 break;
203 }
204 }
205 fclose(f);
206 free(line);
207 return found;
208 #endif
209 }
210
portable_sleep(unsigned int seconds)211 static unsigned int portable_sleep(unsigned int seconds)
212 {
213 #ifdef WIN32
214 Sleep(seconds);
215 #else
216 sleep(seconds);
217 #endif
218 }
219
ip_address_from_container(char * container_id,char ** ip_address_out)220 static int ip_address_from_container(char *container_id, char **ip_address_out)
221 {
222 const char *active_docker_machine = docker_machine_name();
223 if(active_docker_machine != NULL) {
224
225 /* This can be flaky when tests run in parallel (see
226 https://github.com/docker/machine/issues/2612), so we retry a few
227 times with exponential backoff if it fails */
228 int attempt_no = 0;
229 int wait_time = 500;
230 for(;;) {
231 int ret = run_command(ip_address_out, "docker-machine ip %s",
232 active_docker_machine);
233 if(ret == 0) {
234 return 0;
235 }
236 else if(attempt_no > 5) {
237 fprintf(
238 stderr,
239 "Unable to get IP from docker-machine after %d attempts\n",
240 attempt_no);
241 return -1;
242 }
243 else {
244 portable_sleep(wait_time);
245 ++attempt_no;
246 wait_time *= 2;
247 }
248 }
249 }
250 else {
251 if(is_running_inside_a_container()) {
252 return run_command(ip_address_out,
253 "docker inspect --format "
254 "\"{{ .NetworkSettings.IPAddress }}\""
255 " %s",
256 container_id);
257 }
258 else {
259 return run_command(ip_address_out,
260 "docker inspect --format "
261 "\"{{ index (index (index "
262 ".NetworkSettings.Ports "
263 "\\\"22/tcp\\\") 0) \\\"HostIp\\\" }}\" %s",
264 container_id);
265 }
266 }
267 }
268
port_from_container(char * container_id,char ** port_out)269 static int port_from_container(char *container_id, char **port_out)
270 {
271 if(is_running_inside_a_container()) {
272 *port_out = strdup("22");
273 return 0;
274 }
275 else {
276 return run_command(port_out,
277 "docker inspect --format "
278 "\"{{ index (index (index .NetworkSettings.Ports "
279 "\\\"22/tcp\\\") 0) \\\"HostPort\\\" }}\" %s",
280 container_id);
281 }
282 }
283
open_socket_to_container(char * container_id)284 static int open_socket_to_container(char *container_id)
285 {
286 char *ip_address = NULL;
287 char *port_string = NULL;
288 unsigned long hostaddr;
289 int sock;
290 struct sockaddr_in sin;
291 int counter = 0;
292
293 int ret = ip_address_from_container(container_id, &ip_address);
294 if(ret != 0) {
295 fprintf(stderr, "Failed to get IP address for container %s\n",
296 container_id);
297 ret = -1;
298 goto cleanup;
299 }
300
301 ret = port_from_container(container_id, &port_string);
302 if(ret != 0) {
303 fprintf(stderr, "Failed to get port for container %s\n",
304 container_id);
305 ret = -1;
306 }
307
308 /* 0.0.0.0 is returned by Docker for Windows, because the container
309 is reachable from anywhere. But we cannot connect to 0.0.0.0,
310 instead we assume localhost and try to connect to 127.0.0.1. */
311 if(ip_address && strcmp(ip_address, "0.0.0.0") == 0) {
312 free(ip_address);
313 ip_address = strdup("127.0.0.1");
314 }
315
316 hostaddr = inet_addr(ip_address);
317 if(hostaddr == (unsigned long)(-1)) {
318 fprintf(stderr, "Failed to convert %s host address\n", ip_address);
319 ret = -1;
320 goto cleanup;
321 }
322
323 sock = socket(AF_INET, SOCK_STREAM, 0);
324 if(sock <= 0) {
325 fprintf(stderr, "Failed to open socket (%d)\n", sock);
326 ret = -1;
327 goto cleanup;
328 }
329
330 sin.sin_family = AF_INET;
331 sin.sin_port = htons((short)strtol(port_string, NULL, 0));
332 sin.sin_addr.s_addr = hostaddr;
333
334 for(counter = 0; counter < 3; ++counter) {
335 if(connect(sock, (struct sockaddr *)(&sin),
336 sizeof(struct sockaddr_in)) != 0) {
337 ret = -1;
338 fprintf(stderr,
339 "Connection to %s:%s attempt #%d failed: retrying...\n",
340 ip_address, port_string, counter);
341 portable_sleep(1 + 2*counter);
342 }
343 else {
344 ret = sock;
345 break;
346 }
347 }
348 if(ret == -1) {
349 fprintf(stderr, "Failed to connect to %s:%s\n",
350 ip_address, port_string);
351 goto cleanup;
352 }
353
354 cleanup:
355 free(ip_address);
356 free(port_string);
357
358 return ret;
359 }
360
361 static char *running_container_id = NULL;
362
start_openssh_fixture()363 int start_openssh_fixture()
364 {
365 int ret;
366 #ifdef HAVE_WINSOCK2_H
367 WSADATA wsadata;
368
369 ret = WSAStartup(MAKEWORD(2, 0), &wsadata);
370 if(ret != 0) {
371 fprintf(stderr, "WSAStartup failed with error: %d\n", ret);
372 return 1;
373 }
374 #endif
375
376 ret = build_openssh_server_docker_image();
377 if(ret == 0) {
378 return start_openssh_server(&running_container_id);
379 }
380 else {
381 fprintf(stderr, "Failed to build docker image\n");
382 return ret;
383 }
384 }
385
stop_openssh_fixture()386 void stop_openssh_fixture()
387 {
388 if(running_container_id) {
389 stop_openssh_server(running_container_id);
390 free(running_container_id);
391 running_container_id = NULL;
392 }
393 else {
394 fprintf(stderr, "Cannot stop container - none started");
395 }
396 }
397
open_socket_to_openssh_server()398 int open_socket_to_openssh_server()
399 {
400 return open_socket_to_container(running_container_id);
401 }
402