Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : #include "monetdb_config.h"
14 : #include "stream.h" /* include before mapi.h */
15 : #include "stream_socket.h"
16 : #include "mapi.h"
17 : #include "mapi_prompt.h"
18 : #include "mcrypt.h"
19 : #include "matomic.h"
20 : #include "mstring.h"
21 : #include "mutils.h"
22 :
23 : #include "mapi_intern.h"
24 :
25 : #ifdef HAVE_SYS_SOCKET_H
26 : # include <arpa/inet.h> /* addr_in */
27 : #else /* UNIX specific */
28 : #ifdef HAVE_WINSOCK_H /* Windows specific */
29 : # include <winsock.h>
30 : #endif
31 : #endif
32 :
33 :
34 : #ifdef HAVE_SYS_UN_H
35 : #define DO_UNIX_DOMAIN (1)
36 : #else
37 : #define DO_UNIX_DOMAIN (0)
38 : #endif
39 :
40 : #ifdef _MSC_VER
41 : #define SOCKET_STRERROR() wsaerror(WSAGetLastError())
42 : #else
43 : #define SOCKET_STRERROR() strerror(errno)
44 : #endif
45 :
46 :
47 : static MapiMsg scan_sockets(Mapi mid);
48 : static MapiMsg connect_socket(Mapi mid);
49 : static MapiMsg connect_socket_tcp(Mapi mid);
50 : static SOCKET connect_socket_tcp_addr(Mapi mid, struct addrinfo *addr);
51 : static MapiMsg mapi_handshake(Mapi mid);
52 :
53 : #ifndef HAVE_OPENSSL
54 : // The real implementation is in connect_openssl.c.
55 : MapiMsg
56 : wrap_tls(Mapi mid, SOCKET sock)
57 : {
58 : closesocket(sock);
59 : return mapi_setError(mid, "Cannot connect to monetdbs://, not built with OpenSSL support", __func__, MERROR);
60 : }
61 : #endif // HAVE_OPENSSL
62 :
63 : #ifndef HAVE_SYS_UN_H
64 :
65 : MapiMsg
66 : connect_socket_unix(Mapi mid)
67 : {
68 : return mapi_setError(mid, "Unix domain sockets not supported", __func__, MERROR);
69 : }
70 :
71 : static MapiMsg
72 : scan_unix_sockets(Mapi mid)
73 : {
74 : return mapi_setError(mid, "Unix domain sockets not supported", __func__, MERROR);
75 : }
76 :
77 : #endif
78 :
79 :
80 :
81 :
82 : /* (Re-)establish a connection with the server. */
83 : MapiMsg
84 1393 : mapi_reconnect(Mapi mid)
85 : {
86 1393 : char *err = NULL;
87 1393 : if (!msettings_validate(mid->settings, &err)) {
88 0 : mapi_setError(mid, err, __func__, MERROR);
89 0 : free(err);
90 0 : return MERROR;
91 : }
92 :
93 : // If neither host nor port are given, scan the Unix domain sockets in
94 : // /tmp and see if any of them serve this database.
95 : // Otherwise, just try to connect to what was given.
96 1393 : if (msettings_connect_scan(mid->settings))
97 0 : return scan_sockets(mid);
98 : else
99 1393 : return establish_connection(mid);
100 : }
101 :
102 : static MapiMsg
103 0 : scan_sockets(Mapi mid)
104 : {
105 0 : if (scan_unix_sockets(mid) == MOK)
106 : return MOK;
107 :
108 : // When the Unix sockets have been scanned we can freely modify 'original'.
109 0 : msettings_error errmsg = msetting_set_string(mid->settings, MP_HOST, "localhost");
110 0 : char *allocated_errmsg = NULL;
111 0 : if (!errmsg && !msettings_validate(mid->settings, &allocated_errmsg)) {
112 0 : errmsg = allocated_errmsg;
113 : }
114 0 : if (errmsg) {
115 0 : MapiMsg err = mapi_setError(mid, errmsg, __func__, MERROR);
116 0 : free(allocated_errmsg);
117 0 : return err;
118 : }
119 0 : return establish_connection(mid);
120 : }
121 :
122 : /* (Re-)establish a connection with the server. */
123 : MapiMsg
124 1393 : establish_connection(Mapi mid)
125 : {
126 1393 : if (mid->connected) {
127 1 : mapi_log_record(mid, "CONN", "Found leftover open connection");
128 1 : close_connection(mid);
129 : }
130 :
131 : MapiMsg msg = MREDIRECT;
132 2776 : while (msg == MREDIRECT) {
133 : // Generally at this point we need to set up a new TCP or Unix
134 : // domain connection.
135 : //
136 : // The only exception is if mapi_handshake() below has decided
137 : // that the handshake must be restarted on the existing
138 : // connection.
139 1393 : if (!mid->connected) {
140 1393 : msg = connect_socket(mid);
141 1393 : if (msg != MOK)
142 10 : return msg;
143 : }
144 1383 : msg = mapi_handshake(mid);
145 : }
146 :
147 : return msg;
148 : }
149 :
150 : static MapiMsg
151 1393 : connect_socket(Mapi mid)
152 : {
153 1393 : assert(!mid->connected);
154 1393 : const char *sockname = msettings_connect_unix(mid->settings);
155 1393 : const char *tcp_host = msettings_connect_tcp(mid->settings);
156 :
157 1393 : assert(*sockname || *tcp_host);
158 2776 : do {
159 1393 : if (*sockname && connect_socket_unix(mid) == MOK)
160 : break;
161 1327 : if (*tcp_host && connect_socket_tcp(mid) == MOK)
162 : break;
163 10 : assert(mid->error == MERROR);
164 10 : mid->error = MERROR; // in case assert above was not enabled
165 10 : return mid->error;
166 : } while (0);
167 :
168 1383 : mid->connected = true;
169 1383 : return MOK;
170 : }
171 :
172 : MapiMsg
173 1379 : wrap_socket(Mapi mid, SOCKET sock)
174 : {
175 : // do not use check_stream here yet because the socket is not yet in 'mid'
176 1379 : stream *broken_stream = NULL;
177 1379 : MapiMsg msg;
178 1379 : stream *rstream = NULL;
179 1379 : stream *wstream = NULL;
180 :
181 1379 : wstream = socket_wstream(sock, "Mapi client write");
182 1379 : if (wstream == NULL || mnstr_errnr(wstream) != MNSTR_NO__ERROR) {
183 0 : broken_stream = wstream;
184 0 : goto bailout;
185 : }
186 :
187 1379 : rstream = socket_rstream(sock, "Mapi client write");
188 1379 : if (rstream == NULL || mnstr_errnr(rstream) != MNSTR_NO__ERROR) {
189 0 : broken_stream = rstream;
190 0 : goto bailout;
191 : }
192 :
193 1379 : msg = mapi_wrap_streams(mid, rstream, wstream);
194 1379 : if (msg != MOK)
195 0 : goto bailout;
196 : return MOK;
197 :
198 0 : bailout:
199 0 : if (rstream)
200 0 : mnstr_destroy(rstream);
201 0 : if (wstream)
202 0 : mnstr_destroy(wstream);
203 0 : closesocket(sock);
204 0 : if (broken_stream) {
205 0 : char *error_message = "create stream from socket";
206 : // malloc failure is the only way these calls could have failed
207 0 : return mapi_printError(mid, __func__, MERROR, "%s: %s", error_message, mnstr_peek_error(broken_stream));
208 : } else {
209 : return MERROR;
210 : }
211 : }
212 :
213 : static MapiMsg
214 1327 : connect_socket_tcp(Mapi mid)
215 : {
216 1327 : int ret;
217 :
218 1327 : bool use_tls = msetting_bool(mid->settings, MP_TLS);
219 1327 : const char *host = msettings_connect_tcp(mid->settings);
220 1327 : int port = msettings_connect_port(mid->settings);
221 :
222 1327 : assert(host);
223 1327 : char portbuf[10];
224 1327 : snprintf(portbuf, sizeof(portbuf), "%d", port);
225 :
226 1327 : mapi_log_record(mid, "CONN", "Connecting to %s:%d", host, port);
227 :
228 1327 : struct addrinfo hints = (struct addrinfo) {
229 : .ai_family = AF_UNSPEC,
230 : .ai_socktype = SOCK_STREAM,
231 : .ai_protocol = IPPROTO_TCP,
232 : };
233 1327 : struct addrinfo *addresses;
234 1327 : ret = getaddrinfo(host, portbuf, &hints, &addresses);
235 1327 : if (ret != 0) {
236 0 : return mapi_printError(
237 : mid, __func__, MERROR,
238 : "getaddrinfo %s:%s failed: %s", host, portbuf, gai_strerror(ret));
239 : }
240 1327 : if (addresses == NULL) {
241 0 : return mapi_printError(
242 : mid, __func__, MERROR,
243 : "getaddrinfo return 0 addresses");
244 : }
245 :
246 : assert(addresses);
247 : SOCKET s;
248 1331 : for (struct addrinfo *addr = addresses; addr; addr = addr->ai_next) {
249 1329 : s = connect_socket_tcp_addr(mid, addr);
250 1329 : if (s != INVALID_SOCKET)
251 : break;
252 : }
253 1327 : freeaddrinfo(addresses);
254 1327 : if (s == INVALID_SOCKET) {
255 : // connect_socket_tcp_addr has already set an error message
256 : return MERROR;
257 : }
258 :
259 : /* compare our own address with that of our peer and
260 : * if they are the same, we were connected to our own
261 : * socket, so then we can't use this connection */
262 1325 : union {
263 : struct sockaddr_storage ss;
264 : struct sockaddr_in i4;
265 : struct sockaddr_in6 i6;
266 : } myaddr, praddr;
267 1325 : socklen_t myaddrlen, praddrlen;
268 1325 : myaddrlen = (socklen_t) sizeof(myaddr.ss);
269 1325 : praddrlen = (socklen_t) sizeof(praddr.ss);
270 2650 : if (getsockname(s, (struct sockaddr *) &myaddr.ss, &myaddrlen) == 0 &&
271 1325 : getpeername(s, (struct sockaddr *) &praddr.ss, &praddrlen) == 0 &&
272 2650 : myaddr.ss.ss_family == praddr.ss.ss_family &&
273 : (myaddr.ss.ss_family == AF_INET
274 1325 : ? myaddr.i4.sin_port == praddr.i4.sin_port
275 0 : : myaddr.i6.sin6_port == praddr.i6.sin6_port) &&
276 : (myaddr.ss.ss_family == AF_INET
277 0 : ? myaddr.i4.sin_addr.s_addr == praddr.i4.sin_addr.s_addr
278 0 : : memcmp(myaddr.i6.sin6_addr.s6_addr,
279 : praddr.i6.sin6_addr.s6_addr,
280 : sizeof(praddr.i6.sin6_addr.s6_addr)) == 0)) {
281 0 : closesocket(s);
282 0 : return mapi_setError(mid, "connected to self", __func__, MERROR);
283 : }
284 :
285 1325 : mapi_log_record(mid, "CONN", "Network connection established");
286 1325 : MapiMsg msg = use_tls ? wrap_tls(mid, s) : wrap_socket(mid, s);
287 1325 : if (msg != MOK)
288 : return msg;
289 :
290 : return msg;
291 : }
292 :
293 : static SOCKET
294 1329 : connect_socket_tcp_addr(Mapi mid, struct addrinfo *info)
295 : {
296 1329 : if (mid->tracelog) {
297 0 : char addrbuf[100] = {0};
298 0 : const char *addrtext;
299 0 : int port;
300 0 : if (info->ai_family == AF_INET) {
301 0 : struct sockaddr_in *addr4 = (struct sockaddr_in*)info->ai_addr;
302 0 : port = ntohs(addr4->sin_port);
303 0 : void *addr = &addr4->sin_addr;
304 0 : addrtext = inet_ntop(info->ai_family, addr, addrbuf, sizeof(addrbuf));
305 0 : } else if (info->ai_family == AF_INET6) {
306 0 : struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)info->ai_addr;
307 0 : port = ntohs(addr6->sin6_port);
308 0 : void *addr = &addr6->sin6_addr;
309 0 : addrtext = inet_ntop(info->ai_family, addr, addrbuf, sizeof(addrbuf));
310 : } else {
311 : port = -1;
312 : addrtext = NULL;
313 : }
314 0 : mapi_log_record(mid, "CONN", "Trying IP %s port %d", addrtext ? addrtext : "<UNKNOWN>", port);
315 : }
316 :
317 :
318 1329 : int socktype = info->ai_socktype;
319 : #ifdef SOCK_CLOEXEC
320 1329 : socktype |= SOCK_CLOEXEC;
321 : #endif
322 :
323 1329 : SOCKET s = socket(info->ai_family, socktype, info->ai_protocol);
324 1329 : if (s == INVALID_SOCKET) {
325 4 : mapi_printError(
326 : mid, __func__, MERROR,
327 2 : "could not create TCP socket: %s", SOCKET_STRERROR());
328 2 : return INVALID_SOCKET;
329 : }
330 :
331 : #if !defined(SOCK_CLOEXEC) && defined(HAVE_FCNTL)
332 : (void) fcntl(s, F_SETFD, FD_CLOEXEC);
333 : #endif
334 :
335 : // cast addrlen to int to satisfy Windows.
336 1327 : if (connect(s, info->ai_addr, (int)info->ai_addrlen) == SOCKET_ERROR) {
337 2 : mapi_printError(
338 : mid, __func__, MERROR,
339 2 : "could not connect: %s", SOCKET_STRERROR());
340 2 : closesocket(s);
341 2 : return INVALID_SOCKET;
342 : }
343 :
344 : return s;
345 : }
346 :
347 : static MapiMsg
348 1383 : mapi_handshake(Mapi mid)
349 : {
350 1383 : char buf[BLOCK];
351 1383 : size_t len;
352 1383 : MapiHdl hdl;
353 :
354 1383 : const char *username = msetting_string(mid->settings, MP_USER);
355 1383 : const char *password = msetting_string(mid->settings, MP_PASSWORD);
356 :
357 : /* consume server challenge */
358 1383 : len = mnstr_read_block(mid->from, buf, 1, sizeof(buf));
359 1383 : check_stream(mid, mid->from, "Connection terminated while starting handshake", (mid->blk.eos = true, mid->error));
360 :
361 1382 : mapi_log_data(mid, "RECV HANDSHAKE", buf, len);
362 :
363 1382 : assert(len < sizeof(buf));
364 1382 : buf[len] = 0;
365 :
366 1382 : if (len == 0) {
367 0 : mapi_setError(mid, "Challenge string is not valid, it is empty", __func__, MERROR);
368 0 : return mid->error;
369 : }
370 : /* buf at this point looks like "challenge:servertype:protover[:.*]" */
371 :
372 1382 : char *strtok_state = NULL;
373 1382 : char *chal = strtok_r(buf, ":", &strtok_state);
374 1382 : if (chal == NULL) {
375 0 : mapi_setError(mid, "Challenge string is not valid, challenge not found", __func__, MERROR);
376 0 : close_connection(mid);
377 0 : return mid->error;
378 : }
379 :
380 1382 : char *server = strtok_r(NULL, ":", &strtok_state);
381 1382 : if (server == NULL) {
382 0 : mapi_setError(mid, "Challenge string is not valid, server not found", __func__, MERROR);
383 0 : close_connection(mid);
384 0 : return mid->error;
385 : }
386 :
387 1382 : char *protover = strtok_r(NULL, ":", &strtok_state);
388 1382 : if (protover == NULL) {
389 0 : mapi_setError(mid, "Challenge string is not valid, protocol not found", __func__, MERROR);
390 0 : close_connection(mid);
391 0 : return mid->error;
392 : }
393 1382 : int pversion = atoi(protover);
394 1382 : if (pversion != 9) {
395 : /* because the headers changed, and because it makes no sense to
396 : * try and be backwards (or forwards) compatible, we bail out
397 : * with a friendly message saying so */
398 0 : snprintf(buf, sizeof(buf), "unsupported protocol version: %d, "
399 : "this client only supports version 9", pversion);
400 0 : mapi_setError(mid, buf, __func__, MERROR);
401 0 : close_connection(mid);
402 0 : return mid->error;
403 : }
404 :
405 1382 : char *hashes = strtok_r(NULL, ":", &strtok_state);
406 1382 : if (hashes == NULL) {
407 : /* protocol violation, not enough fields */
408 0 : mapi_setError(mid, "Not enough fields in challenge string", __func__, MERROR);
409 0 : close_connection(mid);
410 0 : return mid->error;
411 : }
412 1382 : char *algsv[] = {
413 : "RIPEMD160",
414 : "SHA512",
415 : "SHA384",
416 : "SHA256",
417 : "SHA224",
418 : "SHA1",
419 : NULL
420 : };
421 1382 : char **algs = algsv;
422 :
423 : /* rBuCQ9WTn3:mserver:9:RIPEMD160,SHA256,SHA1,MD5:LIT:SHA1: */
424 :
425 1382 : if (!*username || !*password) {
426 0 : mapi_setError(mid, "username and password must be set",
427 : __func__, MERROR);
428 0 : close_connection(mid);
429 0 : return mid->error;
430 : }
431 :
432 : /* the database has sent a list of supported hashes to us, it's
433 : * in the form of a comma separated list and in the variable
434 : * rest. We try to use the strongest algorithm. */
435 :
436 :
437 : /* in rest now should be the byte order of the server */
438 1382 : char *byteo = strtok_r(NULL, ":", &strtok_state);
439 :
440 : /* Proto v9 is like v8, but mandates that the password is a
441 : * hash, that is salted like in v8. The hash algorithm is
442 : * specified in the 6th field. If we don't support it, we
443 : * can't login. */
444 1382 : char *serverhash = strtok_r(NULL, ":", &strtok_state);
445 :
446 1382 : char *handshake_options = strtok_r(NULL, ":", &strtok_state);
447 1382 : if (handshake_options) {
448 1377 : if (sscanf(handshake_options, "sql=%d", &mid->handshake_options) != 1) {
449 0 : mapi_setError(mid, "invalid handshake options",
450 : __func__, MERROR);
451 0 : close_connection(mid);
452 0 : return mid->error;
453 : }
454 : }
455 :
456 : /* search for OOBINTR option,
457 : * NOTE this consumes the rest of the challenge */
458 1382 : char *rest = strtok_r(NULL, ":", &strtok_state);
459 2759 : while (rest != NULL) {
460 2754 : if (strcmp(rest, "OOBINTR=1") == 0) {
461 1377 : mid->oobintr = true;
462 1377 : break;
463 : }
464 1377 : rest = strtok_r(NULL, ":", &strtok_state);
465 : }
466 :
467 : /* hash password, if not already */
468 1382 : if (password[0] != '\1') {
469 199 : char *pwdhash = NULL;
470 199 : if (strcmp(serverhash, "RIPEMD160") == 0) {
471 0 : pwdhash = mcrypt_RIPEMD160Sum(password,
472 : strlen(password));
473 199 : } else if (strcmp(serverhash, "SHA512") == 0) {
474 199 : pwdhash = mcrypt_SHA512Sum(password,
475 : strlen(password));
476 0 : } else if (strcmp(serverhash, "SHA384") == 0) {
477 0 : pwdhash = mcrypt_SHA384Sum(password,
478 : strlen(password));
479 0 : } else if (strcmp(serverhash, "SHA256") == 0) {
480 0 : pwdhash = mcrypt_SHA256Sum(password,
481 : strlen(password));
482 0 : } else if (strcmp(serverhash, "SHA224") == 0) {
483 0 : pwdhash = mcrypt_SHA224Sum(password,
484 : strlen(password));
485 0 : } else if (strcmp(serverhash, "SHA1") == 0) {
486 0 : pwdhash = mcrypt_SHA1Sum(password,
487 : strlen(password));
488 : } else {
489 0 : (void)pwdhash;
490 0 : snprintf(buf, sizeof(buf), "server requires unknown hash '%.100s'",
491 : serverhash);
492 0 : close_connection(mid);
493 0 : return mapi_setError(mid, buf, __func__, MERROR);
494 : }
495 :
496 199 : if (pwdhash == NULL) {
497 0 : snprintf(buf, sizeof(buf), "allocation failure or unknown hash '%.100s'",
498 : serverhash);
499 0 : close_connection(mid);
500 0 : return mapi_setError(mid, buf, __func__, MERROR);
501 : }
502 :
503 199 : char *replacement_password = malloc(1 + strlen(pwdhash) + 1);
504 199 : if (replacement_password == NULL) {
505 0 : close_connection(mid);
506 0 : return mapi_setError(mid, "malloc failed", __func__, MERROR);
507 : }
508 199 : sprintf(replacement_password, "\1%s", pwdhash);
509 199 : free(pwdhash);
510 199 : msettings_error errmsg = msetting_set_string(mid->settings, MP_PASSWORD, replacement_password);
511 199 : free(replacement_password);
512 199 : if (errmsg != NULL) {
513 0 : close_connection(mid);
514 0 : return mapi_setError(mid, "could not stow hashed password", __func__, MERROR);
515 : }
516 : }
517 :
518 :
519 1382 : const char *pw = msetting_string(mid->settings, MP_PASSWORD);
520 1382 : assert(*pw == '\1');
521 1382 : pw++;
522 :
523 1382 : char *hash = NULL;
524 1382 : for (; *algs != NULL; algs++) {
525 : /* TODO: make this actually obey the separation by
526 : * commas, and only allow full matches */
527 1382 : if (strstr(hashes, *algs) != NULL) {
528 1382 : char *pwh = mcrypt_hashPassword(*algs, pw, chal);
529 1382 : size_t len;
530 1382 : if (pwh == NULL)
531 0 : continue;
532 1382 : len = strlen(pwh) + strlen(*algs) + 3 /* {}\0 */;
533 1382 : hash = malloc(len);
534 1382 : if (hash == NULL) {
535 0 : close_connection(mid);
536 0 : free(pwh);
537 0 : return mapi_setError(mid, "malloc failure", __func__, MERROR);
538 : }
539 1382 : snprintf(hash, len, "{%s}%s", *algs, pwh);
540 1382 : free(pwh);
541 1382 : break;
542 : }
543 : }
544 1382 : if (hash == NULL) {
545 : /* the server doesn't support what we can */
546 0 : snprintf(buf, sizeof(buf), "unsupported hash algorithms: %.100s", hashes);
547 0 : close_connection(mid);
548 0 : return mapi_setError(mid, buf, __func__, MERROR);
549 : }
550 :
551 1382 : mnstr_set_bigendian(mid->from, strcmp(byteo, "BIG") == 0);
552 :
553 1382 : char *p = buf;
554 1382 : int remaining = sizeof(buf);
555 1382 : int n;
556 : #define CHECK_SNPRINTF(...) \
557 : do { \
558 : n = snprintf(p, remaining, __VA_ARGS__); \
559 : if (n < remaining) { \
560 : remaining -= n; \
561 : p += n; \
562 : } else { \
563 : mapi_setError(mid, "combination of database name and user name too long", __func__, MERROR); \
564 : free(hash); \
565 : close_connection(mid); \
566 : return mid->error; \
567 : } \
568 : } while (0)
569 :
570 : #ifdef WORDS_BIGENDIAN
571 : char *our_endian = "BIG";
572 : #else
573 1382 : char *our_endian = "LIT";
574 : #endif
575 : /* note: if we make the database field an empty string, it
576 : * means we want the default. However, it *should* be there. */
577 1382 : const char *language = msetting_string(mid->settings, MP_LANGUAGE);
578 1382 : const char *database = msetting_string(mid->settings, MP_DATABASE);
579 1382 : CHECK_SNPRINTF("%s:%s:%s:%s:%s:FILETRANS:",
580 : our_endian,
581 : username, hash,
582 : language, database);
583 :
584 1382 : if (mid->handshake_options > MAPI_HANDSHAKE_AUTOCOMMIT) {
585 1377 : CHECK_SNPRINTF("auto_commit=%d", msetting_bool(mid->settings, MP_AUTOCOMMIT));
586 : }
587 1382 : if (mid->handshake_options > MAPI_HANDSHAKE_REPLY_SIZE) {
588 1377 : CHECK_SNPRINTF(",reply_size=%ld", msetting_long(mid->settings, MP_REPLYSIZE));
589 : }
590 1382 : if (mid->handshake_options > MAPI_HANDSHAKE_SIZE_HEADER) {
591 1377 : CHECK_SNPRINTF(",size_header=%d", mid->sizeheader); // with underscore, despite X command without
592 : }
593 1382 : if (mid->handshake_options > MAPI_HANDSHAKE_COLUMNAR_PROTOCOL) {
594 1377 : CHECK_SNPRINTF(",columnar_protocol=%d", mid->columnar_protocol);
595 : }
596 1382 : if (mid->handshake_options > MAPI_HANDSHAKE_TIME_ZONE) {
597 1377 : CHECK_SNPRINTF(",time_zone=%ld", msetting_long(mid->settings, MP_TIMEZONE));
598 : }
599 1382 : if (mid->handshake_options > 0) {
600 1377 : CHECK_SNPRINTF(":");
601 : }
602 1382 : CHECK_SNPRINTF("\n");
603 :
604 1382 : free(hash);
605 :
606 1382 : len = strlen(buf);
607 1382 : mapi_log_data(mid, "HANDSHAKE SEND", buf, len);
608 1382 : mnstr_write(mid->to, buf, 1, len);
609 1382 : check_stream(mid, mid->to, "Could not send initial byte sequence", mid->error);
610 1382 : mnstr_flush(mid->to, MNSTR_FLUSH_DATA);
611 1382 : check_stream(mid, mid->to, "Could not send initial byte sequence", mid->error);
612 :
613 : // Clear the redirects before we receive new ones
614 1382 : for (char **r = mid->redirects; *r != NULL; r++) {
615 0 : free(*r);
616 0 : *r = NULL;
617 : }
618 :
619 : /* consume the welcome message from the server */
620 1382 : hdl = mapi_new_handle(mid);
621 1382 : if (hdl == NULL) {
622 0 : close_connection(mid);
623 0 : return MERROR;
624 : }
625 1382 : mid->active = hdl;
626 1382 : read_into_cache(hdl, 0);
627 1382 : if (mid->error) {
628 8 : char *errorstr = NULL;
629 8 : MapiMsg error;
630 8 : struct MapiResultSet *result;
631 : /* propagate error from result to mid, the error probably is in
632 : * the last produced result, not the first
633 : * mapi_close_handle clears the errors, so save them first */
634 16 : for (result = hdl->result; result; result = result->next) {
635 8 : errorstr = result->errorstr;
636 8 : result->errorstr = NULL; /* clear these so errorstr doesn't get freed */
637 : }
638 8 : if (!errorstr)
639 0 : errorstr = mid->errorstr;
640 8 : error = mid->error;
641 :
642 8 : if (hdl->result)
643 8 : hdl->result->errorstr = NULL; /* clear these so errorstr doesn't get freed */
644 8 : mid->errorstr = NULL;
645 8 : mapi_close_handle(hdl);
646 8 : mapi_setError(mid, errorstr, __func__, error);
647 8 : if (errorstr != mapi_nomem)
648 8 : free(errorstr); /* now free it after a copy has been made */
649 8 : close_connection(mid);
650 8 : return mid->error;
651 : }
652 1374 : if (hdl->result && hdl->result->cache.line) {
653 : int i;
654 : size_t motdlen = 0;
655 : struct MapiResultSet *result = hdl->result;
656 :
657 0 : for (i = 0; i < result->cache.writer; i++) {
658 0 : if (result->cache.line[i].rows) {
659 0 : char **r;
660 0 : int m;
661 0 : switch (result->cache.line[i].rows[0]) {
662 0 : case '#':
663 0 : motdlen += strlen(result->cache.line[i].rows) + 1;
664 0 : break;
665 : case '^':
666 : r = mid->redirects;
667 : m = NELEM(mid->redirects) - 1;
668 0 : while (*r != NULL && m > 0) {
669 0 : m--;
670 0 : r++;
671 : }
672 0 : if (m == 0)
673 : break;
674 0 : *r++ = strdup(result->cache.line[i].rows + 1);
675 0 : *r = NULL;
676 0 : break;
677 : }
678 : }
679 : }
680 0 : if (motdlen > 0) {
681 0 : mid->motd = malloc(motdlen + 1);
682 0 : *mid->motd = 0;
683 0 : for (i = 0; i < result->cache.writer; i++)
684 0 : if (result->cache.line[i].rows && result->cache.line[i].rows[0] == '#') {
685 0 : strcat(mid->motd, result->cache.line[i].rows);
686 0 : strcat(mid->motd, "\n");
687 : }
688 : }
689 :
690 0 : if (*mid->redirects != NULL) {
691 : /* redirect, looks like:
692 : * ^mapi:monetdb://localhost:50001/test?lang=sql&user=monetdb
693 : * or
694 : * ^mapi:merovingian://proxy?database=test */
695 :
696 : /* first see if we reached our redirection limit */
697 0 : if (mid->redircnt >= mid->redirmax) {
698 0 : mapi_close_handle(hdl);
699 0 : mapi_setError(mid, "too many redirects", __func__, MERROR);
700 0 : close_connection(mid);
701 0 : return mid->error;
702 : }
703 0 : mid->redircnt++;
704 :
705 : /* we only implement following the first */
706 0 : char *red = mid->redirects[0];
707 :
708 0 : char *error_message = NULL;
709 0 : if (!msettings_parse_url(mid->settings, red, &error_message)
710 0 : || !msettings_validate(mid->settings, &error_message)
711 : ) {
712 0 : mapi_close_handle(hdl);
713 0 : close_connection(mid);
714 0 : MapiMsg err = mapi_printError(
715 : mid, __func__, MERROR,
716 : "%s: %s",
717 0 : error_message ? error_message : "invalid redirect",
718 : red);
719 0 : free(error_message);
720 0 : return err;
721 : }
722 :
723 0 : if (strncmp("mapi:merovingian", red, 16) == 0) {
724 : // do not close the connection so caller knows to restart handshake
725 0 : mapi_log_record(mid, "HANDSHAKE", "Restarting handshake on current socket");
726 0 : assert(mid->connected);
727 : } else {
728 0 : mapi_log_record(mid, "HANDSHAKE", "Redirected elsewhere, closing socket");
729 0 : close_connection(mid);
730 : }
731 0 : return MREDIRECT;
732 : }
733 : }
734 1374 : mapi_close_handle(hdl);
735 :
736 1374 : if (mid->trace)
737 0 : printf("connection established\n");
738 :
739 : // I don't understand this assert.
740 1374 : if (!msettings_lang_is_sql(mid->settings))
741 188 : return mid->error;
742 :
743 1186 : if (mid->error != MOK)
744 : return mid->error;
745 :
746 : /* use X commands to send options that couldn't be sent in the handshake */
747 : /* tell server about auto_complete and cache limit if handshake options weren't used */
748 1186 : bool autocommit = msetting_bool(mid->settings, MP_AUTOCOMMIT);
749 1186 : if (mid->handshake_options <= MAPI_HANDSHAKE_AUTOCOMMIT && autocommit != msetting_bool(msettings_default, MP_AUTOCOMMIT)) {
750 0 : char buf[50];
751 0 : sprintf(buf, "%d", !!autocommit);
752 0 : MapiMsg result = mapi_Xcommand(mid, "auto_commit", buf);
753 0 : if (result != MOK)
754 0 : return mid->error;
755 : }
756 1186 : long replysize = msetting_long(mid->settings, MP_REPLYSIZE);
757 1186 : if (mid->handshake_options <= MAPI_HANDSHAKE_REPLY_SIZE && replysize != msetting_long(msettings_default, MP_REPLYSIZE)) {
758 0 : char buf[50];
759 0 : sprintf(buf, "%ld", replysize);
760 0 : MapiMsg result = mapi_Xcommand(mid, "reply_size", buf);
761 0 : if (result != MOK)
762 0 : return mid->error;
763 : }
764 1186 : if (mid->handshake_options <= MAPI_HANDSHAKE_SIZE_HEADER && mid->sizeheader != MapiStructDefaults.sizeheader) {
765 0 : char buf[50];
766 0 : sprintf(buf, "%d", !!mid->sizeheader);
767 0 : MapiMsg result = mapi_Xcommand(mid, "sizeheader", buf); // no underscore!
768 0 : if (result != MOK)
769 0 : return mid->error;
770 : }
771 : // There is no if (mid->handshake_options <= MAPI_HANDSHAKE_COLUMNAR_PROTOCOL && mid->columnar_protocol != MapiStructDefaults.columnar_protocol)
772 : // The reason is that columnar_protocol is very new. If it isn't supported in the handshake it isn't supported at
773 : // all so sending the Xcommand would just give an error.
774 1186 : if (mid->handshake_options <= MAPI_HANDSHAKE_TIME_ZONE) {
775 0 : mapi_set_time_zone(mid, msetting_long(mid->settings, MP_TIMEZONE));
776 : }
777 :
778 1186 : return mid->error;
779 :
780 : }
|