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