summaryrefslogtreecommitdiffstats
path: root/g10/keydb.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--g10/keydb.c2116
1 files changed, 2116 insertions, 0 deletions
diff --git a/g10/keydb.c b/g10/keydb.c
new file mode 100644
index 0000000..e538fe4
--- /dev/null
+++ b/g10/keydb.c
@@ -0,0 +1,2116 @@
+/* keydb.c - key database dispatcher
+ * Copyright (C) 2001-2013 Free Software Foundation, Inc.
+ * Coyrright (C) 2001-2015 Werner Koch
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "gpg.h"
+#include "../common/util.h"
+#include "../common/sysutils.h"
+#include "options.h"
+#include "main.h" /*try_make_homedir ()*/
+#include "packet.h"
+#include "keyring.h"
+#include "../kbx/keybox.h"
+#include "keydb.h"
+#include "../common/i18n.h"
+
+static int active_handles;
+
+typedef enum
+ {
+ KEYDB_RESOURCE_TYPE_NONE = 0,
+ KEYDB_RESOURCE_TYPE_KEYRING,
+ KEYDB_RESOURCE_TYPE_KEYBOX
+ } KeydbResourceType;
+#define MAX_KEYDB_RESOURCES 40
+
+struct resource_item
+{
+ KeydbResourceType type;
+ union {
+ KEYRING_HANDLE kr;
+ KEYBOX_HANDLE kb;
+ } u;
+ void *token;
+};
+
+static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
+static int used_resources;
+
+/* A pointer used to check for the primary key database by comparing
+ to the struct resource_item's TOKEN. */
+static void *primary_keydb;
+
+/* Whether we have successfully registered any resource. */
+static int any_registered;
+
+/* This is a simple cache used to return the last result of a
+ successful fingerprint search. This works only for keybox resources
+ because (due to lack of a copy_keyblock function) we need to store
+ an image of the keyblock which is fortunately instantly available
+ for keyboxes. */
+enum keyblock_cache_states {
+ KEYBLOCK_CACHE_EMPTY,
+ KEYBLOCK_CACHE_PREPARED,
+ KEYBLOCK_CACHE_FILLED
+};
+
+struct keyblock_cache {
+ enum keyblock_cache_states state;
+ byte fpr[MAX_FINGERPRINT_LEN];
+ iobuf_t iobuf; /* Image of the keyblock. */
+ int pk_no;
+ int uid_no;
+ /* Offset of the record in the keybox. */
+ int resource;
+ off_t offset;
+};
+
+
+struct keydb_handle
+{
+ /* When we locked all of the resources in ACTIVE (using keyring_lock
+ / keybox_lock, as appropriate). */
+ int locked;
+
+ /* If this flag is set a lock will only be released by
+ * keydb_release. */
+ int keep_lock;
+
+ /* The index into ACTIVE of the resources in which the last search
+ result was found. Initially -1. */
+ int found;
+
+ /* Initially -1 (invalid). This is used to save a search result and
+ later restore it as the selected result. */
+ int saved_found;
+
+ /* The number of skipped long blobs since the last search
+ (keydb_search_reset). */
+ unsigned long skipped_long_blobs;
+
+ /* If set, this disables the use of the keyblock cache. */
+ int no_caching;
+
+ /* Whether the next search will be from the beginning of the
+ database (and thus consider all records). */
+ int is_reset;
+
+ /* The "file position." In our case, this is index of the current
+ resource in ACTIVE. */
+ int current;
+
+ /* The number of resources in ACTIVE. */
+ int used;
+
+ /* Cache of the last found and parsed key block (only used for
+ keyboxes, not keyrings). */
+ struct keyblock_cache keyblock_cache;
+
+ /* Copy of ALL_RESOURCES when keydb_new is called. */
+ struct resource_item active[MAX_KEYDB_RESOURCES];
+};
+
+/* Looking up keys is expensive. To hide the cost, we cache whether
+ keys exist in the key database. Then, if we know a key does not
+ exist, we don't have to spend time looking it up. This
+ particularly helps the --list-sigs and --check-sigs commands.
+
+ The cache stores the results in a hash using separate chaining.
+ Concretely: we use the LSB of the keyid to index the hash table and
+ each bucket consists of a linked list of entries. An entry
+ consists of the 64-bit key id. If a key id is not in the cache,
+ then we don't know whether it is in the DB or not.
+
+ To simplify the cache consistency protocol, we simply flush the
+ whole cache whenever a key is inserted or updated. */
+
+#define KID_NOT_FOUND_CACHE_BUCKETS 256
+static struct kid_not_found_cache_bucket *
+ kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS];
+
+struct kid_not_found_cache_bucket
+{
+ struct kid_not_found_cache_bucket *next;
+ u32 kid[2];
+};
+
+struct
+{
+ unsigned int count; /* The current number of entries in the hash table. */
+ unsigned int peak; /* The peak of COUNT. */
+ unsigned int flushes; /* The number of flushes. */
+} kid_not_found_stats;
+
+struct
+{
+ unsigned int handles; /* Number of handles created. */
+ unsigned int locks; /* Number of locks taken. */
+ unsigned int parse_keyblocks; /* Number of parse_keyblock_image calls. */
+ unsigned int get_keyblocks; /* Number of keydb_get_keyblock calls. */
+ unsigned int build_keyblocks; /* Number of build_keyblock_image calls. */
+ unsigned int update_keyblocks;/* Number of update_keyblock calls. */
+ unsigned int insert_keyblocks;/* Number of update_keyblock calls. */
+ unsigned int delete_keyblocks;/* Number of delete_keyblock calls. */
+ unsigned int search_resets; /* Number of keydb_search_reset calls. */
+ unsigned int found; /* Number of successful keydb_search calls. */
+ unsigned int found_cached; /* Ditto but from the cache. */
+ unsigned int notfound; /* Number of failed keydb_search calls. */
+ unsigned int notfound_cached; /* Ditto but from the cache. */
+} keydb_stats;
+
+
+static int lock_all (KEYDB_HANDLE hd);
+static void unlock_all (KEYDB_HANDLE hd);
+
+
+/* Check whether the keyid KID is in key id is definitely not in the
+ database.
+
+ Returns:
+
+ 0 - Indeterminate: the key id is not in the cache; we don't know
+ whether the key is in the database or not. If you want a
+ definitive answer, you'll need to perform a lookup.
+
+ 1 - There is definitely no key with this key id in the database.
+ We searched for a key with this key id previously, but we
+ didn't find it in the database. */
+static int
+kid_not_found_p (u32 *kid)
+{
+ struct kid_not_found_cache_bucket *k;
+
+ for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next)
+ if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
+ {
+ if (DBG_CACHE)
+ log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n",
+ (ulong)kid[0], (ulong)kid[1]);
+ return 1;
+ }
+
+ if (DBG_CACHE)
+ log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n",
+ (ulong)kid[0], (ulong)kid[1]);
+ return 0;
+}
+
+
+/* Insert the keyid KID into the kid_not_found_cache. FOUND is whether
+ the key is in the key database or not.
+
+ Note this function does not check whether the key id is already in
+ the cache. As such, kid_not_found_p() should be called first. */
+static void
+kid_not_found_insert (u32 *kid)
+{
+ struct kid_not_found_cache_bucket *k;
+
+ if (DBG_CACHE)
+ log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n",
+ (ulong)kid[0], (ulong)kid[1]);
+ k = xmalloc (sizeof *k);
+ k->kid[0] = kid[0];
+ k->kid[1] = kid[1];
+ k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS];
+ kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k;
+ kid_not_found_stats.count++;
+}
+
+
+/* Flush the kid not found cache. */
+static void
+kid_not_found_flush (void)
+{
+ struct kid_not_found_cache_bucket *k, *knext;
+ int i;
+
+ if (DBG_CACHE)
+ log_debug ("keydb: kid_not_found_flush\n");
+
+ if (!kid_not_found_stats.count)
+ return;
+
+ for (i=0; i < DIM(kid_not_found_cache); i++)
+ {
+ for (k = kid_not_found_cache[i]; k; k = knext)
+ {
+ knext = k->next;
+ xfree (k);
+ }
+ kid_not_found_cache[i] = NULL;
+ }
+ if (kid_not_found_stats.count > kid_not_found_stats.peak)
+ kid_not_found_stats.peak = kid_not_found_stats.count;
+ kid_not_found_stats.count = 0;
+ kid_not_found_stats.flushes++;
+}
+
+
+static void
+keyblock_cache_clear (struct keydb_handle *hd)
+{
+ hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
+ iobuf_close (hd->keyblock_cache.iobuf);
+ hd->keyblock_cache.iobuf = NULL;
+ hd->keyblock_cache.resource = -1;
+ hd->keyblock_cache.offset = -1;
+}
+
+
+/* Handle the creation of a keyring or a keybox if it does not yet
+ exist. Take into account that other processes might have the
+ keyring/keybox already locked. This lock check does not work if
+ the directory itself is not yet available. If IS_BOX is true the
+ filename is expected to refer to a keybox. If FORCE_CREATE is true
+ the keyring or keybox will be created.
+
+ Return 0 if it is okay to access the specified file. */
+static gpg_error_t
+maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
+{
+ gpg_err_code_t ec;
+ dotlock_t lockhd = NULL;
+ IOBUF iobuf;
+ int rc;
+ mode_t oldmask;
+ char *last_slash_in_filename;
+ char *bak_fname = NULL;
+ char *tmp_fname = NULL;
+ int save_slash;
+
+ /* A quick test whether the filename already exists. */
+ if (!gnupg_access (filename, F_OK))
+ return !gnupg_access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
+
+ /* If we don't want to create a new file at all, there is no need to
+ go any further - bail out right here. */
+ if (!force_create)
+ return gpg_error (GPG_ERR_ENOENT);
+
+ /* First of all we try to create the home directory. Note, that we
+ don't do any locking here because any sane application of gpg
+ would create the home directory by itself and not rely on gpg's
+ tricky auto-creation which is anyway only done for certain home
+ directory name pattern. */
+ last_slash_in_filename = strrchr (filename, DIRSEP_C);
+#if HAVE_W32_SYSTEM
+ {
+ /* Windows may either have a slash or a backslash. Take care of it. */
+ char *p = strrchr (filename, '/');
+ if (!last_slash_in_filename || p > last_slash_in_filename)
+ last_slash_in_filename = p;
+ }
+#endif /*HAVE_W32_SYSTEM*/
+ if (!last_slash_in_filename)
+ return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
+ not happen though. */
+ save_slash = *last_slash_in_filename;
+ *last_slash_in_filename = 0;
+ if (gnupg_access(filename, F_OK))
+ {
+ static int tried;
+
+ if (!tried)
+ {
+ tried = 1;
+ try_make_homedir (filename);
+ }
+ if ((ec = gnupg_access (filename, F_OK)))
+ {
+ rc = gpg_error (ec);
+ *last_slash_in_filename = save_slash;
+ goto leave;
+ }
+ }
+ *last_slash_in_filename = save_slash;
+
+ /* To avoid races with other instances of gpg trying to create or
+ update the keyring (it is removed during an update for a short
+ time), we do the next stuff in a locked state. */
+ lockhd = dotlock_create (filename, 0);
+ if (!lockhd)
+ {
+ rc = gpg_error_from_syserror ();
+ /* A reason for this to fail is that the directory is not
+ writable. However, this whole locking stuff does not make
+ sense if this is the case. An empty non-writable directory
+ with no keyring is not really useful at all. */
+ if (opt.verbose)
+ log_info ("can't allocate lock for '%s': %s\n",
+ filename, gpg_strerror (rc));
+
+ if (!force_create)
+ return gpg_error (GPG_ERR_ENOENT); /* Won't happen. */
+ else
+ return rc;
+ }
+
+ if ( dotlock_take (lockhd, -1) )
+ {
+ rc = gpg_error_from_syserror ();
+ /* This is something bad. Probably a stale lockfile. */
+ log_info ("can't lock '%s': %s\n", filename, gpg_strerror (rc));
+ goto leave;
+ }
+
+ /* Now the real test while we are locked. */
+
+ /* Gpg either uses pubring.gpg or pubring.kbx and thus different
+ * lock files. Now, when one gpg process is updating a pubring.gpg
+ * and thus holding the corresponding lock, a second gpg process may
+ * get to here at the time between the two rename operation used by
+ * the first process to update pubring.gpg. The lock taken above
+ * may not protect the second process if it tries to create a
+ * pubring.kbx file which would be protected by a different lock
+ * file.
+ *
+ * We can detect this case by checking that the two temporary files
+ * used by the update code exist at the same time. In that case we
+ * do not create a new file but act as if FORCE_CREATE has not been
+ * given. Obviously there is a race between our two checks but the
+ * worst thing is that we won't create a new file, which is better
+ * than to accidentally creating one. */
+ rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname);
+ if (rc)
+ goto leave;
+
+ if (!gnupg_access (filename, F_OK))
+ {
+ rc = 0; /* Okay, we may access the file now. */
+ goto leave;
+ }
+ if (!gnupg_access (bak_fname, F_OK) && !gnupg_access (tmp_fname, F_OK))
+ {
+ /* Very likely another process is updating a pubring.gpg and we
+ should not create a pubring.kbx. */
+ rc = gpg_error (GPG_ERR_ENOENT);
+ goto leave;
+ }
+
+
+ /* The file does not yet exist, create it now. */
+ oldmask = umask (077);
+ if (is_secured_filename (filename))
+ {
+ iobuf = NULL;
+ gpg_err_set_errno (EPERM);
+ }
+ else
+ iobuf = iobuf_create (filename, 0);
+ umask (oldmask);
+ if (!iobuf)
+ {
+ rc = gpg_error_from_syserror ();
+ if (is_box)
+ log_error (_("error creating keybox '%s': %s\n"),
+ filename, gpg_strerror (rc));
+ else
+ log_error (_("error creating keyring '%s': %s\n"),
+ filename, gpg_strerror (rc));
+ goto leave;
+ }
+
+ iobuf_close (iobuf);
+ /* Must invalidate that ugly cache */
+ iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, filename);
+
+ /* Make sure that at least one record is in a new keybox file, so
+ that the detection magic will work the next time it is used. */
+ if (is_box)
+ {
+ estream_t fp = es_fopen (filename, "wb");
+ if (!fp)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ rc = _keybox_write_header_blob (fp, 1);
+ es_fclose (fp);
+ }
+ if (rc)
+ {
+ if (is_box)
+ log_error (_("error creating keybox '%s': %s\n"),
+ filename, gpg_strerror (rc));
+ else
+ log_error (_("error creating keyring '%s': %s\n"),
+ filename, gpg_strerror (rc));
+ goto leave;
+ }
+ }
+
+ if (!opt.quiet)
+ {
+ if (is_box)
+ log_info (_("keybox '%s' created\n"), filename);
+ else
+ log_info (_("keyring '%s' created\n"), filename);
+ }
+
+ rc = 0;
+
+ leave:
+ if (lockhd)
+ {
+ dotlock_release (lockhd);
+ dotlock_destroy (lockhd);
+ }
+ xfree (bak_fname);
+ xfree (tmp_fname);
+ return rc;
+}
+
+
+/* Helper for keydb_add_resource. Opens FILENAME to figure out the
+ resource type.
+
+ Returns the specified file's likely type. If the file does not
+ exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0.
+ Otherwise, tries to figure out the file's type. This is either
+ KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or
+ KEYDB_RESOURCE_TYPE_KEYNONE. If the file is a keybox and it has
+ the OpenPGP flag set, then R_OPENPGP is also set. */
+static KeydbResourceType
+rt_from_file (const char *filename, int *r_found, int *r_openpgp)
+{
+ u32 magic;
+ unsigned char verbuf[4];
+ FILE *fp;
+ KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
+
+ *r_found = *r_openpgp = 0;
+ fp = gnupg_fopen (filename, "rb");
+ if (fp)
+ {
+ *r_found = 1;
+
+ if (fread (&magic, 4, 1, fp) == 1 )
+ {
+ if (magic == 0x13579ace || magic == 0xce9a5713)
+ ; /* GDBM magic - not anymore supported. */
+ else if (fread (&verbuf, 4, 1, fp) == 1
+ && verbuf[0] == 1
+ && fread (&magic, 4, 1, fp) == 1
+ && !memcmp (&magic, "KBXf", 4))
+ {
+ if ((verbuf[3] & 0x02))
+ *r_openpgp = 1;
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ }
+ else
+ rt = KEYDB_RESOURCE_TYPE_KEYRING;
+ }
+ else /* Maybe empty: assume keyring. */
+ rt = KEYDB_RESOURCE_TYPE_KEYRING;
+
+ fclose (fp);
+ }
+
+ return rt;
+}
+
+char *
+keydb_search_desc_dump (struct keydb_search_desc *desc)
+{
+ char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
+ char fpr[2 * MAX_FINGERPRINT_LEN + 1];
+
+ switch (desc->mode)
+ {
+ case KEYDB_SEARCH_MODE_EXACT:
+ return xasprintf ("EXACT: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ return xasprintf ("SUBSTR: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_MAIL:
+ return xasprintf ("MAIL: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_MAILSUB:
+ return xasprintf ("MAILSUB: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_MAILEND:
+ return xasprintf ("MAILEND: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_WORDS:
+ return xasprintf ("WORDS: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ return xasprintf ("SHORT_KID: '%s'",
+ format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b)));
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ return xasprintf ("LONG_KID: '%s'",
+ format_keyid (desc->u.kid, KF_LONG, b, sizeof (b)));
+ case KEYDB_SEARCH_MODE_FPR16:
+ bin2hex (desc->u.fpr, 16, fpr);
+ return xasprintf ("FPR16: '%s'",
+ format_hexfingerprint (fpr, b, sizeof (b)));
+ case KEYDB_SEARCH_MODE_FPR20:
+ bin2hex (desc->u.fpr, 20, fpr);
+ return xasprintf ("FPR20: '%s'",
+ format_hexfingerprint (fpr, b, sizeof (b)));
+ case KEYDB_SEARCH_MODE_FPR:
+ bin2hex (desc->u.fpr, 20, fpr);
+ return xasprintf ("FPR: '%s'",
+ format_hexfingerprint (fpr, b, sizeof (b)));
+ case KEYDB_SEARCH_MODE_ISSUER:
+ return xasprintf ("ISSUER: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_ISSUER_SN:
+ return xasprintf ("ISSUER_SN: '%*s'",
+ (int) (desc->snlen == -1
+ ? strlen (desc->sn) : desc->snlen),
+ desc->sn);
+ case KEYDB_SEARCH_MODE_SN:
+ return xasprintf ("SN: '%*s'",
+ (int) (desc->snlen == -1
+ ? strlen (desc->sn) : desc->snlen),
+ desc->sn);
+ case KEYDB_SEARCH_MODE_SUBJECT:
+ return xasprintf ("SUBJECT: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_KEYGRIP:
+ return xasprintf ("KEYGRIP: %s", desc->u.grip);
+ case KEYDB_SEARCH_MODE_FIRST:
+ return xasprintf ("FIRST");
+ case KEYDB_SEARCH_MODE_NEXT:
+ return xasprintf ("NEXT");
+ default:
+ return xasprintf ("Bad search mode (%d)", desc->mode);
+ }
+}
+
+
+
+/* Register a resource (keyring or keybox). The first keyring or
+ * keybox that is added using this function is created if it does not
+ * already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set.
+ *
+ * FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants.
+ *
+ * URL must have the following form:
+ *
+ * gnupg-ring:filename = plain keyring
+ * gnupg-kbx:filename = keybox file
+ * filename = check file's type (create as a plain keyring)
+ *
+ * Note: on systems with drive letters (Windows) invalid URLs (i.e.,
+ * those with an unrecognized part before the ':' such as "c:\...")
+ * will silently be treated as bare filenames. On other systems, such
+ * URLs will cause this function to return GPG_ERR_GENERAL.
+ *
+ * If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring
+ * and the file ends in ".gpg", then this function also checks if a
+ * file with the same name, but the extension ".kbx" exists, is a
+ * keybox and the OpenPGP flag is set. If so, this function opens
+ * that resource instead.
+ *
+ * If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and
+ * the URL ends in ".kbx", then this function will try opening the
+ * same URL, but with the extension ".gpg". If that file is a keybox
+ * with the OpenPGP flag set or it is a keyring, then we use that
+ * instead.
+ *
+ * If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the
+ * file should be created and the file's extension is ".gpg" then we
+ * replace the extension with ".kbx".
+ *
+ * If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a
+ * keyring (not a keybox), then this resource is considered the
+ * primary resource. This is used by keydb_locate_writable(). If
+ * another primary keyring is set, then that keyring is considered the
+ * primary.
+ *
+ * If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a
+ * keyring (not a keybox), then the keyring is marked as read only and
+ * operations just as keyring_insert_keyblock will return
+ * GPG_ERR_ACCESS. */
+gpg_error_t
+keydb_add_resource (const char *url, unsigned int flags)
+{
+ /* The file named by the URL (i.e., without the prototype). */
+ const char *resname = url;
+
+ char *filename = NULL;
+ int create;
+ int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY);
+ int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT);
+ int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF);
+ gpg_error_t err = 0;
+ KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
+ void *token;
+
+ /* Create the resource if it is the first registered one. */
+ create = (!read_only && !any_registered);
+
+ if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) )
+ {
+ rt = KEYDB_RESOURCE_TYPE_KEYRING;
+ resname += 11;
+ }
+ else if (strlen (resname) > 10 && !strncmp (resname, "gnupg-kbx:", 10) )
+ {
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ resname += 10;
+ }
+#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
+ else if (strchr (resname, ':'))
+ {
+ log_error ("invalid key resource URL '%s'\n", url );
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
+
+ if (*resname != DIRSEP_C
+#ifdef HAVE_W32_SYSTEM
+ && *resname != '/' /* Fixme: does not handle drive letters. */
+#endif
+ )
+ {
+ /* Do tilde expansion etc. */
+ if (strchr (resname, DIRSEP_C)
+#ifdef HAVE_W32_SYSTEM
+ || strchr (resname, '/') /* Windows also accepts this. */
+#endif
+ )
+ filename = make_filename (resname, NULL);
+ else
+ filename = make_filename (gnupg_homedir (), resname, NULL);
+ }
+ else
+ filename = xstrdup (resname);
+
+ /* See whether we can determine the filetype. */
+ if (rt == KEYDB_RESOURCE_TYPE_NONE)
+ {
+ int found, openpgp_flag;
+ int pass = 0;
+ size_t filenamelen;
+
+ check_again:
+ filenamelen = strlen (filename);
+ rt = rt_from_file (filename, &found, &openpgp_flag);
+ if (found)
+ {
+ /* The file exists and we have the resource type in RT.
+
+ Now let us check whether in addition to the "pubring.gpg"
+ a "pubring.kbx with openpgp keys exists. This is so that
+ GPG 2.1 will use an existing "pubring.kbx" by default iff
+ that file has been created or used by 2.1. This check is
+ needed because after creation or use of the kbx file with
+ 2.1 an older version of gpg may have created a new
+ pubring.gpg for its own use. */
+ if (!pass && is_default && rt == KEYDB_RESOURCE_TYPE_KEYRING
+ && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
+ {
+ strcpy (filename+filenamelen-4, ".kbx");
+ if ((rt_from_file (filename, &found, &openpgp_flag)
+ == KEYDB_RESOURCE_TYPE_KEYBOX) && found && openpgp_flag)
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ else /* Restore filename */
+ strcpy (filename+filenamelen-4, ".gpg");
+ }
+ }
+ else if (!pass && is_gpgvdef
+ && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx"))
+ {
+ /* Not found but gpgv's default "trustedkeys.kbx" file has
+ been requested. We did not found it so now check whether
+ a "trustedkeys.gpg" file exists and use that instead. */
+ KeydbResourceType rttmp;
+
+ strcpy (filename+filenamelen-4, ".gpg");
+ rttmp = rt_from_file (filename, &found, &openpgp_flag);
+ if (found
+ && ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag)
+ || (rttmp == KEYDB_RESOURCE_TYPE_KEYRING)))
+ rt = rttmp;
+ else /* Restore filename */
+ strcpy (filename+filenamelen-4, ".kbx");
+ }
+ else if (!pass
+ && is_default && create
+ && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
+ {
+ /* The file does not exist, the default resource has been
+ requested, the file shall be created, and the file has a
+ ".gpg" suffix. Change the suffix to ".kbx" and try once
+ more. This way we achieve that we open an existing
+ ".gpg" keyring, but create a new keybox file with an
+ ".kbx" suffix. */
+ strcpy (filename+filenamelen-4, ".kbx");
+ pass++;
+ goto check_again;
+ }
+ else /* No file yet: create keybox. */
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ }
+
+ switch (rt)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ log_error ("unknown type of key resource '%s'\n", url );
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ err = maybe_create_keyring_or_box (filename, 0, create);
+ if (err)
+ goto leave;
+
+ if (keyring_register_filename (filename, read_only, &token))
+ {
+ if (used_resources >= MAX_KEYDB_RESOURCES)
+ err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
+ else
+ {
+ if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
+ primary_keydb = token;
+ all_resources[used_resources].type = rt;
+ all_resources[used_resources].u.kr = NULL; /* Not used here */
+ all_resources[used_resources].token = token;
+ used_resources++;
+ }
+ }
+ else
+ {
+ /* This keyring was already registered, so ignore it.
+ However, we can still mark it as primary even if it was
+ already registered. */
+ if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
+ primary_keydb = token;
+ }
+ break;
+
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ {
+ err = maybe_create_keyring_or_box (filename, 1, create);
+ if (err)
+ goto leave;
+
+ err = keybox_register_file (filename, 0, &token);
+ if (!err)
+ {
+ if (used_resources >= MAX_KEYDB_RESOURCES)
+ err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
+ else
+ {
+ KEYBOX_HANDLE kbxhd;
+
+ if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
+ primary_keydb = token;
+ all_resources[used_resources].type = rt;
+ all_resources[used_resources].u.kb = NULL; /* Not used here */
+ all_resources[used_resources].token = token;
+
+ /* Do a compress run if needed and no other user is
+ * currently using the keybox. */
+ kbxhd = keybox_new_openpgp (token, 0);
+ if (kbxhd)
+ {
+ if (!keybox_lock (kbxhd, 1, 0))
+ {
+ keybox_compress (kbxhd);
+ keybox_lock (kbxhd, 0, 0);
+ }
+
+ keybox_release (kbxhd);
+ }
+
+ used_resources++;
+ }
+ }
+ else if (gpg_err_code (err) == GPG_ERR_EEXIST)
+ {
+ /* Already registered. We will mark it as the primary key
+ if requested. */
+ if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
+ primary_keydb = token;
+ }
+ }
+ break;
+
+ default:
+ log_error ("resource type of '%s' not supported\n", url);
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ /* fixme: check directory permissions and print a warning */
+
+ leave:
+ if (err)
+ {
+ log_error (_("keyblock resource '%s': %s\n"),
+ filename, gpg_strerror (err));
+ write_status_error ("add_keyblock_resource", err);
+ }
+ else
+ any_registered = 1;
+ xfree (filename);
+ return err;
+}
+
+
+void
+keydb_dump_stats (void)
+{
+ log_info ("keydb: handles=%u locks=%u parse=%u get=%u\n",
+ keydb_stats.handles,
+ keydb_stats.locks,
+ keydb_stats.parse_keyblocks,
+ keydb_stats.get_keyblocks);
+ log_info (" build=%u update=%u insert=%u delete=%u\n",
+ keydb_stats.build_keyblocks,
+ keydb_stats.update_keyblocks,
+ keydb_stats.insert_keyblocks,
+ keydb_stats.delete_keyblocks);
+ log_info (" reset=%u found=%u not=%u cache=%u not=%u\n",
+ keydb_stats.search_resets,
+ keydb_stats.found,
+ keydb_stats.notfound,
+ keydb_stats.found_cached,
+ keydb_stats.notfound_cached);
+ log_info ("kid_not_found_cache: count=%u peak=%u flushes=%u\n",
+ kid_not_found_stats.count,
+ kid_not_found_stats.peak,
+ kid_not_found_stats.flushes);
+}
+
+
+/* Create a new database handle. A database handle is similar to a
+ file handle: it contains a local file position. This is used when
+ searching: subsequent searches resume where the previous search
+ left off. To rewind the position, use keydb_search_reset(). This
+ function returns NULL on error, sets ERRNO, and prints an error
+ diagnostic. */
+KEYDB_HANDLE
+keydb_new (void)
+{
+ KEYDB_HANDLE hd;
+ int i, j;
+ int die = 0;
+ int reterrno;
+
+ if (DBG_CLOCK)
+ log_clock ("keydb_new");
+
+ hd = xtrycalloc (1, sizeof *hd);
+ if (!hd)
+ goto leave;
+ hd->found = -1;
+ hd->saved_found = -1;
+ hd->is_reset = 1;
+
+ log_assert (used_resources <= MAX_KEYDB_RESOURCES);
+ for (i=j=0; ! die && i < used_resources; i++)
+ {
+ switch (all_resources[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ hd->active[j].type = all_resources[i].type;
+ hd->active[j].token = all_resources[i].token;
+ hd->active[j].u.kr = keyring_new (all_resources[i].token);
+ if (!hd->active[j].u.kr)
+ {
+ reterrno = errno;
+ die = 1;
+ }
+ j++;
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ hd->active[j].type = all_resources[i].type;
+ hd->active[j].token = all_resources[i].token;
+ hd->active[j].u.kb = keybox_new_openpgp (all_resources[i].token, 0);
+ if (!hd->active[j].u.kb)
+ {
+ reterrno = errno;
+ die = 1;
+ }
+ j++;
+ break;
+ }
+ }
+ hd->used = j;
+
+ active_handles++;
+ keydb_stats.handles++;
+
+ if (die)
+ {
+ keydb_release (hd);
+ gpg_err_set_errno (reterrno);
+ hd = NULL;
+ }
+
+ leave:
+ if (!hd)
+ log_error (_("error opening key DB: %s\n"),
+ gpg_strerror (gpg_error_from_syserror()));
+
+ return hd;
+}
+
+
+void
+keydb_release (KEYDB_HANDLE hd)
+{
+ int i;
+
+ if (!hd)
+ return;
+ log_assert (active_handles > 0);
+ active_handles--;
+
+ hd->keep_lock = 0;
+ unlock_all (hd);
+ for (i=0; i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ keyring_release (hd->active[i].u.kr);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_release (hd->active[i].u.kb);
+ break;
+ }
+ }
+
+ keyblock_cache_clear (hd);
+ xfree (hd);
+}
+
+
+/* Take a lock on the files immediately and not only during insert or
+ * update. This lock is released with keydb_release. */
+gpg_error_t
+keydb_lock (KEYDB_HANDLE hd)
+{
+ gpg_error_t err;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ err = lock_all (hd);
+ if (!err)
+ hd->keep_lock = 1;
+
+ return err;
+}
+
+
+/* Set a flag on the handle to suppress use of cached results. This
+ * is required for updating a keyring and for key listings. Fixme:
+ * Using a new parameter for keydb_new might be a better solution. */
+void
+keydb_disable_caching (KEYDB_HANDLE hd)
+{
+ if (hd)
+ hd->no_caching = 1;
+}
+
+
+/* Return the file name of the resource in which the current search
+ * result was found or, if there is no search result, the filename of
+ * the current resource (i.e., the resource that the file position
+ * points to). Note: the filename is not necessarily the URL used to
+ * open it!
+ *
+ * This function only returns NULL if no handle is specified, in all
+ * other error cases an empty string is returned. */
+const char *
+keydb_get_resource_name (KEYDB_HANDLE hd)
+{
+ int idx;
+ const char *s = NULL;
+
+ if (!hd)
+ return NULL;
+
+ if ( hd->found >= 0 && hd->found < hd->used)
+ idx = hd->found;
+ else if ( hd->current >= 0 && hd->current < hd->used)
+ idx = hd->current;
+ else
+ idx = 0;
+
+ switch (hd->active[idx].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ s = NULL;
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ s = keyring_get_resource_name (hd->active[idx].u.kr);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ s = keybox_get_resource_name (hd->active[idx].u.kb);
+ break;
+ }
+
+ return s? s: "";
+}
+
+
+
+static int
+lock_all (KEYDB_HANDLE hd)
+{
+ int i, rc = 0;
+
+ /* Fixme: This locking scheme may lead to a deadlock if the resources
+ are not added in the same order by all processes. We are
+ currently only allowing one resource so it is not a problem.
+ [Oops: Who claimed the latter]
+
+ To fix this we need to use a lock file to protect lock_all. */
+
+ for (i=0; !rc && i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ rc = keyring_lock (hd->active[i].u.kr, 1);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_lock (hd->active[i].u.kb, 1, -1);
+ break;
+ }
+ }
+
+ if (rc)
+ {
+ /* Revert the already taken locks. */
+ for (i--; i >= 0; i--)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ keyring_lock (hd->active[i].u.kr, 0);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_lock (hd->active[i].u.kb, 0, 0);
+ break;
+ }
+ }
+ }
+ else
+ {
+ hd->locked = 1;
+ keydb_stats.locks++;
+ }
+
+ return rc;
+}
+
+
+static void
+unlock_all (KEYDB_HANDLE hd)
+{
+ int i;
+
+ if (!hd->locked || hd->keep_lock)
+ return;
+
+ for (i=hd->used-1; i >= 0; i--)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ keyring_lock (hd->active[i].u.kr, 0);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_lock (hd->active[i].u.kb, 0, 0);
+ break;
+ }
+ }
+ hd->locked = 0;
+}
+
+
+
+/* Save the last found state and invalidate the current selection
+ * (i.e., the entry selected by keydb_search() is invalidated and
+ * something like keydb_get_keyblock() will return an error). This
+ * does not change the file position. This makes it possible to do
+ * something like:
+ *
+ * keydb_search (hd, ...); // Result 1.
+ * keydb_push_found_state (hd);
+ * keydb_search_reset (hd);
+ * keydb_search (hd, ...); // Result 2.
+ * keydb_pop_found_state (hd);
+ * keydb_get_keyblock (hd, ...); // -> Result 1.
+ *
+ * Note: it is only possible to save a single save state at a time.
+ * In other words, the save stack only has room for a single
+ * instance of the state. */
+void
+keydb_push_found_state (KEYDB_HANDLE hd)
+{
+ if (!hd)
+ return;
+
+ if (hd->found < 0 || hd->found >= hd->used)
+ {
+ hd->saved_found = -1;
+ return;
+ }
+
+ switch (hd->active[hd->found].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ keyring_push_found_state (hd->active[hd->found].u.kr);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_push_found_state (hd->active[hd->found].u.kb);
+ break;
+ }
+
+ hd->saved_found = hd->found;
+ hd->found = -1;
+}
+
+
+/* Restore the previous save state. If the saved state is NULL or
+ invalid, this is a NOP. */
+void
+keydb_pop_found_state (KEYDB_HANDLE hd)
+{
+ if (!hd)
+ return;
+
+ hd->found = hd->saved_found;
+ hd->saved_found = -1;
+ if (hd->found < 0 || hd->found >= hd->used)
+ return;
+
+ switch (hd->active[hd->found].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ keyring_pop_found_state (hd->active[hd->found].u.kr);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_pop_found_state (hd->active[hd->found].u.kb);
+ break;
+ }
+}
+
+
+
+static gpg_error_t
+parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
+ kbnode_t *r_keyblock)
+{
+ gpg_error_t err;
+ struct parse_packet_ctx_s parsectx;
+ PACKET *pkt;
+ kbnode_t keyblock = NULL;
+ kbnode_t node, *tail;
+ int in_cert, save_mode;
+ int pk_count, uid_count;
+
+ *r_keyblock = NULL;
+
+ pkt = xtrymalloc (sizeof *pkt);
+ if (!pkt)
+ return gpg_error_from_syserror ();
+ init_packet (pkt);
+ init_parse_packet (&parsectx, iobuf);
+ save_mode = set_packet_list_mode (0);
+ in_cert = 0;
+ tail = NULL;
+ pk_count = uid_count = 0;
+ while ((err = parse_packet (&parsectx, pkt)) != -1)
+ {
+ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
+ {
+ free_packet (pkt, &parsectx);
+ init_packet (pkt);
+ continue;
+ }
+ if (err)
+ {
+ es_fflush (es_stdout);
+ log_error ("parse_keyblock_image: read error: %s\n",
+ gpg_strerror (err));
+ if (gpg_err_code (err) == GPG_ERR_INV_PACKET)
+ {
+ free_packet (pkt, &parsectx);
+ init_packet (pkt);
+ continue;
+ }
+ /* Unknown version maybe due to v5 keys - we treat this
+ * error different. */
+ if (gpg_err_code (err) != GPG_ERR_UNKNOWN_VERSION)
+ err = gpg_error (GPG_ERR_INV_KEYRING);
+ break;
+ }
+
+ /* Filter allowed packets. */
+ switch (pkt->pkttype)
+ {
+ case PKT_PUBLIC_KEY:
+ case PKT_PUBLIC_SUBKEY:
+ case PKT_SECRET_KEY:
+ case PKT_SECRET_SUBKEY:
+ case PKT_USER_ID:
+ case PKT_ATTRIBUTE:
+ case PKT_SIGNATURE:
+ case PKT_RING_TRUST:
+ break; /* Allowed per RFC. */
+
+ default:
+ log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype);
+ free_packet(pkt, &parsectx);
+ init_packet(pkt);
+ continue;
+ }
+
+ /* Other sanity checks. */
+ if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
+ {
+ log_error ("parse_keyblock_image: first packet in a keybox blob "
+ "is not a public key packet\n");
+ err = gpg_error (GPG_ERR_INV_KEYRING);
+ break;
+ }
+ if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
+ || pkt->pkttype == PKT_SECRET_KEY))
+ {
+ log_error ("parse_keyblock_image: "
+ "multiple keyblocks in a keybox blob\n");
+ err = gpg_error (GPG_ERR_INV_KEYRING);
+ break;
+ }
+ in_cert = 1;
+
+ node = new_kbnode (pkt);
+
+ switch (pkt->pkttype)
+ {
+ case PKT_PUBLIC_KEY:
+ case PKT_PUBLIC_SUBKEY:
+ case PKT_SECRET_KEY:
+ case PKT_SECRET_SUBKEY:
+ if (++pk_count == pk_no)
+ node->flag |= 1;
+ break;
+
+ case PKT_USER_ID:
+ if (++uid_count == uid_no)
+ node->flag |= 2;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!keyblock)
+ keyblock = node;
+ else
+ *tail = node;
+ tail = &node->next;
+ pkt = xtrymalloc (sizeof *pkt);
+ if (!pkt)
+ {
+ err = gpg_error_from_syserror ();
+ break;
+ }
+ init_packet (pkt);
+ }
+ set_packet_list_mode (save_mode);
+
+ if (err == -1 && keyblock)
+ err = 0; /* Got the entire keyblock. */
+
+ if (err)
+ release_kbnode (keyblock);
+ else
+ {
+ *r_keyblock = keyblock;
+ keydb_stats.parse_keyblocks++;
+ }
+ free_packet (pkt, &parsectx);
+ deinit_parse_packet (&parsectx);
+ xfree (pkt);
+ return err;
+}
+
+
+/* Return the keyblock last found by keydb_search() in *RET_KB.
+ *
+ * On success, the function returns 0 and the caller must free *RET_KB
+ * using release_kbnode(). Otherwise, the function returns an error
+ * code.
+ *
+ * The returned keyblock has the kbnode flag bit 0 set for the node
+ * with the public key used to locate the keyblock or flag bit 1 set
+ * for the user ID node. */
+gpg_error_t
+keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
+{
+ gpg_error_t err = 0;
+
+ *ret_kb = NULL;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ if (DBG_CLOCK)
+ log_clock ("keydb_get_keybock enter");
+
+ if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
+ {
+ err = iobuf_seek (hd->keyblock_cache.iobuf, 0);
+ if (err)
+ {
+ log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n");
+ keyblock_cache_clear (hd);
+ }
+ else
+ {
+ err = parse_keyblock_image (hd->keyblock_cache.iobuf,
+ hd->keyblock_cache.pk_no,
+ hd->keyblock_cache.uid_no,
+ ret_kb);
+ if (err)
+ keyblock_cache_clear (hd);
+ if (DBG_CLOCK)
+ log_clock (err? "keydb_get_keyblock leave (cached, failed)"
+ : "keydb_get_keyblock leave (cached)");
+ return err;
+ }
+ }
+
+ if (hd->found < 0 || hd->found >= hd->used)
+ return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
+
+ switch (hd->active[hd->found].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ err = gpg_error (GPG_ERR_GENERAL); /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ err = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ {
+ iobuf_t iobuf;
+ int pk_no, uid_no;
+
+ err = keybox_get_keyblock (hd->active[hd->found].u.kb,
+ &iobuf, &pk_no, &uid_no);
+ if (!err)
+ {
+ err = parse_keyblock_image (iobuf, pk_no, uid_no, ret_kb);
+ if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
+ {
+ hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED;
+ hd->keyblock_cache.iobuf = iobuf;
+ hd->keyblock_cache.pk_no = pk_no;
+ hd->keyblock_cache.uid_no = uid_no;
+ }
+ else
+ {
+ iobuf_close (iobuf);
+ }
+ }
+ }
+ break;
+ }
+
+ if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
+ keyblock_cache_clear (hd);
+
+ if (!err)
+ keydb_stats.get_keyblocks++;
+
+ if (DBG_CLOCK)
+ log_clock (err? "keydb_get_keyblock leave (failed)"
+ : "keydb_get_keyblock leave");
+ return err;
+}
+
+
+/* Build a keyblock image from KEYBLOCK. Returns 0 on success and
+ * only then stores a new iobuf object at R_IOBUF. */
+static gpg_error_t
+build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf)
+{
+ gpg_error_t err;
+ iobuf_t iobuf;
+ kbnode_t kbctx, node;
+
+ *r_iobuf = NULL;
+
+ iobuf = iobuf_temp ();
+ for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
+ {
+ /* Make sure to use only packets valid on a keyblock. */
+ switch (node->pkt->pkttype)
+ {
+ case PKT_PUBLIC_KEY:
+ case PKT_PUBLIC_SUBKEY:
+ case PKT_SIGNATURE:
+ case PKT_USER_ID:
+ case PKT_ATTRIBUTE:
+ case PKT_RING_TRUST:
+ break;
+ default:
+ continue;
+ }
+
+ err = build_packet_and_meta (iobuf, node->pkt);
+ if (err)
+ {
+ iobuf_close (iobuf);
+ return err;
+ }
+ }
+
+ keydb_stats.build_keyblocks++;
+ *r_iobuf = iobuf;
+ return 0;
+}
+
+
+/* Update the keyblock KB (i.e., extract the fingerprint and find the
+ * corresponding keyblock in the keyring).
+ *
+ * This doesn't do anything if --dry-run was specified.
+ *
+ * Returns 0 on success. Otherwise, it returns an error code. Note:
+ * if there isn't a keyblock in the keyring corresponding to KB, then
+ * this function returns GPG_ERR_VALUE_NOT_FOUND.
+ *
+ * This function selects the matching record and modifies the current
+ * file position to point to the record just after the selected entry.
+ * Thus, if you do a subsequent search using HD, you should first do a
+ * keydb_search_reset. Further, if the selected record is important,
+ * you should use keydb_push_found_state and keydb_pop_found_state to
+ * save and restore it. */
+gpg_error_t
+keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
+{
+ gpg_error_t err;
+ PKT_public_key *pk;
+ KEYDB_SEARCH_DESC desc;
+ size_t len;
+
+ log_assert (kb);
+ log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+ pk = kb->pkt->pkt.public_key;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ kid_not_found_flush ();
+ keyblock_cache_clear (hd);
+
+ if (opt.dry_run)
+ return 0;
+
+ err = lock_all (hd);
+ if (err)
+ return err;
+
+#ifdef USE_TOFU
+ tofu_notice_key_changed (ctrl, kb);
+#endif
+
+ memset (&desc, 0, sizeof (desc));
+ fingerprint_from_pk (pk, desc.u.fpr, &len);
+ if (len == 20)
+ desc.mode = KEYDB_SEARCH_MODE_FPR20;
+ else
+ log_bug ("%s: Unsupported key length: %zu\n", __func__, len);
+
+ keydb_search_reset (hd);
+ err = keydb_search (hd, &desc, 1, NULL);
+ if (err)
+ return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
+ log_assert (hd->found >= 0 && hd->found < hd->used);
+
+ switch (hd->active[hd->found].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ err = gpg_error (GPG_ERR_GENERAL); /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ err = keyring_update_keyblock (hd->active[hd->found].u.kr, kb);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ {
+ iobuf_t iobuf;
+
+ err = build_keyblock_image (kb, &iobuf);
+ if (!err)
+ {
+ err = keybox_update_keyblock (hd->active[hd->found].u.kb,
+ iobuf_get_temp_buffer (iobuf),
+ iobuf_get_temp_length (iobuf));
+ iobuf_close (iobuf);
+ }
+ }
+ break;
+ }
+
+ unlock_all (hd);
+ if (!err)
+ keydb_stats.update_keyblocks++;
+ return err;
+}
+
+
+/* Insert a keyblock into one of the underlying keyrings or keyboxes.
+ *
+ * Be default, the keyring / keybox from which the last search result
+ * came is used. If there was no previous search result (or
+ * keydb_search_reset was called), then the keyring / keybox where the
+ * next search would start is used (i.e., the current file position).
+ *
+ * Note: this doesn't do anything if --dry-run was specified.
+ *
+ * Returns 0 on success. Otherwise, it returns an error code. */
+gpg_error_t
+keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
+{
+ gpg_error_t err;
+ int idx;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ kid_not_found_flush ();
+ keyblock_cache_clear (hd);
+
+ if (opt.dry_run)
+ return 0;
+
+ if (hd->found >= 0 && hd->found < hd->used)
+ idx = hd->found;
+ else if (hd->current >= 0 && hd->current < hd->used)
+ idx = hd->current;
+ else
+ return gpg_error (GPG_ERR_GENERAL);
+
+ err = lock_all (hd);
+ if (err)
+ return err;
+
+ switch (hd->active[idx].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ err = gpg_error (GPG_ERR_GENERAL); /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ err = keyring_insert_keyblock (hd->active[idx].u.kr, kb);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ { /* We need to turn our kbnode_t list of packets into a proper
+ keyblock first. This is required by the OpenPGP key parser
+ included in the keybox code. Eventually we can change this
+ kludge to have the caller pass the image. */
+ iobuf_t iobuf;
+
+ err = build_keyblock_image (kb, &iobuf);
+ if (!err)
+ {
+ err = keybox_insert_keyblock (hd->active[idx].u.kb,
+ iobuf_get_temp_buffer (iobuf),
+ iobuf_get_temp_length (iobuf));
+ iobuf_close (iobuf);
+ }
+ }
+ break;
+ }
+
+ unlock_all (hd);
+ if (!err)
+ keydb_stats.insert_keyblocks++;
+ return err;
+}
+
+
+/* Delete the currently selected keyblock. If you haven't done a
+ * search yet on this database handle (or called keydb_search_reset),
+ * then this will return an error.
+ *
+ * Returns 0 on success or an error code, if an error occurs. */
+gpg_error_t
+keydb_delete_keyblock (KEYDB_HANDLE hd)
+{
+ gpg_error_t rc;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ kid_not_found_flush ();
+ keyblock_cache_clear (hd);
+
+ if (hd->found < 0 || hd->found >= hd->used)
+ return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
+
+ if (opt.dry_run)
+ return 0;
+
+ rc = lock_all (hd);
+ if (rc)
+ return rc;
+
+ switch (hd->active[hd->found].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ rc = gpg_error (GPG_ERR_GENERAL);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ rc = keyring_delete_keyblock (hd->active[hd->found].u.kr);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_delete (hd->active[hd->found].u.kb);
+ break;
+ }
+
+ unlock_all (hd);
+ if (!rc)
+ keydb_stats.delete_keyblocks++;
+ return rc;
+}
+
+
+
+/* A database may consists of multiple keyrings / key boxes. This
+ * sets the "file position" to the start of the first keyring / key
+ * box that is writable (i.e., doesn't have the read-only flag set).
+ *
+ * This first tries the primary keyring (the last keyring (not
+ * keybox!) added using keydb_add_resource() and with
+ * KEYDB_RESOURCE_FLAG_PRIMARY set). If that is not writable, then it
+ * tries the keyrings / keyboxes in the order in which they were
+ * added. */
+gpg_error_t
+keydb_locate_writable (KEYDB_HANDLE hd)
+{
+ gpg_error_t rc;
+
+ if (!hd)
+ return GPG_ERR_INV_ARG;
+
+ rc = keydb_search_reset (hd); /* this does reset hd->current */
+ if (rc)
+ return rc;
+
+ /* If we have a primary set, try that one first */
+ if (primary_keydb)
+ {
+ for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
+ {
+ if(hd->active[hd->current].token == primary_keydb)
+ {
+ if(keyring_is_writable (hd->active[hd->current].token))
+ return 0;
+ else
+ break;
+ }
+ }
+
+ rc = keydb_search_reset (hd); /* this does reset hd->current */
+ if (rc)
+ return rc;
+ }
+
+ for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
+ {
+ switch (hd->active[hd->current].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ BUG();
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ if (keyring_is_writable (hd->active[hd->current].token))
+ return 0; /* found (hd->current is set to it) */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ if (keybox_is_writable (hd->active[hd->current].token))
+ return 0; /* found (hd->current is set to it) */
+ break;
+ }
+ }
+
+ return gpg_error (GPG_ERR_NOT_FOUND);
+}
+
+
+/* Rebuild the on-disk caches of all key resources. */
+void
+keydb_rebuild_caches (ctrl_t ctrl, int noisy)
+{
+ int i, rc;
+
+ for (i=0; i < used_resources; i++)
+ {
+ if (!keyring_is_writable (all_resources[i].token))
+ continue;
+ switch (all_resources[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ rc = keyring_rebuild_cache (ctrl, all_resources[i].token,noisy);
+ if (rc)
+ log_error (_("failed to rebuild keyring cache: %s\n"),
+ gpg_strerror (rc));
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ /* N/A. */
+ break;
+ }
+ }
+}
+
+
+/* Return the number of skipped blocks (because they were to large to
+ read from a keybox) since the last search reset. */
+unsigned long
+keydb_get_skipped_counter (KEYDB_HANDLE hd)
+{
+ return hd ? hd->skipped_long_blobs : 0;
+}
+
+
+/* Clears the current search result and resets the handle's position
+ * so that the next search starts at the beginning of the database
+ * (the start of the first resource).
+ *
+ * Returns 0 on success and an error code if an error occurred.
+ * (Currently, this function always returns 0 if HD is valid.) */
+gpg_error_t
+keydb_search_reset (KEYDB_HANDLE hd)
+{
+ gpg_error_t rc = 0;
+ int i;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ keyblock_cache_clear (hd);
+
+ if (DBG_CLOCK)
+ log_clock ("keydb_search_reset");
+
+ if (DBG_CACHE)
+ log_debug ("keydb_search: reset (hd=%p)", hd);
+
+ hd->skipped_long_blobs = 0;
+ hd->current = 0;
+ hd->found = -1;
+ /* Now reset all resources. */
+ for (i=0; !rc && i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ rc = keyring_search_reset (hd->active[i].u.kr);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_search_reset (hd->active[i].u.kb);
+ break;
+ }
+ }
+ hd->is_reset = 1;
+ if (!rc)
+ keydb_stats.search_resets++;
+ return rc;
+}
+
+
+/* Search the database for keys matching the search description. If
+ * the DB contains any legacy keys, these are silently ignored.
+ *
+ * DESC is an array of search terms with NDESC entries. The search
+ * terms are or'd together. That is, the next entry in the DB that
+ * matches any of the descriptions will be returned.
+ *
+ * Note: this function resumes searching where the last search left
+ * off (i.e., at the current file position). If you want to search
+ * from the start of the database, then you need to first call
+ * keydb_search_reset().
+ *
+ * If no key matches the search description, returns
+ * GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error
+ * occurred, returns an error code.
+ *
+ * The returned key is considered to be selected and the raw data can,
+ * for instance, be returned by calling keydb_get_keyblock(). */
+gpg_error_t
+keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
+ size_t ndesc, size_t *descindex)
+{
+ int i;
+ gpg_error_t rc;
+ int was_reset = hd->is_reset;
+ /* If an entry is already in the cache, then don't add it again. */
+ int already_in_cache = 0;
+
+ if (descindex)
+ *descindex = 0; /* Make sure it is always set on return. */
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ if (!any_registered)
+ {
+ write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN));
+ return gpg_error (GPG_ERR_NOT_FOUND);
+ }
+
+ if (DBG_CLOCK)
+ log_clock ("keydb_search enter");
+
+ if (DBG_LOOKUP)
+ {
+ log_debug ("%s: %zd search descriptions:\n", __func__, ndesc);
+ for (i = 0; i < ndesc; i ++)
+ {
+ char *t = keydb_search_desc_dump (&desc[i]);
+ log_debug ("%s %d: %s\n", __func__, i, t);
+ xfree (t);
+ }
+ }
+
+
+ if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
+ && (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 )
+ {
+ if (DBG_CLOCK)
+ log_clock ("keydb_search leave (not found, cached)");
+ keydb_stats.notfound_cached++;
+ return gpg_error (GPG_ERR_NOT_FOUND);
+ }
+
+ /* NB: If one of the exact search modes below is used in a loop to
+ walk over all keys (with the same fingerprint) the caching must
+ have been disabled for the handle. */
+ if (!hd->no_caching
+ && ndesc == 1
+ && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
+ || desc[0].mode == KEYDB_SEARCH_MODE_FPR)
+ && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED
+ && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20)
+ /* Make sure the current file position occurs before the cached
+ result to avoid an infinite loop. */
+ && (hd->current < hd->keyblock_cache.resource
+ || (hd->current == hd->keyblock_cache.resource
+ && (keybox_offset (hd->active[hd->current].u.kb)
+ <= hd->keyblock_cache.offset))))
+ {
+ /* (DESCINDEX is already set). */
+ if (DBG_CLOCK)
+ log_clock ("keydb_search leave (cached)");
+
+ hd->current = hd->keyblock_cache.resource;
+ /* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record.
+ Seek just beyond that. */
+ keybox_seek (hd->active[hd->current].u.kb,
+ hd->keyblock_cache.offset + 1);
+ keydb_stats.found_cached++;
+ return 0;
+ }
+
+ rc = -1;
+ while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
+ && hd->current >= 0 && hd->current < hd->used)
+ {
+ if (DBG_LOOKUP)
+ log_debug ("%s: searching %s (resource %d of %d)\n",
+ __func__,
+ hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
+ ? "keyring"
+ : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
+ ? "keybox" : "unknown type"),
+ hd->current, hd->used);
+
+ switch (hd->active[hd->current].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ BUG(); /* we should never see it here */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYRING:
+ rc = keyring_search (hd->active[hd->current].u.kr, desc,
+ ndesc, descindex, 1);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ do
+ rc = keybox_search (hd->active[hd->current].u.kb, desc,
+ ndesc, KEYBOX_BLOBTYPE_PGP,
+ descindex, &hd->skipped_long_blobs);
+ while (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY
+ || gpg_err_code (rc) == GPG_ERR_UNKNOWN_VERSION)
+ ;
+ break;
+ }
+
+ if (DBG_LOOKUP)
+ log_debug ("%s: searched %s (resource %d of %d) => %s\n",
+ __func__,
+ hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
+ ? "keyring"
+ : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
+ ? "keybox" : "unknown type"),
+ hd->current, hd->used,
+ rc == -1 ? "EOF" : gpg_strerror (rc));
+
+ if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
+ {
+ /* EOF -> switch to next resource */
+ hd->current++;
+ }
+ else if (!rc)
+ hd->found = hd->current;
+ }
+ hd->is_reset = 0;
+
+ rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
+ ? gpg_error (GPG_ERR_NOT_FOUND)
+ : rc);
+
+ keyblock_cache_clear (hd);
+ if (!hd->no_caching
+ && !rc
+ && ndesc == 1 && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
+ || desc[0].mode == KEYDB_SEARCH_MODE_FPR)
+ && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX)
+ {
+ hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED;
+ hd->keyblock_cache.resource = hd->current;
+ /* The current offset is at the start of the next record. Since
+ a record is at least 1 byte, we just use offset - 1, which is
+ within the record. */
+ hd->keyblock_cache.offset
+ = keybox_offset (hd->active[hd->current].u.kb) - 1;
+ memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, 20);
+ }
+
+ if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND
+ && ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset
+ && !already_in_cache)
+ kid_not_found_insert (desc[0].u.kid);
+
+ if (DBG_CLOCK)
+ log_clock (rc? "keydb_search leave (not found)"
+ : "keydb_search leave (found)");
+ if (!rc)
+ keydb_stats.found++;
+ else
+ keydb_stats.notfound++;
+ return rc;
+}
+
+
+/* Return the first non-legacy key in the database.
+ *
+ * If you want the very first key in the database, you can directly
+ * call keydb_search with the search description
+ * KEYDB_SEARCH_MODE_FIRST. */
+gpg_error_t
+keydb_search_first (KEYDB_HANDLE hd)
+{
+ gpg_error_t err;
+ KEYDB_SEARCH_DESC desc;
+
+ err = keydb_search_reset (hd);
+ if (err)
+ return err;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_FIRST;
+ return keydb_search (hd, &desc, 1, NULL);
+}
+
+
+/* Return the next key (not the next matching key!).
+ *
+ * Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this
+ * function silently skips legacy keys. */
+gpg_error_t
+keydb_search_next (KEYDB_HANDLE hd)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_NEXT;
+ return keydb_search (hd, &desc, 1, NULL);
+}
+
+
+/* This is a convenience function for searching for keys with a long
+ * key id.
+ *
+ * Note: this function resumes searching where the last search left
+ * off. If you want to search the whole database, then you need to
+ * first call keydb_search_reset(). */
+gpg_error_t
+keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
+ desc.u.kid[0] = kid[0];
+ desc.u.kid[1] = kid[1];
+ return keydb_search (hd, &desc, 1, NULL);
+}
+
+
+/* This is a convenience function for searching for keys with a long
+ * (20 byte) fingerprint.
+ *
+ * Note: this function resumes searching where the last search left
+ * off. If you want to search the whole database, then you need to
+ * first call keydb_search_reset(). */
+gpg_error_t
+keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_FPR;
+ memcpy (desc.u.fpr, fpr, MAX_FINGERPRINT_LEN);
+ return keydb_search (hd, &desc, 1, NULL);
+}