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