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 : /*
14 : * This code was created by Peter Harvey (mostly during Christmas 98/99).
15 : * This code is LGPL. Please ensure that this message remains in future
16 : * distributions and uses of this code (that's about all I get out of it).
17 : * - Peter Harvey pharvey@codebydesign.com
18 : *
19 : * This file has been modified for the MonetDB project. See the file
20 : * Copyright in this directory for more information.
21 : */
22 :
23 : /**********************************************************************
24 : * SQLConnect()
25 : * CLI Compliance: ISO 92
26 : *
27 : * Author: Martin van Dinther, Sjoerd Mullender
28 : * Date : 30 aug 2002
29 : *
30 : **********************************************************************/
31 :
32 : #include "ODBCGlobal.h"
33 : #include "ODBCDbc.h"
34 : #include "ODBCUtil.h"
35 : #include "ODBCAttrs.h"
36 : #include <time.h>
37 : #include "msettings.h"
38 : #include "mstring.h"
39 :
40 : #ifdef HAVE_ODBCINST_H
41 : #include <odbcinst.h>
42 : #endif
43 :
44 : #ifndef HAVE_SQLGETPRIVATEPROFILESTRING
45 : #define SQLGetPrivateProfileString(section,entry,default,buffer,bufferlen,filename) ((int) strcpy_len(buffer,default,bufferlen))
46 : #endif
47 :
48 : static SQLRETURN
49 32 : get_serverinfo(ODBCDbc *dbc)
50 : {
51 32 : MapiHdl hdl = NULL;
52 32 : SQLRETURN rc; // intentionally uninitialized
53 32 : char *n, *v;
54 :
55 32 : if ((hdl = mapi_query(dbc->mid, "select name, value from sys.env() where name in ('monet_version', 'gdk_dbname', 'max_clients', 'raw_strings')")) == NULL)
56 0 : goto end;
57 32 : dbc->raw_strings = false;
58 160 : while (mapi_fetch_row(hdl)) {
59 128 : n = mapi_fetch_field(hdl, 0);
60 128 : v = mapi_fetch_field(hdl, 1);
61 128 : if (strcmp(n, "monet_version") == 0) {
62 32 : sscanf(v, "%hd.%hd.%hd",
63 : &dbc->major, &dbc->minor, &dbc->patch);
64 : } else
65 96 : if (strcmp(n, "max_clients") == 0) {
66 32 : sscanf(v, "%hu", &dbc->maxclients);
67 64 : } else if (strcmp(n, "raw_strings") == 0) {
68 32 : dbc->raw_strings = strcmp(v, "true") == 0;
69 : } else {
70 32 : assert(strcmp(n, "gdk_dbname") == 0);
71 32 : msetting_set_string(dbc->settings, MP_DATABASE, v);
72 : }
73 : }
74 32 : if (mapi_error(dbc->mid))
75 0 : goto end;
76 32 : mapi_close_handle(hdl);
77 32 : if ((hdl = mapi_query(dbc->mid, "select id from sys._tables where name = 'comments' and schema_id = (select id from sys.schemas where name = 'sys')")) == NULL)
78 0 : goto end;
79 32 : if (mapi_error(dbc->mid))
80 0 : goto end;
81 : n = NULL;
82 64 : while (mapi_fetch_row(hdl)) {
83 32 : n = mapi_fetch_field(hdl, 0);
84 : }
85 32 : dbc->has_comment = n != NULL;
86 :
87 32 : rc = SQL_SUCCESS;
88 32 : end:
89 32 : if (mapi_error(dbc->mid)) {
90 0 : addDbcError(dbc, "08001", mapi_error_str(dbc->mid), 0);
91 0 : rc = SQL_ERROR;
92 : }
93 32 : mapi_close_handle(hdl);
94 32 : return rc;
95 : }
96 :
97 :
98 : // Ensure '*argument' is either NULL or a NUL-terminated string,
99 : // taking into account 'argument_len' being either a proper string length
100 : // or one of the special values SQL_NULL_DATA or SQL_NTS.
101 : //
102 : // Return 'true' on success and 'false' on allocation failure.
103 : //
104 : // If memory needs to be allocated and 'scratch' is not NULL,
105 : // a pointer to the allocated memory will be stored in '*scratch'
106 : // and the previous value of '*scratch' will be free'd.
107 : //
108 : // '*argument' is never free'd.
109 : bool
110 47 : makeNulTerminated(const SQLCHAR **argument, ssize_t argument_len, void **scratch)
111 : {
112 47 : assert(argument != NULL);
113 :
114 47 : if (*argument == NULL || argument_len == SQL_NTS)
115 : return true;
116 2 : if (argument_len == SQL_NULL_DATA) {
117 0 : *argument = NULL;
118 0 : return true;
119 : }
120 :
121 2 : SQLCHAR *value = malloc(argument_len + 1);
122 2 : if (value == NULL)
123 : return false;
124 2 : memmove(value, *argument, argument_len);
125 2 : value[argument_len] = '\0';
126 :
127 2 : *argument = value;
128 2 : if (scratch) {
129 2 : free(*scratch);
130 2 : *scratch = value;
131 : }
132 :
133 : return value;
134 : }
135 :
136 : char*
137 43 : buildConnectionString(const char *dsn, const msettings *settings)
138 : {
139 :
140 43 : size_t pos = 0;
141 43 : size_t cap = 1024;
142 43 : char *buf = malloc(cap); // reallocprintf will deal with allocation failures
143 43 : char *sep = "";
144 43 : char *value = NULL;
145 43 : char *default_value = NULL;
146 43 : bool ok = false;
147 :
148 43 : if (dsn) {
149 43 : if (reallocprintf(&buf, &pos, &cap, "DSN=%s", dsn) < 0)
150 0 : goto end;
151 : sep = ";";
152 : }
153 :
154 989 : for (int i = 0; i < attr_setting_count; i++) {
155 946 : const struct attr_setting *entry = &attr_settings[i];
156 946 : mparm parm = entry->parm;
157 :
158 946 : if (parm == MP_IGNORE || parm == MP_TABLE || parm == MP_TABLESCHEMA)
159 0 : continue;
160 :
161 946 : free(value);
162 946 : value = msetting_as_string(settings, parm);
163 946 : if (!value)
164 0 : goto end;
165 :
166 946 : bool show_this; // intentionally uninitialized
167 946 : if (parm == MP_USER || parm == MP_PASSWORD) {
168 : show_this = true;
169 860 : } else if (parm == MP_PORT && msetting_long(settings, MP_PORT) <= 0) {
170 : show_this = false;
171 854 : } else if (parm == MP_TLS) {
172 43 : show_this = msetting_bool(settings, MP_TLS);
173 811 : } else if (mparm_is_core(parm)) {
174 : show_this = true;
175 : } else {
176 : // skip if still default
177 688 : free(default_value);
178 688 : default_value = msetting_as_string(msettings_default, parm);
179 688 : if (!default_value)
180 0 : goto end;
181 688 : show_this = (strcmp(value, default_value) != 0);
182 : }
183 731 : if (show_this) {
184 217 : if (reallocprintf(&buf, &pos, &cap, "%s%s=%s", sep, entry->name, value) < 0)
185 0 : goto end;
186 : sep = ";";
187 : }
188 : }
189 :
190 : ok = true;
191 :
192 43 : end:
193 43 : free(value);
194 43 : free(default_value);
195 43 : if (ok) {
196 43 : return buf;
197 : } else {
198 0 : free(buf);
199 0 : return NULL;
200 : }
201 : }
202 :
203 : static int
204 814 : lookup(const char *dsn, const struct attr_setting *entry, char *buf, int bufsize)
205 : {
206 814 : int n;
207 814 : assert(entry->name);
208 814 : n = SQLGetPrivateProfileString(dsn, entry->name, "", buf, bufsize, "odbc.ini");
209 814 : if (n > 0)
210 : return n;
211 703 : if (entry->alt_name)
212 666 : n = SQLGetPrivateProfileString(dsn, entry->alt_name, "", buf, bufsize, "odbc.ini");
213 : return n;
214 : }
215 :
216 : const char*
217 37 : takeFromDataSource(ODBCDbc *dbc, msettings *settings, const char *dsn)
218 : {
219 37 : char buf[1024] = { 0 };
220 :
221 851 : for (int i = 0; i < attr_setting_count; i++) {
222 814 : const struct attr_setting *entry = &attr_settings[i];
223 814 : mparm parm = entry->parm;
224 814 : int n = lookup(dsn, entry, buf, sizeof(buf));
225 814 : if (n > 0) {
226 185 : if (sizeof(buf) - n <= 1)
227 : return "01004"; // truncated
228 185 : const char *msg = msetting_parse(settings, parm, buf);
229 185 : if (msg != NULL)
230 0 : return msg;
231 185 : dbc->setting_touched[(int)parm] = 1;
232 : }
233 : }
234 :
235 : return NULL;
236 : }
237 :
238 : SQLRETURN
239 29 : takeFromConnString(
240 : ODBCDbc *dbc,
241 : msettings *settings,
242 : const SQLCHAR *InConnectionString,
243 : SQLSMALLINT StringLength1,
244 : char **dsn_out)
245 : {
246 29 : SQLRETURN rc = SQL_SUCCESS;
247 29 : const char *sqlstate = NULL;
248 29 : const char *sql_explanation = NULL;
249 29 : const SQLCHAR *cursor;
250 29 : SQLSMALLINT n;
251 29 : char *dsn = NULL, *key = NULL, *attr = NULL;
252 :
253 : // figure out the DSN and load its settings
254 29 : cursor = InConnectionString;
255 29 : n = StringLength1;
256 56 : while (ODBCGetKeyAttr(&cursor, &n, &key, &attr) > 0) {
257 48 : if (strcasecmp(key, "dsn") == 0) {
258 21 : dsn = attr;
259 21 : free(key);
260 21 : break;
261 : }
262 27 : free(key);
263 27 : free(attr);
264 : }
265 29 : key = NULL;
266 29 : attr = NULL;
267 29 : if (dsn) {
268 21 : if (strlen(dsn) > SQL_MAX_DSN_LENGTH)
269 : sqlstate = "IM010"; // Data source name too long
270 : else
271 21 : sqlstate = takeFromDataSource(dbc, settings, dsn);
272 : }
273 21 : if (sqlstate)
274 0 : goto end;
275 :
276 : // Override with settings from the connect string itself
277 29 : cursor = InConnectionString;
278 29 : n = StringLength1;
279 92 : while (ODBCGetKeyAttr(&cursor, &n, &key, &attr) > 0) {
280 63 : int i = attr_setting_lookup(key, true);
281 63 : if (i >= 0) {
282 34 : mparm parm = attr_settings[i].parm;
283 34 : sql_explanation = msetting_parse(settings, parm, attr);
284 34 : if (sql_explanation)
285 0 : goto end;
286 34 : dbc->setting_touched[(int)parm] = 1;
287 : }
288 63 : free(key);
289 63 : free(attr);
290 : }
291 29 : key = NULL;
292 29 : attr = NULL;
293 :
294 29 : if (dsn && dsn_out) {
295 21 : *dsn_out = dsn;
296 21 : dsn = NULL;
297 : }
298 :
299 8 : end:
300 29 : if (sql_explanation && !sqlstate)
301 : sqlstate = "HY009";
302 29 : if (sqlstate) {
303 0 : addDbcError(dbc, sqlstate, sql_explanation, 0);
304 0 : rc = SQL_ERROR;
305 : }
306 29 : free(key);
307 29 : free(attr);
308 29 : free(dsn);
309 29 : return rc;
310 : }
311 :
312 :
313 : SQLRETURN
314 16 : MNDBConnect(ODBCDbc *dbc,
315 : const SQLCHAR *ServerName,
316 : SQLSMALLINT NameLength1,
317 : const SQLCHAR *UserName,
318 : SQLSMALLINT NameLength2,
319 : const SQLCHAR *Authentication,
320 : SQLSMALLINT NameLength3)
321 : {
322 : // These will be passed to addDbcError if you 'goto failure'.
323 : // If unset, 'goto failure' will assume an allocation error.
324 16 : const char *error_state = NULL;
325 16 : const char *error_explanation = NULL;
326 16 : SQLRETURN ret = SQL_ERROR;
327 :
328 : // These will be free'd / destroyed at the 'end' label at the bottom of this function
329 16 : char *dsn = NULL;
330 16 : msettings *settings = NULL;
331 16 : void *scratch = NULL;
332 :
333 : // These do not need to be free'd
334 16 : const char *mapiport_env;
335 :
336 : // Check connection state, should not be connected
337 16 : if (dbc->Connected) {
338 0 : error_state = "08002";
339 0 : goto failure;
340 : }
341 :
342 : // Modify a copy so the original remains unchanged when we return an error
343 16 : settings = msettings_clone(dbc->settings);
344 16 : if (settings == NULL)
345 0 : goto failure;
346 :
347 : // ServerName is really the Data Source name
348 16 : if (!makeNulTerminated(&ServerName, NameLength1, &scratch))
349 0 : goto failure;
350 16 : if (ServerName != NULL) {
351 16 : dsn = strdup((char*)ServerName);
352 16 : if (dsn == NULL)
353 0 : goto failure;
354 : }
355 :
356 : // data source settings take precedence over existing ones
357 16 : if (dsn && *dsn) {
358 16 : error_state = takeFromDataSource(dbc, settings, dsn);
359 16 : if (error_state != NULL)
360 0 : goto failure;
361 : }
362 :
363 : #ifdef ODBCDEBUG
364 16 : if (ODBCdebug == NULL || *ODBCdebug == 0) {
365 16 : const char *logfile = msetting_string(settings, MP_LOGFILE);
366 16 : if (*logfile)
367 0 : setODBCdebug(logfile, true);
368 : }
369 : #endif
370 :
371 : // The dedicated parameters for user name, password, host, port and database name
372 : // override the pre-existing values and whatever came from the data source.
373 : // We also take the MAPIPORT environment variable into account.
374 :
375 16 : if (!makeNulTerminated(&UserName, NameLength2, &scratch))
376 0 : goto failure;
377 16 : if (UserName) {
378 11 : if (!*UserName) {
379 1 : error_state = "28000";
380 1 : error_explanation = "user name not set";
381 1 : goto failure;
382 : }
383 10 : error_explanation = msetting_set_string(settings, MP_USER, (char*)UserName);
384 10 : if (error_explanation != NULL)
385 0 : goto failure;
386 : }
387 :
388 15 : if (!makeNulTerminated(&Authentication, NameLength3, &scratch))
389 0 : goto failure;
390 15 : if (Authentication) {
391 11 : if (!*Authentication) {
392 1 : error_state = "28000";
393 1 : error_explanation = "password not set";
394 1 : goto failure;
395 : }
396 10 : error_explanation = msetting_set_string(settings, MP_PASSWORD, (char*)Authentication);
397 10 : if (error_explanation != NULL)
398 0 : goto failure;
399 : }
400 :
401 14 : mapiport_env = getenv("MAPIPORT");
402 14 : if (mapiport_env != NULL)
403 14 : error_explanation = msetting_parse(settings, MP_PORT, mapiport_env);
404 14 : if (error_explanation != NULL)
405 0 : goto failure;
406 :
407 : #ifdef ODBCDEBUG
408 : {
409 14 : free(scratch);
410 14 : char *connstring = scratch = buildConnectionString(dsn, settings);
411 14 : if (!connstring)
412 0 : goto failure;
413 14 : ODBCLOG("SQLConnect: %s\n", connstring);
414 : }
415 : #endif
416 :
417 14 : assert(error_state == NULL);
418 14 : assert(error_explanation == NULL);
419 :
420 14 : ret = MNDBConnectSettings(dbc, dsn, settings);
421 14 : settings = NULL; // must not be free'd now
422 :
423 14 : goto end;
424 :
425 2 : failure:
426 2 : if (error_state == NULL) {
427 0 : if (error_explanation == NULL || msettings_malloc_failed(error_explanation))
428 : error_state = "HY001"; // allocation failure
429 : else
430 : error_state = "HY009"; // invalid argument
431 : }
432 2 : addDbcError(dbc, error_state, error_explanation, 0);
433 :
434 : // fallthrough
435 16 : end:
436 16 : free(dsn);
437 16 : free(scratch);
438 16 : msettings_destroy(settings);
439 :
440 16 : return ret;
441 : }
442 :
443 : SQLRETURN
444 40 : MNDBConnectSettings(ODBCDbc *dbc, const char *dsn, msettings *settings)
445 : {
446 40 : SQLRETURN rc;
447 40 : msettings *clone = msettings_clone(settings);
448 :
449 40 : if (clone == NULL) {
450 0 : addDbcError(dbc, "HY001", NULL, 0);
451 0 : return SQL_ERROR;
452 : }
453 :
454 40 : Mapi mid = mapi_settings(settings);
455 40 : if (mid) {
456 40 : settings = NULL; // will be free'd as part of 'mid' now
457 40 : mapi_setclientprefix(mid, "ODBC " MONETDB_VERSION);
458 40 : mapi_set_size_header(mid, true);
459 40 : mapi_reconnect(mid);
460 : }
461 40 : if (mid == NULL || mapi_error(mid)) {
462 8 : const char *error_state = "08001";
463 8 : const char *error_explanation = mid ? mapi_error_str(mid) : NULL;
464 8 : if (error_explanation) {
465 8 : if (strncmp(error_explanation, "InvalidCredentialsException:", 28) == 0)
466 : error_state = "28000";
467 : else
468 5 : if (strncmp(error_explanation, "could not connect: Connection timed out", 39) == 0)
469 0 : error_state = "HYT00";
470 : }
471 8 : addDbcError(dbc, error_state, error_explanation, 0);
472 8 : if (mid)
473 8 : mapi_destroy(mid);
474 8 : msettings_destroy(settings);
475 8 : msettings_destroy(clone);
476 8 : return SQL_ERROR;
477 : }
478 :
479 32 : free(dbc->dsn);
480 32 : dbc->dsn = dsn ? strdup(dsn) : NULL;
481 :
482 32 : if (dbc->mid)
483 0 : mapi_destroy(dbc->mid);
484 32 : dbc->mid = mid;
485 :
486 32 : msettings_destroy(dbc->settings);
487 32 : dbc->settings = clone;
488 :
489 32 : dbc->mapToLongVarchar = msetting_long(dbc->settings, MP_MAPTOLONGVARCHAR);
490 :
491 32 : dbc->Connected = true;
492 :
493 32 : rc = get_serverinfo(dbc);
494 32 : if (!SQL_SUCCEEDED(rc))
495 0 : return rc;
496 :
497 : return SQL_SUCCESS;
498 : }
499 :
500 :
501 :
502 :
503 : SQLRETURN SQL_API
504 : SQLConnect(SQLHDBC ConnectionHandle,
505 : SQLCHAR *ServerName,
506 : SQLSMALLINT NameLength1,
507 : SQLCHAR *UserName,
508 : SQLSMALLINT NameLength2,
509 : SQLCHAR *Authentication,
510 : SQLSMALLINT NameLength3)
511 : {
512 : #ifdef ODBCDEBUG
513 14 : ODBCLOG("SQLConnect %p\n", ConnectionHandle);
514 : #endif
515 :
516 14 : if (!isValidDbc((ODBCDbc *) ConnectionHandle))
517 : return SQL_INVALID_HANDLE;
518 :
519 14 : clearDbcErrors((ODBCDbc *) ConnectionHandle);
520 :
521 14 : return MNDBConnect((ODBCDbc *) ConnectionHandle,
522 : ServerName, NameLength1,
523 : UserName, NameLength2,
524 : Authentication, NameLength3);
525 : }
526 :
527 : SQLRETURN SQL_API
528 : SQLConnectA(SQLHDBC ConnectionHandle,
529 : SQLCHAR *ServerName,
530 : SQLSMALLINT NameLength1,
531 : SQLCHAR *UserName,
532 : SQLSMALLINT NameLength2,
533 : SQLCHAR *Authentication,
534 : SQLSMALLINT NameLength3)
535 : {
536 0 : return SQLConnect(ConnectionHandle,
537 : ServerName, NameLength1,
538 : UserName, NameLength2,
539 : Authentication, NameLength3);
540 : }
541 :
542 : SQLRETURN SQL_API
543 : SQLConnectW(SQLHDBC ConnectionHandle,
544 : SQLWCHAR *ServerName,
545 : SQLSMALLINT NameLength1,
546 : SQLWCHAR *UserName,
547 : SQLSMALLINT NameLength2,
548 : SQLWCHAR *Authentication,
549 : SQLSMALLINT NameLength3)
550 : {
551 2 : SQLCHAR *ds = NULL, *uid = NULL, *pwd = NULL;
552 2 : SQLRETURN rc = SQL_ERROR;
553 2 : ODBCDbc *dbc = (ODBCDbc *) ConnectionHandle;
554 :
555 : #ifdef ODBCDEBUG
556 2 : ODBCLOG("SQLConnectW %p\n", ConnectionHandle);
557 : #endif
558 :
559 2 : if (!isValidDbc(dbc))
560 : return SQL_INVALID_HANDLE;
561 :
562 2 : clearDbcErrors(dbc);
563 :
564 2 : fixWcharIn(ServerName, NameLength1, SQLCHAR, ds,
565 : addDbcError, dbc, goto bailout);
566 2 : fixWcharIn(UserName, NameLength2, SQLCHAR, uid,
567 : addDbcError, dbc, goto bailout);
568 2 : fixWcharIn(Authentication, NameLength3, SQLCHAR, pwd,
569 : addDbcError, dbc, goto bailout);
570 :
571 2 : rc = MNDBConnect(dbc,
572 : ds, SQL_NTS,
573 : uid, SQL_NTS,
574 : pwd, SQL_NTS);
575 :
576 2 : bailout:
577 2 : if (ds)
578 2 : free(ds);
579 2 : if (uid)
580 2 : free(uid);
581 2 : if (pwd)
582 2 : free(pwd);
583 : return rc;
584 : }
|