/* main.c
 *       Copyright (C) 2001, 2002, 2003, 2007 Timo Schulz
 *
 * This file is part of OpenCDK.
 *
 * OpenCDK is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version. 
 *  
 * OpenCDK is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * GNU General Public License for more details. 
 */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif

#include "opencdk.h"
#include "main.h"
#include "packet.h"


/* Set a default cipher algorithm and a digest algorithm.
   Even if AES and SHA-256 are not 'MUST' in the latest
   OpenPGP draft, AES seems to be a good choice. */
#define DEFAULT_CIPHER_ALGO GCRY_CIPHER_AES
#define DEFAULT_DIGEST_ALGO GCRY_MD_SHA256

/* The site of the secure memory which is allocated in gcrypt. */
#define SECMEM_SIZE 16384


/* Hooks to custom memory allocation functions. */
static void *(*alloc_func) (size_t n) = gcry_xmalloc;
static void *(*alloc_secure_func) (size_t n) = gcry_malloc_secure;
static void *(*realloc_func) (void *p, size_t n) = gcry_realloc;
static void *(*calloc_func) (size_t m, size_t n) = gcry_calloc;
static void (*free_func) (void *) = gcry_free;
static int malloc_hooks = 0;
static int secmem_init = 0;

/* Global settings for the logging. */
static cdk_log_fnc_t log_handler = NULL;
static void *log_handler_value = NULL;
static int log_level = CDK_LOG_NONE;


/**
 * cdk_strerror:
 * @ec: the error number
 *
 * Return an error text for the given id.
 **/
const char*
cdk_strerror (int ec)
{
  static char buf[20];
  
  switch (ec) 
    {
    case CDK_EOF:              return "End Of File";
    case CDK_Success:          return "No error";
    case CDK_General_Error:    return "General error";
    case CDK_File_Error:       return strerror (errno);
    case CDK_Bad_Sig:          return "Bad signature";
    case CDK_Inv_Packet:       return "Invalid packet";
    case CDK_Inv_Algo:         return "Invalid algorithm";
    case CDK_Not_Implemented:  return "This is not implemented yet";
    case CDK_Armor_Error:      return "ASCII armor error";
    case CDK_Armor_CRC_Error:  return "ASCII armored damaged (CRC error)";
    case CDK_MPI_Error:        return "Invalid or missformed MPI";
    case CDK_Inv_Value:        return "Invalid parameter or value";
    case CDK_Error_No_Key:     return "No key available or not found";
    case CDK_Chksum_Error:     return "Check for key does not match";
    case CDK_Time_Conflict:    return "Time conflict";
    case CDK_Zlib_Error:       return "ZLIB error";
    case CDK_Weak_Key:         return "Weak key was detected";
    case CDK_Out_Of_Core:      return "Out of core!!";
    case CDK_Wrong_Seckey:     return "Wrong secret key";
    case CDK_Wrong_Format:     return "Data has wrong format";
    case CDK_Bad_MDC:          return "Manipulated MDC detected";
    case CDK_Inv_Mode:         return "Invalid mode";
    case CDK_Error_No_Keyring: return "No keyring available";
    case CDK_Inv_Packet_Ver:   return "Invalid version for packet";
    case CDK_Too_Short:        return "Buffer or object is too short";
    case CDK_Unusable_Key:     return "Unusable public key";
    case CDK_No_Data:          return "No data";
    case CDK_No_Passphrase:    return "No passphrase supplied";
    case CDK_Network_Error:    return "A network error occurred";
    default:                   sprintf (buf, "ec=%d", ec); return buf;
    }
  return NULL;
}


static void
out_of_core (size_t n)
{
  fprintf (stderr, "\n ** fatal error: out of memory (%d bytes) **\n", n);
}


/**
 * cdk_set_malloc_hooks: 
 * @new_alloc_func: malloc replacement
 * @new_alloc_secure_func: secure malloc replacement
 * @new_realloc_func: realloc replacement
 * @new_calloc_func: calloc replacement
 * @new_free_func: free replacement
 *
 * Set private memory hooks for the library.
 */
void
cdk_set_malloc_hooks (void *(*new_alloc_func) (size_t n),
		      void *(*new_alloc_secure_func) (size_t n),
		      void *(*new_realloc_func) (void *p, size_t n),
		      void *(*new_calloc_func) (size_t m, size_t n),
		      void (*new_free_func) (void *))
{
  alloc_func = new_alloc_func;
  alloc_secure_func = new_alloc_secure_func;
  realloc_func = new_realloc_func;
  calloc_func = new_calloc_func;
  free_func = new_free_func;
  malloc_hooks = 1;
}


/**
 * cdk_malloc_hook_initialized:
 *
 * Return if the malloc hooks are already initialized.
 **/
int
cdk_malloc_hook_initialized (void)
{
  return malloc_hooks;
}


void*
cdk_malloc (size_t size)
{
  void *p = alloc_func (size);
  if (!p)
    out_of_core (size);
  return p;
}


/**
 * cdk_calloc:
 * @n: amount of elements
 * @m: size of one element
 * 
 * Safe wrapper around the c-function calloc.
 **/
void*
cdk_calloc (size_t n, size_t m)
{
  void * p = calloc_func (n, m);
  if (!p)
    out_of_core (m);
  return p;
}


/* Things which need to  be done after the secure memory initialisation. */
static void
_secmem_finish (void)
{
  gcry_control (GCRYCTL_DROP_PRIVS);
}


/* Initialize the secure memory. */
static void
_secmem_init (size_t size)
{
  if (secmem_init == 1)
    return;
  if (size >= SECMEM_SIZE)
    size = SECMEM_SIZE;
  
  /* Check if no other library has already initialized gcrypt. */
  if (!gcry_control (GCRYCTL_ANY_INITIALIZATION_P))
    {
      _cdk_log_debug ("init: libgcrypt initialize.\n");
      gcry_control (GCRYCTL_INIT_SECMEM, size, 0);
      gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
      gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);      
      gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL, 0);
      secmem_init = 1;
    }  
}


/* Things which needs to be done to deinit the secure memory. */
static void
_secmem_end (void)
{
  gcry_control (GCRYCTL_TERM_SECMEM);
  secmem_init = 0;
}


/* The Windows system needs to startup the Winsock interface first
   before we can use any socket related function. */
#ifdef _WIN32
static void
init_sockets (void)
{
  static int initialized = 0;
  WSADATA wsdata;
  
  if (initialized)
    return;
  if (WSAStartup (0x202, &wsdata))
    _cdk_log_debug ("winsock init failed.\n");
  
  initialized = 1;
}

static void
deinit_sockets (void)
{
  WSACleanup ();
}
#else
void init_sockets (void)  {}
void deinit_sockets (void)  {}
#endif


/**
 * cdk_lib_startup:
 * 
 * Prepare the internal structures of the library.
 * This function should be called before any other CDK function.
 */
void
cdk_lib_startup (void)
{
  _secmem_init (SECMEM_SIZE);
  _secmem_finish ();
  init_sockets ();
}


/**
 * cdk_lib_shutdown:
 * 
 * Shutdown the library and free all internal and globally used
 * memory and structures. This function should be called in the
 * exit handler of the calling program.
 */
void
cdk_lib_shutdown (void)
{
  deinit_sockets ();
  _secmem_end ();
}

/**
 * cdk_salloc:
 * @size: how much bytes should be allocated.
 * @clear: shall the buffer cleared after the allocation?
 * 
 * Allocated the requested amount of bytes in 'secure' memory.
 */
void*
cdk_salloc (size_t size, int clear)
{
  void *p;
  
  if (!secmem_init)
    _secmem_init (SECMEM_SIZE);
  
  p = alloc_secure_func (size);
  if (!p)
    out_of_core (size);
  if (clear)
    memset (p, 0, size);
  return p;
}


void *
cdk_realloc (void *ptr, size_t size)
{
  void * p = realloc_func (ptr, size);
  if (!p)
    out_of_core (size);
  return p;
}


char *
cdk_strdup (const char * ptr)
{
  char * p = cdk_malloc (strlen (ptr) + 1);
  if (p)
    strcpy (p, ptr);
  return p;
}


void
cdk_free (void * ptr)
{
  if (ptr)
    free_func (ptr);
}


/* Internal logging routine. */
static void
_cdk_logv (int level, const char *fmt, va_list arg_ptr)
{

  if (log_handler)
    log_handler (log_handler_value, level, fmt, arg_ptr);
  else 
    {
      if (level == CDK_LOG_NONE)
	return;
      if (level == CDK_LOG_DEBUG)
	fputs ("DBG: ", stderr);
      vfprintf (stderr, fmt, arg_ptr); 
    }
}


/**
 * cdk_set_log_handler: 
 * @logfnc: the function pointer
 * @opaque: a private values for the function
 *
 * Set a custom handler for logging.
 **/
void
cdk_set_log_handler (cdk_log_fnc_t logfnc, void *opaque)
{
  log_handler = logfnc;
  log_handler_value = opaque; 
}


/**
 * cdk_set_log_level: 
 * @lvl: the level
 *
 * Set the verbosity level.
 **/
void
cdk_set_log_level (int level)
{
  log_level = level;
}


/* Return the current log level of the lib. */
int
_cdk_get_log_level (void)
{
  return log_level;
}


void
_cdk_log_info (const char *fmt, ...)
{
  va_list arg;
  
  if (log_level == CDK_LOG_NONE)
    return;
  va_start (arg, fmt);
  _cdk_logv (CDK_LOG_INFO, fmt, arg);
  va_end (arg);
}


void
_cdk_log_debug (const char *fmt, ...)
{
  va_list arg;
  
  if (log_level < CDK_LOG_DEBUG)
    return;
  va_start (arg, fmt);
  _cdk_logv (CDK_LOG_DEBUG, fmt, arg);
  va_end (arg);
}


/* Use the passphrase callback in the handle HD or
   return NULL if there is no valid callback. */
char*
_cdk_passphrase_get (cdk_ctx_t hd, const char *prompt)
{
  if (!hd || !hd->passphrase_cb)
    return NULL;
  return hd->passphrase_cb (hd->passphrase_cb_value, prompt);
}


static void
handle_set_cipher (cdk_ctx_t hd, int cipher)
{
  if (!hd)
    return;
  if (gcry_cipher_test_algo (cipher))
    cipher = DEFAULT_CIPHER_ALGO;
  hd->cipher_algo = cipher;   
}


static void
handle_set_digest (cdk_ctx_t hd, int digest)
{
  if (!hd)
    return;
  if (gcry_md_test_algo (digest))
    digest = DEFAULT_DIGEST_ALGO;
  hd->digest_algo = digest;   
}


static void
handle_set_s2k (cdk_ctx_t hd, int mode, int digest, int cipher)
{
  if (!hd)
    return;
  if (gcry_cipher_test_algo (cipher))
    cipher = DEFAULT_CIPHER_ALGO;
  if (gcry_md_test_algo (digest))
    digest = DEFAULT_DIGEST_ALGO;
  if (mode != CDK_S2K_SIMPLE &&
      mode != CDK_S2K_SALTED &&
      mode != CDK_S2K_ITERSALTED)
    mode = CDK_S2K_ITERSALTED;
  hd->_s2k.mode = mode;
  hd->_s2k.digest_algo = digest;
}


static void
handle_set_compress (cdk_ctx_t hd, int algo, int level)
{
  if (!hd)
    return;
  if (algo < 0 || algo > 2)
    algo = 0;
  hd->compress.algo = algo;
  if (!algo)
    hd->opt.compress = 0;
  else 
    {
      if (level > 0 && level < 10)
	hd->compress.level = level;
      else
	hd->compress.level = 6;
    }
}


/**
 * cdk_handle_control:
 * @hd: session handle
 * @action: flag which indicates whether put or get is requested
 * @cmd: command id
 *
 * Perform various control operations for the current session.
 **/
int
cdk_handle_control (cdk_ctx_t hd, int action, int cmd, ...)
{
  va_list arg_ptr;
  int set = action == CDK_CTLF_SET, val = 0;
  
  if (!hd)
    return -1;
  
  if (action != CDK_CTLF_SET && action != CDK_CTLF_GET)
    return -1;
  va_start (arg_ptr, cmd);
  switch( cmd ) 
    {
    case CDK_CTL_ARMOR:
      if (set)
	hd->opt.armor = va_arg( arg_ptr, int );
      else
	val = hd->opt.armor;
      break;

    case CDK_CTL_CIPHER:
      if (set)
	handle_set_cipher (hd, va_arg (arg_ptr, int));
      else
	val = hd->cipher_algo;
      break;
      
    case CDK_CTL_DIGEST:
      if (set)
	handle_set_digest( hd, va_arg( arg_ptr, int ) );
      else
	val = hd->digest_algo;
      break;
      
    case CDK_CTL_OVERWRITE:
      if (set)
	hd->opt.overwrite = va_arg (arg_ptr, int);
      else
	val = hd->opt.overwrite;
      break;
      
    case CDK_CTL_COMPRESS:
      if (set) 
	{
	  int algo = va_arg (arg_ptr, int);
	  int level = va_arg (arg_ptr, int);
	  handle_set_compress (hd, algo, level);
	}
      else
	val = hd->compress.algo;
      break;
      
    case CDK_CTL_S2K:
      if( set ) {
	int mode = va_arg( arg_ptr, int );
	int digest = va_arg( arg_ptr, int );
	int cipher = va_arg( arg_ptr, int );
	handle_set_s2k( hd, mode, digest, cipher );
      }
      else
	val = hd->_s2k.mode;
      break;
      
    case CDK_CTL_FORCE_DIGEST:
      if (set)
	hd->opt.force_digest = va_arg (arg_ptr, int);
      else
	val = hd->opt.force_digest;
      break;
      
    case CDK_CTL_BLOCKMODE_ON:
      if( set )
	hd->opt.blockmode = va_arg( arg_ptr, int );
      else
	val = hd->opt.blockmode;
      break;
      
    default:
      val = -1;
          break;
    }
  va_end (arg_ptr);
  return val;
}

            

/**
 * cdk_handle_new:
 * @r_ctx: context to store the handle
 *
 * create a new session handle.
 **/
cdk_error_t
cdk_handle_new (cdk_ctx_t *r_ctx)
{
  cdk_ctx_t c;
  
  if (!r_ctx)
    return CDK_Inv_Value;
  
  c = cdk_calloc (1, sizeof *c);
  if (!c)
    return CDK_Out_Of_Core;
  
  /* For S2K use the iterated and salted mode and use the
     default digest and cipher algorithms. Because the MDC
     feature will be used, the default cipher should use a 
     blocksize of 128 bits. */
  c->_s2k.mode = CDK_S2K_ITERSALTED;
  c->_s2k.digest_algo = DEFAULT_DIGEST_ALGO;
  
  c->opt.mdc = 1;
  c->opt.compress = 1;
  c->opt.armor = 0;
  c->opt.textmode = 0;
  
  c->digest_algo = DEFAULT_DIGEST_ALGO;
  c->cipher_algo = DEFAULT_CIPHER_ALGO;
  
  c->compress.algo = CDK_COMPRESS_ZIP;
  c->compress.level = 6;

  *r_ctx = c;
  return 0;
}


/**
 * cdk_handle_set_keyring:
 * @hd: session handle
 * @type: public=0 or secret=1 keyring type
 * @kringname: file name of the keyring which shall be used.
 * 
 * Convenient function to set the keyring for the current session.
 */
cdk_error_t
cdk_handle_set_keyring (cdk_ctx_t hd, int type, const char *kringname)
{
  cdk_keydb_hd_t db;
  cdk_error_t err;
  
  err = cdk_keydb_new_from_file (&db, type, kringname);
  if (err)
    return err;
  
  if (!type)
    hd->db.pub = db;
  else
    hd->db.sec = db;
  hd->db.close_db = 1;
  return 0;
}

  
/**
 * cdk_handle_set_keydb:
 * @hd: session handle
 * @db: the database handle
 *
 * set the key database handle.
 * the function automatically detects whether this is a public or
 * secret keyring and the right handle is set.
 **/
void
cdk_handle_set_keydb (cdk_ctx_t hd, cdk_keydb_hd_t db)
{
  if (!hd)
    return;
  if (_cdk_keydb_is_secret (db))
    hd->db.sec = db;
  else
    hd->db.pub = db;
}


/**
 * cdk_handle_get_keydb:
 * @hd: session handle
 * @type: type of the keyring
 *
 * Return the keydb handle from the session handle.
 * The caller should not free these handles.
 **/
cdk_keydb_hd_t
cdk_handle_get_keydb (cdk_ctx_t hd, int type)
{
  if (!hd)
    return NULL;
  if (type == CDK_DBTYPE_PK_KEYRING)
    return hd->db.pub;
  else if (type == CDK_DBTYPE_SK_KEYRING)
    return hd->db.sec;
  return NULL;
}


/**
 * cdk_handle_set_passphrase_cb:
 * @hd: session handle
 * @cb: callback function
 * @cb_value: the opaque value for the cb function
 *
 * set the passphrase callback.
 **/
void cdk_handle_set_passphrase_cb (cdk_ctx_t hd,
                                   char *(*cb) (void *opa, const char *prompt),
                                   void * cb_value)
{
    if (!hd)
        return;
    hd->passphrase_cb = cb;
    hd->passphrase_cb_value = cb_value;
}


/**
 * cdk_handle_verify_get_result:
 * @hd: the session handle
 * 
 * Return the verify result for the current session.
 * Do not free the pointer.
 **/
cdk_verify_result_t 
cdk_handle_verify_get_result (cdk_ctx_t hd)
{
  return hd->result.verify;
}


/**
 * cdk_handle_free:
 * @hd: the handle
 *
 * Release the main handle.
 **/
void
cdk_handle_free (cdk_ctx_t hd)
{
  if (!hd)
    return;
  _cdk_result_verify_free (hd->result.verify);

  /* If cdk_handle_set_keyring() were used, we need to free the key db
     handles here because the handles are not controlled by the user. */
  if (hd->db.close_db)
    {
      if (hd->db.pub)
	cdk_keydb_free (hd->db.pub);
      if (hd->db.sec)
	cdk_keydb_free (hd->db.sec);
      hd->db.pub = hd->db.sec = NULL;
    }  
  cdk_free (hd->dek);
  cdk_free (hd);
}
