| 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 | |