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