⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 dbxmysql.c

📁 老外写的加密库cryptlib(版本3.1)
💻 C
字号:
/****************************************************************************
*																			*
*						 cryptlib MySQL Mapping Routines					*
*						Copyright Peter Gutmann 1997-2001					*
*																			*
****************************************************************************/

/* TODO:

  - This is mostly a direct conversion of the mSQL code to MySQL.  Since I
	don't run MySQL I haven't been able to check the code much.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef INC_CHILD
  #include "../crypt.h"
  #include "keyset.h"
#else
  #include "crypt.h"
  #include "keyset/keyset.h"
#endif /* INC_CHILD */

/****************************************************************************
*																			*
*							MySQL Database Access Functions					*
*																			*
****************************************************************************/

#ifdef USE_MYSQL

/* Translate the SQL into the MySQL variant as appropriate */

static void convertQuery( char *query, const char *command )
	{
	BOOLEAN uniqueIndex = FALSE;

	assert( command != NULL );
	strcpy( query, command );

	/* If it's a CREATE TABLE command, rewrite the blob and date types to
	   the appropriate values for the database backend */
	if( !strncmp( command, "CREATE TABLE", 12 ) )
		{
		char *placeholderPtr;

		if( ( placeholderPtr = strstr( query, " BLOB" ) ) != NULL )
			/* Although MySQL supports blobs, the mechanism for handling
			   them is clunky to say the least (they have to be represented
			   in a kind of quoted-printable form since MySQL doesn't handle
			   bound variables) so we fall back to using the non-blob
			   alternative which is easier */
			memcpy( placeholderPtr, " TEXT", 5 );
		if( ( placeholderPtr = strstr( query, " DATETIME" ) ) != NULL )
			{
			/* Open up a gap and replace the date name placeholder with the
			   MySQL date name */
			memcpy( placeholderPtr, " DATE", 5 );
			memmove( placeholderPtr + 5, placeholderPtr + 9,
					 strlen( placeholderPtr + 9 ) + 1 );
			}
		}
	}

/* Bind a date into a query, which involves manually translating it into the
   DATETIME data because of MySQL's lack of support for bound variables */

#define DATETIME_SIZE		14

static int bindDate( char *query, const time_t boundDate )
	{
	struct tm *timeInfo = gmtime( &boundDate );
	char *datePtr = strchr( query, '?' );
	int length = strlen( query ), ch;

	assert( timeInfo != NULL );
	assert( datePtr != NULL );

	/* If we can't add the date information, return a data overflow
	   error */
	if( length > MAX_SQL_QUERY_SIZE - DATETIME_SIZE )
		return( CRYPT_ERROR_OVERFLOW );

	/* Poke the date info into the query string.  This encodes the data in
	   the ISO 8601 format, which allows comparisons like < and > to work
	   properly.  When calculating the size, we use DATETIME_SIZE + 2 to
	   account for the extra ''s needed to demarcate the date string */
	memmove( datePtr + DATETIME_SIZE + 1, datePtr,
			 strlen( datePtr ) + 1 );
	ch = datePtr[ DATETIME_SIZE + 2 ];
	sprintf( datePtr, "'%04d%02d%02d%02d%02d%02d'",
			 timeInfo->tm_year + 1900, timeInfo->tm_mon + 1,
			 timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min,
			 timeInfo->tm_sec );
	datePtr[ DATETIME_SIZE + 2 ] = ch;	/* Restore value zapped by '\0' */

	return( CRYPT_OK );
	}

/* Get information on a MySQL error */

static int getErrorInfo( DBMS_STATE_INFO *dbmsInfo, const int defaultStatus )
	{
	const char *mysqlErrorMsg = mysql_error( dbmsInfo->connection );
	int length = min( strlen( mysqlErrorMsg ), MAX_ERRMSG_SIZE - 1 );

	/* MySQL returns error information as a static string via mysqlErrMsg().
	   Because we can't get a real error code, we have to pick apart the
	   error string to provide more information on certain types of error */
	strncpy( dbmsInfo->errorMessage, mysqlErrorMsg, length );
	dbmsInfo->errorMessage[ length ] = '\0';
	dbmsInfo->errorCode = mysql_errno( dbmsInfo->connection );

	/* The only information we can get from mysqlSelectDB() and mysqlQuery()
	   is "OK" or "not OK" (and, in 2.x, the number of items returned for
	   mysqlQuery()), so we have to pick apart the returned error message to
	   find out what went wrong.  This is pretty nasty since it may break if
	   the error messages are ever changed */
	if( ( !strncmp( dbmsInfo->errorMessage, "Table", 5 ) && \
		  !strncmp( dbmsInfo->errorMessage + length - 6, "exists", 6 ) ) )
		return( CRYPT_ERROR_DUPLICATE );

	return( defaultStatus );
	}

/* Open and close a connection to a MySQL server */

static int openDatabase( DBMS_STATE_INFO *dbmsInfo, const char *name,
						 const int options, int *featureFlags )
	{
	MYSQL *mysql;
	char *hostNamePtr = ( char * ) host;
	int status = -1;

	UNUSED( options );

	/* Connect to the MySQL server and select the database */
	if( host == NULL )
		hostNamePtr = "localhost";	/* Connect to default host */
	mysql = mysql_init( NULL );
	dbmsInfo->connection = mysql_real_connect( mysql, hostNamePtr, user,
											   password, name, 0, NULL, 0 );
	if( dbmsInfo->connection == NULL )
		{
		dbmsInfo->connection = mysql;
		getErrorInfo( dbmsInfo, CRYPT_ERROR_OPEN );
		dbmsInfo->connection = NULL;
		mysql_close( mysql );		/* Free the MYSQL structure */
		return( CRYPT_ERROR_OPEN );
		}

	/* Set some options to improve performance.  We set the select limit to
	   1 (since we're only ever going to retrieve one row), and tell the
	   server to abort if a select would take a very long time (this
	   shouldn't have any effect on anything created by cryptlib, but it's
	   worth doing anyway for general bulletproofing) */
	mysql_query( dbmsInfo->connection, "SET SQL_SELECT_LIMIT=1" );
	mysql_query( dbmsInfo->connection, "SET SQL_BIG_SELECTS=1" );

	/* Return database backend-specific information to the caller */
	*featureFlags = DBMS_HAS_NONE;

	return( CRYPT_OK );
	}

static void closeDatabase( DBMS_STATE_INFO *dbmsInfo )
	{
	mysql_close( dbmsInfo->connection );
	dbmsInfo->connection = NULL;
	}

/* Perform a transaction which updates the database without returning any
   data */

static int performUpdate( DBMS_STATE_INFO *dbmsInfo, const char *command,
						  const void *boundData, const int boundDataLength,
						  const time_t boundDate,
						  const DBMS_UPDATE_TYPE updateType )
	{
	char query[ MAX_SQL_QUERY_SIZE ];
	int status;

	/* If we're aborting a transaction, tell the server and exit (given
	   MySQL's current non-transaction handling this isn't going to do much,
	   but it's better than nothing) */
	if( updateType == DBMS_UPDATE_ABORT )
		{
		mysql_query( dbmsInfo->connection, "ROLLBACK" );
		return( CRYPT_OK );
		}

	/* If it's the start of a transaction, tell the server.  The MySQL
	   manual is somewhat evasive as to just how effective this type of
	   pseudo-transaction management really is, presumably it just holds the
	   data in memory until a commit or rollback command is sent, however
	   it must do some sort of lazy commit because it can't hold infinite
	   amounts of data in memory forever.  In any case it shouldn't be a
	   problem for cryptlib because it only sends in small data quantities
	   all in one lot */
	if( updateType == DBMS_UPDATE_BEGIN )
		{
		mysql_query( dbmsInfo->connection, "SET AUTOCOMMIT = 0" );
		mysql_query( dbmsInfo->connection, "BEGIN" );
		}

	/* Submit the query to the MySQL server */
	convertQuery( query, command );
	if( boundDate != 0 )
		{
		status = bindDate( query, boundDate );
		if( cryptStatusError( status ) )
			return( status );
		}
	status = mysql_query( dbmsInfo->connection, query );
	if( updateType == DBMS_UPDATE_COMMIT )
		{
		status = mysql_query( dbmsInfo->connection,
							  cryptStatusOK( status ) ? \
							  "COMMIT" : "ROLLBACK" );
		mysql_query( dbmsInfo->connection, "SET AUTOCOMMIT = 1" );
		}
	if( status == -1 )
		return( getErrorInfo( dbmsInfo, CRYPT_ERROR_WRITE ) );

	/* If we're performing a delete, the operation will succeed even though
	   nothing was found to delete so we make sure we actually changed
	   something */
	if( !strnicmp( query, "DELETE", 6 ) && \
		mysql_affected_rows( dbmsInfo->connection ) <= 0 )
		return( CRYPT_ERROR_NOTFOUND );

	return( CRYPT_OK );
	}

/* Perform a transaction which returns information */

static int performQuery( DBMS_STATE_INFO *dbmsInfo, const char *command,
						 char *data, int *dataLength, time_t boundDate,
						 const DBMS_QUERY_TYPE queryType )
	{
	char query[ MAX_SQL_QUERY_SIZE ];
	int status = CRYPT_OK;

	/* If we're cancelling an ongoing query, discard the result set
	   (unfortunately we have to actually read it rather than just
	   dropping it, see the comment further down for the reason), and
	   restore the single-result fetch limit */
	if( queryType == DBMS_QUERY_CANCEL )
		{
		if( dbmsInfo->result != NULL )
			{
			mysql_free_result( dbmsInfo->result );
			dbmsInfo->result = NULL;
			}
		mysql_query( dbmsInfo->connection, "SET SQL_SELECT_LIMIT=1" );
		return( CRYPT_OK );
		}

	/* If we're starting an ongoing query, remove the fetch limit to allow
	   more than one result to be returned */
	if( queryType == DBMS_QUERY_START )
		{
		mysql_query( dbmsInfo->connection, "SET SQL_SELECT_LIMIT=0" );
		dbmsInfo->result = NULL;
		}

	/* Submit the query to the MySQL server.  Unfortunately we have to call
	   mysql_store_result() even if we don't need the result or it'll remain
	   in the server's buffer and be returned on the next fetch, however
	   since the fetch limit is set to one row this isn't much of a problem */
	if( queryType == DBMS_QUERY_START || queryType == DBMS_QUERY_CHECK || \
		queryType == DBMS_QUERY_NORMAL )
		{
		convertQuery( query, command );
		if( boundDate != 0 )
			{
			status = bindDate( query, boundDate );
			if( cryptStatusError( status ) )
				return( status );
			}
		if( mysql_query( dbmsInfo->connection, query ) == -1 )
			return( getErrorInfo( dbmsInfo, CRYPT_ERROR_READ ) );

		/* See what we got */
		dbmsInfo->result = mysql_store_result( dbmsInfo->connection );
		if( dbmsInfo->result == NULL )
			return( CRYPT_ERROR_NOTFOUND );
		}

	/* If we're only doing a check, we're done */
	if( queryType == DBMS_QUERY_CHECK )
		{
		if( mysql_num_rows( dbmsInfo->result ) <= 0 )
			status = CRYPT_ERROR_NOTFOUND;
		mysql_free_result( dbmsInfo->result );
		dbmsInfo->result = NULL;
		return( status );
		}

	/* Fetch the next returned row (this is always just a single value, the
	   key data) */
	if( queryType == DBMS_QUERY_START || queryType == DBMS_QUERY_CONTINUE || \
		queryType == DBMS_QUERY_NORMAL )
		{
		MYSQL_ROW row;

		row = mysql_fetch_row( dbmsInfo->result );
		if( row == NULL )
			status = CRYPT_ERROR_NOTFOUND;
		else
			{
			*dataLength = strlen( row[ 0 ] );
			if( *dataLength >= MAX_QUERY_RESULT_SIZE )
				{
				/* Too much data returned */
				*dataLength = 0;
				status = CRYPT_ERROR_OVERFLOW;
				}
			else
				strcpy( data, row[ 0 ] );
			}
		}

	/* If it's a one-off query, free the result set */
	if( queryType == DBMS_QUERY_NORMAL )
		{
		mysql_free_result( dbmsInfo->result );
		dbmsInfo->result = NULL;
		}

	return( status );
	}

/* Fetch extended error information from the database state info */

static void performErrorQuery( DBMS_STATE_INFO *dbmsInfo, int *errorCode,
							   char *errorMessage )
	{
	*errorCode = dbmsInfo->errorCode;
	strcpy( errorMessage, dbmsInfo->errorMessage );
	}

/* Pull in the shared database RPC routines, renaming the generic dispatch
   function to the MySQL-specific one which is called directly by the
   marshalling code */

#define processCommand( stateInfo, buffer ) \
		mysqlProcessCommand( stateInfo, buffer )

#include "dbx_rpc.c"

#endif /* USE_MYSQL */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -