1 /****************************************************************************
2 *																			*
3 *					  cryptlib Database RPC Client Interface				*
4 *						Copyright Peter Gutmann 1996-2006					*
5 *																			*
6 ****************************************************************************/
7 
8 #include <ctype.h>
9 #include <stdarg.h>
10 #if defined( INC_ALL )
11   #include "crypt.h"
12   #include "keyset.h"
13   #include "dbms.h"
14   #include "rpc.h"
15 #else
16   #include "crypt.h"
17   #include "keyset/keyset.h"
18   #include "keyset/dbms.h"
19   #include "misc/rpc.h"
20 #endif /* Compiler-specific includes */
21 
22 #ifdef USE_DBMS
23 
24 /****************************************************************************
25 *																			*
26 *						Network Database Interface Routines					*
27 *																			*
28 ****************************************************************************/
29 
30 #ifdef USE_DATABASE_PLUGIN
31 
32 #ifdef USE_RPCAPI
33 
netEncodeError(BYTE * buffer,const int status)34 static void netEncodeError( BYTE *buffer, const int status )
35 	{
36 	putMessageType( buffer, COMMAND_RESULT, 0, 1, 0 );
37 	putMessageLength( buffer + COMMAND_WORDSIZE, COMMAND_WORDSIZE );
38 	putMessageWord( buffer + COMMAND_WORD1_OFFSET, status );
39 	}
40 
netProcessCommand(void * stateInfo,BYTE * buffer)41 void netProcessCommand( void *stateInfo, BYTE *buffer )
42 	{
43 	DBMS_STATE_INFO *dbmsInfo = ( DBMS_STATE_INFO * ) stateInfo;
44 	COMMAND_INFO cmd;
45 	int length, status;
46 
47 	memset( &cmd, 0, sizeof( COMMAND_INFO ) );
48 
49 	/* Get the messge information from the header */
50 	getMessageType( buffer, cmd.type, cmd.flags,
51 					cmd.noArgs, cmd.noStrArgs );
52 	length = getMessageLength( buffer + COMMAND_WORDSIZE );
53 	if( cmd.type == DBX_COMMAND_OPEN )
54 		{
55 		NET_CONNECT_INFO connectInfo;
56 		BYTE *bufPtr = buffer + COMMAND_FIXED_DATA_SIZE + COMMAND_WORDSIZE;
57 		int nameLen;
58 
59 		/* Get the length of the server name and null-terminate it */
60 		nameLen = getMessageWord( bufPtr );
61 		bufPtr += COMMAND_WORDSIZE;
62 		bufPtr[ nameLen ] = '\0';
63 
64 		/* Connect to the plugin */
65 		initNetConnectInfo( &connectInfo, DEFAULTUSER_OBJECT_HANDLE,
66 							CRYPT_ERROR, CRYPT_ERROR, NET_OPTION_HOSTNAME );
67 		connectInfo.name = bufPtr;
68 		status = sNetConnect( &dbmsInfo->stream, STREAM_PROTOCOL_TCPIP,
69 							  &connectInfo, dbmsInfo->errorMessage,
70 							  &dbmsInfo->errorCode );
71 		if( cryptStatusError( status ) )
72 			{
73 			netEncodeError( buffer, status );
74 			return;
75 			}
76 		}
77 
78 	/* Send the command to the plugin and read back the response */
79 	status = swrite( &dbmsInfo->stream, buffer,
80 					 COMMAND_FIXED_DATA_SIZE + COMMAND_WORDSIZE + length );
81 	if( cryptStatusOK( status ) )
82 		status = sread( &dbmsInfo->stream, buffer, COMMAND_FIXED_DATA_SIZE );
83 	if( !cryptStatusError( status ) )
84 		{
85 		/* Perform a consistency check on the returned data */
86 		getMessageType( buffer, cmd.type, cmd.flags,
87 						cmd.noArgs, cmd.noStrArgs );
88 		length = getMessageLength( buffer + COMMAND_WORDSIZE );
89 		if( !dbxCheckCommandInfo( &cmd, length ) || \
90 			cmd.type != COMMAND_RESULT )
91 			status = CRYPT_ERROR_BADDATA;
92 		}
93 	if( !cryptStatusError( status ) )
94 		/* Read the rest of the message */
95 		status = sread( &dbmsInfo->stream, buffer + COMMAND_FIXED_DATA_SIZE,
96 						length );
97 
98 	/* If it's a close command, terminate the connection to the plugin.  We
99 	   don't do any error checking once we get this far since there's not
100 	   much that we can still do at this point */
101 	if( cmd.type == DBX_COMMAND_CLOSE )
102 		sNetDisconnect( &dbmsInfo->stream );
103 	else
104 		if( cryptStatusError( status ) )
105 			netEncodeError( buffer, status );
106 	}
107 #else
108 
initDispatchNet(DBMS_INFO * dbmsInfo)109 int initDispatchNet( DBMS_INFO *dbmsInfo )
110 	{
111 	return( CRYPT_ERROR );
112 	}
113 #endif /* USE_RPCAPI */
114 
115 #endif /* USE_DATABASE_PLUGIN */
116 
117 /****************************************************************************
118 *																			*
119 *							Database RPC Routines							*
120 *																			*
121 ****************************************************************************/
122 
123 /* Dispatch functions for various database types.  ODBC is the native keyset
124    for Windows and (if possible) Unix, a cryptlib-native plugin is the
125    fallback for Unix, and the rest are only accessible via database network
126    plugins */
127 
128 #ifdef USE_ODBC
129   void odbcProcessCommand( void *stateInfo, BYTE *buffer );
130   #define initDispatchODBC( dbmsInfo ) \
131 		  ( dbmsInfo->dispatchFunction = odbcProcessCommand ) != NULL
132 #else
133   #define initDispatchODBC( dbmsInfo )		CRYPT_ERROR
134 #endif /* USE_ODBC */
135 #if defined( USE_DATABASE )
136   void databaseProcessCommand( void *stateInfo, BYTE *buffer );
137   #define initDispatchDatabase( dbmsInfo ) \
138 		  ( dbmsInfo->dispatchFunction = databaseProcessCommand ) != NULL
139 #else
140   #define initDispatchDatabase( dbmsInfo )	CRYPT_ERROR
141 #endif /* General database interface */
142 #ifdef USE_DATABASE_PLUGIN
143   int initDispatchNet( DBMS_INFO *dbmsInfo );
144 #else
145   #define initDispatchNet( dbmsInfo )		CRYPT_ERROR
146 #endif /* USE_DATABASE_PLUGIN */
147 
148 /* Make sure that we can fit the largest possible SQL query into the RPC
149    buffer */
150 
151 #if MAX_SQL_QUERY_SIZE + 256 >= DBX_IO_BUFSIZE
152   #error Database RPC buffer size is too small, increase DBX_IO_BUFSIZE and rebuild
153 #endif /* SQL query size larger than RPC buffer size */
154 
155 /* Dispatch data to the back-end */
156 
dispatchCommand(COMMAND_INFO * cmd,void * stateInfo,DISPATCH_FUNCTION dispatchFunction)157 static int dispatchCommand( COMMAND_INFO *cmd, void *stateInfo,
158 							DISPATCH_FUNCTION dispatchFunction )
159 	{
160 	COMMAND_INFO sentCmd = *cmd;
161 	BYTE buffer[ DBX_IO_BUFSIZE + 8 ], *bufPtr = buffer;
162 	BYTE header[ COMMAND_FIXED_DATA_SIZE + 8 ];
163 	const int payloadLength = ( cmd->noArgs * COMMAND_WORDSIZE ) + \
164 							  ( cmd->noStrArgs * COMMAND_WORDSIZE ) + \
165 							  cmd->strArgLen[ 0 ] + cmd->strArgLen[ 1 ] + \
166 							  cmd->strArgLen[ 2 ];
167 	long resultLength;
168 	int i;
169 
170 	assert( payloadLength + 32 < DBX_IO_BUFSIZE );
171 	assert( dispatchFunction != NULL );
172 
173 	/* Clear the return value */
174 	memset( cmd, 0, sizeof( COMMAND_INFO ) );
175 
176 	/* Write the header and message fields to the buffer */
177 	putMessageType( bufPtr, sentCmd.type, sentCmd.flags,
178 					sentCmd.noArgs, sentCmd.noStrArgs );
179 	putMessageLength( bufPtr + COMMAND_WORDSIZE, payloadLength );
180 	bufPtr += COMMAND_FIXED_DATA_SIZE;
181 	for( i = 0; i < sentCmd.noArgs; i++ )
182 		{
183 		putMessageWord( bufPtr, sentCmd.arg[ i ] );
184 		bufPtr += COMMAND_WORDSIZE;
185 		}
186 	for( i = 0; i < sentCmd.noStrArgs; i++ )
187 		{
188 		const int argLength = sentCmd.strArgLen[ i ];
189 
190 		putMessageWord( bufPtr, argLength );
191 		if( argLength > 0 )
192 			memcpy( bufPtr + COMMAND_WORDSIZE, sentCmd.strArg[ i ],
193 					argLength );
194 		bufPtr += COMMAND_WORDSIZE + argLength;
195 		}
196 
197 	/* Send the command to the server and read back the server's message
198 	   header */
199 	dispatchFunction( stateInfo, buffer );
200 	memcpy( header, buffer, COMMAND_FIXED_DATA_SIZE );
201 
202 	/* Process the fixed message header and make sure that it's valid */
203 	getMessageType( header, cmd->type, cmd->flags,
204 					cmd->noArgs, cmd->noStrArgs );
205 	resultLength = getMessageLength( header + COMMAND_WORDSIZE );
206 	if( !dbxCheckCommandInfo( cmd, resultLength ) || \
207 		cmd->type != COMMAND_RESULT )
208 		return( CRYPT_ERROR );
209 	if( ( cmd->noStrArgs && cmd->strArgLen[ 0 ] ) && \
210 		( sentCmd.type != DBX_COMMAND_QUERY && \
211 		  sentCmd.type != DBX_COMMAND_GETERRORINFO ) )
212 		/* Only these commands can return data */
213 		return( CRYPT_ERROR );
214 
215 	/* Read the rest of the server's message */
216 	bufPtr = buffer + COMMAND_FIXED_DATA_SIZE;
217 	for( i = 0; i < cmd->noArgs; i++ )
218 		{
219 		cmd->arg[ i ] = getMessageWord( bufPtr );
220 		bufPtr += COMMAND_WORDSIZE;
221 		}
222 	for( i = 0; i < cmd->noStrArgs; i++ )
223 		{
224 		cmd->strArgLen[ i ] = getMessageWord( bufPtr );
225 		cmd->strArg[ i ] = bufPtr + COMMAND_WORDSIZE;
226 		bufPtr += COMMAND_WORDSIZE + cmd->strArgLen[ i ];
227 		}
228 
229 	/* The first value returned is the status code, if it's nonzero return
230 	   it to the caller, otherwise move the other values down */
231 	if( cryptStatusError( cmd->arg[ 0 ] ) )
232 		return( cmd->arg[ 0 ] );
233 	assert( cryptStatusOK( cmd->arg[ 0 ] ) );
234 	for( i = 1; i < cmd->noArgs; i++ )
235 		cmd->arg[ i - 1 ] = cmd->arg[ i ];
236 	cmd->arg[ i ] = 0;
237 	cmd->noArgs--;
238 
239 	/* Copy any string arg data back to the caller */
240 	if( cmd->noStrArgs && cmd->strArgLen[ 0 ] )
241 		{
242 		const int maxBufSize = ( sentCmd.type == DBX_COMMAND_QUERY ) ? \
243 							   MAX_QUERY_RESULT_SIZE : MAX_ERRMSG_SIZE;
244 		const int argIndex = sentCmd.noStrArgs;
245 
246 		memcpy( sentCmd.strArg[ argIndex ], cmd->strArg[ 0 ],
247 				min( cmd->strArgLen[ 0 ], maxBufSize ) );
248 		cmd->strArg[ 0 ] = sentCmd.strArg[ argIndex ];
249 		}
250 
251 	return( CRYPT_OK );
252 	}
253 
254 /* Initialise query data prior to sending it to the database back-end */
255 
initQueryData(COMMAND_INFO * cmd,const COMMAND_INFO * cmdTemplate,BYTE * encodedDate,DBMS_INFO * dbmsInfo,const char * command,const void * boundData,const int boundDataLength,const time_t boundDate,const int type)256 static int initQueryData( COMMAND_INFO *cmd, const COMMAND_INFO *cmdTemplate,
257 						  BYTE *encodedDate, DBMS_INFO *dbmsInfo,
258 						  const char *command, const void *boundData,
259 						  const int boundDataLength, const time_t boundDate,
260 						  const int type )
261 	{
262 	int argIndex = 1;
263 
264 	memcpy( cmd, cmdTemplate, sizeof( COMMAND_INFO ) );
265 	cmd->arg[ 0 ] = type;
266 	if( command != NULL )
267 		{
268 		cmd->strArg[ 0 ] = ( void * ) command;
269 		cmd->strArgLen[ 0 ] = strlen( command );
270 		}
271 	if( boundDate > 0 )
272 		{
273 #ifndef SYSTEM_64BIT
274 		assert( sizeof( time_t ) <= 4 );
275 #endif /* !SYSTEM_64BIT */
276 
277 		/* Encode the date as a 64-bit value */
278 		memset( encodedDate, 0, 8 );
279 #ifdef SYSTEM_64BIT
280 		encodedDate[ 3 ] = ( BYTE )( ( boundDate >> 32 ) & 0xFF );
281 #endif /* SYSTEM_64BIT */
282 		encodedDate[ 4 ] = ( BYTE )( ( boundDate >> 24 ) & 0xFF );
283 		encodedDate[ 5 ] = ( BYTE )( ( boundDate >> 16 ) & 0xFF );
284 		encodedDate[ 6 ] = ( BYTE )( ( boundDate >> 8 ) & 0xFF );
285 		encodedDate[ 7 ] = ( BYTE )( ( boundDate ) & 0xFF );
286 		cmd->noStrArgs++;
287 		cmd->strArg[ argIndex ] = encodedDate;
288 		cmd->strArgLen[ argIndex++ ] = 8;
289 		}
290 	if( boundData != NULL )
291 		{
292 		/* Copy the bound data into non-ephemeral storage where it'll be
293 		   accessible to the back-end */
294 		memcpy( dbmsInfo->boundData, boundData, boundDataLength );
295 		cmd->noStrArgs++;
296 		cmd->strArg[ argIndex ] = dbmsInfo->boundData;
297 		cmd->strArgLen[ argIndex++ ] = boundDataLength;
298 		}
299 
300 	return( argIndex );
301 	}
302 
303 /* Database access functions */
304 
openDatabase(DBMS_INFO * dbmsInfo,const char * name,const int nameLength,const int options,int * featureFlags)305 static int openDatabase( DBMS_INFO *dbmsInfo, const char *name,
306 						 const int nameLength, const int options,
307 						 int *featureFlags )
308 	{
309 	static const COMMAND_INFO cmdTemplate = \
310 		{ DBX_COMMAND_OPEN, COMMAND_FLAG_NONE, 1, 1 };
311 	COMMAND_INFO cmd;
312 	int status;
313 
314 	assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
315 
316 	/* Dispatch the command */
317 	memcpy( &cmd, &cmdTemplate, sizeof( COMMAND_INFO ) );
318 	cmd.arg[ 0 ] = options;
319 	cmd.strArg[ 0 ] = ( void * ) name;
320 	cmd.strArgLen[ 0 ] = nameLength;
321 	status = DISPATCH_COMMAND_DBX( cmdOpen, cmd, dbmsInfo );
322 	if( cryptStatusOK( status ) && \
323 		( cmd.arg[ 0 ] & DBMS_HAS_BINARYBLOBS ) )
324 		dbmsInfo->flags |= DBMS_FLAG_BINARYBLOBS;
325 	return( status );
326 	}
327 
closeDatabase(DBMS_INFO * dbmsInfo)328 static void closeDatabase( DBMS_INFO *dbmsInfo )
329 	{
330 	static const COMMAND_INFO cmdTemplate = \
331 		{ DBX_COMMAND_CLOSE, COMMAND_FLAG_NONE, 0, 0 };
332 	COMMAND_INFO cmd;
333 
334 	assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
335 
336 	/* Dispatch the command */
337 	memcpy( &cmd, &cmdTemplate, sizeof( COMMAND_INFO ) );
338 	DISPATCH_COMMAND_DBX( cmdClose, cmd, dbmsInfo );
339 	}
340 
performErrorQuery(DBMS_INFO * dbmsInfo)341 static void performErrorQuery( DBMS_INFO *dbmsInfo )
342 	{
343 	static const COMMAND_INFO cmdTemplate = \
344 		{ DBX_COMMAND_GETERRORINFO, COMMAND_FLAG_NONE, 0, 1 };
345 	COMMAND_INFO cmd;
346 	int status;
347 
348 	assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
349 
350 	/* Clear the return values */
351 	memset( dbmsInfo->errorMessage, 0, MAX_ERRMSG_SIZE );
352 	dbmsInfo->errorCode = 0;
353 
354 	/* Dispatch the command */
355 	memcpy( &cmd, &cmdTemplate, sizeof( COMMAND_INFO ) );
356 	cmd.strArg[ 0 ] = dbmsInfo->errorMessage;
357 	cmd.strArgLen[ 0 ] = 0;
358 	status = DISPATCH_COMMAND_DBX( cmdGetErrorInfo, cmd, dbmsInfo );
359 	if( cryptStatusOK( status ) )
360 		{
361 		dbmsInfo->errorCode = cmd.arg[ 0 ];
362 		dbmsInfo->errorMessage[ cmd.strArgLen[ 0 ] ] = '\0';
363 		}
364 	}
365 
performUpdate(DBMS_INFO * dbmsInfo,const char * command,const void * boundData,const int boundDataLength,const time_t boundDate,const DBMS_UPDATE_TYPE updateType)366 static int performUpdate( DBMS_INFO *dbmsInfo, const char *command,
367 						  const void *boundData, const int boundDataLength,
368 						  const time_t boundDate,
369 						  const DBMS_UPDATE_TYPE updateType )
370 	{
371 	static const COMMAND_INFO cmdTemplate = \
372 		{ DBX_COMMAND_UPDATE, COMMAND_FLAG_NONE, 1, 1 };
373 	COMMAND_INFO cmd;
374 	BYTE encodedDate[ 8 + 8 ];
375 	int status;
376 
377 	assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
378 	assert( updateType > DBMS_UPDATE_NONE && \
379 			updateType < DBMS_UPDATE_LAST );
380 
381 	/* If we're trying to abort a transaction that was never begun, don't
382 	   do anything */
383 	if( updateType == DBMS_UPDATE_ABORT && \
384 		!( dbmsInfo->flags & DBMS_FLAG_UPDATEACTIVE ) )
385 		return( CRYPT_OK );
386 
387 	/* Dispatch the command */
388 	initQueryData( &cmd, &cmdTemplate, encodedDate, dbmsInfo, command,
389 				   boundData, boundDataLength, boundDate, updateType );
390 	status = DISPATCH_COMMAND_DBX( cmdUpdate, cmd, dbmsInfo );
391 	if( cryptStatusError( status ) )
392 		performErrorQuery( dbmsInfo );
393 	else
394 		{
395 		/* If we're starting or ending an update, record the update state */
396 		if( updateType == DBMS_UPDATE_BEGIN )
397 			dbmsInfo->flags |= DBMS_FLAG_UPDATEACTIVE;
398 		if( updateType == DBMS_UPDATE_COMMIT || \
399 			updateType == DBMS_UPDATE_ABORT )
400 			dbmsInfo->flags &= ~DBMS_FLAG_UPDATEACTIVE;
401 		}
402 	return( status );
403 	}
404 
performStaticUpdate(DBMS_INFO * dbmsInfo,const char * command)405 static int performStaticUpdate( DBMS_INFO *dbmsInfo, const char *command )
406 	{
407 	return( performUpdate( dbmsInfo, command, NULL, 0, 0,
408 						   DBMS_UPDATE_NORMAL ) );
409 	}
410 
performQuery(DBMS_INFO * dbmsInfo,const char * command,char * data,int * dataLength,const char * queryData,const int queryDataLength,const time_t queryDate,const DBMS_CACHEDQUERY_TYPE queryEntry,const DBMS_QUERY_TYPE queryType)411 static int performQuery( DBMS_INFO *dbmsInfo, const char *command,
412 						 char *data, int *dataLength, const char *queryData,
413 						 const int queryDataLength, const time_t queryDate,
414 						 const DBMS_CACHEDQUERY_TYPE queryEntry,
415 						 const DBMS_QUERY_TYPE queryType )
416 	{
417 	static const COMMAND_INFO cmdTemplate = \
418 		{ DBX_COMMAND_QUERY, COMMAND_FLAG_NONE, 2, 1 };
419 	COMMAND_INFO cmd;
420 	BYTE encodedDate[ 8 + 8 ];
421 	int argIndex, status;
422 
423 	assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
424 	assert( ( data == NULL && dataLength == NULL ) || \
425 			isWritePtr( data, 16 ) );
426 	assert( ( queryData == NULL && queryDataLength == 0 ) || \
427 			( queryDataLength > 0 && \
428 			  isReadPtr( queryData, queryDataLength ) ) );
429 	assert( queryEntry >= DBMS_CACHEDQUERY_NONE && \
430 			queryEntry < DBMS_CACHEDQUERY_LAST );
431 	assert( queryType > DBMS_QUERY_NONE && queryType < DBMS_QUERY_LAST );
432 
433 	/* Additional state checks: If we're starting a new query or performing
434 	   a point query there can't already be one active, and if we're
435 	   continuing or cancelling an existing query there has to be one
436 	   already active */
437 	if( ( ( queryType == DBMS_QUERY_START || \
438 			queryType == DBMS_QUERY_CHECK || \
439 			queryType == DBMS_QUERY_NORMAL ) && \
440 		  ( dbmsInfo->flags & DBMS_FLAG_QUERYACTIVE ) ) ||
441 		( ( queryType == DBMS_QUERY_CONTINUE || \
442 			queryType == DBMS_QUERY_CANCEL ) && \
443 		  !( dbmsInfo->flags & DBMS_FLAG_QUERYACTIVE ) ) )
444 		retIntError();
445 
446 	/* Clear return value */
447 	if( data != NULL )
448 		{
449 		memset( data, 0, 16 );
450 		*dataLength = 0;
451 		}
452 
453 	/* Dispatch the command */
454 	argIndex = initQueryData( &cmd, &cmdTemplate, encodedDate, dbmsInfo,
455 							  command, queryData, queryDataLength,
456 							  queryDate, queryType );
457 	cmd.arg[ 1 ] = queryEntry;
458 	cmd.strArg[ argIndex ] = data;
459 	cmd.strArgLen[ argIndex ] = 0;
460 	cmd.noStrArgs = argIndex + 1;
461 	status = DISPATCH_COMMAND_DBX( cmdQuery, cmd, dbmsInfo );
462 	if( cryptStatusError( status ) )
463 		{
464 		performErrorQuery( dbmsInfo );
465 		return( status );
466 		}
467 
468 	/* Update the state information based on the query that we've just
469 	   performed */
470 	if( queryType == DBMS_QUERY_START  )
471 		dbmsInfo->flags |= DBMS_FLAG_QUERYACTIVE;
472 	if( queryType == DBMS_QUERY_CANCEL )
473 		dbmsInfo->flags &= ~DBMS_FLAG_QUERYACTIVE;
474 	if( dataLength != NULL )
475 		{
476 		*dataLength = cmd.strArgLen[ argIndex ];
477 		if( *dataLength <= 0 || *dataLength > MAX_QUERY_RESULT_SIZE )
478 			{
479 			assert( NOTREACHED );
480 			memset( data, 0, 16 );
481 			*dataLength = 0;
482 			return( CRYPT_ERROR_BADDATA );
483 			}
484 		}
485 	return( CRYPT_OK );
486 	}
487 
performStaticQuery(DBMS_INFO * dbmsInfo,const char * command,const DBMS_CACHEDQUERY_TYPE queryEntry,const DBMS_QUERY_TYPE queryType)488 static int performStaticQuery( DBMS_INFO *dbmsInfo, const char *command,
489 							   const DBMS_CACHEDQUERY_TYPE queryEntry,
490 							   const DBMS_QUERY_TYPE queryType )
491 	{
492 	return( performQuery( dbmsInfo, command, NULL, NULL, NULL, 0, 0,
493 						  queryEntry, queryType ) );
494 	}
495 #endif /* USE_DBMS */
496