1 /* $NetBSD: sql-wrap.c,v 1.3 2021/08/14 16:15:01 christos Exp $ */ 2 3 /* $OpenLDAP$ */ 4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 5 * 6 * Copyright 1999-2021 The OpenLDAP Foundation. 7 * Portions Copyright 1999 Dmitry Kovalev. 8 * Portions Copyright 2002 Pierangelo Masarati. 9 * Portions Copyright 2004 Mark Adamson. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted only as authorized by the OpenLDAP 14 * Public License. 15 * 16 * A copy of this license is available in the file LICENSE in the 17 * top-level directory of the distribution or, alternatively, at 18 * <http://www.OpenLDAP.org/license.html>. 19 */ 20 /* ACKNOWLEDGEMENTS: 21 * This work was initially developed by Dmitry Kovalev for inclusion 22 * by OpenLDAP Software. Additional significant contributors include 23 * Pierangelo Masarati and Mark Adamson. 24 */ 25 26 #include <sys/cdefs.h> 27 __RCSID("$NetBSD: sql-wrap.c,v 1.3 2021/08/14 16:15:01 christos Exp $"); 28 29 #include "portable.h" 30 31 #include <stdio.h> 32 #include "ac/string.h" 33 #include <sys/types.h> 34 35 #include "slap.h" 36 #include "proto-sql.h" 37 38 #define MAX_ATTR_LEN 16384 39 40 void 41 backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc ) 42 { 43 SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH]; /* msg. buffer */ 44 SQLCHAR state[SQL_SQLSTATE_SIZE]; /* statement buf. */ 45 SDWORD iSqlCode; /* return code */ 46 SWORD len = SQL_MAX_MESSAGE_LENGTH - 1; /* return length */ 47 48 Debug( LDAP_DEBUG_TRACE, "Return code: %d\n", rc ); 49 50 for ( ; rc = SQLError( henv, hdbc, sth, state, &iSqlCode, msg, 51 SQL_MAX_MESSAGE_LENGTH - 1, &len ), BACKSQL_SUCCESS( rc ); ) 52 { 53 Debug( LDAP_DEBUG_TRACE, 54 " nativeErrCode=%d SQLengineState=%s msg=\"%s\"\n", 55 (int)iSqlCode, state, msg ); 56 } 57 } 58 59 RETCODE 60 backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char *query, int timeout ) 61 { 62 RETCODE rc; 63 64 rc = SQLAllocStmt( dbh, sth ); 65 if ( rc != SQL_SUCCESS ) { 66 return rc; 67 } 68 69 #ifdef BACKSQL_TRACE 70 Debug( LDAP_DEBUG_TRACE, "==>backsql_Prepare()\n" ); 71 #endif /* BACKSQL_TRACE */ 72 73 #ifdef BACKSQL_MSSQL_WORKAROUND 74 { 75 char drv_name[ 30 ]; 76 SWORD len; 77 78 SQLGetInfo( dbh, SQL_DRIVER_NAME, drv_name, sizeof( drv_name ), &len ); 79 80 #ifdef BACKSQL_TRACE 81 Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): driver name=\"%s\"\n", 82 drv_name ); 83 #endif /* BACKSQL_TRACE */ 84 85 ldap_pvt_str2upper( drv_name ); 86 if ( !strncmp( drv_name, "SQLSRV32.DLL", STRLENOF( "SQLSRV32.DLL" ) ) ) { 87 /* 88 * stupid default result set in MS SQL Server 89 * does not support multiple active statements 90 * on the same connection -- so we are trying 91 * to make it not to use default result set... 92 */ 93 Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): " 94 "enabling MS SQL Server default result " 95 "set workaround\n" ); 96 rc = SQLSetStmtOption( *sth, SQL_CONCURRENCY, 97 SQL_CONCUR_ROWVER ); 98 if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) { 99 Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): " 100 "SQLSetStmtOption(SQL_CONCURRENCY," 101 "SQL_CONCUR_ROWVER) failed:\n" ); 102 backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc ); 103 SQLFreeStmt( *sth, SQL_DROP ); 104 return rc; 105 } 106 } 107 } 108 #endif /* BACKSQL_MSSQL_WORKAROUND */ 109 110 if ( timeout > 0 ) { 111 Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): " 112 "setting query timeout to %d sec.\n", 113 timeout ); 114 rc = SQLSetStmtOption( *sth, SQL_QUERY_TIMEOUT, timeout ); 115 if ( rc != SQL_SUCCESS ) { 116 backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc ); 117 SQLFreeStmt( *sth, SQL_DROP ); 118 return rc; 119 } 120 } 121 122 #ifdef BACKSQL_TRACE 123 Debug( LDAP_DEBUG_TRACE, "<==backsql_Prepare() calling SQLPrepare()\n" ); 124 #endif /* BACKSQL_TRACE */ 125 126 return SQLPrepare( *sth, (SQLCHAR *)query, SQL_NTS ); 127 } 128 129 RETCODE 130 backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx ) 131 { 132 RETCODE rc; 133 134 if ( row == NULL ) { 135 return SQL_ERROR; 136 } 137 138 #ifdef BACKSQL_TRACE 139 Debug( LDAP_DEBUG_TRACE, "==> backsql_BindRowAsStrings()\n" ); 140 #endif /* BACKSQL_TRACE */ 141 142 rc = SQLNumResultCols( sth, &row->ncols ); 143 if ( rc != SQL_SUCCESS ) { 144 #ifdef BACKSQL_TRACE 145 Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings(): " 146 "SQLNumResultCols() failed:\n" ); 147 #endif /* BACKSQL_TRACE */ 148 149 backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, sth, rc ); 150 151 } else { 152 SQLCHAR colname[ 64 ]; 153 SQLSMALLINT name_len, col_type, col_scale, col_null; 154 SQLLEN col_prec; 155 int i; 156 157 #ifdef BACKSQL_TRACE 158 Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " 159 "ncols=%d\n", (int)row->ncols ); 160 #endif /* BACKSQL_TRACE */ 161 162 row->col_names = (BerVarray)ber_memcalloc_x( row->ncols + 1, 163 sizeof( struct berval ), ctx ); 164 if ( row->col_names == NULL ) { 165 goto nomem; 166 } 167 168 row->col_prec = (UDWORD *)ber_memcalloc_x( row->ncols, 169 sizeof( UDWORD ), ctx ); 170 if ( row->col_prec == NULL ) { 171 goto nomem; 172 } 173 174 row->col_type = (SQLSMALLINT *)ber_memcalloc_x( row->ncols, 175 sizeof( SQLSMALLINT ), ctx ); 176 if ( row->col_type == NULL ) { 177 goto nomem; 178 } 179 180 row->cols = (char **)ber_memcalloc_x( row->ncols + 1, 181 sizeof( char * ), ctx ); 182 if ( row->cols == NULL ) { 183 goto nomem; 184 } 185 186 row->value_len = (SQLLEN *)ber_memcalloc_x( row->ncols, 187 sizeof( SQLLEN ), ctx ); 188 if ( row->value_len == NULL ) { 189 goto nomem; 190 } 191 192 if ( 0 ) { 193 nomem: 194 ber_memfree_x( row->col_names, ctx ); 195 row->col_names = NULL; 196 ber_memfree_x( row->col_prec, ctx ); 197 row->col_prec = NULL; 198 ber_memfree_x( row->col_type, ctx ); 199 row->col_type = NULL; 200 ber_memfree_x( row->cols, ctx ); 201 row->cols = NULL; 202 ber_memfree_x( row->value_len, ctx ); 203 row->value_len = NULL; 204 205 Debug( LDAP_DEBUG_ANY, "backsql_BindRowAsStrings: " 206 "out of memory\n" ); 207 208 return LDAP_NO_MEMORY; 209 } 210 211 for ( i = 0; i < row->ncols; i++ ) { 212 SQLSMALLINT TargetType; 213 214 rc = SQLDescribeCol( sth, (SQLSMALLINT)(i + 1), &colname[ 0 ], 215 (SQLUINTEGER)( sizeof( colname ) - 1 ), 216 &name_len, &col_type, 217 &col_prec, &col_scale, &col_null ); 218 /* FIXME: test rc? */ 219 220 ber_str2bv_x( (char *)colname, 0, 1, 221 &row->col_names[ i ], ctx ); 222 #ifdef BACKSQL_TRACE 223 Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " 224 "col_name=%s, col_prec[%d]=%d\n", 225 colname, (int)(i + 1), (int)col_prec ); 226 #endif /* BACKSQL_TRACE */ 227 if ( col_type != SQL_CHAR && col_type != SQL_VARCHAR ) 228 { 229 col_prec = MAX_ATTR_LEN; 230 } 231 232 row->cols[ i ] = (char *)ber_memcalloc_x( col_prec + 1, 233 sizeof( char ), ctx ); 234 row->col_prec[ i ] = col_prec; 235 row->col_type[ i ] = col_type; 236 237 /* 238 * ITS#3386, ITS#3113 - 20070308 239 * Note: there are many differences between various DPMS and ODBC 240 * Systems; some support SQL_C_BLOB, SQL_C_BLOB_LOCATOR. YMMV: 241 * This has only been tested on Linux/MySQL/UnixODBC 242 * For BINARY-type Fields (BLOB, etc), read the data as BINARY 243 */ 244 if ( BACKSQL_IS_BINARY( col_type ) ) { 245 #ifdef BACKSQL_TRACE 246 Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " 247 "col_name=%s, col_type[%d]=%d: reading binary data\n", 248 colname, (int)(i + 1), (int)col_type); 249 #endif /* BACKSQL_TRACE */ 250 TargetType = SQL_C_BINARY; 251 252 } else { 253 /* Otherwise read it as Character data */ 254 #ifdef BACKSQL_TRACE 255 Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " 256 "col_name=%s, col_type[%d]=%d: reading character data\n", 257 colname, (int)(i + 1), (int)col_type); 258 #endif /* BACKSQL_TRACE */ 259 TargetType = SQL_C_CHAR; 260 } 261 262 rc = SQLBindCol( sth, (SQLUSMALLINT)(i + 1), 263 TargetType, 264 (SQLPOINTER)row->cols[ i ], 265 col_prec + 1, 266 &row->value_len[ i ] ); 267 268 /* FIXME: test rc? */ 269 } 270 271 BER_BVZERO( &row->col_names[ i ] ); 272 row->cols[ i ] = NULL; 273 } 274 275 #ifdef BACKSQL_TRACE 276 Debug( LDAP_DEBUG_TRACE, "<== backsql_BindRowAsStrings()\n" ); 277 #endif /* BACKSQL_TRACE */ 278 279 return rc; 280 } 281 282 RETCODE 283 backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row ) 284 { 285 return backsql_BindRowAsStrings_x( sth, row, NULL ); 286 } 287 288 RETCODE 289 backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx ) 290 { 291 if ( row->cols == NULL ) { 292 return SQL_ERROR; 293 } 294 295 ber_bvarray_free_x( row->col_names, ctx ); 296 ber_memfree_x( row->col_prec, ctx ); 297 ber_memfree_x( row->col_type, ctx ); 298 ber_memvfree_x( (void **)row->cols, ctx ); 299 ber_memfree_x( row->value_len, ctx ); 300 301 return SQL_SUCCESS; 302 } 303 304 305 RETCODE 306 backsql_FreeRow( BACKSQL_ROW_NTS *row ) 307 { 308 return backsql_FreeRow_x( row, NULL ); 309 } 310 311 static void 312 backsql_close_db_handle( SQLHDBC dbh ) 313 { 314 if ( dbh == SQL_NULL_HDBC ) { 315 return; 316 } 317 318 Debug( LDAP_DEBUG_TRACE, "==>backsql_close_db_handle(%p)\n", 319 (void *)dbh ); 320 321 /* 322 * Default transact is SQL_ROLLBACK; commit is required only 323 * by write operations, and it is explicitly performed after 324 * each atomic operation succeeds. 325 */ 326 327 /* TimesTen */ 328 SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK ); 329 SQLDisconnect( dbh ); 330 SQLFreeConnect( dbh ); 331 332 Debug( LDAP_DEBUG_TRACE, "<==backsql_close_db_handle(%p)\n", 333 (void *)dbh ); 334 } 335 336 int 337 backsql_conn_destroy( 338 backsql_info *bi ) 339 { 340 return 0; 341 } 342 343 int 344 backsql_init_db_env( backsql_info *bi ) 345 { 346 RETCODE rc; 347 int ret = SQL_SUCCESS; 348 349 Debug( LDAP_DEBUG_TRACE, "==>backsql_init_db_env()\n" ); 350 351 rc = SQLAllocEnv( &bi->sql_db_env ); 352 if ( rc != SQL_SUCCESS ) { 353 Debug( LDAP_DEBUG_TRACE, "init_db_env: SQLAllocEnv failed:\n" ); 354 backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, 355 SQL_NULL_HENV, rc ); 356 ret = SQL_ERROR; 357 } 358 359 Debug( LDAP_DEBUG_TRACE, "<==backsql_init_db_env()=%d\n", ret ); 360 361 return ret; 362 } 363 364 int 365 backsql_free_db_env( backsql_info *bi ) 366 { 367 Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_env()\n" ); 368 369 (void)SQLFreeEnv( bi->sql_db_env ); 370 bi->sql_db_env = SQL_NULL_HENV; 371 372 /* 373 * stop, if frontend waits for all threads to shutdown 374 * before calling this -- then what are we going to delete?? 375 * everything is already deleted... 376 */ 377 Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_env()\n" ); 378 379 return SQL_SUCCESS; 380 } 381 382 static int 383 backsql_open_db_handle( 384 backsql_info *bi, 385 SQLHDBC *dbhp ) 386 { 387 /* TimesTen */ 388 char DBMSName[ 32 ]; 389 int rc; 390 391 assert( dbhp != NULL ); 392 *dbhp = SQL_NULL_HDBC; 393 394 Debug( LDAP_DEBUG_TRACE, "==>backsql_open_db_handle()\n" ); 395 396 rc = SQLAllocConnect( bi->sql_db_env, dbhp ); 397 if ( !BACKSQL_SUCCESS( rc ) ) { 398 Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " 399 "SQLAllocConnect() failed:\n" ); 400 backsql_PrintErrors( bi->sql_db_env, SQL_NULL_HDBC, 401 SQL_NULL_HENV, rc ); 402 return LDAP_UNAVAILABLE; 403 } 404 405 rc = SQLConnect( *dbhp, 406 (SQLCHAR*)bi->sql_dbname, SQL_NTS, 407 (SQLCHAR*)bi->sql_dbuser, SQL_NTS, 408 (SQLCHAR*)bi->sql_dbpasswd, SQL_NTS ); 409 if ( rc != SQL_SUCCESS ) { 410 Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " 411 "SQLConnect() to database \"%s\" %s.\n", 412 bi->sql_dbname, 413 rc == SQL_SUCCESS_WITH_INFO ? 414 "succeeded with info" : "failed" ); 415 backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc ); 416 if ( rc != SQL_SUCCESS_WITH_INFO ) { 417 SQLFreeConnect( *dbhp ); 418 return LDAP_UNAVAILABLE; 419 } 420 } 421 422 /* 423 * TimesTen : Turn off autocommit. We must explicitly 424 * commit any transactions. 425 */ 426 SQLSetConnectOption( *dbhp, SQL_AUTOCOMMIT, 427 BACKSQL_AUTOCOMMIT_ON( bi ) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF ); 428 429 /* 430 * See if this connection is to TimesTen. If it is, 431 * remember that fact for later use. 432 */ 433 /* Assume until proven otherwise */ 434 bi->sql_flags &= ~BSQLF_USE_REVERSE_DN; 435 DBMSName[ 0 ] = '\0'; 436 rc = SQLGetInfo( *dbhp, SQL_DBMS_NAME, (PTR)&DBMSName, 437 sizeof( DBMSName ), NULL ); 438 if ( rc == SQL_SUCCESS ) { 439 if ( strcmp( DBMSName, "TimesTen" ) == 0 || 440 strcmp( DBMSName, "Front-Tier" ) == 0 ) 441 { 442 Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " 443 "TimesTen database!\n" ); 444 bi->sql_flags |= BSQLF_USE_REVERSE_DN; 445 } 446 447 } else { 448 Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " 449 "SQLGetInfo() failed.\n" ); 450 backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc ); 451 SQLDisconnect( *dbhp ); 452 SQLFreeConnect( *dbhp ); 453 return LDAP_UNAVAILABLE; 454 } 455 /* end TimesTen */ 456 457 Debug( LDAP_DEBUG_TRACE, "<==backsql_open_db_handle()\n" ); 458 459 return LDAP_SUCCESS; 460 } 461 462 static void *backsql_db_conn_dummy; 463 464 static void 465 backsql_db_conn_keyfree( 466 void *key, 467 void *data ) 468 { 469 (void)backsql_close_db_handle( (SQLHDBC)data ); 470 } 471 472 int 473 backsql_free_db_conn( Operation *op, SQLHDBC dbh ) 474 { 475 Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_conn()\n" ); 476 477 (void)backsql_close_db_handle( dbh ); 478 ldap_pvt_thread_pool_setkey( op->o_threadctx, 479 &backsql_db_conn_dummy, (void *)SQL_NULL_HDBC, 480 backsql_db_conn_keyfree, NULL, NULL ); 481 482 Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_conn()\n" ); 483 484 return LDAP_SUCCESS; 485 } 486 487 int 488 backsql_get_db_conn( Operation *op, SQLHDBC *dbhp ) 489 { 490 backsql_info *bi = (backsql_info *)op->o_bd->be_private; 491 int rc = LDAP_SUCCESS; 492 SQLHDBC dbh = SQL_NULL_HDBC; 493 494 Debug( LDAP_DEBUG_TRACE, "==>backsql_get_db_conn()\n" ); 495 496 assert( dbhp != NULL ); 497 *dbhp = SQL_NULL_HDBC; 498 499 if ( op->o_threadctx ) { 500 void *data = NULL; 501 502 ldap_pvt_thread_pool_getkey( op->o_threadctx, 503 &backsql_db_conn_dummy, &data, NULL ); 504 dbh = (SQLHDBC)data; 505 506 } else { 507 dbh = bi->sql_dbh; 508 } 509 510 if ( dbh == SQL_NULL_HDBC ) { 511 rc = backsql_open_db_handle( bi, &dbh ); 512 if ( rc != LDAP_SUCCESS ) { 513 return rc; 514 } 515 516 if ( op->o_threadctx ) { 517 void *data = (void *)dbh; 518 519 ldap_pvt_thread_pool_setkey( op->o_threadctx, 520 &backsql_db_conn_dummy, data, 521 backsql_db_conn_keyfree, NULL, NULL ); 522 523 } else { 524 bi->sql_dbh = dbh; 525 } 526 } 527 528 *dbhp = dbh; 529 530 Debug( LDAP_DEBUG_TRACE, "<==backsql_get_db_conn()\n" ); 531 532 return LDAP_SUCCESS; 533 } 534 535