/* keylist.c
 * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
 *
 * This file is part of GnuPG.
 *
 * GnuPG 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.
 *
 * GnuPG 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h> 
#include <time.h>
#include <assert.h>

#include "gpgsm.h"

#include <gcrypt.h>
#include <ksba.h>

#include "keydb.h"
#include "i18n.h"

struct list_external_parm_s {
  FILE *fp;
  int print_header;
  int with_colons;
  int with_chain;
};



static void
print_key_data (KsbaCert cert, FILE *fp)
{
#if 0  
  int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
  int i;

  for(i=0; i < n; i++ ) 
    {
      fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
      mpi_print(stdout, pk->pkey[i], 1 );
      putchar(':');
      putchar('\n');
    }
#endif
}

static void
print_capabilities (KsbaCert cert, FILE *fp)
{
  KsbaError err;
  unsigned int use;

  err = ksba_cert_get_key_usage (cert, &use);
  if (err == KSBA_No_Data)
    {
      putc ('e', fp);
      putc ('s', fp);
      putc ('c', fp);
      putc ('E', fp);
      putc ('S', fp);
      putc ('C', fp);
      return;
    }
  if (err)
    { 
      log_error (_("error getting key usage information: %s\n"),
                 ksba_strerror (err));
      return;
    } 

  if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
    putc ('e', fp);
  if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
    putc ('s', fp);
  if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
    putc ('c', fp);
  if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
    putc ('E', fp);
  if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
    putc ('S', fp);
  if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
    putc ('C', fp);
}


static void
print_time (time_t t, FILE *fp)
{
  if (!t)
    ;
  else if ( t == (time_t)(-1) )
    putc ('?', fp);
  else
    fprintf (fp, "%lu", (unsigned long)t);
}


/* return an allocated string with the email address extracted from a
   DN */
static char *
email_kludge (const char *name)
{
  const unsigned char *p;
  unsigned char *buf;
  int n;

  if (strncmp (name, "1.2.840.113549.1.9.1=#", 22))
    return NULL;
  /* This looks pretty much like an email address in the subject's DN
     we use this to add an additional user ID entry.  This way,
     openSSL generated keys get a nicer and usable listing */
  name += 22;    
  for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
    ;
  if (*p != '#' || !n)
    return NULL;
  buf = xtrymalloc (n+3);
  if (!buf)
    return NULL; /* oops, out of core */
  *buf = '<';
  for (n=1, p=name; *p != '#'; p +=2, n++)
    buf[n] = xtoi_2 (p);
  buf[n++] = '>';
  buf[n] = 0;
  return buf;
}




/* List one certificate in colon mode */
static void
list_cert_colon (KsbaCert cert, FILE *fp, int have_secret)
{
  int idx, trustletter = 0;
  char *p;
  KsbaSexp sexp;
  char *fpr;

  fputs (have_secret? "crs:":"crt:", fp);
  trustletter = 0;
#if 0
  if (is_not_valid (cert))
    putc ('i', fp);
  else if ( is_revoked (cert) )
    putc ('r', fp);
  else if ( has_expired (cert))
    putcr ('e', fp);
  else
#endif
    {
      trustletter = '?'; /*get_validity_info ( pk, NULL );*/
      putc (trustletter, fp);
    }

  fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
  fprintf (fp, ":%u:%d:%s:",
           /*keylen_of_cert (cert)*/1024,
           /* pubkey_algo_of_cert (cert)*/1,
           fpr+24);

  /* we assume --fixed-list-mode for gpgsm */
  print_time ( ksba_cert_get_validity (cert, 0), fp);
  putc (':', fp);
  print_time ( ksba_cert_get_validity (cert, 1), fp);
  putc (':', fp);
  /* field 8, serial number: */
  if ((sexp = ksba_cert_get_serial (cert)))
    {
      int len;
      const unsigned char *s = sexp;
      
      if (*s == '(')
        {
          s++;
          for (len=0; *s && *s != ':' && digitp (s); s++)
            len = len*10 + atoi_1 (s);
          if (*s == ':')
            for (s++; len; len--, s++)
              fprintf (fp,"%02X", *s);
        }
      xfree (sexp);
    }
  putc (':', fp);
  /* field 9, ownertrust - not used here */
  putc (':', fp);
  /* field 10, old user ID - we use it here for the issuer DN */
  if ((p = ksba_cert_get_issuer (cert,0)))
    {
      print_sanitized_string (fp, p, ':');
      xfree (p);
    }
  putc (':', fp);
  /* field 11, signature class - not used */ 
  putc (':', fp);
  /* field 12, capabilities: */ 
  print_capabilities (cert, fp);
  putc (':', fp);
  putc ('\n', fp);

  /* FPR record */
  fprintf (fp, "fpr:::::::::%s:::", fpr);
  xfree (fpr); fpr = NULL;
  /* print chaining ID (field 13)*/
  {
    KsbaCert next;
    
    if (!gpgsm_walk_cert_chain (cert, &next))
      {
        p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
        fputs (p, fp);
        xfree (p);
        ksba_cert_release (next);
      }
  }
  putc (':', fp);
  putc ('\n', fp);


  if (opt.with_key_data)
    {
      if ( (p = gpgsm_get_keygrip_hexstring (cert)))
        {
          fprintf (fp, "grp:::::::::%s:\n", p);
          xfree (p);
        }
      print_key_data (cert, fp);
    }

  for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++)
    {
      fprintf (fp, "uid:%c::::::::", trustletter);
      print_sanitized_string (fp, p, ':');
      putc (':', fp);
      putc (':', fp);
      putc ('\n', fp);
      if (!idx)
        {
          /* It would be better to get the faked email address from
             the keydb.  But as long as we don't have a way to pass
             the meta data back, we just check it the same way as the
             code used to create the keybox meta data does */
          char *pp = email_kludge (p);
          if (pp)
            {
              fprintf (fp, "uid:%c::::::::", trustletter);
              print_sanitized_string (fp, pp, ':');
              putc (':', fp);
              putc (':', fp);
              putc ('\n', fp);
              xfree (pp);
            }
        }
      xfree (p);
    }
}


/* List one certificate in standard mode */
static void
list_cert_std (KsbaCert cert, FILE *fp, int have_secret)
{
  KsbaError kerr;
  KsbaSexp sexp;
  char *dn;
  time_t t;
  int idx;
  int is_ca, chainlen;
  unsigned int kusage;
  char *string, *p;

  sexp = ksba_cert_get_serial (cert);
  fputs ("Serial number: ", fp);
  gpgsm_print_serial (fp, sexp);
  ksba_free (sexp);
  putc ('\n', fp);

  dn = ksba_cert_get_issuer (cert, 0);
  fputs ("       Issuer: ", fp);
  gpgsm_print_name (fp, dn);
  ksba_free (dn);
  putc ('\n', fp);
  for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
    {
      fputs ("          aka: ", fp);
      gpgsm_print_name (fp, dn);
      ksba_free (dn);
      putc ('\n', fp);
    }

  dn = ksba_cert_get_subject (cert, 0);
  fputs ("      Subject: ", fp);
  gpgsm_print_name (fp, dn);
  ksba_free (dn);
  putc ('\n', fp);
  for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
    {
      fputs ("          aka: ", fp);
      gpgsm_print_name (fp, dn);
      ksba_free (dn);
      putc ('\n', fp);
    }

  t = ksba_cert_get_validity (cert, 0);
  fputs ("     validity: ", fp);
  gpgsm_print_time (fp, t);
  fputs (" through ", fp);
  t = ksba_cert_get_validity (cert, 1);
  gpgsm_print_time (fp, t);
  putc ('\n', fp);

  kerr = ksba_cert_get_key_usage (cert, &kusage);
  if (kerr != KSBA_No_Data)
    {
      fputs ("    key usage:", fp);
      if (kerr)
        fprintf (fp, " [error: %s]", ksba_strerror (kerr));
      else
        {
          if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
            fputs (" digitalSignature", fp);
          if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))  
            fputs (" nonRepudiation", fp);
          if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) 
            fputs (" keyEncipherment", fp);
          if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
            fputs (" dataEncipherment", fp);
          if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))    
            fputs (" keyAgreement", fp);
          if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
            fputs (" certSign", fp);
          if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))  
            fputs (" crlSign", fp);
          if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
            fputs (" encipherOnly", fp);
          if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))  
            fputs (" decipherOnly", fp);
        }
      putc ('\n', fp);
    }

  kerr = ksba_cert_get_cert_policies (cert, &string);
  if (kerr != KSBA_No_Data)
    {
      fputs ("     policies: ", fp);
      if (kerr)
        fprintf (fp, "[error: %s]", ksba_strerror (kerr));
      else
        {
          for (p=string; *p; p++)
            {
              if (*p == '\n')
                *p = ',';
            }
          print_sanitized_string (fp, string, 0);
          xfree (string);
        }
      putc ('\n', fp);
    }

  kerr = ksba_cert_is_ca (cert, &is_ca, &chainlen);
  if (kerr || is_ca)
    {
      fputs (" chain length: ", fp);
      if (kerr)
        fprintf (fp, "[error: %s]", ksba_strerror (kerr));
      else if (chainlen == -1)
        fputs ("unlimited", fp);
      else
        fprintf (fp, "%d", chainlen);
      putc ('\n', fp);
    }


  dn = gpgsm_get_fingerprint_string (cert, 0);
  fprintf (fp, "  fingerprint: %s\n", dn?dn:"error");
  xfree (dn);
}

/* Same as standard mode mode list all certifying certts too */
static void
list_cert_chain (KsbaCert cert, FILE *fp)
{
  KsbaCert next = NULL;

  list_cert_std (cert, fp, 0);
  ksba_cert_ref (cert);
  while (!gpgsm_walk_cert_chain (cert, &next))
    {
      ksba_cert_release (cert);
      fputs ("Certified by\n", fp);
      list_cert_std (next, fp, 0);
      cert = next;
    }
  ksba_cert_release (cert);
  putc ('\n', fp);
}



/* List all internal keys or just the key given as NAMES.
 */
static void
list_internal_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode)
{
  KEYDB_HANDLE hd;
  KEYDB_SEARCH_DESC *desc = NULL;
  STRLIST sl;
  int ndesc;
  KsbaCert cert = NULL;
  int rc=0;
  const char *lastresname, *resname;
  int have_secret;

  hd = keydb_new (0);
  if (!hd)
    {
      log_error ("keydb_new failed\n");
      goto leave;
    }

  if (!names)
    ndesc = 1;
  else
    {
      for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) 
        ;
    }

  desc = xtrycalloc (ndesc, sizeof *desc);
  if (!ndesc)
    {
      log_error ("out of core\n");
      goto leave;
    }

  if (!names)
    desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
  else 
    {
      for (ndesc=0, sl=names; sl; sl = sl->next) 
        {
          rc = keydb_classify_name (sl->d, desc+ndesc);
          if (rc)
            {
              log_error ("key `%s' not found: %s\n",
                         sl->d, gpg_strerror (rc));
              rc = 0;
            }
          else
            ndesc++;
        }
      
    }

  /* it would be nice to see which of the given users did actually
     match one in the keyring.  To implement this we need to have a
     found flag for each entry in desc and to set this we must check
     all those entries after a match to mark all matched one -
     currently we stop at the first match.  To do this we need an
     extra flag to enable this feature so */

  lastresname = NULL;
  while (!(rc = keydb_search (hd, desc, ndesc)))
    {
      if (!names) 
        desc[0].mode = KEYDB_SEARCH_MODE_NEXT;

      rc = keydb_get_cert (hd, &cert);
      if (rc) 
        {
          log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
          goto leave;
        }
      
      resname = keydb_get_resource_name (hd);
      
      if (lastresname != resname ) 
        {
          int i;
          
          if (ctrl->no_server)
            {
              fprintf (fp, "%s\n", resname );
              for (i=strlen(resname); i; i-- )
                putchar('-');
              putc ('\n', fp);
              lastresname = resname;
            }
        }

      have_secret = 0;
      if (mode)
        {
          char *p = gpgsm_get_keygrip_hexstring (cert);
          if (p)
            {
              if (!gpgsm_agent_havekey (p))
                have_secret = 1;
              xfree (p);
            }
        }

      if (!mode
          || ((mode & 1) && !have_secret)
          || ((mode & 2) && have_secret)  )
        {
          if (ctrl->with_colons)
            list_cert_colon (cert, fp, have_secret);
          else if (ctrl->with_chain)
            list_cert_chain (cert, fp);
          else
            {
              list_cert_std (cert, fp, have_secret);
              putc ('\n', fp);
            }
        }
      ksba_cert_release (cert); 
      cert = NULL;
    }
  if (rc && rc != -1)
    log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
  
 leave:
  ksba_cert_release (cert);
  xfree (desc);
  keydb_release (hd);
}



static void
list_external_cb (void *cb_value, KsbaCert cert)
{
  struct list_external_parm_s *parm = cb_value;

  if (keydb_store_cert (cert, 1, NULL))
    log_error ("error storing certificate as ephemeral\n");

  if (parm->print_header)
    {
      const char *resname = "[external keys]";
      int i;

      fprintf (parm->fp, "%s\n", resname );
      for (i=strlen(resname); i; i-- )
        putchar('-');
      putc ('\n', parm->fp);
      parm->print_header = 0;
    }

  if (parm->with_colons)
    list_cert_colon (cert, parm->fp, 0);
  else if (parm->with_chain)
    list_cert_chain (cert, parm->fp);
  else
    {
      list_cert_std (cert, parm->fp, 0);
      putc ('\n', parm->fp);
    }
}


/* List external keys similar to internal one.  Note: mode does not
   make sense here because it would be unwise to list external secret
   keys */
static void
list_external_keys (CTRL ctrl, STRLIST names, FILE *fp)
{
  int rc;
  struct list_external_parm_s parm;

  parm.fp = fp;
  parm.print_header = ctrl->no_server;
  parm.with_colons = ctrl->with_colons;
  parm.with_chain = ctrl->with_chain;

  rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm);
  if (rc)
    log_error ("listing external keys failed: %s\n", gpg_strerror (rc));
}

/* List all keys or just the key given as NAMES.
   MODE controls the operation mode: 
    Bit 0-2:
      0 = list all public keys but don't flag secret ones
      1 = list only public keys
      2 = list only secret keys
      3 = list secret and public keys
    Bit 6: list internal keys
    Bit 7: list external keys
 */
void
gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode)
{
  if ((mode & (1<<6)))
      list_internal_keys (ctrl, names, fp, (mode & 3));
  if ((mode & (1<<7)))
      list_external_keys (ctrl, names, fp); 
}
