1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2011-2020 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include "config.h"
18 #include "check_config.h"
19 #include "server.h"
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <ctype.h>
24 
25 #ifndef _WIN32
26 #define DIRSEP "/"
27 #include <pthread.h>
28 #include <sys/wait.h>
29 #include <arpa/inet.h>
30 #include <netinet/tcp.h>
31 #include <netinet/in.h>
32 #include <signal.h>
33 #ifdef linux
34 #undef ntohs
35 #undef ntohl
36 #undef htons
37 #undef htonl
38 #endif
39 #else
40 #define DIRSEP "\\"
41 #include <io.h> /* for access() */
42 #endif          /* ! _WIN32 */
43 
create_monitor(struct test_server_info *info)44 static int create_monitor(struct test_server_info *info)
45 {
46     struct addrinfo hints, *next, *ai;
47     int error;
48 
49     memset(&hints, 0, sizeof(hints));
50     hints.ai_flags = AI_PASSIVE;
51     hints.ai_family = AF_UNSPEC;
52     hints.ai_socktype = SOCK_STREAM;
53 
54     info->sock = -1;
55     error = getaddrinfo(NULL, "0", &hints, &ai);
56     if (error != 0) {
57 #ifdef _WIN32
58         if (0) {
59 #else
60         if (error != EAI_SYSTEM) {
61 #endif
62             fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(error));
63         } else {
64             perror("getaddrinfo failed:");
65         }
66         return 0;
67     }
68 
69     for (next = ai; next; next = next->ai_next) {
70         int flags = 1;
71         socklen_t len;
72 
73         if ((info->sock = socket(next->ai_family, next->ai_socktype, next->ai_protocol)) == -1) {
74             continue;
75         }
76 
77         setsockopt(info->sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
78 
79         if (bind(info->sock, next->ai_addr, next->ai_addrlen) == -1) {
80             closesocket(info->sock);
81             info->sock = -1;
82             continue;
83         } else if (listen(info->sock, 10) == -1) {
84             closesocket(info->sock);
85             info->sock = -1;
86             continue;
87         }
88 
89         /* Ok, I've got a working socket :) */
90         len = sizeof(info->storage);
91         if (getsockname(info->sock, (struct sockaddr *)&info->storage, &len) == -1) {
92             closesocket(info->sock);
93             info->sock = -1;
94             continue;
95         }
96         if (next->ai_addr->sa_family == AF_INET) {
97             info->port = ntohs((*(struct sockaddr_in *)&info->storage).sin_port);
98         } else {
99             info->port = ntohs((*(struct sockaddr_in6 *)&info->storage).sin6_port);
100         }
101     }
102 
103     freeaddrinfo(ai);
104     return info->sock != -1;
105 }
106 
107 static void wait_for_server(const char *port)
108 {
109     struct addrinfo hints, *next, *ai;
110     int sock = -1;
111     int error;
112 
113     memset(&hints, 0, sizeof(hints));
114     hints.ai_flags = AI_PASSIVE;
115     hints.ai_family = AF_UNSPEC;
116     hints.ai_socktype = SOCK_STREAM;
117 
118     error = getaddrinfo("localhost", port, &hints, &ai);
119     if (error != 0) {
120 #ifndef _WIN32
121         if (error != EAI_SYSTEM) {
122 #else
123         if (0) {
124 #endif
125             fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(error));
126         } else {
127             perror("getaddrinfo failed:");
128         }
129         abort();
130     }
131 
132     while (1) {
133         for (next = ai; next; next = next->ai_next) {
134             if ((sock = socket(next->ai_family, next->ai_socktype, next->ai_protocol)) == -1) {
135                 continue;
136             }
137 
138             if (connect(sock, next->ai_addr, next->ai_addrlen) == 0) {
139                 closesocket(sock);
140                 freeaddrinfo(ai);
141                 return;
142             }
143 
144             closesocket(sock);
145         }
146         usleep(250);
147     }
148 }
149 
150 /**
151  * Parse server parameters from environment;
152  * format is host,bucket,username,password
153  */
154 static int parse_server_conf(struct test_server_info *info, const char *param)
155 {
156     char *strings[10] = {NULL};
157     int curix = 0;
158     char *param_copy = strdup(param);
159     param = param_copy;
160 
161     while (*param && curix < 10) {
162         const char *curfld;
163         char *curval;
164         size_t diff;
165 
166         for (curfld = param; *param && *param != ','; param++)
167             ;
168         diff = (param - curfld);
169         curval = calloc(1, diff + 1);
170         curval[diff] = '\0';
171         memcpy(curval, curfld, diff);
172         strings[curix++] = curval;
173         if (*param == ',') {
174             param++;
175         }
176     }
177 
178     info->http = strings[0];
179     info->bucket = strings[1];
180     info->username = strings[2];
181     info->password = strings[3];
182 
183     free(param_copy);
184 
185     if (!info->http) {
186         fprintf(stderr, "Must have node entry point for real cluster test\n");
187         return 0;
188     }
189     return 1;
190 }
191 
192 static int start_mock_process(struct test_server_info *info, char **argv)
193 {
194     char argbuf[4096] = {0};
195     char **arg;
196     for (arg = argv; *arg; arg++) {
197         strcat(argbuf, *arg);
198         strcat(argbuf, " ");
199     }
200 
201     memset(&info->process, 0, sizeof(info->process));
202     info->process.name = argbuf;
203 
204     return create_process(&info->process);
205 }
206 
207 static void kill_mock_process(struct test_server_info *info)
208 {
209     kill_process(&info->process, 1);
210     wait_process(&info->process, 1);
211     cleanup_process(&info->process);
212 }
213 
214 #ifndef _WIN32
215 #define WRAPPER_BASE "start_mock.sh"
216 #else
217 #define WRAPPER_BASE "start_mock.bat"
218 #endif
219 
220 static void negotiate_mock_connection(struct test_server_info *info)
221 {
222     char buffer[1024];
223     lcb_ssize_t offset;
224     lcb_ssize_t nr;
225     int ii;
226 
227     /* wait until the server connects */
228     for (ii = 0; ii < 10; ii++) {
229         info->client = accept(info->sock, NULL, NULL);
230         if (info->client == -1) {
231             /* running this in gdb on OS X, I got an EINTR a few times */
232             if (errno == EINTR) {
233                 fprintf(stderr, "start_mock_server: Sleeping 1 second on EINTR\n");
234                 sleep(1);
235             } else {
236                 perror("start_mock_server");
237                 abort();
238             }
239         } else {
240             break;
241         }
242     }
243 
244     lcb_assert(info->client != -1);
245     /* Get the port number of the http server */
246     offset = snprintf(buffer, sizeof(buffer), "localhost:");
247     nr = recv(info->client, buffer + offset, sizeof(buffer) - (size_t)offset - 1, 0);
248     lcb_assert(nr > 0);
249     buffer[nr + offset] = '\0';
250     info->http = strdup(buffer);
251     wait_for_server(buffer + offset);
252 }
253 
254 static int start_mock_server(struct test_server_info *info, char **cmdline)
255 {
256 
257     char wrapper[1024];
258     char monitor[1024];
259     char *argv[1024];
260 #ifdef _WIN32
261     int access_mode = 00;
262 #else
263     int access_mode = X_OK;
264 #endif
265     const char *srcdir = getenv("srcdir");
266 
267     if (srcdir == NULL) {
268         srcdir = TEST_SRC_DIR;
269     }
270 
271     snprintf(wrapper, sizeof(wrapper), "%s" DIRSEP "tests" DIRSEP WRAPPER_BASE, srcdir);
272 
273     if (access(wrapper, access_mode) == -1) {
274         fprintf(stderr, "Failed to locate \"%s\": %s\n", wrapper, strerror(errno));
275         return 0;
276     }
277 
278     if (!create_monitor(info)) {
279         return 0;
280     }
281 
282     {
283         int arg = 0;
284         argv[arg++] = (char *)wrapper;
285         sprintf(monitor, "--harakiri-monitor=localhost:%d", info->port);
286         argv[arg++] = monitor;
287 
288         if (cmdline != NULL) {
289             int ii = 0;
290             while (cmdline[ii] != NULL && arg < 1022) {
291                 argv[arg++] = cmdline[ii++];
292             }
293         }
294         argv[arg++] = NULL;
295     }
296 
297     start_mock_process(info, argv);
298     negotiate_mock_connection(info);
299     return 1;
300 }
301 
302 const void *start_test_server(char **cmdline)
303 {
304     const char *clconf = getenv(LCB_TEST_REALCLUSTER_ENV);
305     int server_ok = 0;
306     struct test_server_info *info = calloc(1, sizeof(*info));
307 
308 #ifdef _WIN32
309     /** Winsock boilerplate */
310     {
311         WSADATA wsaData;
312         if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) {
313             fprintf(stderr, "WSAStartup failed. Abort\n");
314             abort();
315         }
316     }
317 #endif
318 
319     if (info == NULL) {
320         return NULL;
321     }
322 
323     if (clconf) {
324         server_ok = parse_server_conf(info, clconf);
325         info->is_mock = 0;
326     } else {
327         server_ok = start_mock_server(info, cmdline);
328         info->is_mock = 1;
329     }
330 
331     if (!server_ok) {
332         fprintf(stderr, "Couldn't setup server!\n");
333         abort();
334     }
335 
336     return info;
337 }
338 
339 void shutdown_mock_server(const void *handle)
340 {
341     struct test_server_info *info = (void *)handle;
342     if (info != NULL) {
343         free(info->http);
344         free(info->bucket);
345         free(info->username);
346         free(info->password);
347         if (info->is_mock) {
348             closesocket(info->client);
349             closesocket(info->sock);
350             kill_mock_process(info);
351         }
352         free((void *)handle);
353     }
354 }
355 
356 const char *get_mock_http_server(const void *handle)
357 {
358     struct test_server_info *info = (void *)handle;
359     return info->http;
360 }
361 
362 void get_mock_std_creds(const void *handle, const char **userp, const char **passp)
363 {
364     const struct test_server_info *info = handle;
365     if (info->is_mock) {
366         *userp = NULL;
367         *passp = NULL;
368     } else {
369         *userp = info->username;
370         *passp = info->password;
371     }
372 }
373 
374 int is_using_real_cluster(void)
375 {
376     return getenv(LCB_TEST_REALCLUSTER_ENV) != NULL;
377 }
378