mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
dc417bf0c5
* g10/keydb.c (keydb_update_keyblock): Don't replace the record at the current offset. After taking the lock, extract the fingerprint from the keyblock, find it and then replace it. -- Signed-off-by: Neal H. Walfield <neal@g10code.com> GnuPG-bug-id: 2193 Between locating the record to update and actually updating the keyblock, it is possible that another process modifies the keyring, which causes the update to corrupt the keyring. This is due to a time of check to time of use bug. The fix is straightforward: both operations must be done while holding the lock. This changes the semantics of the function slightly, but no callers need to be modified. Further, it now becomes impossible to replace key A with B; this function will only ever update B.
1877 lines
51 KiB
C
1877 lines
51 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <assert.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <unistd.h>
|
||
|
||
#include "gpg.h"
|
||
#include "util.h"
|
||
#include "options.h"
|
||
#include "main.h" /*try_make_homedir ()*/
|
||
#include "packet.h"
|
||
#include "keyring.h"
|
||
#include "../kbx/keybox.h"
|
||
#include "keydb.h"
|
||
#include "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;
|
||
static void *primary_keyring=NULL;
|
||
|
||
|
||
/* 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. */
|
||
u32 *sigstatus;
|
||
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;
|
||
|
||
/* 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];
|
||
|
||
/* The total number of entries in the hash table. */
|
||
static unsigned int kid_not_found_cache_count;
|
||
|
||
struct kid_not_found_cache_bucket
|
||
{
|
||
struct kid_not_found_cache_bucket *next;
|
||
u32 kid[2];
|
||
};
|
||
|
||
|
||
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_cache_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_cache_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;
|
||
}
|
||
kid_not_found_cache_count = 0;
|
||
}
|
||
|
||
|
||
static void
|
||
keyblock_cache_clear (struct keydb_handle *hd)
|
||
{
|
||
hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
|
||
xfree (hd->keyblock_cache.sigstatus);
|
||
hd->keyblock_cache.sigstatus = NULL;
|
||
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 int
|
||
maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
|
||
{
|
||
dotlock_t lockhd = NULL;
|
||
IOBUF iobuf;
|
||
int rc;
|
||
mode_t oldmask;
|
||
char *last_slash_in_filename;
|
||
int save_slash;
|
||
|
||
/* A quick test whether the filename already exists. */
|
||
if (!access (filename, F_OK))
|
||
return 0;
|
||
|
||
/* 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 (access(filename, F_OK))
|
||
{
|
||
static int tried;
|
||
|
||
if (!tried)
|
||
{
|
||
tried = 1;
|
||
try_make_homedir (filename);
|
||
}
|
||
if (access (filename, F_OK))
|
||
{
|
||
rc = gpg_error_from_syserror ();
|
||
*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. */
|
||
if (!access (filename, F_OK))
|
||
{
|
||
rc = 0; /* Okay, we may access the file now. */
|
||
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)
|
||
{
|
||
FILE *fp = fopen (filename, "w");
|
||
if (!fp)
|
||
rc = gpg_error_from_syserror ();
|
||
else
|
||
{
|
||
rc = _keybox_write_header_blob (fp, 1);
|
||
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);
|
||
}
|
||
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 = 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);
|
||
}
|
||
}
|
||
|
||
gpg_error_t
|
||
keydb_add_resource (const char *url, unsigned int flags)
|
||
{
|
||
/* Whether we have successfully registered a resource. */
|
||
static int any_registered;
|
||
/* 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);
|
||
int rc = 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 );
|
||
rc = 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 (opt.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 );
|
||
rc = gpg_error (GPG_ERR_GENERAL);
|
||
goto leave;
|
||
|
||
case KEYDB_RESOURCE_TYPE_KEYRING:
|
||
rc = maybe_create_keyring_or_box (filename, 0, create);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
if (keyring_register_filename (filename, read_only, &token))
|
||
{
|
||
if (used_resources >= MAX_KEYDB_RESOURCES)
|
||
rc = gpg_error (GPG_ERR_RESOURCE_LIMIT);
|
||
else
|
||
{
|
||
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
|
||
primary_keyring = 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_keyring = token;
|
||
}
|
||
break;
|
||
|
||
case KEYDB_RESOURCE_TYPE_KEYBOX:
|
||
{
|
||
rc = maybe_create_keyring_or_box (filename, 1, create);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
/* FIXME: How do we register a read-only keybox? */
|
||
token = keybox_register_file (filename, 0);
|
||
if (token)
|
||
{
|
||
if (used_resources >= MAX_KEYDB_RESOURCES)
|
||
rc = gpg_error (GPG_ERR_RESOURCE_LIMIT);
|
||
else
|
||
{
|
||
/* if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) */
|
||
/* primary_keyring = token; */
|
||
all_resources[used_resources].type = rt;
|
||
all_resources[used_resources].u.kb = NULL; /* Not used here */
|
||
all_resources[used_resources].token = token;
|
||
|
||
/* FIXME: Do a compress run if needed and no other
|
||
user is currently using the keybox. */
|
||
|
||
used_resources++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Already registered. We will mark it as the primary key
|
||
if requested. */
|
||
/* FIXME: How to do that? Change the keybox interface? */
|
||
/* if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) */
|
||
/* primary_keyring = token; */
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
log_error ("resource type of '%s' not supported\n", url);
|
||
rc = gpg_error (GPG_ERR_GENERAL);
|
||
goto leave;
|
||
}
|
||
|
||
/* fixme: check directory permissions and print a warning */
|
||
|
||
leave:
|
||
if (rc)
|
||
log_error (_("keyblock resource '%s': %s\n"), filename, gpg_strerror (rc));
|
||
else
|
||
any_registered = 1;
|
||
xfree (filename);
|
||
return rc;
|
||
}
|
||
|
||
|
||
void
|
||
keydb_dump_stats (void)
|
||
{
|
||
if (kid_not_found_cache_count)
|
||
log_info ("keydb: kid_not_found_cache: total: %u\n",
|
||
kid_not_found_cache_count);
|
||
}
|
||
|
||
|
||
/* 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;
|
||
|
||
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++;
|
||
|
||
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;
|
||
assert (active_handles > 0);
|
||
active_handles--;
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
xfree (hd);
|
||
}
|
||
|
||
|
||
void
|
||
keydb_disable_caching (KEYDB_HANDLE hd)
|
||
{
|
||
if (hd)
|
||
hd->no_caching = 1;
|
||
}
|
||
|
||
|
||
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);
|
||
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:
|
||
rc = keybox_lock (hd->active[i].u.kb, 0);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
hd->locked = 1;
|
||
|
||
return rc;
|
||
}
|
||
|
||
|
||
static void
|
||
unlock_all (KEYDB_HANDLE hd)
|
||
{
|
||
int i;
|
||
|
||
if (!hd->locked)
|
||
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);
|
||
break;
|
||
}
|
||
}
|
||
hd->locked = 0;
|
||
}
|
||
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
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,
|
||
const u32 *sigstatus, kbnode_t *r_keyblock)
|
||
{
|
||
gpg_error_t err;
|
||
PACKET *pkt;
|
||
kbnode_t keyblock = NULL;
|
||
kbnode_t node, *tail;
|
||
int in_cert, save_mode;
|
||
u32 n_sigs;
|
||
int pk_count, uid_count;
|
||
|
||
*r_keyblock = NULL;
|
||
|
||
pkt = xtrymalloc (sizeof *pkt);
|
||
if (!pkt)
|
||
return gpg_error_from_syserror ();
|
||
init_packet (pkt);
|
||
save_mode = set_packet_list_mode (0);
|
||
in_cert = 0;
|
||
n_sigs = 0;
|
||
tail = NULL;
|
||
pk_count = uid_count = 0;
|
||
while ((err = parse_packet (iobuf, pkt)) != -1)
|
||
{
|
||
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
|
||
{
|
||
free_packet (pkt);
|
||
init_packet (pkt);
|
||
continue;
|
||
}
|
||
if (err)
|
||
{
|
||
log_error ("parse_keyblock_image: read error: %s\n",
|
||
gpg_strerror (err));
|
||
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:
|
||
break; /* Allowed per RFC. */
|
||
|
||
default:
|
||
/* Note that can't allow ring trust packets here and some of
|
||
the other GPG specific packets don't make sense either. */
|
||
log_error ("skipped packet of type %d in keybox\n",
|
||
(int)pkt->pkttype);
|
||
free_packet(pkt);
|
||
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;
|
||
|
||
if (pkt->pkttype == PKT_SIGNATURE && sigstatus)
|
||
{
|
||
PKT_signature *sig = pkt->pkt.signature;
|
||
|
||
n_sigs++;
|
||
if (n_sigs > sigstatus[0])
|
||
{
|
||
log_error ("parse_keyblock_image: "
|
||
"more signatures than found in the meta data\n");
|
||
err = gpg_error (GPG_ERR_INV_KEYRING);
|
||
break;
|
||
|
||
}
|
||
if (sigstatus[n_sigs])
|
||
{
|
||
sig->flags.checked = 1;
|
||
if (sigstatus[n_sigs] == 1 )
|
||
; /* missing key */
|
||
else if (sigstatus[n_sigs] == 2 )
|
||
; /* bad signature */
|
||
else if (sigstatus[n_sigs] < 0x10000000)
|
||
; /* bad flag */
|
||
else
|
||
{
|
||
sig->flags.valid = 1;
|
||
/* Fixme: Shall we set the expired flag here? */
|
||
}
|
||
}
|
||
}
|
||
|
||
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 && sigstatus && n_sigs != sigstatus[0])
|
||
{
|
||
log_error ("parse_keyblock_image: signature count does not match\n");
|
||
err = gpg_error (GPG_ERR_INV_KEYRING);
|
||
}
|
||
|
||
if (err)
|
||
release_kbnode (keyblock);
|
||
else
|
||
*r_keyblock = keyblock;
|
||
free_packet (pkt);
|
||
xfree (pkt);
|
||
return err;
|
||
}
|
||
|
||
|
||
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,
|
||
hd->keyblock_cache.sigstatus,
|
||
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;
|
||
u32 *sigstatus;
|
||
int pk_no, uid_no;
|
||
|
||
err = keybox_get_keyblock (hd->active[hd->found].u.kb,
|
||
&iobuf, &pk_no, &uid_no, &sigstatus);
|
||
if (!err)
|
||
{
|
||
err = parse_keyblock_image (iobuf, pk_no, uid_no, sigstatus,
|
||
ret_kb);
|
||
if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
|
||
{
|
||
hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED;
|
||
hd->keyblock_cache.sigstatus = sigstatus;
|
||
hd->keyblock_cache.iobuf = iobuf;
|
||
hd->keyblock_cache.pk_no = pk_no;
|
||
hd->keyblock_cache.uid_no = uid_no;
|
||
}
|
||
else
|
||
{
|
||
xfree (sigstatus);
|
||
iobuf_close (iobuf);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
|
||
keyblock_cache_clear (hd);
|
||
|
||
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 and a signature
|
||
status vecotor at R_SIGSTATUS. */
|
||
static gpg_error_t
|
||
build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf, u32 **r_sigstatus)
|
||
{
|
||
gpg_error_t err;
|
||
iobuf_t iobuf;
|
||
kbnode_t kbctx, node;
|
||
u32 n_sigs;
|
||
u32 *sigstatus;
|
||
|
||
*r_iobuf = NULL;
|
||
if (r_sigstatus)
|
||
*r_sigstatus = NULL;
|
||
|
||
/* Allocate a vector for the signature cache. This is an array of
|
||
u32 values with the first value giving the number of elements to
|
||
follow and each element descriping the cache status of the
|
||
signature. */
|
||
if (r_sigstatus)
|
||
{
|
||
for (kbctx=NULL, n_sigs=0; (node = walk_kbnode (keyblock, &kbctx, 0));)
|
||
if (node->pkt->pkttype == PKT_SIGNATURE)
|
||
n_sigs++;
|
||
sigstatus = xtrycalloc (1+n_sigs, sizeof *sigstatus);
|
||
if (!sigstatus)
|
||
return gpg_error_from_syserror ();
|
||
}
|
||
else
|
||
sigstatus = NULL;
|
||
|
||
iobuf = iobuf_temp ();
|
||
for (kbctx = NULL, n_sigs = 0; (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:
|
||
/* Note that we don't want the ring trust packets. They are
|
||
not useful. */
|
||
break;
|
||
default:
|
||
continue;
|
||
}
|
||
|
||
err = build_packet (iobuf, node->pkt);
|
||
if (err)
|
||
{
|
||
iobuf_close (iobuf);
|
||
return err;
|
||
}
|
||
|
||
/* Build signature status vector. */
|
||
if (node->pkt->pkttype == PKT_SIGNATURE)
|
||
{
|
||
PKT_signature *sig = node->pkt->pkt.signature;
|
||
|
||
n_sigs++;
|
||
/* Fixme: Detect the "missing key" status. */
|
||
if (sig->flags.checked && sigstatus)
|
||
{
|
||
if (sig->flags.valid)
|
||
{
|
||
if (!sig->expiredate)
|
||
sigstatus[n_sigs] = 0xffffffff;
|
||
else if (sig->expiredate < 0x1000000)
|
||
sigstatus[n_sigs] = 0x10000000;
|
||
else
|
||
sigstatus[n_sigs] = sig->expiredate;
|
||
}
|
||
else
|
||
sigstatus[n_sigs] = 0x00000002; /* Bad signature. */
|
||
}
|
||
}
|
||
}
|
||
if (sigstatus)
|
||
sigstatus[0] = n_sigs;
|
||
|
||
*r_iobuf = iobuf;
|
||
if (r_sigstatus)
|
||
*r_sigstatus = sigstatus;
|
||
return 0;
|
||
}
|
||
|
||
|
||
gpg_error_t
|
||
keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
|
||
{
|
||
gpg_error_t err;
|
||
PKT_public_key *pk = kb->pkt->pkt.public_key;
|
||
KEYDB_SEARCH_DESC desc;
|
||
size_t len;
|
||
|
||
assert (kb);
|
||
assert (kb->pkt->pkttype == 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;
|
||
|
||
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: %zd\n", __func__, len);
|
||
|
||
keydb_search_reset (hd);
|
||
err = keydb_search (hd, &desc, 1, NULL);
|
||
if (err)
|
||
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
|
||
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, NULL);
|
||
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);
|
||
return err;
|
||
}
|
||
|
||
|
||
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;
|
||
u32 *sigstatus;
|
||
|
||
err = build_keyblock_image (kb, &iobuf, &sigstatus);
|
||
if (!err)
|
||
{
|
||
err = keybox_insert_keyblock (hd->active[idx].u.kb,
|
||
iobuf_get_temp_buffer (iobuf),
|
||
iobuf_get_temp_length (iobuf),
|
||
sigstatus);
|
||
xfree (sigstatus);
|
||
iobuf_close (iobuf);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
unlock_all (hd);
|
||
return err;
|
||
}
|
||
|
||
|
||
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);
|
||
return rc;
|
||
}
|
||
|
||
|
||
|
||
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_keyring)
|
||
{
|
||
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
|
||
{
|
||
if(hd->active[hd->current].token==primary_keyring)
|
||
{
|
||
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);
|
||
}
|
||
|
||
void
|
||
keydb_rebuild_caches (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 (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;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
unsigned long
|
||
keydb_get_skipped_counter (KEYDB_HANDLE hd)
|
||
{
|
||
return hd ? hd->skipped_long_blobs : 0;
|
||
}
|
||
|
||
|
||
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;
|
||
return rc;
|
||
}
|
||
|
||
|
||
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 (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)");
|
||
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);
|
||
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 (rc == GPG_ERR_LEGACY_KEY);
|
||
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)");
|
||
return rc;
|
||
}
|
||
|
||
|
||
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);
|
||
}
|
||
|
||
|
||
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);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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);
|
||
}
|