/*
 * Copyright notice from original mutt:
 * Copyright (C) 1998-2000 Werner Koch <werner.koch@guug.de>
 * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
 *
 * This file is part of mutt-ng, see http://www.muttng.org/.
 * It's licensed under the GNU General Public License,
 * please see the file GPL in the top level source directory.
 */

/*
 * NOTE
 * 
 * This code used to be the parser for GnuPG's output.
 * 
 * Nowadays, we are using an external pubring lister with PGP which mimics 
 * gpg's output format.
 * 
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <ctype.h>

#include "mutt.h"
#include "pgp.h"
#include "charset.h"

/* for hexval */
#include "mime.h"

#include "lib/mem.h"
#include "lib/str.h"
#include "lib/debug.h"

/****************
 * Read the GNUPG keys.  For now we read the complete keyring by
 * calling gnupg in a special mode.
 *
 * The output format of gpgm is colon delimited with these fields:
 *   - record type ("pub","uid","sig","rev" etc.)
 *   - trust info
 *   - key length
 *   - pubkey algo
 *   - 16 hex digits with the long keyid.
 *   - timestamp (1998-02-28)
 *   - Local id
 *   - ownertrust
 *   - name
 *   - signature class
 */

/* decode the backslash-escaped user ids. */

static char *_chs = 0;

static void fix_uid (char *uid)
{
  char *s, *d;
  iconv_t cd;

  for (s = d = uid; *s;) {
    if (*s == '\\' && *(s + 1) == 'x' && isxdigit ((unsigned char) *(s + 2))
        && isxdigit ((unsigned char) *(s + 3))) {
      *d++ = hexval (*(s + 2)) << 4 | hexval (*(s + 3));
      s += 4;
    }
    else
      *d++ = *s++;
  }
  *d = '\0';

  if (_chs && (cd = mutt_iconv_open (_chs, "utf-8", 0)) != (iconv_t) - 1) {
    int n = s - uid + 1;        /* chars available in original buffer */
    char *buf;
    ICONV_CONST char *ib;
    char *ob;
    size_t ibl, obl;

    buf = mem_malloc (n + 1);
    ib = uid, ibl = d - uid + 1, ob = buf, obl = n;
    iconv (cd, &ib, &ibl, &ob, &obl);
    if (!ibl) {
      if (ob - buf < n) {
        memcpy (uid, buf, ob - buf);
        uid[ob - buf] = '\0';
      }
      else if (ob - buf == n && (buf[n] = 0, str_len (buf) < n))
        memcpy (uid, buf, n);
    }
    mem_free (&buf);
    iconv_close (cd);
  }
}

static pgp_key_t parse_pub_line (char *buf, int *is_subkey, pgp_key_t k)
{
  pgp_uid_t *uid = NULL;
  int field = 0, is_uid = 0;
  char *pend, *p;
  int trust = 0;
  int flags = 0;

  *is_subkey = 0;
  if (!*buf)
    return NULL;

  debug_print (2, ("buf = `%s'\n", buf));

  for (p = buf; p; p = pend) {
    if ((pend = strchr (p, ':')))
      *pend++ = 0;
    field++;
    if (field > 1 && !*p)
      continue;

    switch (field) {
    case 1:                    /* record type */
      {
        debug_print (2, ("record type: %s\n", p));

        if (!str_cmp (p, "pub"));
        else if (!str_cmp (p, "sub"))
          *is_subkey = 1;
        else if (!str_cmp (p, "sec"));
        else if (!str_cmp (p, "ssb"))
          *is_subkey = 1;
        else if (!str_cmp (p, "uid"))
          is_uid = 1;
        else
          return NULL;

        if (!(is_uid || (*is_subkey && option (OPTPGPIGNORESUB))))
          k = mem_calloc (sizeof *k, 1);

        break;
      }
    case 2:                    /* trust info */
      {
        debug_print (2, ("trust info: %s\n", p));

        switch (*p) {           /* look only at the first letter */
        case 'e':
          flags |= KEYFLAG_EXPIRED;
          break;
        case 'r':
          flags |= KEYFLAG_REVOKED;
          break;
        case 'd':
          flags |= KEYFLAG_DISABLED;
          break;
        case 'n':
          trust = 1;
          break;
        case 'm':
          trust = 2;
          break;
        case 'f':
          trust = 3;
          break;
        case 'u':
          trust = 3;
          break;
        }

        if (!is_uid && !(*is_subkey && option (OPTPGPIGNORESUB)))
          k->flags |= flags;

        break;
      }
    case 3:                    /* key length  */
      {

        debug_print (2, ("key len: %s\n", p));

        if (!(*is_subkey && option (OPTPGPIGNORESUB)))
          k->keylen = atoi (p); /* fixme: add validation checks */
        break;
      }
    case 4:                    /* pubkey algo */
      {

        debug_print (2, ("pubkey algorithm: %s\n", p));

        if (!(*is_subkey && option (OPTPGPIGNORESUB))) {
          k->numalg = atoi (p);
          k->algorithm = pgp_pkalgbytype (atoi (p));
        }
        break;
      }
    case 5:                    /* 16 hex digits with the long keyid. */
      {
        debug_print (2, ("key id: %s\n", p));

        if (!(*is_subkey && option (OPTPGPIGNORESUB)))
          str_replace (&k->keyid, p);
        break;

      }
    case 6:                    /* timestamp (1998-02-28) */
      {
        char tstr[11];
        struct tm time;

        debug_print (2, ("time stamp: %s\n", p));

        if (!p)
          break;
        time.tm_sec = 0;
        time.tm_min = 0;
        time.tm_hour = 12;
        strncpy (tstr, p, 11);
        tstr[4] = '\0';
        time.tm_year = atoi (tstr) - 1900;
        tstr[7] = '\0';
        time.tm_mon = (atoi (tstr + 5)) - 1;
        time.tm_mday = atoi (tstr + 8);
        k->gen_time = mutt_mktime (&time, 0);
        break;
      }
    case 7:                    /* valid for n days */
      break;
    case 8:                    /* Local id         */
      break;
    case 9:                    /* ownertrust       */
      break;
    case 10:                   /* name             */
      {
        if (!pend || !*p)
          break;                /* empty field or no trailing colon */

        /* ignore user IDs on subkeys */
        if (!is_uid && (*is_subkey && option (OPTPGPIGNORESUB)))
          break;

        debug_print (2, ("user ID: %s\n", p));

        uid = mem_calloc (sizeof (pgp_uid_t), 1);
        fix_uid (p);
        uid->addr = str_dup (p);
        uid->trust = trust;
        uid->flags |= flags;
        uid->parent = k;
        uid->next = k->address;
        k->address = uid;

        if (strstr (p, "ENCR"))
          k->flags |= KEYFLAG_PREFER_ENCRYPTION;
        if (strstr (p, "SIGN"))
          k->flags |= KEYFLAG_PREFER_SIGNING;

        break;
      }
    case 11:                   /* signature class  */
      break;
    case 12:                   /* key capabilities */
      debug_print (2, ("capabilities info: %s\n", p));

      while (*p) {
        switch (*p++) {
        case 'D':
          flags |= KEYFLAG_DISABLED;
          break;

        case 'e':
          flags |= KEYFLAG_CANENCRYPT;
          break;

        case 's':
          flags |= KEYFLAG_CANSIGN;
          break;
        }
      }

      if (!is_uid && (!*is_subkey || !option (OPTPGPIGNORESUB)
                      || !((flags & KEYFLAG_DISABLED)
                           || (flags & KEYFLAG_REVOKED)
                           || (flags & KEYFLAG_EXPIRED))))
        k->flags |= flags;

      break;

    default:
      break;
    }
  }
  return k;
}

pgp_key_t pgp_get_candidates (pgp_ring_t keyring, LIST * hints)
{
  FILE *fp;
  pid_t thepid;
  char buf[LONG_STRING];
  pgp_key_t db = NULL, *kend, k = NULL, kk, mainkey = NULL;
  int is_sub;
  int devnull;

  if ((devnull = open ("/dev/null", O_RDWR)) == -1)
    return NULL;

  str_replace (&_chs, Charset);

  thepid = pgp_invoke_list_keys (NULL, &fp, NULL, -1, -1, devnull,
                                 keyring, hints);
  if (thepid == -1) {
    close (devnull);
    return NULL;
  }

  kend = &db;
  k = NULL;
  while (fgets (buf, sizeof (buf) - 1, fp)) {
    if (!(kk = parse_pub_line (buf, &is_sub, k)))
      continue;

    /* Only append kk to the list if it's new. */
    if (kk != k) {
      if (k)
        kend = &k->next;
      *kend = k = kk;

      if (is_sub) {
        pgp_uid_t **l;

        k->flags |= KEYFLAG_SUBKEY;
        k->parent = mainkey;
        for (l = &k->address; *l; l = &(*l)->next);
        *l = pgp_copy_uids (mainkey->address, k);
      }
      else
        mainkey = k;
    }
  }

  if (ferror (fp))
    mutt_perror ("fgets");

  fclose (fp);
  mutt_wait_filter (thepid);

  close (devnull);

  return db;
}


syntax highlighted by Code2HTML, v. 0.9.1