/* -*- Mode: C; c-file-style: "gnu" -*- */
/* minpg.c - Minimal Privacy Guard (MinPG)
 *        Copyright (C) 2002, 2003 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenCDK; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * This is only a regression test and should _NOT_ used for real work.
 * But on the other hand, it is _not_ insecure.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <popt.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

#include "opencdk.h"

enum
{
  cSignEncr = 'b',
  cEnarmor = 'a',
  cDearmor = 'u',
  cHelp = 'h',
  cVersion = 'V',
  cSymmetric = 'c',
  cKeyList = 'k',
  cVerify = 'y',
  cGenKey = 'w',
  cSigList = 'l',
  cEncr = 'e',
  cEncrFiles = '@',
  cDecr = 'd',
  cDecrFiles = '$',
  cExport = 'x',
  cImport = 'i',
  cShowpref = 'q',
  oCompress = 'z',
  oRecipient = 'r',
  oVerbose = 'v',
  oOutput = 'o',
  oCipherAlg = 'n',
  oDigestAlgo = 'm',
  oFetchKey = 'f',
  cSign = 's',
  cSignClear = 't',
  oArmor = 'p',
  oCompat = 'g',
  oNoCompress = '0',
  oDetachSign = 'j',  
};


static struct
{
  const char * name;
  int algid;
} algo_table[] = {
  {"3DES", CDK_CIPHER_3DES},
  {"CAST5", CDK_CIPHER_CAST5},
  {"BLOWFISH", CDK_CIPHER_BLOWFISH},
  {"TWOFISH", CDK_CIPHER_TWOFISH},
  {"AES", CDK_CIPHER_AES},
  {"AES192", CDK_CIPHER_AES192},
  {"AES256", CDK_CIPHER_AES256},
  {"MD5", CDK_MD_MD5},
  {"SHA1", CDK_MD_SHA1},
  {"RIPEMD160", CDK_MD_RMD160},
  {"SHA256", CDK_MD_SHA256},
  {NULL},
};

char * file = "(none)";
char * user = "(none)";
char * outname = "(none)";
char * cipher_algo = "(none)";
char * digest_algo = "(none)";
char * ks_keyid = "(none)";
char * compress_level = "(none)";
int cipher_algid = 0;
int digest_algid = 0;
int use_outname = 0;


struct poptOption arguments[] =
{
  {"show-prefs", cShowpref, POPT_ARG_STRING, &user, cShowpref,
   "show the preferences of a key", 0},
  {"detach-sign", oDetachSign, POPT_ARG_NONE, 0, oDetachSign,
   "create a detached signature", 0},
  {"gen-key", cGenKey, POPT_ARG_NONE, 0, cGenKey, "Generate a key", 0},
  {"no-compression", oNoCompress, POPT_ARG_NONE, 0, oNoCompress,
   "Turns off compression", 0},
  {"compat", oCompat, POPT_ARG_NONE, 0, oCompat, "PGP compat mode", 0},
  {"verify", cVerify, POPT_ARG_STRING, &file, cVerify,
   "Verify a signature", 0},
  {"sign", cSign, POPT_ARG_STRING, &file, cSign, "Sign a file", 0},
  {"sign-encrypt", cSignEncr, POPT_ARG_STRING, &file, cSignEncr,
   "Sign and encrypt a file", 0},
  {"clearsign", cSignClear, POPT_ARG_STRING, &file, cSignClear,
   "Sign a text file", 0},
  {"fetch-key", oFetchKey, POPT_ARG_STRING, &ks_keyid, oFetchKey,
   "Fetch key from keyserver", 0},
  {"cipher-algo", oCipherAlg, POPT_ARG_STRING, &cipher_algo, oCipherAlg,
   "Cipher algorithm", 0},
  {"digest-algo", oDigestAlgo, POPT_ARG_STRING, &digest_algo, oDigestAlgo,
   "Digest algorithm", 0},
  {"list-sigs", cSigList, POPT_ARG_STRING, &user, cSigList,
   "List signatures", 0},
  {"output", oOutput, POPT_ARG_STRING, &outname, oOutput,
   "Output filename", 0},
  {"export", cExport, POPT_ARG_STRING, &user, cExport,
   "Export keys", 0},
  {"import", cImport, POPT_ARG_STRING, &file, cImport, "Import keys", 0},
  {"list-keys", cKeyList, POPT_ARG_STRING, &user, cKeyList,
   "List keys", 0},
  {"help", cHelp, POPT_ARG_NONE, 0, cHelp, "Show help message", 0},
  {"version", cVersion, POPT_ARG_NONE, 0, cVersion,
   "Show program version", 0},
  {"enarmor", cEnarmor, POPT_ARG_STRING, &file, cEnarmor,
   "Protect a file with ASCII-Armor", 0},  
  {"dearmor", cDearmor, POPT_ARG_STRING, &file, cDearmor,
   "Remove ASCII-Armor from a file", 0},
  {"compress", oCompress, POPT_ARG_STRING, &compress_level, oCompress,
   "Use compression", 0},
  {"symmetric", cSymmetric, POPT_ARG_STRING, &file, cSymmetric,
   "Symmetrically encrypt a file", 0},
  {"recipient", oRecipient, POPT_ARG_STRING, &user, oRecipient,
   "Use this recipient", 0},
  {"encrypt", cEncr, POPT_ARG_STRING, &file, cEncr, "Encrypt a file", 0},
  {"encrypt-files", cEncrFiles, POPT_ARG_STRING, &file, cEncrFiles,
   "Encrypt |files|", 0},
  {"decrypt", cDecr, POPT_ARG_STRING, &file, cDecr, "Decrypt a file", 0},
  {"decrypt-files", cDecrFiles, POPT_ARG_STRING, &file, cDecrFiles,
   "Decrypt |files|", 0},
  {"verbose", oVerbose, POPT_ARG_NONE, 0, oVerbose, "Verbosity", 0},
  {"armor", oArmor, POPT_ARG_NONE, 0, oArmor, "Armor output", 0},
  {NULL}
};


static void
out_of_core (void)
{
  fputs ("\n** out of core **\n", stderr);
  exit (1);
}


static void
callback (void * opaque, int type, const char * s)
{
  fprintf (stdout, "type=%d val=%s\n", type, s);
}


static void
progr_callback (void * opaque, unsigned off, unsigned len)
{
  printf ("** off=%d len=%d\n", off, len);
}


static char *
do_add_ext (const char * filename, const char * ext)
{
  char * output = NULL;

  if (!filename)
    return NULL;
  output = cdk_calloc (1, strlen (filename) + 5);
  if (!output)
    out_of_core ();
  strcpy (output, filename);
  strcat (output, ext ? ext : ".gpg");
  return output;
}


static char *
do_remove_ext (const char * filename)
{
  char * output = NULL;

  if (!filename)
    return NULL;
  if (!strstr (filename, ".gpg") && !strstr (filename, ".pgp")
      && !strstr (filename, ".sig") && !strstr (filename, ".asc"))
    {
      output = cdk_calloc (1, strlen (filename) + 5);
      if (!output)
        out_of_core ();
      strcpy (output, filename);
      strcat (output, ".dec");
      return output;
    }
  output = cdk_strdup (filename);
  if (!output)
    out_of_core ();
  output[strlen (output) - 4] = '\0';
  return output;
}


static char
key_get_letter (int pubkey_algo)
{
  switch (pubkey_algo)
    {
    case CDK_PK_RSA:   return 'R';
    case CDK_PK_RSA_E: return 'r';
    case CDK_PK_RSA_S: return 'r';
    case CDK_PK_ELG_E: return 'g';
    case CDK_PK_ELG:   return 'G';
    case CDK_PK_DSA:   return 'D';
    default:            return '?';
    }
  return '?';
}


static const char *
strdate_from_long (long timestamp)
{
  static char kdate[32];
  struct tm * tm;

  tm = localtime (&timestamp);
  sprintf (kdate, "%04d-%02d-%02d",
           tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
  return kdate;
}


static char
sig_get_letter (cdkPKT_signature * sig)
{
  if (sig->flags.missing_key)
    return '%';
  if (sig->flags.checked)
    {
      if (sig->flags.valid == 1)
	return sig->flags.valid ? '!' : '-';
    }
  return '?';
}


static char
sig_get_status (cdkPKT_signature * sig)
{
  if (!sig->flags.exportable)
    return 'L';
  if (sig->sig_class == 0x13)
    return '3';
  else if (sig->sig_class == 0x12)
    return '2';
  else if (sig->sig_class == 0x11)
    return '1';
  return ' ';
}


static int
algo_get_id (const char *algo, int what)
{
  int i = 0;

  for (i = 0; algo_table[i].name; i++)
    {
      if (!strcmp (algo, algo_table[i].name))
	return algo_table[i].algid;
    }
  i = what? CDK_CIPHER_CAST5: CDK_MD_SHA1;
  return i;
}


static char *
get_keyvalid_char (cdkPKT_public_key * pk)
{
  static char flags[2];
  memset (flags, 0, 2);
  /*printf ("[invalid=%d]\n", pk->is_invalid);*/
  if (pk->has_expired)
    strcat (flags, "e");
  else
    strcat (flags, " ");
  if (pk->is_revoked)
    strcat (flags, "r");
  else
    strcat (flags, " ");
  if (pk->is_invalid)
    strcat (flags, "?");
  else if (!pk->has_expired && !pk->is_revoked)
    strcat (flags, "!");
  else
    strcat (flags, " ");
  return flags;
}

static void
do_show_key (CDK_KBNODE pk, int with_sigs)
{
  CDK_KBNODE p, ctx = NULL;
  CDK_PACKET * pkt;
  
  while ((p = cdk_kbnode_walk (pk, &ctx, 0)))
    {
      pkt = cdk_kbnode_get_packet (p);
      if (pkt->pkttype == CDK_PKT_PUBLIC_KEY)
	{
	  cdkPKT_public_key * key = pkt->pkt.public_key;
	  fprintf (stdout, "pub[%s]  %04d%c/%08lX %s",
                   get_keyvalid_char (key),
		   cdk_pk_get_nbits (key),
		   key_get_letter (key->pubkey_algo),
		   (unsigned long)cdk_pk_get_keyid (key, NULL),
		   strdate_from_long (key->timestamp));
          if (!key->expiredate)
            fputc ('\n', stdout);
          else
            fprintf (stdout, "  [expires: %s]\n",
                     strdate_from_long (key->expiredate));
	}
      else if (pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
	{
	  cdkPKT_public_key * key = pkt->pkt.public_key;
	  fprintf (stdout, "sub[%s]  %04d%c/%08lX %s",
                   get_keyvalid_char (key),
		   cdk_pk_get_nbits (key),
		   key_get_letter (key->pubkey_algo),
		   (unsigned long)cdk_pk_get_keyid (key, NULL),
		   strdate_from_long (key->timestamp));
          if (!key->expiredate)
            fputc ('\n', stdout);
          else
            fprintf (stdout, "  [expires: %s]\n",
                     strdate_from_long (key->expiredate));
	}
      else if (pkt->pkttype == CDK_PKT_USER_ID)
	{
	  cdkPKT_user_id * id = pkt->pkt.user_id;
          char * uid = cdk_utf8_decode (id->name, id->len, 0);
          fprintf (stdout, "uid %s %s\n",
                   id->is_revoked ? "[revoked]" : "         ", uid);
          cdk_free (uid);
	}
      else if (with_sigs && pkt->pkttype == CDK_PKT_SIGNATURE)
	{
	  cdkPKT_signature * sig = pkt->pkt.signature;
	  fprintf (stdout, " sig%c %c %08lX %s\n",
		   sig_get_letter (sig),
		   sig_get_status (sig),
		   (unsigned long)cdk_sig_get_keyid (sig, NULL),
		   strdate_from_long (sig->timestamp));
	}
    }
}


static int
check_for_wildcards (CDK_STRLIST list)
{
  CDK_STRLIST ctx = NULL;
  const char * s = cdk_strlist_walk (list, &ctx);
  if (*s == '*')
    return 1;
  return 0;
}


int
do_list_keys (CDK_KEYDB_HD hd, CDK_STRLIST keys)
{
  const char * s;
  unsigned char fpr[20];
  CDK_PACKET * pkt;
  CDK_STREAM inp;
  CDK_KBNODE pk = NULL;
  CDK_STRLIST ctx = NULL;
  int rc = 0, i;

  if (check_for_wildcards (keys))
    keys = NULL;
  if (keys)
    {
      while ((s = cdk_strlist_walk (keys, &ctx)))
        {
	  rc = cdk_keydb_get_bypattern (hd, s, &pk);
	  if (rc)
	    fprintf (stdout, "get key `%s': %s\n", s, cdk_strerror (rc));
	  if (!rc)
            {
              do_show_key (pk, 0);
              pkt = cdk_kbnode_get_packet (pk);
              printf ("Fingerprint ");
              cdk_pk_get_fingerprint (pkt->pkt.public_key, fpr);
              for (i = 0; i < 20/2; i++)
                printf ("%02X%02X ", fpr[2*i], fpr[2*i+1]);
              printf ("\n");
            }
              
	  cdk_kbnode_release (pk);
	  pk = NULL;
	}
      return 0;
    }
  /* Show all keys */
  cdk_set_log_level (CDK_LOG_DEBUG);
  
  rc = cdk_keydb_open (hd, &inp);
  if (rc)
    {
      fprintf (stderr, "keydb open: %s\n", cdk_strerror (rc));
      return rc;
    }

  while (!(rc = cdk_keydb_get_keyblock (inp, &pk)))
    {
      do_show_key (pk, 0);
      fputc ('\n', stdout);
      cdk_kbnode_release (pk);
      pk = NULL;
      rc = cdk_stream_eof (inp);
      if (rc)
        break;
    }
  return 0;
}


int
do_export_keys (CDK_HD ctx, CDK_KEYDB_HD hd, CDK_STRLIST keys, int secret)
{
  CDK_STREAM outp = NULL;
  int rc = 0;

  if (!use_outname)
    {
      fprintf (stdout, "please use --output for a file.\n");
      return CDK_General_Error;
    }

  rc = cdk_stream_create (outname, &outp);
  if (rc)
    {
      fprintf (stdout, "can't open `%s': %s\n", outname, cdk_strerror (rc));
      return rc;
    }
  if (keys)
    {
      if (cdk_handle_control (ctx, CDK_CTLF_GET, CDK_CTL_ARMOR))
        cdk_stream_set_armor_flag (outp, CDK_ARMOR_PUBKEY);
      rc = cdk_keydb_export (hd, outp, keys);
      if (rc)
	fprintf (stdout, "key export `%s'\n", cdk_strerror (rc));
    }
  cdk_stream_close (outp);
  return rc;
}


int
do_import_keys (CDK_KEYDB_HD hd, const char * keyfile)
{
  CDK_KBNODE node = NULL, n, ctx = NULL;
  CDK_STREAM s;
  int rc, res[4];

  if (!hd)
    {
      fprintf (stderr, "no public keyring available.\n");
      return -1;
    }
  rc = cdk_stream_open (keyfile, &s);
  if (rc)
    {
      fprintf (stderr, "open `%s' failed: %s\n", keyfile, cdk_strerror (rc));
      return -1;
    }
  if (cdk_armor_filter_use (s))
    cdk_stream_set_armor_flag (s, 0);
  rc = cdk_keydb_get_keyblock (s, &node);
  if (!node)
    {
      cdk_stream_close (s);
      fprintf (stderr, "get keyblock failed: %s\n", cdk_strerror (rc));
      return -1;
    }
  rc = cdk_keydb_import (hd, node, res);
  if (rc)
    fprintf (stderr, "import failed: %s\n", cdk_strerror (rc));
  else
    {
      while ((n = cdk_kbnode_walk (node, &ctx, 0)))
        {
          CDK_PACKET * pkt = cdk_kbnode_get_packet (n);
          if (pkt->pkttype == CDK_PKT_USER_ID)
            {
              printf ("public keys read/updated: %d/%d\n"
                      "secret keys read/updated: %d/%d\n",
                      res[0], res[2], res[1], res[3]);
              if (res[2] || res[3])
                printf ("`%s' successfully imported.\n",
                        pkt->pkt.user_id->name);
              else
                printf ("nothing changed.\n");
              break;
            }
        }
    }
  cdk_stream_close (s);
  cdk_kbnode_release (node);
  return 0;
}
  

int
do_list_sigs (CDK_KEYDB_HD hd, CDK_STRLIST keys)
{
  CDK_KBNODE pk = NULL;
  CDK_STRLIST ctx = NULL;
  const char * s;
  int kstat = 0;
  int rc = 0;

  if (keys)
    {
      while ((s = cdk_strlist_walk (keys, &ctx)))
	{
	  rc = cdk_keydb_get_bypattern (hd, s, &pk);
	  if (rc)
	    fprintf (stdout, "get key `%s': %s\n", s, cdk_strerror (rc));
	  rc = cdk_key_check_sigs (pk, hd, &kstat);
	  if (rc)
	    fprintf (stdout, "check sigs `%s': %s\n", s, cdk_strerror (rc));
	  do_show_key (pk, 1);
	}
    }

  return 0;
}


char *
get_passphrase (void * hd, const char *prompt)
{
  char * p = NULL;
  int maxlen = 255;

  fprintf (stdout, "%s", prompt);
  p = cdk_calloc (1, 256);
  if (!p)
    out_of_core ();

  fgets (p, maxlen, stdin);
  p[strlen (p) - 1] = '\0';
  return p;

  return NULL;
}


int
ks_fetch_key (const char * kid)
{
  unsigned long keyid;
  unsigned char kidbuf[4];
  CDK_KBNODE pk = NULL;
  CDK_PACKET * pkt;
  int rc;
  
  if (!strncmp (kid, "0x", 2))
    kid += 2;
  if (strlen (kid) != 8 && strlen (kid) != 16)
    {
      printf ("wrong keyID usage\n");
      return -1;
    }
  if (strlen (kid) == 16)
    keyid = strtoul (kid + 8, NULL, 16);
  else
    keyid = strtoul (kid, NULL, 16);

  kidbuf[0] = keyid >> 24;
  kidbuf[1] = keyid >> 16;
  kidbuf[2] = keyid >>  8;
  kidbuf[3] = keyid;
  cdk_set_log_level (CDK_LOG_DEBUG);
  printf ("fetch %08lX from the keyserver...", keyid);
  rc = cdk_keyserver_recv_key ("http://horowitz.surfnet.nl", 11371,
                               kidbuf, CDK_DBSEARCH_SHORT_KEYID, &pk);
  if (rc)
      printf ("%s\n", cdk_strerror (rc));
  else
      printf ("OK\n");
  pkt = cdk_kbnode_find_packet (pk, CDK_PKT_USER_ID);
  if (pkt)
    printf ("%s\n", pkt->pkt.user_id->name);

  cdk_kbnode_release (pk);
  return 0;
}


static const char *
get_sig_status (int id)
{
  const char * s = "unknown error";

  switch (id)
    {
    case CDK_SIGSTAT_NONE : s = "no signature"; break;
    case CDK_SIGSTAT_GOOD : s = "good signature"; break;
    case CDK_SIGSTAT_BAD  : s = "bad signature"; break;
    case CDK_SIGSTAT_NOKEY: s = "no key for checking the signature"; break; 
    }
  return s;
}

static const char *
get_sig_algo (int pubkey_algo)
{
  switch (pubkey_algo)
    {
    case CDK_PK_RSA_E:
    case CDK_PK_RSA_S:
    case CDK_PK_RSA  : return "RSA";
    case CDK_PK_ELG  :
    case CDK_PK_ELG_E: return "ELG";
    case CDK_PK_DSA  : return "DSA";
    default           : return "???"; 
    }
  return NULL; 
}

static const char *
get_sig_time (unsigned long timestamp)
{
  static char buf[128];
  struct tm * tmbuf;

  tmbuf = localtime (&timestamp);
  sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d",
           tmbuf->tm_year+1900, tmbuf->tm_mon+1, tmbuf->tm_mday,
           tmbuf->tm_hour, tmbuf->tm_min, tmbuf->tm_sec);
  return buf;  
}


void
print_mpibuf (const unsigned char * buf, int pubkey_algo)
{
  int i, j, n;

  printf ("signature data:\n");
  for (i = 0, j = 0; i < cdk_pk_get_nsig (pubkey_algo); i++)
    {
      n  = buf[j++] << 8;
      n |= buf[j++];
      n = (n + 7) / 8;
      while (n--)
        printf ("%02x", buf[j++]);
    }
  printf ("\n");
}
      

void
print_verify_stats (CDK_HD hd, int extflag)
{
  const unsigned long * keyid;
  unsigned long created;
  int sigstat, pkalgo;

  sigstat = cdk_sig_get_ulong_attr (hd, 0, CDK_ATTR_STATUS);
  if (sigstat)
    {
      keyid = cdk_sig_get_data_attr (hd, 0, CDK_ATTR_KEYID);
      created = cdk_sig_get_ulong_attr (hd, 0, CDK_ATTR_CREATED);
      pkalgo = cdk_sig_get_ulong_attr (hd, 0, CDK_ATTR_ALGO_PK);
      printf ("(%s) keyid %08lX%08lX algo %s created %s\n",
              get_sig_status (sigstat), keyid[0], keyid[1],
              get_sig_algo (pkalgo), get_sig_time (created));
      if (extflag)
        print_mpibuf (cdk_sig_get_data_attr (hd, 0, CDK_ATTR_MPI), pkalgo);
    }
}


int
genkey (void)
{
  int c, algo = 0, rc =0;
  unsigned char mdbuf[3] = {CDK_MD_MD5, CDK_MD_RMD160, CDK_MD_SHA1};
  CDK_KEYGEN_CTX hd;
  
  printf ("(1) DSA (sign only)\n");
  printf ("(2) DSA and ElGamal\n");
  printf ("(3) DSA and RSA\n");
  printf ("(4) RSA and ElGamal\n");
  printf ("your choice: ");
  c = fgetc (stdin);
  switch (c)
    {
    case '1':
    case '2': 
    case '3': algo = CDK_PK_DSA; break;
    case '4': algo = CDK_PK_RSA; break;
    default: printf ("invalid public key algorithm\n"); break;
    }
  if (!algo)
    return CDK_Inv_Algo;
  cdk_salloc (1, 0);
  cdk_keygen_new (&hd);
  cdk_keygen_set_algo_info (hd, 0, algo, 768);
  if (c > '1' && c < '5')
    {
      switch (c)
        {
        case '4':
        case '2': cdk_keygen_set_algo_info (hd, 1, CDK_PK_ELG_E, 768); break;
        case '3': cdk_keygen_set_algo_info (hd, 1, CDK_PK_RSA_E, 768); break;
        }
      cdk_keygen_set_expire_date (hd, 1, 8640000);
    }
  cdk_keygen_set_name (hd, "Otto Bismarck");
  cdk_keygen_set_mdc_feature (hd, 1);
  cdk_keygen_set_expire_date (hd, 0, 864000);
  cdk_keygen_set_prefs (hd, CDK_PREFTYPE_HASH, mdbuf, 3);
  cdk_keygen_set_passphrase (hd, "abc");
  rc = cdk_keygen_start (hd);
  if (!rc)
    rc = cdk_keygen_save (hd, "pub.test", "sec.test");
  cdk_keygen_free (hd);
  return rc;
}


static void
show_one_array (cdkPKT_user_id * id, int type)
{
  size_t n = 0, i = 0;
  unsigned char * p;

  p = cdk_prefs_get_array (id, type, &n);
  if (p)
    {
      for (i = 0; i < n; i++)
        {
          switch (type)
            {
            case CDK_PREFTYPE_SYM : fputc ('S', stdout); break;
            case CDK_PREFTYPE_HASH: fputc ('H', stdout); break;
            case CDK_PREFTYPE_ZIP : fputc ('Z', stdout); break;
            }
          printf ("%d ", p[i]);
        }
      cdk_free (p);
    }    
}


static void
show_prefs (const char * name, CDK_KEYDB_HD db)
{
  CDK_KBNODE knode = NULL;
  CDK_PACKET * pkt;
  int rc;
  
  rc = cdk_keydb_get_bypattern (db, name, &knode);
  if (rc)
      printf ("keydb get error %s: `%s'\n", name, cdk_strerror (rc));
  else
    {
      pkt = cdk_kbnode_find_packet (knode, CDK_PKT_USER_ID);
      if (!pkt)
        {
          printf ("no userid not found!\n");
          goto leave;
        }
      printf ("%s preferences\n", pkt->pkt.user_id->name);
      show_one_array (pkt->pkt.user_id, CDK_PREFTYPE_SYM);
      show_one_array (pkt->pkt.user_id, CDK_PREFTYPE_HASH);
      show_one_array (pkt->pkt.user_id, CDK_PREFTYPE_ZIP);
      printf ("\n");
    }
 leave:
  cdk_kbnode_release (knode);
}

    
void
file_error (const char * fil, const char * fnc, int rc)
{
  fprintf (stderr, "minpg: %s %s: `%s'\n", fil, fnc, cdk_strerror (rc));
}


int
main (int argc, char ** argv)
{
  CDK_HD hd;
  CDK_STRLIST remusr = NULL, klist = NULL;
  CDK_KEYDB_HD sec_db, pub_db;
  poptContext opt_ctx;
  char * output = NULL, * s;
  char u[128];
  int c = 0, verbose = 0, i = 0, sigmode = 0, zip_level = 6;
  int rc = 0, nfiles = 0;

  s = "/home/twoaday/.gnupg/pubring.cdk";
  /*s = "pubring.gpg";*/
  rc = cdk_keydb_new (&pub_db, CDK_DBTYPE_PK_KEYRING, s, strlen (s));
  if (rc)
    {
      fprintf (stderr, "keydb new: `%s'\n", cdk_strerror (rc));
      return 0;
    }
  s = "sec-with-pwd.gpg";
  /*s = "secring.gpg";*/
  rc = cdk_keydb_new (&sec_db, CDK_DBTYPE_SK_KEYRING, s, strlen (s));
  if (rc)
    {
      fprintf (stderr, "keydb new: `%s'\n", cdk_strerror (rc));
      return 0;
    }
  

  cdk_handle_new (&hd);
  cdk_handle_set_passphrase_cb (hd, get_passphrase, NULL);
  cdk_handle_set_callback (hd, callback, NULL);
  cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_OVERWRITE, 1);
  cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_COMPRESS, 2, 5);
  cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_KEYCACHE_ON, 1);
  /*cdk_set_progress_handler (progr_callback, NULL);*/
  opt_ctx = poptGetContext ("pred", argc, argv, arguments, 0);
  while ((c = poptGetNextOpt (opt_ctx)) >= 0)
    {
      switch (c)
	{
	case 'v':
	  verbose++;
	  break;

        case oNoCompress:
          if (verbose)
            fprintf (stdout, "disable compression\n");
          cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_COMPRESS, 0, 0);
          break;
          
        case 'p':
          if (verbose)
            fprintf (stdout, "protect output with ascii armor\n");
          cdk_handle_set_armor( hd, 1 );
          break;

	case 'o':
	  if (verbose)
	    fprintf (stdout, "output filename `%s'\n", outname);
	  use_outname = 1;
	  break;

        case 'g':
          if (verbose)
            fprintf (stdout, "set PGP compat mode\n");
          cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_COMPAT, 1);
          break;

	case 'h':
	  fprintf (stderr, "opencdk (MinPG) %s\n\n", OPENCDK_VERSION);
	  poptPrintHelp (opt_ctx, stdout, 0);
	  return 0;

	case 'V':
	  fprintf (stderr, "Minimal Privacy Guard (minpg) %s\n\n", OPENCDK_VERSION);
	  fprintf (stderr, "Supported algorithms:\n");
	  fprintf (stderr, "Cipher, Digest: ");
	  for (i = 0; algo_table[i].name; i++)
	    fprintf (stderr, "%s ", algo_table[i].name);
	  fprintf (stderr, "\n");
	  return 0;

        case 'j':
          sigmode = CDK_SIGMODE_DETACHED;
          break;

        case 'q':
          show_prefs (user, pub_db);
          break;

        case 'm':
          digest_algid = algo_get_id (digest_algo, 0);
          if (verbose)
            fprintf (stdout, "use %s as the message digest (%d)\n",
                     digest_algo, digest_algid);
          break;
          
	case 'n':
	  cipher_algid = algo_get_id (cipher_algo, 1);
	  if (verbose)
	    fprintf (stdout, "use %s for encryption (%d)\n",
		     cipher_algo, cipher_algid);
	  break;

        case 'y':
          cdk_set_log_level (CDK_LOG_DEBUG);
          cdk_handle_set_keydb (hd, pub_db);
          rc = cdk_file_verify (hd, file, NULL);
          if (rc)
            file_error ("verify", file, rc);
          else
            print_verify_stats (hd, 0);
          break;
          
	case 'u':
          /*cdk_set_log_level (CDK_LOG_DEBUG);*/
	  output = cdk_calloc (1, strlen (file) + 1);
          if (!output)
            out_of_core ();
	  if (strstr (file, ".asc"))
	    strncpy (output, file, strlen (file) - 4);
	  else
	    strcpy (output, file);
	  if (verbose)
	    fprintf (stdout, "dearmor `%s' -> %s\n", file, output);
	  rc = cdk_file_dearmor (file, output);
	  if (rc)
            file_error ("dearmor file", file, rc);
	  break;

	case 'l':
          /*cdk_set_log_level (CDK_LOG_DEBUG);*/
	  cdk_strlist_add (&klist, user);
	  do_list_sigs (pub_db, klist);
	  break;

	case 'a':
	  output = do_add_ext (file, ".asc");
	  if (verbose)
	    fprintf (stdout, "armor `%s' -> %s\n", file, output);
	  rc = cdk_file_armor (hd, file, output);
	  if (rc)
            file_error ("armor file", file, rc);
	  break;

	case 'z':
          zip_level = atol (compress_level);
          if (zip_level < 0 || zip_level > 10)
            {
              printf ("invalid compression level `%d', changed to 6.\n",
                      zip_level);
              zip_level = 6;
            }
          cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_COMPRESS,
                              CDK_COMPRESS_ZIP, zip_level);
	  break;

	case 'c':
          /*cdk_set_log_level (CDK_LOG_DEBUG);*/
	  output = do_add_ext (file, ".gpg");
	  if (verbose)
	    fprintf (stdout, "symencrypt `%s' -> %s\n", file, output);
	  if (cipher_algid)
            cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_CIPHER,
                                cipher_algid);
          if (digest_algid)
            {
              cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_DIGEST,
                                  digest_algid);
              if (!cipher_algid)
                cipher_algid = CDK_CIPHER_3DES;
              cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_S2K,
                                  3, digest_algid, cipher_algid);
            }
          cdk_handle_set_keydb (hd, pub_db);
	  rc = cdk_file_encrypt (hd, NULL, file, output);
	  if (rc)
            file_error ("symmetric file", file, rc);
	  break;

	case 'k':
	  cdk_strlist_add (&klist, user);
	  do_list_keys (pub_db, klist);
	  break;

	case 'x':
	  cdk_strlist_add (&klist, user);
	  do_export_keys (hd, pub_db, klist, 0);
	  break;

        case 'i':
          do_import_keys (pub_db, file);
          break;

        case 'f':
          ks_fetch_key (ks_keyid);
          break;

	case 'r':
	  cdk_strlist_add (&remusr, user);
	  break;

	case 'e':
        case '@':
	  if (!remusr)
	    {
	      fprintf (stdout, "No recipient was chosen.\n\n");
	      fprintf (stdout, "Recipient: ");
	      fgets (u, sizeof u, stdin);
	      cdk_strlist_add (&remusr, user);
	    }
          /*cdk_set_log_level (CDK_LOG_DEBUG);*/
	  output = do_add_ext (file, ".gpg");
	  if (verbose)
	    fprintf (stdout, "encrypt `%s' -> %s\n", file, output);
          cdk_handle_set_keydb (hd, pub_db);
	  rc = cdk_file_encrypt (hd, remusr, file, output);
	  if (rc)
            file_error ("encrypt file", file, rc);
          if (c == '@')
            nfiles = 1;
	  break;

	case 'd':
        case '$':
          cdk_set_log_level (CDK_LOG_DEBUG);
	  output = do_remove_ext (file);
	  if (verbose)
	    fprintf (stdout, "decrypt `%s' -> %s\n", file, output);
          cdk_handle_set_keydb (hd, sec_db);
          cdk_handle_set_keydb (hd, pub_db);
	  rc = cdk_file_decrypt (hd, file, output);
	  if (rc)
            file_error ("decrypt file", file, rc);
          else
            print_verify_stats (hd, 0);
          if (c == '$')
            nfiles = 2;
	  break;

        case 'w':
          rc = genkey ();
          if (rc)
            printf ("key generation failed `%s'\n", cdk_strerror (rc));
          break;
          
        case 's':
          cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_FORCE_DIGEST, 1);
          if (digest_algid)
            cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_DIGEST,digest_algid);
          if (verbose)
            fprintf (stdout, "force digest algo\n");
          if (!remusr)
            {
              fprintf (stdout, "No user was chosen.\n");
              exit (0);
            } 
          output = do_add_ext (file, ".sig");
          if (verbose)
            fprintf (stdout, "sign `%s' -> %s\n", file, output);
          cdk_handle_set_keydb (hd, sec_db);
          if (sigmode == CDK_SIGMODE_DETACHED)
            fprintf (stdout, "create a detached signature\n");
          rc = cdk_file_sign (hd, remusr, NULL, file, output, sigmode, 0);
          if (rc)
            file_error ("sign file", file, rc);
          break;

        case 't':
          if (!remusr)
            {
              fprintf (stdout, "No user was chosen.\n");
              exit (0);
            }
          output = do_add_ext (file, ".asc");
          cdk_handle_set_keydb (hd, sec_db);
          rc = cdk_file_sign (hd, remusr, NULL, file, output, CDK_SIGMODE_CLEAR, 0);
          if (rc)
            file_error ("clearsign file", file, rc);
          break;

        case 'b':
          if (!remusr)
            {
              fprintf (stderr, "No recipient was chosen.\n");
              exit (0);
            }
          output = do_add_ext (file, ".gpg");
          cdk_strlist_add (&klist, "opencdk");
          cdk_handle_set_keydb (hd, pub_db);
          cdk_handle_set_keydb (hd, sec_db);
          rc = cdk_file_sign (hd, klist, remusr, file, output, CDK_SIGMODE_NORMAL, 1);
          if (rc)
            file_error ("sign encrypt", file, rc);
          break;
	}
    }

  if (nfiles)
    {
      const char ** files = poptGetArgs (opt_ctx);
      /*cdk_handle_control (hd, CDK_CTLF_SET, CDK_CTL_KEYCACHE_FREE, 1);*/
      while (*files)
        {
          if (nfiles == 1)
            {
              output = do_add_ext (*files, ".gpg");
              if (verbose)
                printf ("encrypt `%s' -> %s\n", *files, output);
              rc = cdk_file_encrypt (hd, remusr, *files, output);
            }
          else if (nfiles == 2)
            {
              output = do_remove_ext (*files);
              if (verbose)
                printf ("decrypt `%s' -> %s\n", *files, output);
              rc = cdk_file_decrypt (hd, *files, output);
            }
          if (rc)
              printf ("%s file `%s': %s\n", nfiles==1? "encrypt" : "decrypt",
                      *files, cdk_strerror (rc));
          files++;
          cdk_free (output);
          output = NULL;
        }
    }

  cdk_keydb_free (pub_db);
  cdk_keydb_free (sec_db);
  cdk_handle_free (hd);
  cdk_strlist_free (remusr);
  cdk_free (output);
  return 0;
}
