/****************************************************************
 *								*
 *	Copyright 2009, 2010 Fidelity Information Services, Inc 	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#ifndef	GTMCRYPT_H
#define GTMCRYPT_H

#include <signal.h>
#include <rtnhdr.h>
#include "stack_frame.h"
#include "gtmxc_types.h"
#include "gtmcrypt_interface.h"
#include "gtmimagename.h"
#include "gtm_stdio.h"
#include "gtm_string.h"
#include "gtm_stdlib.h"
#include "have_crit.h"
#include "deferred_signal_handler.h"
#include "gtmmsg.h"
#include "gtmci.h"
#include "wbox_test_init.h"

typedef xc_status_t	(*gtmcrypt_init_t)(int);
typedef xc_status_t	(*gtmcrypt_close_t)();
typedef xc_status_t	(*gtmcrypt_hash_gen_t)(gtmcrypt_key_t, xc_string_t *);
typedef xc_status_t	(*gtmcrypt_encode_t)(gtmcrypt_key_t, xc_string_t *, xc_string_t *);
typedef xc_status_t	(*gtmcrypt_decode_t)(gtmcrypt_key_t, xc_string_t *, xc_string_t *);
typedef xc_status_t	(*gtmcrypt_getkey_by_name_t)(xc_string_t *, gtmcrypt_key_t *);
typedef xc_status_t	(*gtmcrypt_getkey_by_hash_t)(xc_string_t *, gtmcrypt_key_t *);
typedef char*		(*gtmcrypt_strerror_t)();

GBLREF	gtmcrypt_init_t			gtmcrypt_init_fnptr;
GBLREF	gtmcrypt_close_t		gtmcrypt_close_fnptr;
GBLREF	gtmcrypt_hash_gen_t		gtmcrypt_hash_gen_fnptr;
GBLREF	gtmcrypt_encode_t		gtmcrypt_encode_fnptr;
GBLREF	gtmcrypt_decode_t		gtmcrypt_decode_fnptr;
GBLREF	gtmcrypt_getkey_by_name_t 	gtmcrypt_getkey_by_name_fnptr;
GBLREF	gtmcrypt_getkey_by_hash_t 	gtmcrypt_getkey_by_hash_fnptr;
GBLREF	gtmcrypt_strerror_t		gtmcrypt_strerror_fnptr;

/* The standard shared library suffix for HPUX on HPPA is .sl.
 * On HPUX/IA64, the standard suffix was changed to .so (to match other Unixes) but for
 * the sake of compatibility, they still accept (and look for) .sl if .so is not present.
 * Nevertheless, we use the standard suffix on all platforms.
 */
#if (defined(__hpux) && defined(__hppa))
#	define	GTMCRYPT_LIBNAME	"libgtmcrypt.sl"
#elif defined(__MVS__)
#	define	GTMCRYPT_LIBNAME        "libgtmcrypt.dll"
#else
#	define	GTMCRYPT_LIBNAME	"libgtmcrypt.so"
#endif

#define	GTMCRYPT_LIBFLAGS		(RTLD_NOW | RTLD_GLOBAL)

#define	GTMCRYPT_INIT_FNAME		"gtmcrypt_init"
#define	GTMCRYPT_CLOSE_FNAME		"gtmcrypt_close"
#define	GTMCRYPT_HASH_GEN_FNAME		"gtmcrypt_hash_gen"
#define	GTMCRYPT_ENCODE_FNAME		"gtmcrypt_encode"
#define	GTMCRYPT_DECODE_FNAME		"gtmcrypt_decode"
#define GTMCRYPT_GETKEY_BY_NAME		"gtmcrypt_getkey_by_name"
#define GTMCRYPT_GETKEY_BY_HASH		"gtmcrypt_getkey_by_hash"
#define	GTMCRYPT_STRERROR		"gtmcrypt_strerror"

#define	GTM_PASSWD			"gtm_passwd"
/* Global variable GBLDEF'ed in gbldefs.c */
GBLREF	int4	gtmcrypt_init_state;
/* Possible states for encryption library */
typedef enum
{
	GTMCRYPT_UNINITIALIZED,		/* This is when, so far in the code, gtmcrypt_setup or gtmcrypt_init hasn't been called */
	GTMCRYPT_INITIALIZED,		/* This is the state when both dlopen and gtmcrypt_init has successfully passed */
} gtmcrypt_init_values;

void gtmcrypt_entry(void);

/* =====================================================================================================*/
/* 					Error Reporting Macros						*/
/* =====================================================================================================*/

/* Call the plugin's strerror equivalent to get the last error message */
#define GC_GET_ERR_STRING(err)				\
{							\
	assert(NULL != gtmcrypt_strerror_fnptr);	\
	err = (*gtmcrypt_strerror_fnptr)();		\
}

/* ERR_CRYPTKEYFETCHFAILED is a unique error that has to be handled a bit differently. Whenever GT.M calls the plugin
 * to get the encryption key for a give hash, the plugin is not aware of the filename for which the hash was
 * passed. Also, GT.M itself might not be aware of the filenames in cases like bin_load. To handle this case,
 * GC_GTM_PUTMSG and GC_RTS_ERROR will explicitly carry the filename for which the current operation
 * is being done. In case GT.M cannot figure out the filename, it will pass NULL. The following constructs the
 * appropriate error message in the event that the filename is present and for the case where GT.M has passed
 * NULL. */

#define GC_GTM_PUTMSG(err_id, FNAME)												\
{																\
	char 		*err;													\
	GBLREF char	dl_err[];												\
																\
	error_def(ERR_CRYPTKEYFETCHFAILED);											\
	error_def(ERR_CRYPTKEYFETCHFAILEDNF);											\
	error_def(ERR_CRYPTINIT);												\
	error_def(ERR_CRYPTDLNOOPEN);												\
																\
	if (ERR_CRYPTDLNOOPEN != err_id)											\
	{															\
		GC_GET_ERR_STRING(err);												\
		if (ERR_CRYPTKEYFETCHFAILED == err_id)										\
		{														\
			if (NULL != FNAME)											\
				gtm_putmsg(VARLSTCNT(6) ERR_CRYPTKEYFETCHFAILED, 4, LEN_AND_STR(FNAME), LEN_AND_STR(err));	\
			else													\
				gtm_putmsg(VARLSTCNT(4) ERR_CRYPTKEYFETCHFAILEDNF, 2, LEN_AND_STR(err));			\
		} else 														\
			gtm_putmsg(VARLSTCNT(4) err_id, 2, LEN_AND_STR(err)); 							\
	} else															\
		gtm_putmsg(VARLSTCNT(4) ERR_CRYPTDLNOOPEN, 2, LEN_AND_STR(dl_err));						\
}

/* Fetch the last error string from the encryption plugin. If the error is ERR_KEYFETCHFAILED, we need to
 * handle them a bit differently as described above. This macro should be called whenever GT.M wants to do
 * rts_error. */
#define GC_RTS_ERROR(err_id, FNAME)												\
{																\
	char 		*err;													\
	GBLREF char	dl_err[];												\
																\
	error_def(ERR_CRYPTKEYFETCHFAILED);											\
	error_def(ERR_CRYPTKEYFETCHFAILEDNF);											\
	error_def(ERR_CRYPTINIT);												\
	error_def(ERR_CRYPTDLNOOPEN);												\
																\
	if (ERR_CRYPTDLNOOPEN != err_id)											\
	{															\
		GC_GET_ERR_STRING(err);												\
		if (ERR_CRYPTKEYFETCHFAILED == err_id)										\
		{														\
			if (NULL != FNAME)											\
				rts_error(VARLSTCNT(6) ERR_CRYPTKEYFETCHFAILED, 4, LEN_AND_STR(FNAME), LEN_AND_STR(err));	\
			else													\
				rts_error(VARLSTCNT(4) ERR_CRYPTKEYFETCHFAILEDNF, 2, LEN_AND_STR(err));				\
		} else 														\
			rts_error(VARLSTCNT(4) err_id, 2, LEN_AND_STR(err)); 							\
	} else															\
		rts_error(VARLSTCNT(4) ERR_CRYPTDLNOOPEN, 2, LEN_AND_STR(dl_err));						\
}

/* Following are the error identifiers visible to the GT.M user. Depending on the operation performed,
 * mark the appropriate status. */
#define GC_MARK_STATUS(status, RC, ERR_ID) 											\
{																\
	error_def(ERR_CRYPTDLNOOPEN);												\
	error_def(ERR_CRYPTINIT);												\
	error_def(ERR_CRYPTKEYFETCHFAILED);											\
	error_def(ERR_CRYPTOPFAILED);												\
	error_def(ERR_CRYPTHASHGENFAILED);											\
																\
	RC = (0 != status) ? ERR_ID : 0;											\
}

/* =====================================================================================================*/
/* 					Utility Macros							*/
/* =====================================================================================================*/

/* Package the required addresses in various xc_string_t which can be used by various decode, encode macros */
#define PACKAGE_XCSTRING(xcstring, buf, buflen)											\
{																\
	xcstring.address = buf;													\
	xcstring.length = buflen;												\
}

/* =====================================================================================================*/
/* 					GT.M Related Macros						*/
/* =====================================================================================================*/
#define IS_BLK_ENCRYPTED(LEVL, BSIZ)			((0 <= ((char)LEVL)) && (0 < BSIZ))

#define BLK_NEEDS_ENCRYPTION(LEVL, BSIZ)		IS_BLK_ENCRYPTED(LEVL, BSIZ)

#define BLOCK_REQUIRE_ENCRYPTION(FLAG, LEVL, BSIZ) 	(FLAG && IS_BLK_ENCRYPTED(LEVL, BSIZ))

#define ENCR_INITIALIZED 		(GTMCRYPT_INITIALIZED == gtmcrypt_init_state)
#define ENCR_WBOX_ENABLED		(gtm_white_box_test_case_enabled 							\
				 			&& (WBTEST_ENCRYPT_INIT_ERROR == gtm_white_box_test_case_number))

#define ASSERT_ENCRYPTION_INITIALIZED			assert(ENCR_INITIALIZED || ENCR_WBOX_ENABLED)

#define GTMCRYPT_COPY_HASH(src, dst)												\
{																\
	memcpy(dst->encryption_hash, src->encryption_hash, GTMCRYPT_HASH_LEN);							\
	dst->is_encrypted = src->is_encrypted;											\
}																\

#define	ALLOC_BUFF_GET_ENCR_KEY(CSA, HASH, ALLOC_SIZE, RC)									\
{																\
	assert(0 < ALLOC_SIZE && NULL != CSA);											\
	RC = 0;															\
	GTMCRYPT_GETKEY(HASH, CSA->encr_key_handle, RC);									\
	if (0 == RC)														\
		(CSA)->encrypted_blk_contents = (char *)malloc(ALLOC_SIZE); 							\
}

#define INIT_DB_ENCRYPTION(fname, CSA, CSD, RC)											\
{																\
	GBLREF stack_frame		*frame_pointer;										\
	GBLREF uint4			dollar_tlevel;										\
	char 				*ptr, *key_hash = CSD->encryption_hash;							\
	boolean_t			call_ci_ret_code_quit = FALSE, prompt_passwd = FALSE;					\
																\
	error_def(ERR_CRYPTNOPSWDINTP);												\
	RC = 0;															\
	/* If we are in a TP transaction and the environment is setup in such a way that we will be doing a gtm_ci call 	\
	 * then let's error out as gtm_ci doesn't work inside a TP transaction */						\
	prompt_passwd = PROMPT_PASSWD;												\
	if (prompt_passwd && dollar_tlevel)											\
		rts_error(VARLSTCNT(1) ERR_CRYPTNOPSWDINTP);									\
	/* Make sure we are not in gtm_ci already. */										\
	assert(!IS_MUMPS_IMAGE || !(frame_pointer->flags & SFF_CI));								\
	/* The below macro eventually calls gtmcrypt_getkey_by_hash to get the encryption key for the database based on 	\
	 * the hash in the database file header. It could be possible that initially the user had a wrong password and 		\
	 * tried accessing a global which might have landed in db_init and would have successfully done encryption 		\
	 * initialization. But later call to this macro would have failed since the password turned out to be wrong and 	\
	 * the encryption library failed to do the decryption. Now, after setting the password to a null string, the user	\
	 * now tries to access the global again which will reach this macro again but now the call to gtmcrypt_getkey_by_hash	\
	 * will call gtm_ci for prompting password. Hence before calling the encryption library, make a note if we had 		\
	 * to do ci_ret_code_quit below. */											\
	call_ci_ret_code_quit = (prompt_passwd && !(frame_pointer->flags & SFF_CI));						\
	ALLOC_BUFF_GET_ENCR_KEY(CSA, key_hash, (CSD->blk_size + SIZEOF(int4)), RC);						\
	if (call_ci_ret_code_quit)												\
		ci_ret_code_quit();												\
}

#define PROMPT_PASSWD			(IS_MUMPS_IMAGE										\
					 && (NULL != (ptr = (char *)getenv(GTM_PASSWD))) 					\
					 && (0 == strlen(ptr)))

/* =====================================================================================================*/
/* 					Plugin Related Macros						*/
/* =====================================================================================================*/

/* INIT_PROC_ENCRYPTION is called whenever GT.M wants to initialize the encryption library and it's related
 * modules. The context in which the caller calls INIT_PROC_ENCRYPTION might force non encryption related
 * tasks (like MUPIP JOURNAL -SHOW=HEADER -NOVERIFY -FORWARD) to error out in case the below macro fails. To
 * avoid this, we note down the error status and the corresponding error string in global variable. This way,
 * any task requiring the actual encryption (MUPIP INTEG -FILE) will verify if the global error code is holding
 * a non zero value and error out accordingly. */
#define INIT_PROC_ENCRYPTION(RC)												\
{																\
	GBLREF int4			gbl_encryption_ecode;									\
	GBLREF stack_frame		*frame_pointer;										\
	GBLREF uint4			dollar_tlevel;										\
	boolean_t			call_ci_ret_code_quit = FALSE, prompt_passwd = FALSE;					\
																\
	error_def(ERR_CRYPTNOPSWDINTP);												\
	error_def(ERR_CRYPTINIT);												\
	RC = 0;															\
	gbl_encryption_ecode = 0;												\
	if (GTMCRYPT_UNINITIALIZED == gtmcrypt_init_state)									\
	{															\
		gtmcrypt_entry();												\
		/* If in the above call, dlopen failed for some reason, then gbl_encryption_ecode will be set to		\
		 * ERR_CRYPTDLNOOPEN. Also, the dlopen error message will be stored in dl_err. */				\
		RC = gbl_encryption_ecode;											\
		if (0 == gbl_encryption_ecode)											\
		{														\
			char		*ptr;											\
			boolean_t	has_prompted_passwd;									\
																\
			/* dlopen on the encryption library succeeded. */							\
			assert(NULL != gtmcrypt_init_fnptr);									\
			/* If we are in a TP transaction and the environment is setup in such a way that we will be doing a 	\
			 * gtm_ci call then let's error out as gtm_ci doesn't work inside a TP transaction */			\
			prompt_passwd = PROMPT_PASSWD;										\
			if (prompt_passwd && dollar_tlevel)								\
				rts_error(VARLSTCNT(1) ERR_CRYPTNOPSWDINTP);							\
			/* Make sure we are not in gtm_ci already. */								\
			assert(!IS_MUMPS_IMAGE || !(frame_pointer->flags & SFF_CI));						\
			/* The call to gtmcrypt_init below will try to call gtm_ci on finding that password is set to 		\
			 * empty string and if the calling process is MUMPS. Make a note of the condition under which we	\
			 * would be calling ci_ret_code_quit. */								\
			call_ci_ret_code_quit = (prompt_passwd && !(frame_pointer->flags & SFF_CI));				\
			/* Call the encryption library's init routine. Also, pass a boolean indicating whether 			\
			 * the library should do the password prompting(for MUMPS) or not(for MUPIP, DSE, etc.)*/		\
			xc_status_t init_ret_status = (*gtmcrypt_init_fnptr)(IS_MUMPS_IMAGE);					\
			/* Unwind the stack frames if necessary. */								\
			if (call_ci_ret_code_quit)										\
				ci_ret_code_quit();										\
			/* If the call failed, we have to indicate the caller that an error happened. Also, we			\
			 * will mark the gbl_encryption_ecode to ERR_CRYPTINIT. */						\
			if (0 != init_ret_status)										\
				RC = gbl_encryption_ecode = ERR_CRYPTINIT;							\
			else													\
			{													\
				/* Everything went on smoothly. So indicate that we don't need to do the init			\
				 * again. */											\
				gtmcrypt_init_state = GTMCRYPT_INITIALIZED;							\
				gbl_encryption_ecode = RC = 0;									\
			}													\
		}														\
	}															\
}

/* GTMCRYPT_GETKEY is mostly called when the caller wants to get the encryption key well before the actual
 * encryption or decryption happens. As mentioned above the context in which GTMCRYPT_GETKEY is called
 * might force the non encryption related tasks to error out. So we follow a similar approach as mentioned above. */
#define GTMCRYPT_GETKEY(hash, key_handle, RC)											\
{																\
	xc_string_t	xc_hash;												\
	xc_status_t	status;													\
	GBLREF int4	gbl_encryption_ecode;											\
																\
	error_def(ERR_CRYPTKEYFETCHFAILED);											\
	/* Call the encryption library only if we are clean from the last encryption						\
	 * call. We find this from gbl_encryption_ecode. */									\
	RC = gbl_encryption_ecode;												\
	if (0 == RC)														\
	{															\
		PACKAGE_XCSTRING(xc_hash, hash, GTMCRYPT_HASH_LEN);								\
		assert(NULL != gtmcrypt_getkey_by_hash_fnptr);									\
		status = (*gtmcrypt_getkey_by_hash_fnptr)(&xc_hash, &key_handle);						\
		RC = gbl_encryption_ecode = (0 != status) ? ERR_CRYPTKEYFETCHFAILED : 0;					\
	}															\
}

#define GTMCRYPT_HASH_CHK(hash, RC)												\
{																\
	gtmcrypt_key_t		handle;												\
																\
	GTMCRYPT_GETKEY(hash, handle, RC);											\
}

/* DSE CHANGE -FILE -ENCRYPTION_HASH will call the GTMCRYPT_HASH_GEN  macro to reset the encryption hash in the database file
 * header. But, before doing so, it would have encountered an error in db_init but instead of reporting the error in db_init, it
 * stores the error code in gbl_encryption_ecode. But the GTMCRYPT_HASH_GEN macro cannot proceed if it finds that the error code is
 * non-zero. The below GTMCRYPT_RESET_ERR macro will reset this global error code back to zero so that GTMCRYPT_HASH_GEN can
 * proceed. However, we don't want to reset the error if the initialization failed because of a dlopen error as this would mean
 * that we won't have the function pointers to encryption library APIs properly initialized which would be used by GTMCRYPT_HASH_GEN
 * when we should ideally be reporting an error. */
#define GTMCRYPT_RESET_HASH_MISMATCH_ERR											\
{																\
	GBLREF int4		gbl_encryption_ecode;										\
																\
	error_def(ERR_CRYPTDLNOOPEN);												\
	if (0 != gbl_encryption_ecode && (ERR_CRYPTDLNOOPEN != gbl_encryption_ecode))						\
		gbl_encryption_ecode = 0;											\
}

#define GTMCRYPT_HASH_GEN(filename, filename_len, hash, RC)									\
{																\
	xc_status_t		status;												\
	xc_string_t		xc_filename, xc_hash;										\
	gtmcrypt_key_t		handle;												\
	GBLREF int4		gbl_encryption_ecode;										\
																\
	error_def(ERR_CRYPTDLNOOPEN);												\
	RC = gbl_encryption_ecode;												\
	if (0 == RC || (ERR_CRYPTDLNOOPEN != RC))										\
	{															\
		PACKAGE_XCSTRING(xc_filename, filename, filename_len);								\
		PACKAGE_XCSTRING(xc_hash, hash, GTMCRYPT_HASH_LEN);								\
		assert(NULL != gtmcrypt_getkey_by_name_fnptr);									\
		status = (*gtmcrypt_getkey_by_name_fnptr)(&xc_filename, &handle);						\
		GC_MARK_STATUS(status, RC, ERR_CRYPTKEYFETCHFAILED);								\
		if (0 == RC)													\
		{														\
			assert(NULL != gtmcrypt_hash_gen_fnptr);								\
			status = (*gtmcrypt_hash_gen_fnptr)(handle, &xc_hash);							\
			GC_MARK_STATUS(status, RC, ERR_CRYPTHASHGENFAILED);							\
			if (0 == RC)												\
				memcpy(hash, xc_hash.address, GTMCRYPT_HASH_LEN);						\
		}														\
	}															\
}

#define GTMCRYPT_ENCODE_FAST(key_handle, inbuf, inbuf_len, outbuf, RC)								\
{																\
	GBLREF  volatile int4   fast_lock_count;										\
	xc_string_t		unencrypted_block, encrypted_block;								\
	xc_status_t		status;												\
	int             	save_fast_lock_count;										\
	GBLREF int4		gbl_encryption_ecode;										\
																\
	RC = gbl_encryption_ecode;												\
	if (0 == RC)														\
	{															\
		PACKAGE_XCSTRING(unencrypted_block, inbuf, inbuf_len);								\
		PACKAGE_XCSTRING(encrypted_block, outbuf, inbuf_len);								\
		assert(NULL != gtmcrypt_encode_fnptr); 										\
		save_fast_lock_count = fast_lock_count;										\
		DEFER_INTERRUPTS(INTRPT_IN_CRYPT_SECTION);									\
		fast_lock_count++;												\
		status = (*gtmcrypt_encode_fnptr)(key_handle,									\
						&unencrypted_block,								\
						&encrypted_block);								\
		ENABLE_INTERRUPTS(INTRPT_IN_CRYPT_SECTION);									\
		GC_MARK_STATUS(status, RC, ERR_CRYPTOPFAILED);									\
		fast_lock_count = save_fast_lock_count;										\
	}															\
}

#define GTMCRYPT_DECODE_FAST(key_handle, inbuf, inbuf_len, outbuf, RC)								\
{																\
	GBLREF  volatile int4   fast_lock_count;										\
	xc_string_t		unencrypted_block, encrypted_block;								\
	xc_status_t		status;												\
	int             	save_fast_lock_count;										\
	GBLREF int4		gbl_encryption_ecode;										\
																\
	RC = gbl_encryption_ecode;												\
	if (0 == RC)														\
	{															\
		PACKAGE_XCSTRING(encrypted_block, inbuf, inbuf_len);								\
		PACKAGE_XCSTRING(unencrypted_block, outbuf, inbuf_len);								\
		assert(NULL != gtmcrypt_decode_fnptr); 										\
		save_fast_lock_count = fast_lock_count;										\
		fast_lock_count++;												\
		DEFER_INTERRUPTS(INTRPT_IN_CRYPT_SECTION);									\
		status = (*gtmcrypt_decode_fnptr)(key_handle,									\
						&encrypted_block,								\
						&unencrypted_block);								\
		ENABLE_INTERRUPTS(INTRPT_IN_CRYPT_SECTION);									\
		GC_MARK_STATUS(status, RC, ERR_CRYPTOPFAILED);									\
		fast_lock_count = save_fast_lock_count;										\
	}															\
}

#define GTMCRYPT_CLOSE														\
{																\
	if (GTMCRYPT_INITIALIZED == gtmcrypt_init_state)									\
	{															\
		(*gtmcrypt_close_fnptr)();											\
		gtmcrypt_init_state = GTMCRYPT_UNINITIALIZED;									\
	}															\
}

#endif /* GTMCRYPT_H */
