1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V.
7 */
8
9#include "monetdb_config.h"
10#include "control.h"
11#include <unistd.h> /* close */
12#include <string.h> /* strerror */
13#include <sys/socket.h> /* socket */
14#ifdef HAVE_SYS_UN_H
15#include <sys/un.h> /* sockaddr_un */
16#endif
17#include <netdb.h>
18#include <netinet/in.h>
19#include <fcntl.h>
20
21#include "stream.h"
22#include "stream_socket.h"
23#include "mcrypt.h"
24#include "mstring.h"
25
26#define SOCKPTR struct sockaddr *
27
28/* Sends command for database to merovingian listening at host and port.
29 * If host is a path, and port is -1, a UNIX socket connection for host
30 * is opened. The response of merovingian is returned as a malloced
31 * string. If wait is set to a non-zero value, this function will only
32 * return after it has seen an EOF from the server. This is useful with
33 * multi-line responses, but can lock up for single line responses where
34 * the server allows pipelining (and hence doesn't close the
35 * connection).
36 */
37char* control_send(
38 char** ret,
39 char* host,
40 int port,
41 char* database,
42 char* command,
43 char wait,
44 char* pass)
45{
46 char sbuf[8096];
47 char rbuf[8096];
48 char *buf;
49 int sock = -1;
50 ssize_t len;
51 stream *fdin = NULL;
52 stream *fdout = NULL;
53
54 *ret = NULL; /* gets overwritten in case of success */
55 if (port == -1) {
56 struct sockaddr_un server;
57 /* UNIX socket connect */
58 if ((sock = socket(PF_UNIX, SOCK_STREAM
59#ifdef SOCK_CLOEXEC
60 | SOCK_CLOEXEC
61#endif
62 , 0)) == -1) {
63 snprintf(sbuf, sizeof(sbuf), "cannot open connection: %s",
64 strerror(errno));
65 return(strdup(sbuf));
66 }
67#if !defined(SOCK_CLOEXEC) && defined(HAVE_FCNTL)
68 (void) fcntl(sock, F_SETFD, FD_CLOEXEC);
69#endif
70 server = (struct sockaddr_un) {
71 .sun_family = AF_UNIX,
72 };
73 strcpy_len(server.sun_path, host, sizeof(server.sun_path));
74 if (connect(sock, (SOCKPTR) &server, sizeof(struct sockaddr_un)) == -1) {
75 snprintf(sbuf, sizeof(sbuf), "cannot connect: %s", strerror(errno));
76 closesocket(sock);
77 return(strdup(sbuf));
78 }
79 } else {
80 int check;
81 char ver = 0, *p;
82 char sport[16];
83 struct addrinfo *res, *rp, hints = (struct addrinfo) {
84 .ai_family = AF_UNSPEC,
85 .ai_socktype = SOCK_STREAM,
86 .ai_protocol = IPPROTO_TCP,
87 };
88
89 snprintf(sport, sizeof(sport), "%d", port & 0xFFFF);
90 check = getaddrinfo(host, sport, &hints, &res);
91 if (check) {
92 snprintf(sbuf, sizeof(sbuf), "cannot connect: %s", gai_strerror(check));
93 return(strdup(sbuf));
94 }
95 for (rp = res; rp; rp = rp->ai_next) {
96 sock = socket(rp->ai_family, rp->ai_socktype
97#ifdef SOCK_CLOEXEC
98 | SOCK_CLOEXEC
99#endif
100 , rp->ai_protocol);
101 if (sock == INVALID_SOCKET)
102 continue;
103 if (connect(sock, rp->ai_addr, (socklen_t) rp->ai_addrlen) != SOCKET_ERROR)
104 break; /* success */
105 }
106 freeaddrinfo(res);
107 if (rp == NULL) {
108 snprintf(sbuf, sizeof(sbuf), "cannot connect to %s:%s: %s", host, sport,
109#ifdef _MSC_VER
110 wsaerror(WSAGetLastError())
111#else
112 strerror(errno)
113#endif
114 );
115 return(strdup(sbuf));
116 }
117#if !defined(SOCK_CLOEXEC) && defined(HAVE_FCNTL)
118 (void) fcntl(sock, F_SETFD, FD_CLOEXEC);
119#endif
120
121 /* try reading length */
122 len = recv(sock, rbuf, 2, 0);
123 if (len == 2)
124 len += recv(sock, rbuf + len, sizeof(rbuf) - len - 1, 0);
125 /* perform login ritual */
126 if (len <= 2) {
127 snprintf(sbuf, sizeof(sbuf), "no response from monetdbd");
128 closesocket(sock);
129 return(strdup(sbuf));
130 }
131 rbuf[len] = 0;
132 /* we only understand merovingian:1 and :2 (backwards compat
133 * <=Aug2011) and mapi v9 on merovingian */
134 if (strncmp(rbuf, "merovingian:1:", strlen("merovingian:1:")) == 0) {
135 buf = rbuf + strlen("merovingian:1:");
136 ver = 1;
137 } else if (strncmp(rbuf, "merovingian:2:", strlen("merovingian:2:")) == 0) {
138 buf = rbuf + strlen("merovingian:2:");
139 ver = 2;
140 } else if (strstr(rbuf + 2, ":merovingian:9:") != NULL) {
141 buf = rbuf + 2;
142 ver = 9;
143
144 fdin = block_stream(socket_rstream(sock, "client in"));
145 fdout = block_stream(socket_wstream(sock, "client out"));
146 } else {
147 if (len > 2 &&
148 (strstr(rbuf + 2, ":BIG:") != NULL ||
149 strstr(rbuf + 2, ":LIT:") != NULL))
150 {
151 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
152 "server looks like a mapi server, "
153 "are you connecting to an mserver directly "
154 "instead of monetdbd?");
155 } else {
156 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
157 "unsupported monetdbd server");
158 }
159 closesocket(sock);
160 return(strdup(sbuf));
161 }
162
163 switch (ver) {
164 case 1:
165 case 2: /* we never really used the mode specifier of v2 */
166 p = strchr(buf, ':');
167 if (p != NULL)
168 *p = '\0';
169 p = control_hash(pass, buf);
170 len = snprintf(sbuf, sizeof(sbuf), "%s%s\n",
171 p, ver == 2 ? ":control" : "");
172 len = send(sock, sbuf, len, 0);
173 free(p);
174 if (len == -1) {
175 closesocket(sock);
176 return(strdup("cannot send challenge response to server"));
177 }
178 break;
179 case 9:
180 {
181 char *chal = NULL;
182 char *algos = NULL;
183 char *shash = NULL;
184 char *phash = NULL;
185 char *algsv[] = {
186#ifdef HAVE_RIPEMD160_UPDATE
187 "RIPEMD160",
188#endif
189#ifdef HAVE_SHA256_UPDATE
190 "SHA256",
191#endif
192#ifdef HAVE_SHA1_UPDATE
193 "SHA1",
194#endif
195#ifdef HAVE_MD5_UPDATE
196 "MD5",
197#endif
198 NULL
199 };
200 char **algs = algsv;
201
202 /* buf at this point looks like
203 * "challenge:servertype:protover:algos:endian:hash:" */
204 chal = buf; /* chal */
205 p = strchr(chal, ':');
206 if (p == NULL) {
207 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
208 "invalid challenge from monetdbd server");
209 close_stream(fdout);
210 close_stream(fdin);
211 return(strdup(sbuf));
212 }
213 *p++ = '\0'; /* servertype */
214 p = strchr(p, ':');
215 if (p == NULL) {
216 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
217 "invalid challenge from monetdbd server");
218 close_stream(fdout);
219 close_stream(fdin);
220 return(strdup(sbuf));
221 }
222 *p++ = '\0'; /* protover */
223 p = strchr(p, ':');
224 if (p == NULL) {
225 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
226 "invalid challenge from monetdbd server");
227 close_stream(fdout);
228 close_stream(fdin);
229 return(strdup(sbuf));
230 }
231 *p++ = '\0'; /* algos */
232 algos = p;
233 p = strchr(p, ':');
234 if (p == NULL) {
235 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
236 "invalid challenge from monetdbd server");
237 close_stream(fdout);
238 close_stream(fdin);
239 return(strdup(sbuf));
240 }
241 *p++ = '\0'; /* endian */
242 p = strchr(p, ':');
243 if (p == NULL) {
244 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
245 "invalid challenge from monetdbd server");
246 close_stream(fdout);
247 close_stream(fdin);
248 return(strdup(sbuf));
249 }
250 *p++ = '\0'; /* hash */
251 shash = p;
252 p = strchr(p, ':');
253 if (p == NULL) {
254 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
255 "invalid challenge from monetdbd server");
256 close_stream(fdout);
257 close_stream(fdin);
258 return(strdup(sbuf));
259 }
260 *p = '\0';
261
262 /* we first need to hash our password in the form the
263 * server stores it too */
264#ifdef HAVE_RIPEMD160_UPDATE
265 if (strcmp(shash, "RIPEMD160") == 0) {
266 phash = mcrypt_RIPEMD160Sum(pass, strlen(pass));
267 } else
268#endif
269#ifdef HAVE_SHA512_UPDATE
270 if (strcmp(shash, "SHA512") == 0) {
271 phash = mcrypt_SHA512Sum(pass, strlen(pass));
272 } else
273#endif
274#ifdef HAVE_SHA384_UPDATE
275 if (strcmp(shash, "SHA384") == 0) {
276 phash = mcrypt_SHA384Sum(pass, strlen(pass));
277 } else
278#endif
279#ifdef HAVE_SHA256_UPDATE
280 if (strcmp(shash, "SHA256") == 0) {
281 phash = mcrypt_SHA256Sum(pass, strlen(pass));
282 } else
283#endif
284#ifdef HAVE_SHA224_UPDATE
285 if (strcmp(shash, "SHA224") == 0) {
286 phash = mcrypt_SHA224Sum(pass, strlen(pass));
287 } else
288#endif
289#ifdef HAVE_SHA1_UPDATE
290 if (strcmp(shash, "SHA1") == 0) {
291 phash = mcrypt_SHA1Sum(pass, strlen(pass));
292 } else
293#endif
294#ifdef HAVE_MD5_UPDATE
295 if (strcmp(shash, "MD5") == 0) {
296 phash = mcrypt_MD5Sum(pass, strlen(pass));
297 } else
298#endif
299 {
300 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
301 "monetdbd server requires unknown hash: %s", shash);
302 close_stream(fdout);
303 close_stream(fdin);
304 return(strdup(sbuf));
305 }
306
307 if (!phash) {
308 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
309 "allocation failure while establishing connection");
310 close_stream(fdout);
311 close_stream(fdin);
312 return(strdup(sbuf));
313 }
314
315 /* now hash the password hash with the provided
316 * challenge */
317 for (; *algs != NULL; algs++) {
318 /* TODO: make this actually obey the separation by
319 * commas, and only allow full matches */
320 if (strstr(algos, *algs) != NULL) {
321 p = mcrypt_hashPassword(*algs, phash, chal);
322 if (p == NULL)
323 continue;
324 mnstr_printf(fdout,
325 "BIG:monetdb:{%s}%s:control:merovingian:\n",
326 *algs, p);
327 mnstr_flush(fdout);
328 free(p);
329 break;
330 }
331 }
332 free(phash);
333 if (p == NULL) {
334 /* the server doesn't support what we can */
335 snprintf(sbuf, sizeof(sbuf), "cannot connect: "
336 "unsupported hash algoritms: %s", algos);
337 close_stream(fdout);
338 close_stream(fdin);
339 return(strdup(sbuf));
340 }
341 }
342 }
343
344 if (fdin != NULL) {
345 /* stream.h is sooo broken :( */
346 memset(rbuf, '\0', sizeof(rbuf));
347 if ((len = mnstr_read_block(fdin, rbuf, sizeof(rbuf) - 1, 1)) < 0) {
348 close_stream(fdout);
349 close_stream(fdin);
350 return(strdup("no response from monetdbd after login"));
351 }
352 rbuf[len - 1] = '\0';
353 } else {
354 if ((len = recv(sock, rbuf, sizeof(rbuf), 0)) <= 0) {
355 closesocket(sock);
356 return(strdup("no response from monetdbd after login"));
357 }
358 rbuf[len - 1] = '\0';
359 }
360
361 if (strncmp(rbuf, "=OK", 3) != 0 && strncmp(rbuf, "OK", 2) != 0) {
362 buf = rbuf;
363 if (*buf == '!')
364 buf++;
365 if (fdin != NULL) {
366 close_stream(fdout);
367 close_stream(fdin);
368 } else {
369 closesocket(sock);
370 }
371 return(strdup(buf));
372 }
373 }
374
375 if (fdout != NULL) {
376 mnstr_printf(fdout, "%s %s\n", database, command);
377 mnstr_flush(fdout);
378 } else {
379 len = snprintf(sbuf, sizeof(sbuf), "%s %s\n", database, command);
380 if (send(sock, sbuf, len, 0) == -1) {
381 closesocket(sock);
382 return(strdup("failed to send control command to server"));
383 }
384 }
385 if (wait != 0) {
386 size_t buflen = sizeof(sbuf);
387 size_t bufpos = 0;
388 char *bufp;
389 bufp = buf = malloc(sizeof(char) * buflen);
390 if (buf == NULL) {
391 if (fdin != NULL) {
392 close_stream(fdin);
393 close_stream(fdout);
394 } else {
395 closesocket(sock);
396 }
397 return(strdup("failed to allocate memory"));
398 }
399 while (1) {
400 if (fdin != NULL) {
401 /* stream.h is sooo broken :( */
402 memset(buf + bufpos, '\0', buflen - bufpos);
403 len = mnstr_read_block(fdin, buf + bufpos, buflen - bufpos - 1, 1);
404 if (len >= 0)
405 len = strlen(buf + bufpos);
406 } else {
407 len = recv(sock, buf + bufpos, buflen - bufpos, 0);
408 }
409 if (len <= 0)
410 break;
411 if ((size_t)len == buflen - bufpos) {
412 buflen *= 2;
413 bufp = realloc(buf, sizeof(char) * buflen);
414 if (bufp == NULL) {
415 free(buf);
416 if (fdin != NULL) {
417 close_stream(fdin);
418 close_stream(fdout);
419 } else {
420 closesocket(sock);
421 }
422 return(strdup("failed to allocate more memory"));
423 }
424 buf = bufp;
425 }
426 bufpos += (size_t)len;
427 }
428 if (bufpos == 0) {
429 if (fdin != NULL) {
430 close_stream(fdin);
431 close_stream(fdout);
432 } else {
433 closesocket(sock);
434 }
435 free(buf);
436 return(strdup("incomplete response from monetdbd"));
437 }
438 buf[bufpos - 1] = '\0';
439
440 if (fdin) {
441 /* strip out protocol = */
442 memmove(bufp, bufp + 1, strlen(bufp + 1) + 1);
443 while ((bufp = strstr(bufp, "\n=")) != NULL)
444 memmove(bufp + 1, bufp + 2, strlen(bufp + 2) + 1);
445 }
446 *ret = buf;
447 } else {
448 if (fdin != NULL) {
449 if (mnstr_read_block(fdin, rbuf, sizeof(rbuf) - 1, 1) < 0) {
450 close_stream(fdin);
451 close_stream(fdout);
452 return(strdup("incomplete response from monetdbd"));
453 }
454 rbuf[strlen(rbuf) - 1] = '\0';
455 *ret = strdup(rbuf + 1);
456 } else {
457 if ((len = recv(sock, rbuf, sizeof(rbuf), 0)) <= 0) {
458 closesocket(sock);
459 return(strdup("incomplete response from monetdbd"));
460 }
461 rbuf[len - 1] = '\0';
462 *ret = strdup(rbuf);
463 }
464 }
465
466 if (fdin != NULL) {
467 close_stream(fdin);
468 close_stream(fdout);
469 } else {
470 closesocket(sock);
471 }
472
473 return(NULL);
474}
475
476/**
477 * Returns a hash for pass and salt, to use when logging in on a remote
478 * merovingian. The result is a malloced string.
479 * DEPRECATED
480 * This function is deprecated, and only used for authorisation to v1
481 * and v2 protocol merovingians (<=Aug2011).
482 */
483char *
484control_hash(char *pass, char *salt) {
485 unsigned int ho;
486 unsigned int h = 0;
487 char buf[32];
488
489 /* use a very simple hash function designed for a single int val
490 * (hash buckets), we can make this more interesting if necessary in
491 * the future.
492 * http://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html */
493
494 while (*pass != '\0') {
495 ho = h & 0xf8000000;
496 h <<= 5;
497 h ^= ho >> 27;
498 h ^= (unsigned int)(*pass);
499 pass++;
500 }
501
502 while (*salt != '\0') {
503 ho = h & 0xf8000000;
504 h <<= 5;
505 h ^= ho >> 27;
506 h ^= (unsigned int)(*salt);
507 salt++;
508 }
509
510 snprintf(buf, sizeof(buf), "%u", h);
511 return(strdup(buf));
512}
513
514/**
515 * Returns if the merovingian server at host, port using pass is alive
516 * or not. If alive, 0 is returned. The same rules for host, port and
517 * pass hold as for control_send regarding the use of a UNIX vs TCP
518 * socket.
519 */
520char
521control_ping(char *host, int port, char *pass) {
522 char *res;
523 char *err;
524 if ((err = control_send(&res, host, port, "", "ping", 0, pass)) == NULL) {
525 if (res != NULL)
526 free(res);
527 return(0);
528 }
529 free(err);
530 return(1);
531}
532