diff --git a/kbx/keybox-init.c b/kbx/keybox-init.c index 6cabaea73..f07ba8db3 100644 --- a/kbx/keybox-init.c +++ b/kbx/keybox-init.c @@ -1,332 +1,352 @@ /* keybox-init.c - Initialization of the library * Copyright (C) 2001 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include "keybox-defs.h" #include "../common/sysutils.h" #include "../common/mischelp.h" static KB_NAME kb_names; /* Register a filename for plain keybox files. Returns 0 on success, * GPG_ERR_EEXIST if it has already been registered, or another error * code. On success or with error code GPG_ERR_EEXIST a token usable * to access the keybox handle is stored at R_TOKEN, NULL is stored * for all other errors. */ gpg_error_t keybox_register_file (const char *fname, int secret, void **r_token) { KB_NAME kr; *r_token = NULL; for (kr=kb_names; kr; kr = kr->next) { if (same_file_p (kr->fname, fname) ) { *r_token = kr; return gpg_error (GPG_ERR_EEXIST); /* Already registered. */ } } kr = xtrymalloc (sizeof *kr + strlen (fname)); if (!kr) return gpg_error_from_syserror (); strcpy (kr->fname, fname); kr->secret = !!secret; kr->handle_table = NULL; kr->handle_table_size = 0; kr->lockhd = NULL; kr->is_locked = 0; kr->did_full_scan = 0; /* keep a list of all issued pointers */ kr->next = kb_names; kb_names = kr; /* create the offset table the first time a function here is used */ /* if (!kb_offtbl) */ /* kb_offtbl = new_offset_hash_table (); */ *r_token = kr; return 0; } int keybox_is_writable (void *token) { KB_NAME r = token; return r? !gnupg_access (r->fname, W_OK) : 0; } static KEYBOX_HANDLE do_keybox_new (KB_NAME resource, int secret, int for_openpgp) { KEYBOX_HANDLE hd; int idx; assert (resource && !resource->secret == !secret); hd = xtrycalloc (1, sizeof *hd); if (hd) { hd->kb = resource; hd->secret = !!secret; hd->for_openpgp = for_openpgp; if (!resource->handle_table) { resource->handle_table_size = 3; resource->handle_table = xtrycalloc (resource->handle_table_size, sizeof *resource->handle_table); if (!resource->handle_table) { resource->handle_table_size = 0; xfree (hd); return NULL; } } for (idx=0; idx < resource->handle_table_size; idx++) if (!resource->handle_table[idx]) { resource->handle_table[idx] = hd; break; } if (!(idx < resource->handle_table_size)) { KEYBOX_HANDLE *tmptbl; size_t newsize; newsize = resource->handle_table_size + 5; tmptbl = xtryrealloc (resource->handle_table, newsize * sizeof (*tmptbl)); if (!tmptbl) { xfree (hd); return NULL; } resource->handle_table = tmptbl; resource->handle_table_size = newsize; resource->handle_table[idx] = hd; for (idx++; idx < resource->handle_table_size; idx++) resource->handle_table[idx] = NULL; } } return hd; } /* Create a new handle for the resource associated with TOKEN. SECRET is just a cross-check. This is the OpenPGP version. The returned handle must be released using keybox_release. */ KEYBOX_HANDLE keybox_new_openpgp (void *token, int secret) { KB_NAME resource = token; return do_keybox_new (resource, secret, 1); } /* Create a new handle for the resource associated with TOKEN. SECRET is just a cross-check. This is the X.509 version. The returned handle must be released using keybox_release. */ KEYBOX_HANDLE keybox_new_x509 (void *token, int secret) { KB_NAME resource = token; return do_keybox_new (resource, secret, 0); } void keybox_release (KEYBOX_HANDLE hd) { if (!hd) return; if (hd->kb->handle_table) { int idx; for (idx=0; idx < hd->kb->handle_table_size; idx++) if (hd->kb->handle_table[idx] == hd) hd->kb->handle_table[idx] = NULL; } _keybox_release_blob (hd->found.blob); _keybox_release_blob (hd->saved_found.blob); if (hd->fp) { es_fclose (hd->fp); hd->fp = NULL; } xfree (hd->word_match.name); xfree (hd->word_match.pattern); xfree (hd); } /* Save the current found state in HD for later retrieval by keybox_restore_found_state. Only one state may be saved. */ void keybox_push_found_state (KEYBOX_HANDLE hd) { if (hd->saved_found.blob) { _keybox_release_blob (hd->saved_found.blob); hd->saved_found.blob = NULL; } hd->saved_found = hd->found; hd->found.blob = NULL; } /* Restore the saved found state in HD. */ void keybox_pop_found_state (KEYBOX_HANDLE hd) { if (hd->found.blob) { _keybox_release_blob (hd->found.blob); hd->found.blob = NULL; } hd->found = hd->saved_found; hd->saved_found.blob = NULL; } const char * keybox_get_resource_name (KEYBOX_HANDLE hd) { if (!hd || !hd->kb) return NULL; return hd->kb->fname; } int keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes) { if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); hd->ephemeral = yes; return 0; } /* Close the file of the resource identified by HD. For consistent results this function closes the files of all handles pointing to the resource identified by HD. */ void _keybox_close_file (KEYBOX_HANDLE hd) { int idx; KEYBOX_HANDLE roverhd; if (!hd || !hd->kb || !hd->kb->handle_table) return; for (idx=0; idx < hd->kb->handle_table_size; idx++) if ((roverhd = hd->kb->handle_table[idx])) { if (roverhd->fp) { es_fclose (roverhd->fp); roverhd->fp = NULL; } } log_assert (!hd->fp); } +/* Close all the files associated with the resource identified by TOKEN. */ +void +keybox_close_all_files (void *token) +{ + KB_NAME resource = token; + KEYBOX_HANDLE roverhd; + int idx; + + if (!resource) + return; + + for (idx=0; idx < resource->handle_table_size; idx++) + if ((roverhd = resource->handle_table[idx]) && roverhd->fp) + { + es_fclose (roverhd->fp); + roverhd->fp = NULL; + } +} + + /* * Lock the keybox at handle HD, or unlock if YES is false. * Lock the keybox at handle HD, or unlock if YES is false. TIMEOUT * is the value used for dotlock_take. In general -1 should be used * when taking a lock; use 0 when releasing a lock. */ gpg_error_t keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout) { gpg_error_t err = 0; KB_NAME kb = hd->kb; if (!keybox_is_writable (kb)) return 0; /* Make sure the lock handle has been created. */ if (!kb->lockhd) { kb->lockhd = dotlock_create (kb->fname, 0); if (!kb->lockhd) { err = gpg_error_from_syserror (); log_info ("can't allocate lock for '%s'\n", kb->fname ); return err; } } if (yes) /* Take the lock. */ { if (!kb->is_locked) { #ifdef HAVE_W32_SYSTEM /* Under Windows we need to close the file before we try * to lock it. This is because another process might have * taken the lock and is using keybox_file_rename to * rename the base file. Now if our dotlock_take below is * waiting for the lock but we have the base file still * open, keybox_file_rename will never succeed as we are * in a deadlock. */ _keybox_close_file (hd); #endif /*HAVE_W32_SYSTEM*/ if (dotlock_take (kb->lockhd, timeout)) { err = gpg_error_from_syserror (); if (!timeout && gpg_err_code (err) == GPG_ERR_EACCES) ; /* No diagnostic if we only tried to lock. */ else log_info ("can't lock '%s'\n", kb->fname ); } else kb->is_locked = 1; } } else /* Release the lock. */ { if (kb->is_locked) { if (dotlock_release (kb->lockhd)) { err = gpg_error_from_syserror (); log_info ("can't unlock '%s'\n", kb->fname ); } else kb->is_locked = 0; } } return err; } diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 53ed66b67..263a16617 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -1,1317 +1,1322 @@ /* keybox-search.c - Search operations * Copyright (C) 2001, 2002, 2003, 2004, 2012, * 2013 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include "keybox-defs.h" #include #include "../common/host2net.h" #include "../common/mbox-util.h" #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) struct sn_array_s { int snlen; unsigned char *sn; }; #define get32(a) buf32_to_ulong ((a)) #define get16(a) buf16_to_ulong ((a)) static inline unsigned int blob_get_blob_flags (KEYBOXBLOB blob) { const unsigned char *buffer; size_t length; buffer = _keybox_get_blob_image (blob, &length); if (length < 8) return 0; /* oops */ return get16 (buffer + 6); } /* Return the first keyid from the blob. Returns true if available. */ static int blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid) { const unsigned char *buffer; size_t length, nkeys, keyinfolen; buffer = _keybox_get_blob_image (blob, &length); if (length < 48) return 0; /* blob too short */ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18); if (!nkeys || keyinfolen < 28) return 0; /* invalid blob */ kid[0] = get32 (buffer + 32); kid[1] = get32 (buffer + 36); return 1; } /* Return information on the flag WHAT within the blob BUFFER,LENGTH. Return the offset and the length (in bytes) of the flag in FLAGOFF,FLAG_SIZE. */ gpg_err_code_t _keybox_get_flag_location (const unsigned char *buffer, size_t length, int what, size_t *flag_off, size_t *flag_size) { size_t pos; size_t nkeys, keyinfolen; size_t nuids, uidinfolen; size_t nserial; size_t nsigs, siginfolen, siginfooff; switch (what) { case KEYBOX_FLAG_BLOB: if (length < 8) return GPG_ERR_INV_OBJ; *flag_off = 6; *flag_size = 2; break; case KEYBOX_FLAG_OWNERTRUST: case KEYBOX_FLAG_VALIDITY: case KEYBOX_FLAG_CREATED_AT: case KEYBOX_FLAG_SIG_INFO: if (length < 20) return GPG_ERR_INV_OBJ; /* Key info. */ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return GPG_ERR_INV_OBJ; pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return GPG_ERR_INV_OBJ; /* Out of bounds. */ /* Serial number. */ nserial = get16 (buffer+pos); pos += 2 + nserial; if (pos+4 > length) return GPG_ERR_INV_OBJ; /* Out of bounds. */ /* User IDs. */ nuids = get16 (buffer + pos); pos += 2; uidinfolen = get16 (buffer + pos); pos += 2; if (uidinfolen < 12 ) return GPG_ERR_INV_OBJ; pos += uidinfolen*nuids; if (pos+4 > length) return GPG_ERR_INV_OBJ ; /* Out of bounds. */ /* Signature info. */ siginfooff = pos; nsigs = get16 (buffer + pos); pos += 2; siginfolen = get16 (buffer + pos); pos += 2; if (siginfolen < 4 ) return GPG_ERR_INV_OBJ; pos += siginfolen*nsigs; if (pos+1+1+2+4+4+4+4 > length) return GPG_ERR_INV_OBJ ; /* Out of bounds. */ *flag_size = 1; *flag_off = pos; switch (what) { case KEYBOX_FLAG_VALIDITY: *flag_off += 1; break; case KEYBOX_FLAG_CREATED_AT: *flag_size = 4; *flag_off += 1+2+4+4+4; break; case KEYBOX_FLAG_SIG_INFO: *flag_size = siginfolen * nsigs; *flag_off = siginfooff; break; default: break; } break; default: return GPG_ERR_INV_FLAG; } return 0; } /* Return one of the flags WHAT in VALUE from the blob BUFFER of LENGTH bytes. Return 0 on success or an raw error code. */ static gpg_err_code_t get_flag_from_image (const unsigned char *buffer, size_t length, int what, unsigned int *value) { gpg_err_code_t ec; size_t pos, size; *value = 0; ec = _keybox_get_flag_location (buffer, length, what, &pos, &size); if (!ec) switch (size) { case 1: *value = buffer[pos]; break; case 2: *value = get16 (buffer + pos); break; case 4: *value = get32 (buffer + pos); break; default: ec = GPG_ERR_BUG; break; } return ec; } static int blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; size_t nserial; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return 0; /* out of bounds */ /*serial*/ nserial = get16 (buffer+pos); off = pos + 2; if (off+nserial > length) return 0; /* out of bounds */ return nserial == snlen && !memcmp (buffer+off, sn, snlen); } /* Returns 0 if not found or the number of the key which was found. For X.509 this is always 1, for OpenPGP this is 1 for the primary key and 2 and more for the subkeys. */ static int blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; int idx; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20; if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) return 0; /* out of bounds */ for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; if (!memcmp (buffer + off, fpr, 20)) return idx+1; /* found */ } return 0; /* not found */ } static int blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, int fproff, int fprlen) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; int idx; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20; if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) return 0; /* out of bounds */ for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; if (!memcmp (buffer + off + fproff, fpr, fprlen)) return idx+1; /* found */ } return 0; /* not found */ } static int blob_cmp_name (KEYBOXBLOB blob, int idx, const char *name, size_t namelen, int substr, int x509) { const unsigned char *buffer; size_t length; size_t pos, off, len; size_t nkeys, keyinfolen; size_t nuids, uidinfolen; size_t nserial; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20 + keyinfolen*nkeys; if ((uint64_t)pos+2 > (uint64_t)length) return 0; /* out of bounds */ /*serial*/ nserial = get16 (buffer+pos); pos += 2 + nserial; if (pos+4 > length) return 0; /* out of bounds */ /* user ids*/ nuids = get16 (buffer + pos); pos += 2; uidinfolen = get16 (buffer + pos); pos += 2; if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */) return 0; /* invalid blob */ if (pos + uidinfolen*nuids > length) return 0; /* out of bounds */ if (idx < 0) { /* Compare all names. Note that for X.509 we start with index 1 so to skip the issuer at index 0. */ for (idx = !!x509; idx < nuids; idx++) { size_t mypos = pos; mypos += idx*uidinfolen; off = get32 (buffer+mypos); len = get32 (buffer+mypos+4); if ((uint64_t)off+(uint64_t)len > (uint64_t)length) return 0; /* error: better stop here out of bounds */ if (len < 1) continue; /* empty name */ if (substr) { if (ascii_memcasemem (buffer+off, len, name, namelen)) return idx+1; /* found */ } else { if (len == namelen && !memcmp (buffer+off, name, len)) return idx+1; /* found */ } } } else { if (idx > nuids) return 0; /* no user ID with that idx */ pos += idx*uidinfolen; off = get32 (buffer+pos); len = get32 (buffer+pos+4); if (off+len > length) return 0; /* out of bounds */ if (len < 1) return 0; /* empty name */ if (substr) { if (ascii_memcasemem (buffer+off, len, name, namelen)) return idx+1; /* found */ } else { if (len == namelen && !memcmp (buffer+off, name, len)) return idx+1; /* found */ } } return 0; /* not found */ } /* Compare all email addresses of the subject. With SUBSTR given as True a substring search is done in the mail address. The X509 flag indicated whether the search is done on an X.509 blob. */ static int blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr, int x509) { const unsigned char *buffer; size_t length; size_t pos, off, len; size_t nkeys, keyinfolen; size_t nuids, uidinfolen; size_t nserial; int idx; /* fixme: this code is common to blob_cmp_mail */ buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return 0; /* out of bounds */ /*serial*/ nserial = get16 (buffer+pos); pos += 2 + nserial; if (pos+4 > length) return 0; /* out of bounds */ /* user ids*/ nuids = get16 (buffer + pos); pos += 2; uidinfolen = get16 (buffer + pos); pos += 2; if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */) return 0; /* invalid blob */ if (pos + uidinfolen*nuids > length) return 0; /* out of bounds */ if (namelen < 1) return 0; /* Note that for X.509 we start at index 1 because index 0 is used for the issuer name. */ for (idx=!!x509 ;idx < nuids; idx++) { size_t mypos = pos; size_t mylen; mypos += idx*uidinfolen; off = get32 (buffer+mypos); len = get32 (buffer+mypos+4); if ((uint64_t)off+(uint64_t)len > (uint64_t)length) return 0; /* error: better stop here - out of bounds */ if (x509) { if (len < 2 || buffer[off] != '<') continue; /* empty name or trailing 0 not stored */ len--; /* one back */ if ( len < 3 || buffer[off+len] != '>') continue; /* not a proper email address */ off++; len--; } else /* OpenPGP. */ { /* We need to forward to the mailbox part. */ mypos = off; mylen = len; for ( ; len && buffer[off] != '<'; len--, off++) ; if (len < 2 || buffer[off] != '<') { /* Mailbox not explicitly given or too short. Restore OFF and LEN and check whether the entire string resembles a mailbox without the angle brackets. */ off = mypos; len = mylen; if (!is_valid_mailbox_mem (buffer+off, len)) continue; /* Not a mail address. */ } else /* Seems to be standard user id with mail address. */ { off++; /* Point to first char of the mail address. */ len--; /* Search closing '>'. */ for (mypos=off; len && buffer[mypos] != '>'; len--, mypos++) ; if (!len || buffer[mypos] != '>' || off == mypos) continue; /* Not a proper mail address. */ len = mypos - off; } } if (substr) { if (ascii_memcasemem (buffer+off, len, name, namelen)) return idx+1; /* found */ } else { if (len == namelen && !ascii_memcasecmp (buffer+off, name, len)) return idx+1; /* found */ } } return 0; /* not found */ } /* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. * We don't have the keygrips as meta data, thus we need to parse the * certificate. Fixme: We might want to return proper error codes * instead of failing a search for invalid certificates etc. */ static int blob_openpgp_has_grip (KEYBOXBLOB blob, const unsigned char *grip) { int rc = 0; const unsigned char *buffer; size_t length; size_t cert_off, cert_len; struct _keybox_openpgp_info info; struct _keybox_openpgp_key_info *k; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* Too short. */ cert_off = get32 (buffer+8); cert_len = get32 (buffer+12); if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) return 0; /* Too short. */ if (_keybox_parse_openpgp (buffer + cert_off, cert_len, NULL, &info)) return 0; /* Parse error. */ if (!memcmp (info.primary.grip, grip, 20)) { rc = 1; goto leave; } if (info.nsubkeys) { k = &info.subkeys; do { if (!memcmp (k->grip, grip, 20)) { rc = 1; goto leave; } k = k->next; } while (k); } leave: _keybox_destroy_openpgp_info (&info); return rc; } #ifdef KEYBOX_WITH_X509 /* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. We don't have the keygrips as meta data, thus we need to parse the certificate. Fixme: We might want to return proper error codes instead of failing a search for invalid certificates etc. */ static int blob_x509_has_grip (KEYBOXBLOB blob, const unsigned char *grip) { int rc; const unsigned char *buffer; size_t length; size_t cert_off, cert_len; ksba_reader_t reader = NULL; ksba_cert_t cert = NULL; ksba_sexp_t p = NULL; gcry_sexp_t s_pkey; unsigned char array[20]; unsigned char *rcp; size_t n; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* Too short. */ cert_off = get32 (buffer+8); cert_len = get32 (buffer+12); if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) return 0; /* Too short. */ rc = ksba_reader_new (&reader); if (rc) return 0; /* Problem with ksba. */ rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len); if (rc) goto failed; rc = ksba_cert_new (&cert); if (rc) goto failed; rc = ksba_cert_read_der (cert, reader); if (rc) goto failed; p = ksba_cert_get_public_key (cert); if (!p) goto failed; n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) goto failed; rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n); if (rc) { gcry_sexp_release (s_pkey); goto failed; } rcp = gcry_pk_get_keygrip (s_pkey, array); gcry_sexp_release (s_pkey); if (!rcp) goto failed; /* Can't calculate keygrip. */ xfree (p); ksba_cert_release (cert); ksba_reader_release (reader); return !memcmp (array, grip, 20); failed: xfree (p); ksba_cert_release (cert); ksba_reader_release (reader); return 0; } #endif /*KEYBOX_WITH_X509*/ /* The has_foo functions are used as helpers for search */ static inline int has_short_kid (KEYBOXBLOB blob, u32 lkid) { unsigned char buf[4]; buf[0] = lkid >> 24; buf[1] = lkid >> 16; buf[2] = lkid >> 8; buf[3] = lkid; return blob_cmp_fpr_part (blob, buf, 16, 4); } static inline int has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid) { unsigned char buf[8]; buf[0] = mkid >> 24; buf[1] = mkid >> 16; buf[2] = mkid >> 8; buf[3] = mkid; buf[4] = lkid >> 24; buf[5] = lkid >> 16; buf[6] = lkid >> 8; buf[7] = lkid; return blob_cmp_fpr_part (blob, buf, 12, 8); } static inline int has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr) { return blob_cmp_fpr (blob, fpr); } static inline int has_keygrip (KEYBOXBLOB blob, const unsigned char *grip) { if (blob_get_type (blob) == KEYBOX_BLOBTYPE_PGP) return blob_openpgp_has_grip (blob, grip); #ifdef KEYBOX_WITH_X509 if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509) return blob_x509_has_grip (blob, grip); #endif return 0; } static inline int has_issuer (KEYBOXBLOB blob, const char *name) { size_t namelen; return_val_if_fail (name, 0); if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) return 0; namelen = strlen (name); return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1); } static inline int has_issuer_sn (KEYBOXBLOB blob, const char *name, const unsigned char *sn, int snlen) { size_t namelen; return_val_if_fail (name, 0); return_val_if_fail (sn, 0); if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) return 0; namelen = strlen (name); return (blob_cmp_sn (blob, sn, snlen) && blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1)); } static inline int has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) { return_val_if_fail (sn, 0); if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) return 0; return blob_cmp_sn (blob, sn, snlen); } static inline int has_subject (KEYBOXBLOB blob, const char *name) { size_t namelen; return_val_if_fail (name, 0); if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) return 0; namelen = strlen (name); return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0, 1); } static inline int has_username (KEYBOXBLOB blob, const char *name, int substr) { size_t namelen; int btype; return_val_if_fail (name, 0); btype = blob_get_type (blob); if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509) return 0; namelen = strlen (name); return blob_cmp_name (blob, -1 /* all subject/user names */, name, namelen, substr, (btype == KEYBOX_BLOBTYPE_X509)); } static inline int has_mail (KEYBOXBLOB blob, const char *name, int substr) { size_t namelen; int btype; return_val_if_fail (name, 0); btype = blob_get_type (blob); if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509) return 0; if (btype == KEYBOX_BLOBTYPE_PGP && *name == '<') name++; /* Hack to remove the leading '<' for gpg. */ namelen = strlen (name); if (namelen && name[namelen-1] == '>') namelen--; return blob_cmp_mail (blob, name, namelen, substr, (btype == KEYBOX_BLOBTYPE_X509)); } static void release_sn_array (struct sn_array_s *array, size_t size) { size_t n; for (n=0; n < size; n++) xfree (array[n].sn); xfree (array); } /* Helper to open the file. */ static gpg_error_t open_file (KEYBOX_HANDLE hd) { hd->fp = es_fopen (hd->kb->fname, "rb"); if (!hd->fp) { hd->error = gpg_error_from_syserror (); return hd->error; } return 0; } /* The search API */ gpg_error_t keybox_search_reset (KEYBOX_HANDLE hd) { if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (hd->found.blob) { _keybox_release_blob (hd->found.blob); hd->found.blob = NULL; } if (hd->fp) { +#if HAVE_W32_SYSTEM + es_fclose (hd->fp); + hd->fp = NULL; +#else if (es_fseeko (hd->fp, 0, SEEK_SET)) { /* Ooops. Seek did not work. Close so that the search will * open the file again. */ es_fclose (hd->fp); hd->fp = NULL; } +#endif } hd->error = 0; hd->eof = 0; return 0; } /* Note: When in ephemeral mode the search function does visit all blobs but in standard mode, blobs flagged as ephemeral are ignored. If WANT_BLOBTYPE is not 0 only blobs of this type are considered. The value at R_SKIPPED is updated by the number of skipped long records (counts PGP and X.509). */ gpg_error_t keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, keybox_blobtype_t want_blobtype, size_t *r_descindex, unsigned long *r_skipped) { gpg_error_t rc; size_t n; int need_words, any_skip; KEYBOXBLOB blob = NULL; struct sn_array_s *sn_array = NULL; int pk_no, uid_no; off_t lastfoundoff; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); /* Clear last found result but reord the offset of the last found * blob which we may need later. */ if (hd->found.blob) { lastfoundoff = _keybox_get_blob_fileoffset (hd->found.blob); _keybox_release_blob (hd->found.blob); hd->found.blob = NULL; } else lastfoundoff = 0; if (hd->error) return hd->error; /* still in error state */ if (hd->eof) return -1; /* still EOF */ /* figure out what information we need */ need_words = any_skip = 0; for (n=0; n < ndesc; n++) { switch (desc[n].mode) { case KEYDB_SEARCH_MODE_WORDS: need_words = 1; break; case KEYDB_SEARCH_MODE_FIRST: /* always restart the search in this mode */ keybox_search_reset (hd); lastfoundoff = 0; break; default: break; } if (desc[n].skipfnc) any_skip = 1; if (desc[n].snlen == -1 && !sn_array) { sn_array = xtrycalloc (ndesc, sizeof *sn_array); if (!sn_array) return (hd->error = gpg_error_from_syserror ()); } } (void)need_words; /* Not yet implemented. */ if (!hd->fp) { rc = open_file (hd); if (rc) { xfree (sn_array); return rc; } /* log_debug ("%s: re-opened file\n", __func__); */ if (ndesc && desc[0].mode != KEYDB_SEARCH_MODE_FIRST && lastfoundoff) { /* Search mode is not first and the last search operation * returned a blob which also was not the first one. We now * need to skip over that blob and hope that the file has * not changed. */ if (es_fseeko (hd->fp, lastfoundoff, SEEK_SET)) { rc = gpg_error_from_syserror (); log_debug ("%s: seeking to last found offset failed: %s\n", __func__, gpg_strerror (rc)); xfree (sn_array); return gpg_error (GPG_ERR_NOTHING_FOUND); } /* log_debug ("%s: re-opened file and sought to last offset\n", */ /* __func__); */ rc = _keybox_read_blob (NULL, hd->fp, NULL); if (rc) { log_debug ("%s: skipping last found blob failed: %s\n", __func__, gpg_strerror (rc)); xfree (sn_array); return gpg_error (GPG_ERR_NOTHING_FOUND); } } } /* Kludge: We need to convert an SN given as hexstring to its binary representation - in some cases we are not able to store it in the search descriptor, because due to the way we use it, it is not possible to free allocated memory. */ if (sn_array) { const unsigned char *s; int i, odd; size_t snlen; for (n=0; n < ndesc; n++) { if (!desc[n].sn) ; else if (desc[n].snlen == -1) { unsigned char *sn; s = desc[n].sn; for (i=0; *s && *s != '/'; s++, i++) ; odd = (i & 1); snlen = (i+1)/2; sn_array[n].sn = xtrymalloc (snlen); if (!sn_array[n].sn) { hd->error = gpg_error_from_syserror (); release_sn_array (sn_array, n); return hd->error; } sn_array[n].snlen = snlen; sn = sn_array[n].sn; s = desc[n].sn; if (odd) { *sn++ = xtoi_1 (s); s++; } for (; *s && *s != '/'; s += 2) *sn++ = xtoi_2 (s); } else { const unsigned char *sn; sn = desc[n].sn; snlen = desc[n].snlen; sn_array[n].sn = xtrymalloc (snlen); if (!sn_array[n].sn) { hd->error = gpg_error_from_syserror (); release_sn_array (sn_array, n); return hd->error; } sn_array[n].snlen = snlen; memcpy (sn_array[n].sn, sn, snlen); } } } pk_no = uid_no = 0; for (;;) { unsigned int blobflags; int blobtype; _keybox_release_blob (blob); blob = NULL; rc = _keybox_read_blob (&blob, hd->fp, NULL); if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE && gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX) { ++*r_skipped; continue; /* Skip too large records. */ } if (rc) break; blobtype = blob_get_type (blob); if (blobtype == KEYBOX_BLOBTYPE_HEADER) continue; if (want_blobtype && blobtype != want_blobtype) continue; blobflags = blob_get_blob_flags (blob); if (!hd->ephemeral && (blobflags & 2)) continue; /* Not in ephemeral mode but blob is flagged ephemeral. */ for (n=0; n < ndesc; n++) { switch (desc[n].mode) { case KEYDB_SEARCH_MODE_NONE: never_reached (); break; case KEYDB_SEARCH_MODE_EXACT: uid_no = has_username (blob, desc[n].u.name, 0); if (uid_no) goto found; break; case KEYDB_SEARCH_MODE_MAIL: uid_no = has_mail (blob, desc[n].u.name, 0); if (uid_no) goto found; break; case KEYDB_SEARCH_MODE_MAILSUB: uid_no = has_mail (blob, desc[n].u.name, 1); if (uid_no) goto found; break; case KEYDB_SEARCH_MODE_SUBSTR: uid_no = has_username (blob, desc[n].u.name, 1); if (uid_no) goto found; break; case KEYDB_SEARCH_MODE_MAILEND: case KEYDB_SEARCH_MODE_WORDS: /* not yet implemented */ break; case KEYDB_SEARCH_MODE_ISSUER: if (has_issuer (blob, desc[n].u.name)) goto found; break; case KEYDB_SEARCH_MODE_ISSUER_SN: if (has_issuer_sn (blob, desc[n].u.name, sn_array? sn_array[n].sn : desc[n].sn, sn_array? sn_array[n].snlen : desc[n].snlen)) goto found; break; case KEYDB_SEARCH_MODE_SN: if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, sn_array? sn_array[n].snlen : desc[n].snlen)) goto found; break; case KEYDB_SEARCH_MODE_SUBJECT: if (has_subject (blob, desc[n].u.name)) goto found; break; case KEYDB_SEARCH_MODE_SHORT_KID: pk_no = has_short_kid (blob, desc[n].u.kid[1]); if (pk_no) goto found; break; case KEYDB_SEARCH_MODE_LONG_KID: pk_no = has_long_kid (blob, desc[n].u.kid[0], desc[n].u.kid[1]); if (pk_no) goto found; break; case KEYDB_SEARCH_MODE_FPR: case KEYDB_SEARCH_MODE_FPR20: pk_no = has_fingerprint (blob, desc[n].u.fpr); if (pk_no) goto found; break; case KEYDB_SEARCH_MODE_KEYGRIP: if (has_keygrip (blob, desc[n].u.grip)) goto found; break; case KEYDB_SEARCH_MODE_FIRST: goto found; break; case KEYDB_SEARCH_MODE_NEXT: goto found; break; default: rc = gpg_error (GPG_ERR_INV_VALUE); goto found; } } continue; found: /* Record which DESC we matched on. Note this value is only meaningful if this function returns with no errors. */ if(r_descindex) *r_descindex = n; for (n=any_skip?0:ndesc; n < ndesc; n++) { u32 kid[2]; if (desc[n].skipfnc && blob_get_first_keyid (blob, kid) && desc[n].skipfnc (desc[n].skipfncvalue, kid, uid_no)) break; } if (n == ndesc) break; /* got it */ } if (!rc) { hd->found.blob = blob; hd->found.pk_no = pk_no; hd->found.uid_no = uid_no; } else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) { _keybox_release_blob (blob); hd->eof = 1; } else { _keybox_release_blob (blob); hd->error = rc; } if (sn_array) release_sn_array (sn_array, ndesc); return rc; } /* Functions to return a certificate or a keyblock. To be used after a successful search operation. */ /* Return the last found keyblock. Returns 0 on success and stores a * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to retun the * number of the key or user id which was matched the search criteria; * if not known they are set to 0. */ gpg_error_t keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf, int *r_pk_no, int *r_uid_no) { gpg_error_t err; const unsigned char *buffer; size_t length; size_t image_off, image_len; size_t siginfo_off, siginfo_len; *r_iobuf = NULL; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP) return gpg_error (GPG_ERR_WRONG_BLOB_TYPE); buffer = _keybox_get_blob_image (hd->found.blob, &length); if (length < 40) return gpg_error (GPG_ERR_TOO_SHORT); image_off = get32 (buffer+8); image_len = get32 (buffer+12); if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length) return gpg_error (GPG_ERR_TOO_SHORT); err = _keybox_get_flag_location (buffer, length, KEYBOX_FLAG_SIG_INFO, &siginfo_off, &siginfo_len); if (err) return err; *r_pk_no = hd->found.pk_no; *r_uid_no = hd->found.uid_no; *r_iobuf = iobuf_temp_with_content (buffer+image_off, image_len); return 0; } #ifdef KEYBOX_WITH_X509 /* Return the last found cert. Caller must free it. */ int keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert) { const unsigned char *buffer; size_t length; size_t cert_off, cert_len; ksba_reader_t reader = NULL; ksba_cert_t cert = NULL; int rc; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_X509) return gpg_error (GPG_ERR_WRONG_BLOB_TYPE); buffer = _keybox_get_blob_image (hd->found.blob, &length); if (length < 40) return gpg_error (GPG_ERR_TOO_SHORT); cert_off = get32 (buffer+8); cert_len = get32 (buffer+12); if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) return gpg_error (GPG_ERR_TOO_SHORT); rc = ksba_reader_new (&reader); if (rc) return rc; rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len); if (rc) { ksba_reader_release (reader); /* fixme: need to map the error codes */ return gpg_error (GPG_ERR_GENERAL); } rc = ksba_cert_new (&cert); if (rc) { ksba_reader_release (reader); return rc; } rc = ksba_cert_read_der (cert, reader); if (rc) { ksba_cert_release (cert); ksba_reader_release (reader); /* fixme: need to map the error codes */ return gpg_error (GPG_ERR_GENERAL); } *r_cert = cert; ksba_reader_release (reader); return 0; } #endif /*KEYBOX_WITH_X509*/ /* Return the flags named WHAT at the address of VALUE. IDX is used only for certain flags and should be 0 if not required. */ int keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value) { const unsigned char *buffer; size_t length; gpg_err_code_t ec; (void)idx; /* Not yet used. */ if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); buffer = _keybox_get_blob_image (hd->found.blob, &length); ec = get_flag_from_image (buffer, length, what, value); return ec? gpg_error (ec):0; } off_t keybox_offset (KEYBOX_HANDLE hd) { if (!hd->fp) return 0; return es_ftello (hd->fp); } gpg_error_t keybox_seek (KEYBOX_HANDLE hd, off_t offset) { gpg_error_t err; if (hd->error) return hd->error; /* still in error state */ if (! hd->fp) { if (!offset) { /* No need to open the file. An unopened file is effectively at offset 0. */ return 0; } err = open_file (hd); if (err) return err; } err = es_fseeko (hd->fp, offset, SEEK_SET); hd->error = gpg_error_from_errno (err); return hd->error; } diff --git a/kbx/keybox.h b/kbx/keybox.h index 565274c10..f90ea1c83 100644 --- a/kbx/keybox.h +++ b/kbx/keybox.h @@ -1,135 +1,137 @@ /* keybox.h - Keybox operations * Copyright (C) 2001, 2003, 2012 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef KEYBOX_H #define KEYBOX_H 1 #ifdef __cplusplus extern "C" { #if 0 } #endif #endif #include "../common/iobuf.h" #include "keybox-search-desc.h" #ifdef KEYBOX_WITH_X509 # include #endif typedef struct keybox_handle *KEYBOX_HANDLE; typedef enum { KEYBOX_FLAG_BLOB, /* The blob flags. */ KEYBOX_FLAG_VALIDITY, /* The validity of the entire key. */ KEYBOX_FLAG_OWNERTRUST, /* The assigned ownertrust. */ KEYBOX_FLAG_KEY, /* The key flags; requires a key index. */ KEYBOX_FLAG_UID, /* The user ID flags; requires an uid index. */ KEYBOX_FLAG_UID_VALIDITY,/* The validity of a specific uid, requires an uid index. */ KEYBOX_FLAG_CREATED_AT, /* The date the block was created. */ KEYBOX_FLAG_SIG_INFO, /* The signature info block. */ } keybox_flag_t; /* Flag values used with KEYBOX_FLAG_BLOB. */ #define KEYBOX_FLAG_BLOB_SECRET 1 #define KEYBOX_FLAG_BLOB_EPHEMERAL 2 /* The keybox blob types. */ typedef enum { KEYBOX_BLOBTYPE_EMPTY = 0, KEYBOX_BLOBTYPE_HEADER = 1, KEYBOX_BLOBTYPE_PGP = 2, KEYBOX_BLOBTYPE_X509 = 3 } keybox_blobtype_t; /*-- keybox-init.c --*/ gpg_error_t keybox_register_file (const char *fname, int secret, void **r_token); int keybox_is_writable (void *token); KEYBOX_HANDLE keybox_new_openpgp (void *token, int secret); KEYBOX_HANDLE keybox_new_x509 (void *token, int secret); +void keybox_close_all_files (void *token); + void keybox_release (KEYBOX_HANDLE hd); void keybox_push_found_state (KEYBOX_HANDLE hd); void keybox_pop_found_state (KEYBOX_HANDLE hd); const char *keybox_get_resource_name (KEYBOX_HANDLE hd); int keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes); gpg_error_t keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout); /*-- keybox-file.c --*/ /* Fixme: This function does not belong here: Provide a better interface to create a new keybox file. */ int _keybox_write_header_blob (estream_t fp, int openpgp_flag); /*-- keybox-search.c --*/ gpg_error_t keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf, int *r_uid_no, int *r_pk_no); #ifdef KEYBOX_WITH_X509 int keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *ret_cert); #endif /*KEYBOX_WITH_X509*/ int keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value); gpg_error_t keybox_search_reset (KEYBOX_HANDLE hd); gpg_error_t keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, keybox_blobtype_t want_blobtype, size_t *r_descindex, unsigned long *r_skipped); off_t keybox_offset (KEYBOX_HANDLE hd); gpg_error_t keybox_seek (KEYBOX_HANDLE hd, off_t offset); /*-- keybox-update.c --*/ gpg_error_t keybox_insert_keyblock (KEYBOX_HANDLE hd, const void *image, size_t imagelen); gpg_error_t keybox_update_keyblock (KEYBOX_HANDLE hd, const void *image, size_t imagelen); #ifdef KEYBOX_WITH_X509 int keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert, unsigned char *sha1_digest); #endif /*KEYBOX_WITH_X509*/ int keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value); int keybox_delete (KEYBOX_HANDLE hd); int keybox_compress (KEYBOX_HANDLE hd); /*-- --*/ #if 0 int keybox_locate_writable (KEYBOX_HANDLE hd); int keybox_rebuild_cache (void *); #endif /*-- keybox-util.c --*/ gpg_error_t keybox_tmp_names (const char *filename, int for_keyring, char **r_bakname, char **r_tmpname); #ifdef __cplusplus } #endif #endif /*KEYBOX_H*/ diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 36afd2231..e024ee7f8 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -1,1092 +1,1098 @@ /* call-dirmngr.c - Communication with the dirmngr * Copyright (C) 2002, 2003, 2005, 2007, 2008, * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "../common/i18n.h" #include "keydb.h" #include "../common/asshelp.h" struct membuf { size_t len; size_t size; char *buf; int out_of_core; }; /* fixme: We need a context for each thread or serialize the access to the dirmngr. */ static assuan_context_t dirmngr_ctx = NULL; static assuan_context_t dirmngr2_ctx = NULL; static int dirmngr_ctx_locked; static int dirmngr2_ctx_locked; struct inq_certificate_parm_s { ctrl_t ctrl; assuan_context_t ctx; ksba_cert_t cert; ksba_cert_t issuer_cert; }; struct isvalid_status_parm_s { ctrl_t ctrl; int seen; unsigned char fpr[20]; }; struct lookup_parm_s { ctrl_t ctrl; assuan_context_t ctx; void (*cb)(void *, ksba_cert_t); void *cb_value; struct membuf data; int error; }; struct run_command_parm_s { ctrl_t ctrl; assuan_context_t ctx; }; static gpg_error_t get_cached_cert (assuan_context_t ctx, const unsigned char *fpr, ksba_cert_t *r_cert); /* A simple implementation of a dynamic buffer. Use init_membuf() to create a buffer, put_membuf to append bytes and get_membuf to release and return the buffer. Allocation errors are detected but only returned at the final get_membuf(), this helps not to clutter the code with out of core checks. */ static void init_membuf (struct membuf *mb, int initiallen) { mb->len = 0; mb->size = initiallen; mb->out_of_core = 0; mb->buf = xtrymalloc (initiallen); if (!mb->buf) mb->out_of_core = 1; } static void put_membuf (struct membuf *mb, const void *buf, size_t len) { if (mb->out_of_core) return; if (mb->len + len >= mb->size) { char *p; mb->size += len + 1024; p = xtryrealloc (mb->buf, mb->size); if (!p) { mb->out_of_core = 1; return; } mb->buf = p; } memcpy (mb->buf + mb->len, buf, len); mb->len += len; } static void * get_membuf (struct membuf *mb, size_t *len) { char *p; if (mb->out_of_core) { xfree (mb->buf); mb->buf = NULL; return NULL; } p = mb->buf; *len = mb->len; mb->buf = NULL; mb->out_of_core = 1; /* don't allow a reuse */ return p; } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx, const char *servername, int mode) { gpg_error_t err; char *serverversion; const char *myversion = strusage (13); err = get_assuan_server_version (ctx, mode, &serverversion); if (err) log_error (_("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); if (!opt.quiet) { log_info (_("Note: Outdated servers may lack important" " security fixes.\n")); log_info (_("Note: Use the command \"%s\" to restart them.\n"), "gpgconf --kill all"); } gpgsm_status2 (ctrl, STATUS_WARNING, "server_version_mismatch 0", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } /* This function prepares the dirmngr for a new session. The audit-events option is used so that other dirmngr clients won't get disturbed by such events. */ static void prepare_dirmngr (ctrl_t ctrl, assuan_context_t ctx, gpg_error_t err) { struct keyserver_spec *server; if (!err) err = warn_version_mismatch (ctrl, ctx, DIRMNGR_NAME, 0); if (!err) { err = assuan_transact (ctx, "OPTION audit-events=1", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* Allow the use of old dirmngr versions. */ } audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); if (!ctx || err) return; server = opt.keyserver; while (server) { char line[ASSUAN_LINELENGTH]; char *user = server->user ? server->user : ""; char *pass = server->pass ? server->pass : ""; char *base = server->base ? server->base : ""; snprintf (line, DIM (line), "LDAPSERVER %s:%i:%s:%s:%s", server->host, server->port, user, pass, base); assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); /* The code below is not required because we don't return an error. */ /* err = [above call] */ /* if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) */ /* err = 0; /\* Allow the use of old dirmngr versions. *\/ */ server = server->next; } } /* Return a new assuan context for a Dirmngr connection. */ static gpg_error_t start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r) { gpg_error_t err; assuan_context_t ctx; if (opt.disable_dirmngr || ctrl->offline) return gpg_error (GPG_ERR_NO_DIRMNGR); if (*ctx_r) return 0; /* Note: if you change this to multiple connections, you also need to take care of the implicit option sending caching. */ err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program, opt.autostart, opt.verbose, DBG_IPC, gpgsm_status2, ctrl); if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR) { static int shown; if (!shown) { shown = 1; log_info (_("no dirmngr running in this session\n")); } } prepare_dirmngr (ctrl, ctx, err); if (err) return err; *ctx_r = ctx; return 0; } static int start_dirmngr (ctrl_t ctrl) { gpg_error_t err; assert (! dirmngr_ctx_locked); dirmngr_ctx_locked = 1; err = start_dirmngr_ext (ctrl, &dirmngr_ctx); /* We do not check ERR but the existence of a context because the error might come from a failed command send to the dirmngr. Fixme: Why don't we close the drimngr context if we encountered an error in prepare_dirmngr? */ if (!dirmngr_ctx) dirmngr_ctx_locked = 0; return err; } static void release_dirmngr (ctrl_t ctrl) { (void)ctrl; if (!dirmngr_ctx_locked) log_error ("WARNING: trying to release a non-locked dirmngr ctx\n"); dirmngr_ctx_locked = 0; } static int start_dirmngr2 (ctrl_t ctrl) { gpg_error_t err; assert (! dirmngr2_ctx_locked); dirmngr2_ctx_locked = 1; err = start_dirmngr_ext (ctrl, &dirmngr2_ctx); if (!dirmngr2_ctx) dirmngr2_ctx_locked = 0; return err; } static void release_dirmngr2 (ctrl_t ctrl) { (void)ctrl; if (!dirmngr2_ctx_locked) log_error ("WARNING: trying to release a non-locked dirmngr2 ctx\n"); dirmngr2_ctx_locked = 0; } /* Handle a SENDCERT inquiry. */ static gpg_error_t inq_certificate (void *opaque, const char *line) { struct inq_certificate_parm_s *parm = opaque; const char *s; int rc; size_t n; const unsigned char *der; size_t derlen; int issuer_mode = 0; ksba_sexp_t ski = NULL; if ((s = has_leading_keyword (line, "SENDCERT"))) { line = s; } else if ((s = has_leading_keyword (line, "SENDCERT_SKI"))) { /* Send a certificate where a sourceKeyIdentifier is included. */ line = s; ski = make_simple_sexp_from_hexstr (line, &n); line += n; while (*line == ' ') line++; } else if ((s = has_leading_keyword (line, "SENDISSUERCERT"))) { line = s; issuer_mode = 1; } else if ((s = has_leading_keyword (line, "ISTRUSTED"))) { /* The server is asking us whether the certificate is a trusted root certificate. */ char fpr[41]; struct rootca_flags_s rootca_flags; line = s; for (s=line,n=0; hexdigitp (s); s++, n++) ; if (*s || n != 40) return gpg_error (GPG_ERR_ASS_PARAMETER); for (s=line, n=0; n < 40; s++, n++) fpr[n] = (*s >= 'a')? (*s & 0xdf): *s; fpr[n] = 0; if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags)) rc = assuan_send_data (parm->ctx, "1", 1); else rc = 0; return rc; } else { log_error ("unsupported certificate inquiry '%s'\n", line); return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } if (!*line) { /* Send the current certificate. */ der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert, &derlen); if (!der) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); } else if (issuer_mode) { log_error ("sending specific issuer certificate back " "is not yet implemented\n"); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } else { /* Send the given certificate. */ int err; ksba_cert_t cert; err = gpgsm_find_cert (parm->ctrl, line, ski, &cert, 1); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); rc = gpg_error (GPG_ERR_NOT_FOUND); } else { der = ksba_cert_get_image (cert, &derlen); if (!der) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); ksba_cert_release (cert); } } xfree (ski); return rc; } /* Take a 20 byte hexencoded string and put it into the provided 20 byte buffer FPR in binary format. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if (*s || (n != 40)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s; s += 2, n++) fpr[n] = xtoi_2 (s); return 1; /* okay */ } static gpg_error_t isvalid_status_cb (void *opaque, const char *line) { struct isvalid_status_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "PROGRESS"))) { if (parm->ctrl) { line = s; if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) return gpg_error (GPG_ERR_ASS_CANCELED); } } else if ((s = has_leading_keyword (line, "ONLY_VALID_IF_CERT_VALID"))) { parm->seen++; if (!*s || !unhexify_fpr (s, parm->fpr)) parm->seen++; /* Bumb it to indicate an error. */ } return 0; } /* Call the directory manager to check whether the certificate is valid Returns 0 for valid or usually one of the errors: GPG_ERR_CERTIFICATE_REVOKED GPG_ERR_NO_CRL_KNOWN GPG_ERR_CRL_TOO_OLD Values for USE_OCSP: 0 = Do CRL check. 1 = Do an OCSP check but fallback to CRL unless CRLS are disabled. 2 = Do only an OCSP check using only the default responder. */ int gpgsm_dirmngr_isvalid (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp) { static int did_options; int rc; char *certid, *certfpr; char line[ASSUAN_LINELENGTH]; struct inq_certificate_parm_s parm; struct isvalid_status_parm_s stparm; + keydb_close_all_files (); + rc = start_dirmngr (ctrl); if (rc) return rc; certfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); certid = gpgsm_get_certid (cert); if (!certid) { log_error ("error getting the certificate ID\n"); release_dirmngr (ctrl); return gpg_error (GPG_ERR_GENERAL); } if (opt.verbose > 1) { char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); log_info ("asking dirmngr about %s%s\n", fpr, use_ocsp? " (using OCSP)":""); xfree (fpr); } parm.ctx = dirmngr_ctx; parm.ctrl = ctrl; parm.cert = cert; parm.issuer_cert = issuer_cert; stparm.ctrl = ctrl; stparm.seen = 0; memset (stparm.fpr, 0, 20); /* It is sufficient to send the options only once because we have * one connection per process only. */ if (!did_options) { if (opt.force_crl_refresh) assuan_transact (dirmngr_ctx, "OPTION force-crl-refresh=1", NULL, NULL, NULL, NULL, NULL, NULL); did_options = 1; } snprintf (line, DIM(line), "ISVALID%s%s %s%s%s", use_ocsp == 2 || opt.no_crl_check ? " --only-ocsp":"", use_ocsp == 2? " --force-default-responder":"", certid, use_ocsp? " ":"", use_ocsp? certfpr:""); xfree (certid); xfree (certfpr); rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, inq_certificate, &parm, isvalid_status_cb, &stparm); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay"); if (!rc && stparm.seen) { /* Need to also check the certificate validity. */ if (stparm.seen != 1) { log_error ("communication problem with dirmngr detected\n"); rc = gpg_error (GPG_ERR_INV_CRL); } else { ksba_cert_t rspcert = NULL; if (get_cached_cert (dirmngr_ctx, stparm.fpr, &rspcert)) { /* Ooops: Something went wrong getting the certificate from the dirmngr. Try our own cert store now. */ KEYDB_HANDLE kh; kh = keydb_new (); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); if (!rc) rc = keydb_search_fpr (ctrl, kh, stparm.fpr); if (!rc) rc = keydb_get_cert (kh, &rspcert); if (rc) { log_error ("unable to find the certificate used " "by the dirmngr: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_CRL); } keydb_release (kh); } if (!rc) { rc = gpgsm_cert_use_ocsp_p (rspcert); if (rc) rc = gpg_error (GPG_ERR_INV_CRL); else { /* Note the no_dirmngr flag: This avoids checking this certificate over and over again. */ rc = gpgsm_validate_chain (ctrl, rspcert, "", NULL, 0, NULL, VALIDATE_FLAG_NO_DIRMNGR, NULL); if (rc) { log_error ("invalid certificate used for CRL/OCSP: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_CRL); } } } ksba_cert_release (rspcert); } } release_dirmngr (ctrl); return rc; } /* Lookup helpers*/ static gpg_error_t lookup_cb (void *opaque, const void *buffer, size_t length) { struct lookup_parm_s *parm = opaque; size_t len; char *buf; ksba_cert_t cert; int rc; if (parm->error) return 0; if (buffer) { put_membuf (&parm->data, buffer, length); return 0; } /* END encountered - process what we have */ buf = get_membuf (&parm->data, &len); if (!buf) { parm->error = gpg_error (GPG_ERR_ENOMEM); return 0; } rc = ksba_cert_new (&cert); if (rc) { parm->error = rc; return 0; } rc = ksba_cert_init_from_mem (cert, buf, len); if (rc) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); } else { parm->cb (parm->cb_value, cert); } ksba_cert_release (cert); init_membuf (&parm->data, 4096); return 0; } /* Return a properly escaped pattern from NAMES. The only error return is NULL to indicate a malloc failure. */ static char * pattern_from_strlist (strlist_t names) { strlist_t sl; int n; const char *s; char *pattern, *p; for (n=0, sl=names; sl; sl = sl->next) { for (s=sl->d; *s; s++, n++) { if (*s == '%' || *s == ' ' || *s == '+') n += 2; } n++; } p = pattern = xtrymalloc (n+1); if (!pattern) return NULL; for (sl=names; sl; sl = sl->next) { for (s=sl->d; *s; s++) { switch (*s) { case '%': *p++ = '%'; *p++ = '2'; *p++ = '5'; break; case ' ': *p++ = '%'; *p++ = '2'; *p++ = '0'; break; case '+': *p++ = '%'; *p++ = '2'; *p++ = 'B'; break; default: *p++ = *s; break; } } *p++ = ' '; } if (p == pattern) *pattern = 0; /* is empty */ else p[-1] = '\0'; /* remove trailing blank */ return pattern; } static gpg_error_t lookup_status_cb (void *opaque, const char *line) { struct lookup_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "PROGRESS"))) { if (parm->ctrl) { line = s; if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) return gpg_error (GPG_ERR_ASS_CANCELED); } } else if ((s = has_leading_keyword (line, "TRUNCATED"))) { if (parm->ctrl) { line = s; gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line); } } return 0; } /* Run the Directory Manager's lookup command using the pattern compiled from the strings given in NAMES or from URI. The caller must provide the callback CB which will be passed cert by cert. Note that CTRL is optional. With CACHE_ONLY the dirmngr will search only its own key cache. */ int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, const char *uri, int cache_only, void (*cb)(void*, ksba_cert_t), void *cb_value) { int rc; char line[ASSUAN_LINELENGTH]; struct lookup_parm_s parm; size_t len; assuan_context_t ctx; const char *s; if ((names && uri) || (!names && !uri)) return gpg_error (GPG_ERR_INV_ARG); + keydb_close_all_files (); + /* The lookup function can be invoked from the callback of a lookup function, for example to walk the chain. */ if (!dirmngr_ctx_locked) { rc = start_dirmngr (ctrl); if (rc) return rc; ctx = dirmngr_ctx; } else if (!dirmngr2_ctx_locked) { rc = start_dirmngr2 (ctrl); if (rc) return rc; ctx = dirmngr2_ctx; } else { log_fatal ("both dirmngr contexts are in use\n"); } if (names) { char *pattern = pattern_from_strlist (names); if (!pattern) { if (ctx == dirmngr_ctx) release_dirmngr (ctrl); else release_dirmngr2 (ctrl); return out_of_core (); } snprintf (line, DIM(line), "LOOKUP%s %s", cache_only? " --cache-only":"", pattern); xfree (pattern); } else { for (s=uri; *s; s++) if (*s <= ' ') { if (ctx == dirmngr_ctx) release_dirmngr (ctrl); else release_dirmngr2 (ctrl); return gpg_error (GPG_ERR_INV_URI); } snprintf (line, DIM(line), "LOOKUP --url %s", uri); } parm.ctrl = ctrl; parm.ctx = ctx; parm.cb = cb; parm.cb_value = cb_value; parm.error = 0; init_membuf (&parm.data, 4096); rc = assuan_transact (ctx, line, lookup_cb, &parm, NULL, NULL, lookup_status_cb, &parm); xfree (get_membuf (&parm.data, &len)); if (ctx == dirmngr_ctx) release_dirmngr (ctrl); else release_dirmngr2 (ctrl); if (rc) return rc; return parm.error; } static gpg_error_t get_cached_cert_data_cb (void *opaque, const void *buffer, size_t length) { struct membuf *mb = opaque; if (buffer) put_membuf (mb, buffer, length); return 0; } /* Return a certificate from the Directory Manager's cache. This function only returns one certificate which must be specified using the fingerprint FPR and will be stored at R_CERT. On error NULL is stored at R_CERT and an error code returned. Note that the caller must provide the locked dirmngr context CTX. */ static gpg_error_t get_cached_cert (assuan_context_t ctx, const unsigned char *fpr, ksba_cert_t *r_cert) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char hexfpr[2*20+1]; struct membuf mb; char *buf; size_t buflen = 0; ksba_cert_t cert; *r_cert = NULL; bin2hex (fpr, 20, hexfpr); snprintf (line, DIM(line), "LOOKUP --single --cache-only 0x%s", hexfpr); init_membuf (&mb, 4096); err = assuan_transact (ctx, line, get_cached_cert_data_cb, &mb, NULL, NULL, NULL, NULL); buf = get_membuf (&mb, &buflen); if (err) { xfree (buf); return err; } if (!buf) return gpg_error (GPG_ERR_ENOMEM); err = ksba_cert_new (&cert); if (err) { xfree (buf); return err; } err = ksba_cert_init_from_mem (cert, buf, buflen); xfree (buf); if (err) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (err)); ksba_cert_release (cert); return err; } *r_cert = cert; return 0; } /* Run Command helpers*/ /* Fairly simple callback to write all output of dirmngr to stdout. */ static gpg_error_t run_command_cb (void *opaque, const void *buffer, size_t length) { (void)opaque; if (buffer) { if ( fwrite (buffer, length, 1, stdout) != 1 ) log_error ("error writing to stdout: %s\n", strerror (errno)); } return 0; } /* Handle inquiries from the dirmngr COMMAND. */ static gpg_error_t run_command_inq_cb (void *opaque, const char *line) { struct run_command_parm_s *parm = opaque; const char *s; int rc = 0; if ((s = has_leading_keyword (line, "SENDCERT"))) { /* send the given certificate */ int err; ksba_cert_t cert; const unsigned char *der; size_t derlen; line = s; if (!*line) return gpg_error (GPG_ERR_ASS_PARAMETER); err = gpgsm_find_cert (parm->ctrl, line, NULL, &cert, 1); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); rc = gpg_error (GPG_ERR_NOT_FOUND); } else { der = ksba_cert_get_image (cert, &derlen); if (!der) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); ksba_cert_release (cert); } } else if ((s = has_leading_keyword (line, "PRINTINFO"))) { /* Simply show the message given in the argument. */ line = s; log_info ("dirmngr: %s\n", line); } else if ((s = has_leading_keyword (line, "ISTRUSTED"))) { /* The server is asking us whether the certificate is a trusted root certificate. */ char fpr[41]; struct rootca_flags_s rootca_flags; int n; line = s; for (s=line,n=0; hexdigitp (s); s++, n++) ; if (*s || n != 40) return gpg_error (GPG_ERR_ASS_PARAMETER); for (s=line, n=0; n < 40; s++, n++) fpr[n] = (*s >= 'a')? (*s & 0xdf): *s; fpr[n] = 0; if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags)) rc = assuan_send_data (parm->ctx, "1", 1); else rc = 0; return rc; } else { log_error ("unsupported command inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return rc; } static gpg_error_t run_command_status_cb (void *opaque, const char *line) { ctrl_t ctrl = opaque; const char *s; if (opt.verbose) { log_info ("dirmngr status: %s\n", line); } if ((s = has_leading_keyword (line, "PROGRESS"))) { if (ctrl) { line = s; if (gpgsm_status (ctrl, STATUS_PROGRESS, line)) return gpg_error (GPG_ERR_ASS_CANCELED); } } return 0; } /* Pass COMMAND to dirmngr and print all output generated by Dirmngr to stdout. A couple of inquiries are defined (see above). ARGC arguments in ARGV are given to the Dirmngr. Spaces, plus and percent characters within the argument strings are percent escaped so that blanks can act as delimiters. */ int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, int argc, char **argv) { int rc; int i; const char *s; char *line, *p; size_t len; struct run_command_parm_s parm; + keydb_close_all_files (); + rc = start_dirmngr (ctrl); if (rc) return rc; parm.ctrl = ctrl; parm.ctx = dirmngr_ctx; len = strlen (command) + 1; for (i=0; i < argc; i++) len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */ line = xtrymalloc (len); if (!line) { release_dirmngr (ctrl); return out_of_core (); } p = stpcpy (line, command); for (i=0; i < argc; i++) { *p++ = ' '; for (s=argv[i]; *s; s++) { if (!isascii (*s)) *p++ = *s; else if (*s == ' ') *p++ = '+'; else if (!isprint (*s) || *s == '+') { sprintf (p, "%%%02X", *(const unsigned char *)s); p += 3; } else *p++ = *s; } } *p = 0; rc = assuan_transact (dirmngr_ctx, line, run_command_cb, NULL, run_command_inq_cb, &parm, run_command_status_cb, ctrl); xfree (line); log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay"); release_dirmngr (ctrl); return rc; } diff --git a/sm/keydb.c b/sm/keydb.c index c4803f8b7..49d3404a3 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -1,1303 +1,1323 @@ /* keydb.c - key database dispatcher * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * Copyright (C) 2014 g10 Code GmbH * * 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 . */ #include #include #include #include #include #include #include #include #include #include "gpgsm.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_KEYBOX } KeydbResourceType; #define MAX_KEYDB_RESOURCES 20 struct resource_item { KeydbResourceType type; union { KEYBOX_HANDLE kr; } u; void *token; dotlock_t lockhandle; }; static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; static int used_resources; /* Whether we have successfully registered any resource. */ static int any_registered; struct keydb_handle { int locked; int found; int saved_found; int current; int is_ephemeral; int used; /* items in active */ struct resource_item active[MAX_KEYDB_RESOURCES]; }; static int lock_all (KEYDB_HANDLE hd); static void unlock_all (KEYDB_HANDLE hd); static void try_make_homedir (const char *fname) { if ( opt.dry_run || opt.no_homedir_creation ) return; gnupg_maybe_make_homedir (fname, opt.quiet); } /* Handle the creation of a keybox if it does not yet exist. Take into acount that other processes might have the keybox already locked. This lock check does not work if the directory itself is not yet available. If R_CREATED is not NULL it will be set to true if the function created a new keybox. */ static gpg_error_t maybe_create_keybox (char *filename, int force, int *r_created) { gpg_err_code_t ec; dotlock_t lockhd = NULL; estream_t fp; int rc; mode_t oldmask; char *last_slash_in_filename; int save_slash; if (r_created) *r_created = 0; /* 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) 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 some home directory name patterns. */ 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 keybox (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) { /* 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'\n", filename ); if (!force) return gpg_error (GPG_ERR_ENOENT); else return gpg_error (GPG_ERR_GENERAL); } if ( dotlock_take (lockhd, -1) ) { /* This is something bad. Probably a stale lockfile. */ log_info ("can't lock '%s'\n", filename); rc = gpg_error (GPG_ERR_GENERAL); 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); fp = es_fopen (filename, "wb"); if (!fp) { rc = gpg_error_from_syserror (); umask (oldmask); log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (rc)); goto leave; } umask (oldmask); /* Make sure that at least one record is in a new keybox file, so that the detection magic for OpenPGP keyboxes works the next time it is used. */ rc = _keybox_write_header_blob (fp, 0); if (rc) { es_fclose (fp); log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (rc)); goto leave; } if (!opt.quiet) log_info (_("keybox '%s' created\n"), filename); if (r_created) *r_created = 1; es_fclose (fp); rc = 0; leave: if (lockhd) { dotlock_release (lockhd); dotlock_destroy (lockhd); } return rc; } /* * Register a resource (which currently may only be a keybox file). * The first keybox which is added by this function is created if it * does not exist. If AUTO_CREATED is not NULL it will be set to true * if the function has created a new keybox. */ gpg_error_t keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created) { const char *resname = url; char *filename = NULL; gpg_error_t err = 0; KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; if (auto_created) *auto_created = 0; /* Do we have an URL? gnupg-kbx:filename := this is a plain keybox filename := See what it is, but create as plain keybox. */ if (strlen (resname) > 10) { if (!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 ) { /* do tilde expansion etc */ if (strchr(resname, DIRSEP_C) ) filename = make_filename (resname, NULL); else filename = make_filename (gnupg_homedir (), resname, NULL); } else filename = xstrdup (resname); if (!force) force = !any_registered; /* see whether we can determine the filetype */ if (rt == KEYDB_RESOURCE_TYPE_NONE) { estream_t fp; fp = es_fopen( filename, "rb" ); if (fp) { u32 magic; /* FIXME: check for the keybox magic */ if (es_fread (&magic, 4, 1, fp) == 1 ) { if (magic == 0x13579ace || magic == 0xce9a5713) ; /* GDBM magic - no more support */ else rt = KEYDB_RESOURCE_TYPE_KEYBOX; } else /* maybe empty: assume keybox */ rt = KEYDB_RESOURCE_TYPE_KEYBOX; es_fclose (fp); } 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_KEYBOX: err = maybe_create_keybox (filename, force, auto_created); if (err) goto leave; /* Now register the file */ { void *token; err = keybox_register_file (filename, 0, &token); if (gpg_err_code (err) == GPG_ERR_EEXIST) ; /* Already registered - ignore. */ else if (err) ; /* Other error. */ else if (used_resources >= MAX_KEYDB_RESOURCES) err = gpg_error (GPG_ERR_RESOURCE_LIMIT); else { all_resources[used_resources].type = rt; all_resources[used_resources].u.kr = NULL; /* Not used here */ all_resources[used_resources].token = token; all_resources[used_resources].lockhandle = dotlock_create (filename, 0); if (!all_resources[used_resources].lockhandle) log_fatal ( _("can't create lock for '%s'\n"), filename); /* Do a compress run if needed and the file is not locked. */ if (!dotlock_take (all_resources[used_resources].lockhandle, 0)) { KEYBOX_HANDLE kbxhd = keybox_new_x509 (token, 0); if (kbxhd) { keybox_compress (kbxhd); keybox_release (kbxhd); } dotlock_release (all_resources[used_resources].lockhandle); } used_resources++; } } break; default: log_error ("resource type of '%s' not supported\n", url); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* fixme: check directory permissions and print a warning */ leave: if (err) { log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err)); gpgsm_status_with_error (ctrl, STATUS_ERROR, "add_keyblock_resource", err); } else any_registered = 1; xfree (filename); return err; } +/* This is a helper requyired under Windows to close all files so that + * a rename will work. */ +void +keydb_close_all_files (void) +{ +#ifdef HAVE_W32_SYSTEM + int i; + + log_assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=0; i < used_resources; i++) + if (all_resources[i].type == KEYDB_RESOURCE_TYPE_KEYBOX) + keybox_close_all_files (all_resources[i].token); +#endif +} + + + KEYDB_HANDLE keydb_new (void) { KEYDB_HANDLE hd; int i, j; hd = xcalloc (1, sizeof *hd); hd->found = -1; hd->saved_found = -1; assert (used_resources <= MAX_KEYDB_RESOURCES); for (i=j=0; i < used_resources; i++) { switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ 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].lockhandle = all_resources[i].lockhandle; hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0); if (!hd->active[j].u.kr) { xfree (hd); return NULL; /* fixme: release all previously allocated handles*/ } j++; break; } } hd->used = j; active_handles++; 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_KEYBOX: keybox_release (hd->active[i].u.kr); break; } } xfree (hd); } /* Return the name of the current resource. This is function first looks for the last found found, then for the current search position, and last returns the first available resource. The returned string is only valid as long as the handle exists. This function does only return 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_KEYBOX: s = keybox_get_resource_name (hd->active[idx].u.kr); break; } return s? s: ""; } /* Switch the handle into ephemeral mode and return the original value. */ int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes) { int i; if (!hd) return 0; yes = !!yes; if (hd->is_ephemeral != yes) { for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_set_ephemeral (hd->active[i].u.kr, yes); break; } } } i = hd->is_ephemeral; hd->is_ephemeral = yes; return i; } /* If the keyring has not yet been locked, lock it now. This operation is required before any update operation; it is optional for an insert operation. The lock is released with keydb_released. */ gpg_error_t keydb_lock (KEYDB_HANDLE hd) { if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); if (hd->locked) return 0; /* Already locked. */ return lock_all (hd); } static int lock_all (KEYDB_HANDLE hd) { int i, rc = 0; /* Fixme: This locking scheme may lead to 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. */ for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (hd->active[i].lockhandle) rc = dotlock_take (hd->active[i].lockhandle, -1); break; } if (rc) break; } if (rc) { /* revert the already set locks */ for (i--; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (hd->active[i].lockhandle) dotlock_release (hd->active[i].lockhandle); break; } } } else hd->locked = 1; /* make_dotlock () does not yet guarantee that errno is set, thus we can't rely on the error reason and will simply use EACCES. */ return rc? gpg_error (GPG_ERR_EACCES) : 0; } 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_KEYBOX: if (hd->active[i].lockhandle) dotlock_release (hd->active[i].lockhandle); break; } } hd->locked = 0; } /* Push the last found state if any. */ 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_KEYBOX: keybox_push_found_state (hd->active[hd->found].u.kr); break; } hd->saved_found = hd->found; hd->found = -1; } /* Pop the last found state. */ 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_KEYBOX: keybox_pop_found_state (hd->active[hd->found].u.kr); break; } } /* Return the last found object. Caller must free it. The returned keyblock has the kbode 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. */ int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert) { int rc = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_get_cert (hd->active[hd->found].u.kr, r_cert); break; } return rc; } /* Return a flag of the last found object. WHICH is the flag requested; it should be one of the KEYBOX_FLAG_ values. If the operation is successful, the flag value will be stored at the address given by VALUE. Return 0 on success or an error code. */ gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value) { int err = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_NOTHING_FOUND); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value); break; } return err; } /* Set a flag of the last found object. WHICH is the flag to be set; it should be one of the KEYBOX_FLAG_ values. If the operation is successful, the flag value will be stored in the keybox. Note, that some flag values can't be updated and thus may return an error, some other flag values may be masked out before an update. Returns 0 on success or an error code. */ gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value) { int err = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_NOTHING_FOUND); if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value); break; } return err; } /* * Insert a new Certificate into one of the resources. */ int keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { int rc = -1; int idx; unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); 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); if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest); break; } unlock_all (hd); return rc; } /* * The current keyblock or cert will be deleted. */ int keydb_delete (KEYDB_HANDLE hd, int unlock) { int rc = -1; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ if( opt.dry_run ) return 0; if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_delete (hd->active[hd->found].u.kr); break; } if (unlock) unlock_all (hd); return rc; } /* * Locate the default writable key resource, so that the next * operation (which is only relevant for inserts) will be done on this * resource. */ int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) { int rc; (void)reserved; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); 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_KEYBOX: if (keybox_is_writable (hd->active[hd->current].token)) return 0; /* found (hd->current is set to it) */ break; } } return -1; } /* * Rebuild the caches of all key resources. */ void keydb_rebuild_caches (void) { int i; for (i=0; i < used_resources; i++) { switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: /* rc = keybox_rebuild_cache (all_resources[i].token); */ /* if (rc) */ /* log_error (_("failed to rebuild keybox cache: %s\n"), */ /* g10_errstr (rc)); */ break; } } } /* * Start the next search on this handle right at the beginning */ gpg_error_t keydb_search_reset (KEYDB_HANDLE hd) { int i; gpg_error_t rc = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); hd->current = 0; hd->found = -1; /* and 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_KEYBOX: rc = keybox_search_reset (hd->active[i].u.kr); break; } } return rc; } /* * Search through all keydb resources, starting at the current position, * for a keyblock which contains one of the keys described in the DESC array. */ int keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) { int rc = -1; unsigned long skipped; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!any_registered) { gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN)); return gpg_error (GPG_ERR_NOT_FOUND); } while (rc == -1 && hd->current >= 0 && 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_KEYBOX: rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc, KEYBOX_BLOBTYPE_X509, NULL, &skipped); break; } if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) { /* EOF -> switch to next resource */ hd->current++; } else if (!rc) hd->found = hd->current; } return rc; } int keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_NEXT; return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid) { KEYDB_SEARCH_DESC desc; (void)kid; 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 (ctrl, hd, &desc, 1); } int keydb_search_fpr (ctrl_t ctrl, 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, 20); return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer) { KEYDB_SEARCH_DESC desc; int rc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_ISSUER; desc.u.name = issuer; rc = keydb_search (ctrl, hd, &desc, 1); return rc; } int keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer, ksba_const_sexp_t serial) { KEYDB_SEARCH_DESC desc; int rc; const unsigned char *s; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN; s = serial; if (*s !='(') return gpg_error (GPG_ERR_INV_VALUE); s++; for (desc.snlen = 0; digitp (s); s++) desc.snlen = 10*desc.snlen + atoi_1 (s); if (*s !=':') return gpg_error (GPG_ERR_INV_VALUE); desc.sn = s+1; desc.u.name = issuer; rc = keydb_search (ctrl, hd, &desc, 1); return rc; } int keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *name) { KEYDB_SEARCH_DESC desc; int rc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_SUBJECT; desc.u.name = name; rc = keydb_search (ctrl, hd, &desc, 1); return rc; } /* Store the certificate in the key DB but make sure that it does not already exists. We do this simply by comparing the fingerprint. If EXISTED is not NULL it will be set to true if the certificate was already in the DB. */ int keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int *existed) { KEYDB_HANDLE kh; int rc; unsigned char fpr[20]; if (existed) *existed = 0; if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) { log_error (_("failed to get the fingerprint\n")); return gpg_error (GPG_ERR_GENERAL); } kh = keydb_new (); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); return gpg_error (GPG_ERR_ENOMEM);; } /* Set the ephemeral flag so that the search looks at all records. */ keydb_set_ephemeral (kh, 1); + keydb_close_all_files (); rc = lock_all (kh); if (rc) return rc; rc = keydb_search_fpr (ctrl, kh, fpr); if (rc != -1) { keydb_release (kh); if (!rc) { if (existed) *existed = 1; if (!ephemeral) { /* Remove ephemeral flags from existing certificate to "store" it permanently. */ rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0, KEYBOX_FLAG_BLOB_EPHEMERAL, 0); if (rc) { log_error ("clearing ephemeral flag failed: %s\n", gpg_strerror (rc)); return rc; } } return 0; /* okay */ } log_error (_("problem looking for existing certificate: %s\n"), gpg_strerror (rc)); return rc; } /* Reset the ephemeral flag if not requested. */ if (!ephemeral) keydb_set_ephemeral (kh, 0); rc = keydb_locate_writable (kh, 0); if (rc) { log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc)); keydb_release (kh); return rc; } rc = keydb_insert_cert (kh, cert); if (rc) { log_error (_("error storing certificate: %s\n"), gpg_strerror (rc)); keydb_release (kh); return rc; } keydb_release (kh); return 0; } /* This is basically keydb_set_flags but it implements a complete transaction by locating the certificate in the DB and updating the flags. */ gpg_error_t keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int which, int idx, unsigned int mask, unsigned int value) { KEYDB_HANDLE kh; gpg_error_t err; unsigned char fpr[20]; unsigned int old_value; if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) { log_error (_("failed to get the fingerprint\n")); return gpg_error (GPG_ERR_GENERAL); } kh = keydb_new (); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); return gpg_error (GPG_ERR_ENOMEM);; } if (ephemeral) keydb_set_ephemeral (kh, 1); + keydb_close_all_files (); err = keydb_lock (kh); if (err) { log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } err = keydb_search_fpr (ctrl, kh, fpr); if (err) { if (err == -1) err = gpg_error (GPG_ERR_NOT_FOUND); else log_error (_("problem re-searching certificate: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } err = keydb_get_flags (kh, which, idx, &old_value); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } value = ((old_value & ~mask) | (value & mask)); if (value != old_value) { err = keydb_set_flags (kh, which, idx, value); if (err) { log_error (_("error storing flags: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } } keydb_release (kh); return 0; } /* Reset all the certificate flags we have stored with the certificates for performance reasons. */ void keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names) { gpg_error_t err; KEYDB_HANDLE hd = NULL; KEYDB_SEARCH_DESC *desc = NULL; int ndesc; strlist_t sl; int rc=0; unsigned int old_value, value; (void)ctrl; hd = keydb_new (); if (!hd) { log_error ("keydb_new failed\n"); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { log_error ("allocating memory failed: %s\n", gpg_strerror (out_of_core ())); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = classify_user_id (sl->d, desc+ndesc, 0); if (rc) log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc)); else ndesc++; } } + keydb_close_all_files (); err = keydb_lock (hd); if (err) { log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); goto leave; } while (!(rc = keydb_search (ctrl, hd, desc, ndesc))) { if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); goto leave; } value = (old_value & ~VALIDITY_REVOKED); if (value != old_value) { err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value); if (err) { log_error (_("error storing flags: %s\n"), gpg_strerror (err)); goto leave; } } } if (rc && rc != -1) - log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + log_error ("%s failed: %s\n", __func__, gpg_strerror (rc)); leave: xfree (desc); keydb_release (hd); } diff --git a/sm/keydb.h b/sm/keydb.h index f4db5d394..226cac226 100644 --- a/sm/keydb.h +++ b/sm/keydb.h @@ -1,77 +1,79 @@ /* keydb.h - Key database * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef GNUPG_KEYDB_H #define GNUPG_KEYDB_H #include #include "../common/userids.h" typedef struct keydb_handle *KEYDB_HANDLE; /* Flag value used with KEYBOX_FLAG_VALIDITY. */ #define VALIDITY_REVOKED (1<<5) /*-- keydb.c --*/ gpg_error_t keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created); +void keydb_close_all_files (void); + KEYDB_HANDLE keydb_new (void); void keydb_release (KEYDB_HANDLE hd); int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes); const char *keydb_get_resource_name (KEYDB_HANDLE hd); gpg_error_t keydb_lock (KEYDB_HANDLE hd); gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value); gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value); void keydb_push_found_state (KEYDB_HANDLE hd); void keydb_pop_found_state (KEYDB_HANDLE hd); int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert); int keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert); int keydb_delete (KEYDB_HANDLE hd, int unlock); int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved); void keydb_rebuild_caches (void); gpg_error_t keydb_search_reset (KEYDB_HANDLE hd); int keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc); int keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd); int keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd); int keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid); int keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr); int keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer); int keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer, const unsigned char *serial); int keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer); int keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int *existed); gpg_error_t keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int which, int idx, unsigned int mask, unsigned int value); void keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names); #endif /*GNUPG_KEYDB_H*/