keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc,
		size_t ndesc, size_t *descindex)
{
  int rc;
  PACKET pkt;
  int save_mode;
  off_t offset, main_offset;
  size_t n;
  int need_uid, need_words, need_keyid, need_fpr, any_skip;
  int pk_no, uid_no;
  int initial_skip;
  int use_offtbl;
  PKT_user_id *uid = NULL;
  PKT_public_key *pk = NULL;
  u32 aki[2];

  /* figure out what information we need */
  need_uid = need_words = need_keyid = need_fpr = any_skip = 0;
  for (n=0; n < ndesc; n++)
    {
      switch (desc[n].mode)
        {
        case KEYDB_SEARCH_MODE_EXACT:
        case KEYDB_SEARCH_MODE_SUBSTR:
        case KEYDB_SEARCH_MODE_MAIL:
        case KEYDB_SEARCH_MODE_MAILSUB:
        case KEYDB_SEARCH_MODE_MAILEND:
          need_uid = 1;
          break;
        case KEYDB_SEARCH_MODE_WORDS:
          need_uid = 1;
          need_words = 1;
          break;
        case KEYDB_SEARCH_MODE_SHORT_KID:
        case KEYDB_SEARCH_MODE_LONG_KID:
          need_keyid = 1;
          break;
        case KEYDB_SEARCH_MODE_FPR16:
        case KEYDB_SEARCH_MODE_FPR20:
        case KEYDB_SEARCH_MODE_FPR:
          need_fpr = 1;
          break;
        case KEYDB_SEARCH_MODE_FIRST:
          /* always restart the search in this mode */
          keyring_search_reset (hd);
          break;
        default: break;
	}
      if (desc[n].skipfnc)
        {
          any_skip = 1;
          need_keyid = 1;
        }
    }

  rc = prepare_search (hd);
  if (rc)
    return rc;

  use_offtbl = !!kr_offtbl;
  if (!use_offtbl)
    ;
  else if (!kr_offtbl_ready)
    need_keyid = 1;
  else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID)
    {
      struct off_item *oi;

      oi = lookup_offset_hash_table (kr_offtbl, desc[0].u.kid);
      if (!oi)
        { /* We know that we don't have this key */
          hd->found.kr = NULL;
          hd->current.eof = 1;
          return -1;
        }
      /* We could now create a positive search status and return.
       * However the problem is that another instance of gpg may
       * have changed the keyring so that the offsets are not valid
       * anymore - therefore we don't do it
       */
    }

  if (need_words)
    {
      const char *name = NULL;

      log_debug ("word search mode does not yet work\n");
      /* FIXME: here is a long standing bug in our function and in addition we
         just use the first search description */
      for (n=0; n < ndesc && !name; n++)
        {
          if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS)
            name = desc[n].u.name;
        }
      assert (name);
      if ( !hd->word_match.name || strcmp (hd->word_match.name, name) )
        {
          /* name changed */
          xfree (hd->word_match.name);
          xfree (hd->word_match.pattern);
          hd->word_match.name = xstrdup (name);
          hd->word_match.pattern = prepare_word_match (name);
        }
      /*  name = hd->word_match.pattern; */
    }

  init_packet(&pkt);
  save_mode = set_packet_list_mode(0);

  hd->found.kr = NULL;
  main_offset = 0;
  pk_no = uid_no = 0;
  initial_skip = 1; /* skip until we see the start of a keyblock */
  while (!(rc=search_packet (hd->current.iobuf, &pkt, &offset, need_uid)))
    {
      byte afp[MAX_FINGERPRINT_LEN];
      size_t an;

      if (pkt.pkttype == PKT_PUBLIC_KEY  || pkt.pkttype == PKT_SECRET_KEY)
        {
          main_offset = offset;
          pk_no = uid_no = 0;
          initial_skip = 0;
        }
      if (initial_skip)
        {
          free_packet (&pkt);
          continue;
        }

      pk = NULL;
      uid = NULL;
      if (   pkt.pkttype == PKT_PUBLIC_KEY
             || pkt.pkttype == PKT_PUBLIC_SUBKEY
             || pkt.pkttype == PKT_SECRET_KEY
             || pkt.pkttype == PKT_SECRET_SUBKEY)
        {
          pk = pkt.pkt.public_key;
          ++pk_no;

          if (need_fpr) {
            fingerprint_from_pk (pk, afp, &an);
            while (an < 20) /* fill up to 20 bytes */
              afp[an++] = 0;
          }
          if (need_keyid)
            keyid_from_pk (pk, aki);

          if (use_offtbl && !kr_offtbl_ready)
            update_offset_hash_table (kr_offtbl, aki, main_offset);
        }
      else if (pkt.pkttype == PKT_USER_ID)
        {
          uid = pkt.pkt.user_id;
          ++uid_no;
        }

      for (n=0; n < ndesc; n++)
        {
          switch (desc[n].mode) {
          case KEYDB_SEARCH_MODE_NONE:
            BUG ();
            break;
          case KEYDB_SEARCH_MODE_EXACT:
          case KEYDB_SEARCH_MODE_SUBSTR:
          case KEYDB_SEARCH_MODE_MAIL:
          case KEYDB_SEARCH_MODE_MAILSUB:
          case KEYDB_SEARCH_MODE_MAILEND:
          case KEYDB_SEARCH_MODE_WORDS:
            if ( uid && !compare_name (desc[n].mode,
                                       desc[n].u.name,
                                       uid->name, uid->len))
              goto found;
            break;

          case KEYDB_SEARCH_MODE_SHORT_KID:
            if (pk && desc[n].u.kid[1] == aki[1])
              goto found;
            break;
          case KEYDB_SEARCH_MODE_LONG_KID:
            if (pk && desc[n].u.kid[0] == aki[0]
                && desc[n].u.kid[1] == aki[1])
              goto found;
            break;
          case KEYDB_SEARCH_MODE_FPR16:
            if (pk && !memcmp (desc[n].u.fpr, afp, 16))
              goto found;
            break;
          case KEYDB_SEARCH_MODE_FPR20:
          case KEYDB_SEARCH_MODE_FPR:
            if (pk && !memcmp (desc[n].u.fpr, afp, 20))
              goto found;
            break;
          case KEYDB_SEARCH_MODE_FIRST:
            if (pk)
              goto found;
            break;
          case KEYDB_SEARCH_MODE_NEXT:
            if (pk)
              goto found;
            break;
          default:
            rc = GPG_ERR_INV_ARG;
            goto found;
          }
	}
      free_packet (&pkt);
      continue;
    found:
      /* Record which desc we matched on.  Note this value is only
	 meaningful if this function returns with no errors. */
      if(descindex)
	*descindex=n;
      for (n=any_skip?0:ndesc; n < ndesc; n++)
        {
          if (desc[n].skipfnc
              && desc[n].skipfnc (desc[n].skipfncvalue, aki, uid))
            break;
        }
      if (n == ndesc)
        goto real_found;
      free_packet (&pkt);
    }
 real_found:
  if (!rc)
    {
      hd->found.offset = main_offset;
      hd->found.kr = hd->current.kr;
      hd->found.pk_no = pk? pk_no : 0;
      hd->found.uid_no = uid? uid_no : 0;
    }
  else if (rc == -1)
    {
      hd->current.eof = 1;
      /* if we scanned all keyrings, we are sure that
       * all known key IDs are in our offtbl, mark that. */
      if (use_offtbl && !kr_offtbl_ready)
        {
          KR_NAME kr;

          /* First set the did_full_scan flag for this keyring.  */
          for (kr=kr_names; kr; kr = kr->next)
            {
              if (hd->resource == kr)
                {
                  kr->did_full_scan = 1;
                  break;
                }
            }
          /* Then check whether all flags are set and if so, mark the
             offtbl ready */
          for (kr=kr_names; kr; kr = kr->next)
            {
              if (!kr->did_full_scan)
                break;
            }
          if (!kr)
            kr_offtbl_ready = 1;
        }
    }
  else
    hd->current.error = rc;

  free_packet(&pkt);
  set_packet_list_mode(save_mode);
  return rc;
}
keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc,
		size_t ndesc, size_t *descindex)
{
  int rc;
  PACKET pkt;
  int save_mode;
  off_t offset, main_offset;
  size_t n;
  int need_uid, need_words, need_keyid, need_fpr, any_skip;
  int pk_no, uid_no;
  int initial_skip;
  int use_offtbl;
  PKT_user_id *uid = NULL;
  PKT_public_key *pk = NULL;
  u32 aki[2];

   
  need_uid = need_words = need_keyid = need_fpr = any_skip = 0;
  for (n=0; n < ndesc; n++)
    {
      switch (desc[n].mode)
        {
        case KEYDB_SEARCH_MODE_EXACT:
        case KEYDB_SEARCH_MODE_SUBSTR:
        case KEYDB_SEARCH_MODE_MAIL:
        case KEYDB_SEARCH_MODE_MAILSUB:
        case KEYDB_SEARCH_MODE_MAILEND:
          need_uid = 1;
          break;
        case KEYDB_SEARCH_MODE_WORDS:
          need_uid = 1;
          need_words = 1;
          break;
        case KEYDB_SEARCH_MODE_SHORT_KID:
        case KEYDB_SEARCH_MODE_LONG_KID:
          need_keyid = 1;
          break;
        case KEYDB_SEARCH_MODE_FPR16:
        case KEYDB_SEARCH_MODE_FPR20:
        case KEYDB_SEARCH_MODE_FPR:
          need_fpr = 1;
          break;
        case KEYDB_SEARCH_MODE_FIRST:
           
          keyring_search_reset (hd);
          break;
        default: break;
	}
      if (desc[n].skipfnc)
        {
          any_skip = 1;
          need_keyid = 1;
        }
    }

  rc = prepare_search (hd);
  if (rc)
    return rc;

  use_offtbl = !!kr_offtbl;
  if (!use_offtbl)
    ;
  else if (!kr_offtbl_ready)
    need_keyid = 1;
  else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID)
    {
      struct off_item *oi;

      oi = lookup_offset_hash_table (kr_offtbl, desc[0].u.kid);
      if (!oi)
        {  
          hd->found.kr = NULL;
          hd->current.eof = 1;
          return -1;
        }
       
    }

  if (need_words)
    {
      const char *name = NULL;

      log_debug ("word search mode does not yet work\n");
       
      for (n=0; n < ndesc && !name; n++)
        {
          if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS)
            name = desc[n].u.name;
        }
      assert (name);
      if ( !hd->word_match.name || strcmp (hd->word_match.name, name) )
        {
           
          xfree (hd->word_match.name);
          xfree (hd->word_match.pattern);
          hd->word_match.name = xstrdup (name);
          hd->word_match.pattern = prepare_word_match (name);
        }
       
    }

  init_packet(&pkt);
  save_mode = set_packet_list_mode(0);

  hd->found.kr = NULL;
  main_offset = 0;
  pk_no = uid_no = 0;
  initial_skip = 1;  
  while (!(rc=search_packet (hd->current.iobuf, &pkt, &offset, need_uid)))
    {
      byte afp[MAX_FINGERPRINT_LEN];
      size_t an;

      if (pkt.pkttype == PKT_PUBLIC_KEY  || pkt.pkttype == PKT_SECRET_KEY)
        {
          main_offset = offset;
          pk_no = uid_no = 0;
          initial_skip = 0;
        }
      if (initial_skip)
        {
          free_packet (&pkt);
          continue;
        }

      pk = NULL;
      uid = NULL;
      if (   pkt.pkttype == PKT_PUBLIC_KEY
             || pkt.pkttype == PKT_PUBLIC_SUBKEY
             || pkt.pkttype == PKT_SECRET_KEY
             || pkt.pkttype == PKT_SECRET_SUBKEY)
        {
          pk = pkt.pkt.public_key;
          ++pk_no;

          if (need_fpr) {
            fingerprint_from_pk (pk, afp, &an);
            while (an < 20)  
              afp[an++] = 0;
          }
          if (need_keyid)
            keyid_from_pk (pk, aki);

          if (use_offtbl && !kr_offtbl_ready)
            update_offset_hash_table (kr_offtbl, aki, main_offset);
        }
      else if (pkt.pkttype == PKT_USER_ID)
        {
          uid = pkt.pkt.user_id;
          ++uid_no;
        }

      for (n=0; n < ndesc; n++)
        {
          switch (desc[n].mode) {
          case KEYDB_SEARCH_MODE_NONE:
            BUG ();
            break;
          case KEYDB_SEARCH_MODE_EXACT:
          case KEYDB_SEARCH_MODE_SUBSTR:
          case KEYDB_SEARCH_MODE_MAIL:
          case KEYDB_SEARCH_MODE_MAILSUB:
          case KEYDB_SEARCH_MODE_MAILEND:
          case KEYDB_SEARCH_MODE_WORDS:
            if ( uid && !compare_name (desc[n].mode,
                                       desc[n].u.name,
                                       uid->name, uid->len))
              goto found;
            break;

          case KEYDB_SEARCH_MODE_SHORT_KID:
            if (pk && desc[n].u.kid[1] == aki[1])
              goto found;
            break;
          case KEYDB_SEARCH_MODE_LONG_KID:
            if (pk && desc[n].u.kid[0] == aki[0]
                && desc[n].u.kid[1] == aki[1])
              goto found;
            break;
          case KEYDB_SEARCH_MODE_FPR16:
            if (pk && !memcmp (desc[n].u.fpr, afp, 16))
              goto found;
            break;
          case KEYDB_SEARCH_MODE_FPR20:
          case KEYDB_SEARCH_MODE_FPR:
            if (pk && !memcmp (desc[n].u.fpr, afp, 20))
              goto found;
            break;
          case KEYDB_SEARCH_MODE_FIRST:
            if (pk)
              goto found;
            break;
          case KEYDB_SEARCH_MODE_NEXT:
            if (pk)
              goto found;
            break;
          default:
            rc = GPG_ERR_INV_ARG;
            goto found;
          }
	}
      free_packet (&pkt);
      continue;
    found:
       
      if(descindex)
	*descindex=n;
      for (n=any_skip?0:ndesc; n < ndesc; n++)
        {
          if (desc[n].skipfnc
              && desc[n].skipfnc (desc[n].skipfncvalue, aki, uid))
            break;
        }
      if (n == ndesc)
        goto real_found;
      free_packet (&pkt);
    }
 real_found:
  if (!rc)
    {
      hd->found.offset = main_offset;
      hd->found.kr = hd->current.kr;
      hd->found.pk_no = pk? pk_no : 0;
      hd->found.uid_no = uid? uid_no : 0;
    }
  else if (rc == -1)
    {
      hd->current.eof = 1;
       
      if (use_offtbl && !kr_offtbl_ready)
        {
          KR_NAME kr;

           
          for (kr=kr_names; kr; kr = kr->next)
            {
              if (hd->resource == kr)
                {
                  kr->did_full_scan = 1;
                  break;
                }
            }
           
          for (kr=kr_names; kr; kr = kr->next)
            {
              if (!kr->did_full_scan)
                break;
            }
          if (!kr)
            kr_offtbl_ready = 1;
        }
    }
  else
    hd->current.error = rc;

  free_packet(&pkt);
  set_packet_list_mode(save_mode);
  return rc;
}