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
backsql_PrintErrors(SQLHENV henv,SQLHDBC hdbc,SQLHSTMT sth,int rc)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
backsql_Prepare(SQLHDBC dbh,SQLHSTMT * sth,const char * query,int timeout)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
backsql_BindRowAsStrings_x(SQLHSTMT sth,BACKSQL_ROW_NTS * row,void * ctx)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
backsql_BindRowAsStrings(SQLHSTMT sth,BACKSQL_ROW_NTS * row)283 backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row )
284 {
285 return backsql_BindRowAsStrings_x( sth, row, NULL );
286 }
287
288 RETCODE
backsql_FreeRow_x(BACKSQL_ROW_NTS * row,void * ctx)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
backsql_FreeRow(BACKSQL_ROW_NTS * row)306 backsql_FreeRow( BACKSQL_ROW_NTS *row )
307 {
308 return backsql_FreeRow_x( row, NULL );
309 }
310
311 static void
backsql_close_db_handle(SQLHDBC dbh)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
backsql_conn_destroy(backsql_info * bi)337 backsql_conn_destroy(
338 backsql_info *bi )
339 {
340 return 0;
341 }
342
343 int
backsql_init_db_env(backsql_info * bi)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
backsql_free_db_env(backsql_info * bi)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
backsql_open_db_handle(backsql_info * bi,SQLHDBC * dbhp)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
backsql_db_conn_keyfree(void * key,void * data)465 backsql_db_conn_keyfree(
466 void *key,
467 void *data )
468 {
469 (void)backsql_close_db_handle( (SQLHDBC)data );
470 }
471
472 int
backsql_free_db_conn(Operation * op,SQLHDBC dbh)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
backsql_get_db_conn(Operation * op,SQLHDBC * dbhp)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