/* keybox-search.c - Search operations
 * Copyright (C) 2001, 2002, 2003, 2004, 2012,
 *               2013 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 3 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, see <https://www.gnu.org/licenses/>.
 */

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

#include "keybox-defs.h"
#include <gcrypt.h>
#include "../common/host2net.h"
#include "../common/mbox-util.h"

#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))


struct sn_array_s {
    int snlen;
    unsigned char *sn;
};


#define get32(a) buf32_to_ulong ((a))
#define get16(a) buf16_to_ulong ((a))


static inline unsigned int
blob_get_blob_flags (KEYBOXBLOB blob)
{
  const unsigned char *buffer;
  size_t length;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 8)
    return 0; /* oops */

  return get16 (buffer + 6);
}


/* Return the first keyid from the blob.  Returns true if
   available.  */
static int
blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid)
{
  const unsigned char *buffer;
  size_t length, nkeys, keyinfolen;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 48)
    return 0; /* blob too short */

  nkeys = get16 (buffer + 16);
  keyinfolen = get16 (buffer + 18);
  if (!nkeys || keyinfolen < 28)
    return 0; /* invalid blob */

  kid[0] = get32 (buffer + 32);
  kid[1] = get32 (buffer + 36);

  return 1;
}


/* Return information on the flag WHAT within the blob BUFFER,LENGTH.
   Return the offset and the length (in bytes) of the flag in
   FLAGOFF,FLAG_SIZE. */
gpg_err_code_t
_keybox_get_flag_location (const unsigned char *buffer, size_t length,
                           int what, size_t *flag_off, size_t *flag_size)
{
  size_t pos;
  size_t nkeys, keyinfolen;
  size_t nuids, uidinfolen;
  size_t nserial;
  size_t nsigs, siginfolen, siginfooff;

  switch (what)
    {
    case KEYBOX_FLAG_BLOB:
      if (length < 8)
        return GPG_ERR_INV_OBJ;
      *flag_off = 6;
      *flag_size = 2;
      break;

    case KEYBOX_FLAG_OWNERTRUST:
    case KEYBOX_FLAG_VALIDITY:
    case KEYBOX_FLAG_CREATED_AT:
    case KEYBOX_FLAG_SIG_INFO:
      if (length < 20)
        return GPG_ERR_INV_OBJ;
      /* Key info. */
      nkeys = get16 (buffer + 16);
      keyinfolen = get16 (buffer + 18 );
      if (keyinfolen < 28)
        return GPG_ERR_INV_OBJ;
      pos = 20 + keyinfolen*nkeys;
      if (pos+2 > length)
        return GPG_ERR_INV_OBJ; /* Out of bounds. */
      /* Serial number. */
      nserial = get16 (buffer+pos);
      pos += 2 + nserial;
      if (pos+4 > length)
        return GPG_ERR_INV_OBJ; /* Out of bounds. */
      /* User IDs. */
      nuids = get16 (buffer + pos); pos += 2;
      uidinfolen = get16 (buffer + pos); pos += 2;
      if (uidinfolen < 12 )
        return GPG_ERR_INV_OBJ;
      pos += uidinfolen*nuids;
      if (pos+4 > length)
        return GPG_ERR_INV_OBJ ; /* Out of bounds. */
      /* Signature info. */
      siginfooff = pos;
      nsigs = get16 (buffer + pos); pos += 2;
      siginfolen = get16 (buffer + pos); pos += 2;
      if (siginfolen < 4 )
        return GPG_ERR_INV_OBJ;
      pos += siginfolen*nsigs;
      if (pos+1+1+2+4+4+4+4 > length)
        return GPG_ERR_INV_OBJ ; /* Out of bounds. */
      *flag_size = 1;
      *flag_off = pos;
      switch (what)
        {
        case KEYBOX_FLAG_VALIDITY:
          *flag_off += 1;
          break;
        case KEYBOX_FLAG_CREATED_AT:
          *flag_size = 4;
          *flag_off += 1+2+4+4+4;
          break;
        case KEYBOX_FLAG_SIG_INFO:
          *flag_size = siginfolen * nsigs;
          *flag_off = siginfooff;
          break;
        default:
          break;
        }
      break;

    default:
      return GPG_ERR_INV_FLAG;
    }
  return 0;
}



/* Return one of the flags WHAT in VALUE from the blob BUFFER of
   LENGTH bytes.  Return 0 on success or an raw error code. */
static gpg_err_code_t
get_flag_from_image (const unsigned char *buffer, size_t length,
                     int what, unsigned int *value)
{
  gpg_err_code_t ec;
  size_t pos, size;

  *value = 0;
  ec = _keybox_get_flag_location (buffer, length, what, &pos, &size);
  if (!ec)
    switch (size)
      {
      case 1: *value = buffer[pos]; break;
      case 2: *value = get16 (buffer + pos); break;
      case 4: *value = get32 (buffer + pos); break;
      default: ec = GPG_ERR_BUG; break;
      }

  return ec;
}


static int
blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
  const unsigned char *buffer;
  size_t length;
  size_t pos, off;
  size_t nkeys, keyinfolen;
  size_t nserial;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 40)
    return 0; /* blob too short */

  /*keys*/
  nkeys = get16 (buffer + 16);
  keyinfolen = get16 (buffer + 18 );
  if (keyinfolen < 28)
    return 0; /* invalid blob */
  pos = 20 + keyinfolen*nkeys;
  if (pos+2 > length)
    return 0; /* out of bounds */

  /*serial*/
  nserial = get16 (buffer+pos);
  off = pos + 2;
  if (off+nserial > length)
    return 0; /* out of bounds */

  return nserial == snlen && !memcmp (buffer+off, sn, snlen);
}


/* Returns 0 if not found or the number of the key which was found.
   For X.509 this is always 1, for OpenPGP this is 1 for the primary
   key and 2 and more for the subkeys.  */
static int
blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr)
{
  const unsigned char *buffer;
  size_t length;
  size_t pos, off;
  size_t nkeys, keyinfolen;
  int idx;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 40)
    return 0; /* blob too short */

  /*keys*/
  nkeys = get16 (buffer + 16);
  keyinfolen = get16 (buffer + 18 );
  if (keyinfolen < 28)
    return 0; /* invalid blob */
  pos = 20;
  if (pos + keyinfolen*nkeys > length)
    return 0; /* out of bounds */

  for (idx=0; idx < nkeys; idx++)
    {
      off = pos + idx*keyinfolen;
      if (!memcmp (buffer + off, fpr, 20))
        return idx+1; /* found */
    }
  return 0; /* not found */
}

static int
blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr,
                   int fproff, int fprlen)
{
  const unsigned char *buffer;
  size_t length;
  size_t pos, off;
  size_t nkeys, keyinfolen;
  int idx;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 40)
    return 0; /* blob too short */

  /*keys*/
  nkeys = get16 (buffer + 16);
  keyinfolen = get16 (buffer + 18 );
  if (keyinfolen < 28)
    return 0; /* invalid blob */
  pos = 20;
  if (pos + keyinfolen*nkeys > length)
    return 0; /* out of bounds */

  for (idx=0; idx < nkeys; idx++)
    {
      off = pos + idx*keyinfolen;
      if (!memcmp (buffer + off + fproff, fpr, fprlen))
        return idx+1; /* found */
    }
  return 0; /* not found */
}


static int
blob_cmp_name (KEYBOXBLOB blob, int idx,
               const char *name, size_t namelen, int substr, int x509)
{
  const unsigned char *buffer;
  size_t length;
  size_t pos, off, len;
  size_t nkeys, keyinfolen;
  size_t nuids, uidinfolen;
  size_t nserial;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 40)
    return 0; /* blob too short */

  /*keys*/
  nkeys = get16 (buffer + 16);
  keyinfolen = get16 (buffer + 18 );
  if (keyinfolen < 28)
    return 0; /* invalid blob */
  pos = 20 + keyinfolen*nkeys;
  if (pos+2 > length)
    return 0; /* out of bounds */

  /*serial*/
  nserial = get16 (buffer+pos);
  pos += 2 + nserial;
  if (pos+4 > length)
    return 0; /* out of bounds */

  /* user ids*/
  nuids = get16 (buffer + pos);  pos += 2;
  uidinfolen = get16 (buffer + pos);  pos += 2;
  if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
    return 0; /* invalid blob */
  if (pos + uidinfolen*nuids > length)
    return 0; /* out of bounds */

  if (idx < 0)
    { /* Compare all names.  Note that for X.509 we start with index 1
         so to skip the issuer at index 0.  */
      for (idx = !!x509; idx < nuids; idx++)
        {
          size_t mypos = pos;

          mypos += idx*uidinfolen;
          off = get32 (buffer+mypos);
          len = get32 (buffer+mypos+4);
          if (off+len > length)
            return 0; /* error: better stop here out of bounds */
          if (len < 1)
            continue; /* empty name */
          if (substr)
            {
              if (ascii_memcasemem (buffer+off, len, name, namelen))
                return idx+1; /* found */
            }
          else
            {
              if (len == namelen && !memcmp (buffer+off, name, len))
                return idx+1; /* found */
            }
        }
    }
  else
    {
      if (idx > nuids)
        return 0; /* no user ID with that idx */
      pos += idx*uidinfolen;
      off = get32 (buffer+pos);
      len = get32 (buffer+pos+4);
      if (off+len > length)
        return 0; /* out of bounds */
      if (len < 1)
        return 0; /* empty name */

      if (substr)
        {
          if (ascii_memcasemem (buffer+off, len, name, namelen))
            return idx+1; /* found */
        }
      else
        {
          if (len == namelen && !memcmp (buffer+off, name, len))
            return idx+1; /* found */
        }
    }
  return 0; /* not found */
}


/* Compare all email addresses of the subject.  With SUBSTR given as
   True a substring search is done in the mail address.  The X509 flag
   indicated whether the search is done on an X.509 blob.  */
static int
blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr,
               int x509)
{
  const unsigned char *buffer;
  size_t length;
  size_t pos, off, len;
  size_t nkeys, keyinfolen;
  size_t nuids, uidinfolen;
  size_t nserial;
  int idx;

  /* fixme: this code is common to blob_cmp_mail */
  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 40)
    return 0; /* blob too short */

  /*keys*/
  nkeys = get16 (buffer + 16);
  keyinfolen = get16 (buffer + 18 );
  if (keyinfolen < 28)
    return 0; /* invalid blob */
  pos = 20 + keyinfolen*nkeys;
  if (pos+2 > length)
    return 0; /* out of bounds */

  /*serial*/
  nserial = get16 (buffer+pos);
  pos += 2 + nserial;
  if (pos+4 > length)
    return 0; /* out of bounds */

  /* user ids*/
  nuids = get16 (buffer + pos);  pos += 2;
  uidinfolen = get16 (buffer + pos);  pos += 2;
  if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
    return 0; /* invalid blob */
  if (pos + uidinfolen*nuids > length)
    return 0; /* out of bounds */

  if (namelen < 1)
    return 0;

  /* Note that for X.509 we start at index 1 because index 0 is used
     for the issuer name.  */
  for (idx=!!x509 ;idx < nuids; idx++)
    {
      size_t mypos = pos;
      size_t mylen;

      mypos += idx*uidinfolen;
      off = get32 (buffer+mypos);
      len = get32 (buffer+mypos+4);
      if (off+len > length)
        return 0; /* error: better stop here - out of bounds */
      if (x509)
        {
          if (len < 2 || buffer[off] != '<')
            continue; /* empty name or trailing 0 not stored */
          len--; /* one back */
          if ( len < 3 || buffer[off+len] != '>')
            continue; /* not a proper email address */
          off++;
          len--;
        }
      else /* OpenPGP.  */
        {
          /* We need to forward to the mailbox part.  */
          mypos = off;
          mylen = len;
          for ( ; len && buffer[off] != '<'; len--, off++)
            ;
          if (len < 2 || buffer[off] != '<')
            {
              /* Mailbox not explicitly given or too short.  Restore
                 OFF and LEN and check whether the entire string
                 resembles a mailbox without the angle brackets.  */
              off = mypos;
              len = mylen;
              if (!is_valid_mailbox_mem (buffer+off, len))
                continue; /* Not a mail address. */
            }
          else /* Seems to be standard user id with mail address.  */
            {
              off++; /* Point to first char of the mail address.  */
              len--;
              /* Search closing '>'.  */
              for (mypos=off; len && buffer[mypos] != '>'; len--, mypos++)
                ;
              if (!len || buffer[mypos] != '>' || off == mypos)
                continue; /* Not a proper mail address.  */
              len = mypos - off;
            }

        }

      if (substr)
        {
          if (ascii_memcasemem (buffer+off, len, name, namelen))
            return idx+1; /* found */
        }
      else
        {
          if (len == namelen && !ascii_memcasecmp (buffer+off, name, len))
            return idx+1; /* found */
        }
    }
  return 0; /* not found */
}


#ifdef KEYBOX_WITH_X509
/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
   We don't have the keygrips as meta data, thus we need to parse the
   certificate. Fixme: We might want to return proper error codes
   instead of failing a search for invalid certificates etc.  */
static int
blob_x509_has_grip (KEYBOXBLOB blob, const unsigned char *grip)
{
  int rc;
  const unsigned char *buffer;
  size_t length;
  size_t cert_off, cert_len;
  ksba_reader_t reader = NULL;
  ksba_cert_t cert = NULL;
  ksba_sexp_t p = NULL;
  gcry_sexp_t s_pkey;
  unsigned char array[20];
  unsigned char *rcp;
  size_t n;

  buffer = _keybox_get_blob_image (blob, &length);
  if (length < 40)
    return 0; /* Too short. */
  cert_off = get32 (buffer+8);
  cert_len = get32 (buffer+12);
  if (cert_off+cert_len > length)
    return 0; /* Too short.  */

  rc = ksba_reader_new (&reader);
  if (rc)
    return 0; /* Problem with ksba. */
  rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
  if (rc)
    goto failed;
  rc = ksba_cert_new (&cert);
  if (rc)
    goto failed;
  rc = ksba_cert_read_der (cert, reader);
  if (rc)
    goto failed;
  p = ksba_cert_get_public_key (cert);
  if (!p)
    goto failed;
  n = gcry_sexp_canon_len (p, 0, NULL, NULL);
  if (!n)
    goto failed;
  rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
  if (rc)
    {
      gcry_sexp_release (s_pkey);
      goto failed;
    }
  rcp = gcry_pk_get_keygrip (s_pkey, array);
  gcry_sexp_release (s_pkey);
  if (!rcp)
    goto failed; /* Can't calculate keygrip. */

  xfree (p);
  ksba_cert_release (cert);
  ksba_reader_release (reader);
  return !memcmp (array, grip, 20);
 failed:
  xfree (p);
  ksba_cert_release (cert);
  ksba_reader_release (reader);
  return 0;
}
#endif /*KEYBOX_WITH_X509*/



/*
  The has_foo functions are used as helpers for search
*/
static inline int
has_short_kid (KEYBOXBLOB blob, u32 lkid)
{
  unsigned char buf[4];
  buf[0] = lkid >> 24;
  buf[1] = lkid >> 16;
  buf[2] = lkid >> 8;
  buf[3] = lkid;
  return blob_cmp_fpr_part (blob, buf, 16, 4);
}

static inline int
has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid)
{
  unsigned char buf[8];
  buf[0] = mkid >> 24;
  buf[1] = mkid >> 16;
  buf[2] = mkid >> 8;
  buf[3] = mkid;
  buf[4] = lkid >> 24;
  buf[5] = lkid >> 16;
  buf[6] = lkid >> 8;
  buf[7] = lkid;
  return blob_cmp_fpr_part (blob, buf, 12, 8);
}

static inline int
has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr)
{
  return blob_cmp_fpr (blob, fpr);
}

static inline int
has_keygrip (KEYBOXBLOB blob, const unsigned char *grip)
{
#ifdef KEYBOX_WITH_X509
  if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509)
    return blob_x509_has_grip (blob, grip);
#else
  (void)blob;
  (void)grip;
#endif
  return 0;
}


static inline int
has_issuer (KEYBOXBLOB blob, const char *name)
{
  size_t namelen;

  return_val_if_fail (name, 0);

  if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
    return 0;

  namelen = strlen (name);
  return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1);
}

static inline int
has_issuer_sn (KEYBOXBLOB blob, const char *name,
               const unsigned char *sn, int snlen)
{
  size_t namelen;

  return_val_if_fail (name, 0);
  return_val_if_fail (sn, 0);

  if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
    return 0;

  namelen = strlen (name);

  return (blob_cmp_sn (blob, sn, snlen)
          && blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1));
}

static inline int
has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
  return_val_if_fail (sn, 0);

  if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
    return 0;
  return blob_cmp_sn (blob, sn, snlen);
}

static inline int
has_subject (KEYBOXBLOB blob, const char *name)
{
  size_t namelen;

  return_val_if_fail (name, 0);

  if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
    return 0;

  namelen = strlen (name);
  return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0, 1);
}


static inline int
has_username (KEYBOXBLOB blob, const char *name, int substr)
{
  size_t namelen;
  int btype;

  return_val_if_fail (name, 0);

  btype = blob_get_type (blob);
  if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
    return 0;

  namelen = strlen (name);
  return blob_cmp_name (blob, -1 /* all subject/user names */, name,
                        namelen, substr, (btype == KEYBOX_BLOBTYPE_X509));
}


static inline int
has_mail (KEYBOXBLOB blob, const char *name, int substr)
{
  size_t namelen;
  int btype;

  return_val_if_fail (name, 0);

  btype = blob_get_type (blob);
  if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
    return 0;

  if (btype == KEYBOX_BLOBTYPE_PGP && *name == '<')
    name++; /* Hack to remove the leading '<' for gpg.  */

  namelen = strlen (name);
  if (namelen && name[namelen-1] == '>')
    namelen--;
  return blob_cmp_mail (blob, name, namelen, substr,
                        (btype == KEYBOX_BLOBTYPE_X509));
}


static void
release_sn_array (struct sn_array_s *array, size_t size)
{
  size_t n;

  for (n=0; n < size; n++)
    xfree (array[n].sn);
  xfree (array);
}


/* Helper to open the file.  */
static gpg_error_t
open_file (KEYBOX_HANDLE hd)
{

  hd->fp = fopen (hd->kb->fname, "rb");
  if (!hd->fp)
    {
      hd->error = gpg_error_from_syserror ();
      return hd->error;
    }

  return 0;
}



/*

  The search API

*/

gpg_error_t
keybox_search_reset (KEYBOX_HANDLE hd)
{
  if (!hd)
    return gpg_error (GPG_ERR_INV_VALUE);

  if (hd->found.blob)
    {
      _keybox_release_blob (hd->found.blob);
      hd->found.blob = NULL;
    }

  if (hd->fp)
    {
      if (fseeko (hd->fp, 0, SEEK_SET))
        {
          /* Ooops.  Seek did not work.  Close so that the search will
           * open the file again.  */
          fclose (hd->fp);
          hd->fp = NULL;
        }
    }
  hd->error = 0;
  hd->eof = 0;
  return 0;
}


/* Note: When in ephemeral mode the search function does visit all
   blobs but in standard mode, blobs flagged as ephemeral are ignored.
   If WANT_BLOBTYPE is not 0 only blobs of this type are considered.
   The value at R_SKIPPED is updated by the number of skipped long
   records (counts PGP and X.509). */
gpg_error_t
keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc,
               keybox_blobtype_t want_blobtype,
               size_t *r_descindex, unsigned long *r_skipped)
{
  gpg_error_t rc;
  size_t n;
  int need_words, any_skip;
  KEYBOXBLOB blob = NULL;
  struct sn_array_s *sn_array = NULL;
  int pk_no, uid_no;

  if (!hd)
    return gpg_error (GPG_ERR_INV_VALUE);

  /* clear last found result */
  if (hd->found.blob)
    {
      _keybox_release_blob (hd->found.blob);
      hd->found.blob = NULL;
    }

  if (hd->error)
    return hd->error; /* still in error state */
  if (hd->eof)
    return -1; /* still EOF */

  /* figure out what information we need */
  need_words = any_skip = 0;
  for (n=0; n < ndesc; n++)
    {
      switch (desc[n].mode)
        {
        case KEYDB_SEARCH_MODE_WORDS:
          need_words = 1;
          break;
        case KEYDB_SEARCH_MODE_FIRST:
          /* always restart the search in this mode */
          keybox_search_reset (hd);
          break;
        default:
          break;
	}
      if (desc[n].skipfnc)
        any_skip = 1;
      if (desc[n].snlen == -1 && !sn_array)
        {
          sn_array = xtrycalloc (ndesc, sizeof *sn_array);
          if (!sn_array)
            return (hd->error = gpg_error_from_syserror ());
        }
    }

  (void)need_words;  /* Not yet implemented.  */

  if (!hd->fp)
    {
      rc = open_file (hd);
      if (rc)
        {
          xfree (sn_array);
          return rc;
        }
    }

  /* Kludge: We need to convert an SN given as hexstring to its binary
     representation - in some cases we are not able to store it in the
     search descriptor, because due to the way we use it, it is not
     possible to free allocated memory. */
  if (sn_array)
    {
      const unsigned char *s;
      int i, odd;
      size_t snlen;

      for (n=0; n < ndesc; n++)
        {
          if (!desc[n].sn)
            ;
          else if (desc[n].snlen == -1)
            {
              unsigned char *sn;

              s = desc[n].sn;
              for (i=0; *s && *s != '/'; s++, i++)
                ;
              odd = (i & 1);
              snlen = (i+1)/2;
              sn_array[n].sn = xtrymalloc (snlen);
              if (!sn_array[n].sn)
                {
                  hd->error = gpg_error_from_syserror ();
                  release_sn_array (sn_array, n);
                  return hd->error;
                }
              sn_array[n].snlen = snlen;
              sn = sn_array[n].sn;
              s = desc[n].sn;
              if (odd)
                {
                  *sn++ = xtoi_1 (s);
                  s++;
                }
              for (; *s && *s != '/';  s += 2)
                *sn++ = xtoi_2 (s);
            }
          else
            {
              const unsigned char *sn;

              sn = desc[n].sn;
              snlen = desc[n].snlen;
              sn_array[n].sn = xtrymalloc (snlen);
              if (!sn_array[n].sn)
                {
                  hd->error = gpg_error_from_syserror ();
                  release_sn_array (sn_array, n);
                  return hd->error;
                }
              sn_array[n].snlen = snlen;
              memcpy (sn_array[n].sn, sn, snlen);
            }
        }
    }


  pk_no = uid_no = 0;
  for (;;)
    {
      unsigned int blobflags;
      int blobtype;

      _keybox_release_blob (blob); blob = NULL;
      rc = _keybox_read_blob (&blob, hd->fp, NULL);
      if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
          && gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
        {
          ++*r_skipped;
          continue; /* Skip too large records.  */
        }

      if (rc)
        break;

      blobtype = blob_get_type (blob);
      if (blobtype == KEYBOX_BLOBTYPE_HEADER)
        continue;
      if (want_blobtype && blobtype != want_blobtype)
        continue;

      blobflags = blob_get_blob_flags (blob);
      if (!hd->ephemeral && (blobflags & 2))
        continue; /* Not in ephemeral mode but blob is flagged ephemeral.  */

      for (n=0; n < ndesc; n++)
        {
          switch (desc[n].mode)
            {
            case KEYDB_SEARCH_MODE_NONE:
              never_reached ();
              break;
            case KEYDB_SEARCH_MODE_EXACT:
              uid_no = has_username (blob, desc[n].u.name, 0);
              if (uid_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_MAIL:
              uid_no = has_mail (blob, desc[n].u.name, 0);
              if (uid_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_MAILSUB:
              uid_no = has_mail (blob, desc[n].u.name, 1);
              if (uid_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_SUBSTR:
              uid_no =  has_username (blob, desc[n].u.name, 1);
              if (uid_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_MAILEND:
            case KEYDB_SEARCH_MODE_WORDS:
              /* not yet implemented */
              break;
            case KEYDB_SEARCH_MODE_ISSUER:
              if (has_issuer (blob, desc[n].u.name))
                goto found;
              break;
            case KEYDB_SEARCH_MODE_ISSUER_SN:
              if (has_issuer_sn (blob, desc[n].u.name,
                                 sn_array? sn_array[n].sn : desc[n].sn,
                                 sn_array? sn_array[n].snlen : desc[n].snlen))
                goto found;
              break;
            case KEYDB_SEARCH_MODE_SN:
              if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn,
                                sn_array? sn_array[n].snlen : desc[n].snlen))
                goto found;
              break;
            case KEYDB_SEARCH_MODE_SUBJECT:
              if (has_subject (blob, desc[n].u.name))
                goto found;
              break;
            case KEYDB_SEARCH_MODE_SHORT_KID:
              pk_no = has_short_kid (blob, desc[n].u.kid[1]);
              if (pk_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_LONG_KID:
              pk_no = has_long_kid (blob, desc[n].u.kid[0], desc[n].u.kid[1]);
              if (pk_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_FPR:
            case KEYDB_SEARCH_MODE_FPR20:
              pk_no = has_fingerprint (blob, desc[n].u.fpr);
              if (pk_no)
                goto found;
              break;
            case KEYDB_SEARCH_MODE_KEYGRIP:
              if (has_keygrip (blob, desc[n].u.grip))
                goto found;
              break;
            case KEYDB_SEARCH_MODE_FIRST:
              goto found;
              break;
            case KEYDB_SEARCH_MODE_NEXT:
              goto found;
              break;
            default:
              rc = gpg_error (GPG_ERR_INV_VALUE);
              goto found;
            }
	}
      continue;
    found:
      /* Record which DESC we matched on.  Note this value is only
	 meaningful if this function returns with no errors. */
      if(r_descindex)
	*r_descindex = n;
      for (n=any_skip?0:ndesc; n < ndesc; n++)
        {
          u32 kid[2];

          if (desc[n].skipfnc
              && blob_get_first_keyid (blob, kid)
	      && desc[n].skipfnc (desc[n].skipfncvalue, kid, uid_no))
		break;
        }
      if (n == ndesc)
        break; /* got it */
    }

  if (!rc)
    {
      hd->found.blob = blob;
      hd->found.pk_no = pk_no;
      hd->found.uid_no = uid_no;
    }
  else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
    {
      _keybox_release_blob (blob);
      hd->eof = 1;
    }
  else
    {
      _keybox_release_blob (blob);
      hd->error = rc;
    }

  if (sn_array)
    release_sn_array (sn_array, ndesc);

  return rc;
}




/*
   Functions to return a certificate or a keyblock.  To be used after
   a successful search operation.
*/


/* Return the last found keyblock.  Returns 0 on success and stores a
 * new iobuf at R_IOBUF.  R_UID_NO and R_PK_NO are used to retun the
 * number of the key or user id which was matched the search criteria;
 * if not known they are set to 0. */
gpg_error_t
keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
                     int *r_pk_no, int *r_uid_no)
{
  gpg_error_t err;
  const unsigned char *buffer;
  size_t length;
  size_t image_off, image_len;
  size_t siginfo_off, siginfo_len;

  *r_iobuf = NULL;

  if (!hd)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!hd->found.blob)
    return gpg_error (GPG_ERR_NOTHING_FOUND);

  if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP)
    return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);

  buffer = _keybox_get_blob_image (hd->found.blob, &length);
  if (length < 40)
    return gpg_error (GPG_ERR_TOO_SHORT);
  image_off = get32 (buffer+8);
  image_len = get32 (buffer+12);
  if (image_off+image_len > length)
    return gpg_error (GPG_ERR_TOO_SHORT);

  err = _keybox_get_flag_location (buffer, length, KEYBOX_FLAG_SIG_INFO,
                                   &siginfo_off, &siginfo_len);
  if (err)
    return err;

  *r_pk_no  = hd->found.pk_no;
  *r_uid_no = hd->found.uid_no;
  *r_iobuf = iobuf_temp_with_content (buffer+image_off, image_len);
  return 0;
}


#ifdef KEYBOX_WITH_X509
/*
  Return the last found cert.  Caller must free it.
 */
int
keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert)
{
  const unsigned char *buffer;
  size_t length;
  size_t cert_off, cert_len;
  ksba_reader_t reader = NULL;
  ksba_cert_t cert = NULL;
  int rc;

  if (!hd)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!hd->found.blob)
    return gpg_error (GPG_ERR_NOTHING_FOUND);

  if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_X509)
    return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);

  buffer = _keybox_get_blob_image (hd->found.blob, &length);
  if (length < 40)
    return gpg_error (GPG_ERR_TOO_SHORT);
  cert_off = get32 (buffer+8);
  cert_len = get32 (buffer+12);
  if (cert_off+cert_len > length)
    return gpg_error (GPG_ERR_TOO_SHORT);

  rc = ksba_reader_new (&reader);
  if (rc)
    return rc;
  rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
  if (rc)
    {
      ksba_reader_release (reader);
      /* fixme: need to map the error codes */
      return gpg_error (GPG_ERR_GENERAL);
    }

  rc = ksba_cert_new (&cert);
  if (rc)
    {
      ksba_reader_release (reader);
      return rc;
    }

  rc = ksba_cert_read_der (cert, reader);
  if (rc)
    {
      ksba_cert_release (cert);
      ksba_reader_release (reader);
      /* fixme: need to map the error codes */
      return gpg_error (GPG_ERR_GENERAL);
    }

  *r_cert = cert;
  ksba_reader_release (reader);
  return 0;
}

#endif /*KEYBOX_WITH_X509*/

/* Return the flags named WHAT at the address of VALUE. IDX is used
   only for certain flags and should be 0 if not required. */
int
keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value)
{
  const unsigned char *buffer;
  size_t length;
  gpg_err_code_t ec;

  (void)idx; /* Not yet used.  */

  if (!hd)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!hd->found.blob)
    return gpg_error (GPG_ERR_NOTHING_FOUND);

  buffer = _keybox_get_blob_image (hd->found.blob, &length);
  ec = get_flag_from_image (buffer, length, what, value);
  return ec? gpg_error (ec):0;
}

off_t
keybox_offset (KEYBOX_HANDLE hd)
{
  if (!hd->fp)
    return 0;
  return ftello (hd->fp);
}

gpg_error_t
keybox_seek (KEYBOX_HANDLE hd, off_t offset)
{
  gpg_error_t err;

  if (hd->error)
    return hd->error; /* still in error state */

  if (! hd->fp)
    {
      if (!offset)
        {
          /* No need to open the file.  An unopened file is effectively at
             offset 0.  */
          return 0;
        }

      err = open_file (hd);
      if (err)
        return err;
    }

  err = fseeko (hd->fp, offset, SEEK_SET);
  hd->error = gpg_error_from_errno (err);

  return hd->error;
}
