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