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
44static 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
107static 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 */
154static 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
192static 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
207static 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
220static 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
254static 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
302const 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
339void 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
356const char *get_mock_http_server(const void *handle)
357{
358    struct test_server_info *info = (void *)handle;
359    return info->http;
360}
361
362void 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
374int is_using_real_cluster(void)
375{
376    return getenv(LCB_TEST_REALCLUSTER_ENV) != NULL;
377}
378