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 | */ |
37 | char* 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 | */ |
483 | char * |
484 | control_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 | */ |
520 | char |
521 | control_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 | |