--- trunk/Src/wptKeyCache.cpp 2005/10/24 08:03:48 32 +++ trunk/Src/wptKeyCache.cpp 2006/01/27 10:08:10 168 @@ -1,596 +1,838 @@ -/* wptKeyCache.cpp- Caching for the pub- and the secring - * Copyright (C) 2001-2005 Timo Schulz - * - * This file is part of MyGPGME. - * - * MyGPGME 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 2 of the License, or - * (at your option) any later version. - * - * MyGPGME 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ -#include -#include -#include -#include -#include -#include - -#include "w32gpgme.h" -#include "wptKeyCache.h" -#include "openpgp.h" -#include "wptNLS.h" -#include "wptErrors.h" -#include "wptW32API.h" - - -/* convert a binary buffer into its hex representation. */ -static void -buffer_to_string (char *dst, size_t dlen, const byte *buf, size_t nbytes) -{ - char dig[3]; - size_t i; - - memset (dst, 0, dlen); - for (i = 0; i < nbytes && dlen > 0; i++) { - sprintf (dig, "%02X", buf[i]); - strcat (dst, dig); - dlen -= 2; - } -} - - -/* Parse the secret keyring and retrieve some additional information - for each key which was found. */ -static void -parse_secring (gpg_keycache_t cache, const char *kid, const char *secring) -{ - PACKET *pkt = (PACKET*)calloc (1, sizeof *pkt); - PKT_secret_key *sk; - gpg_iobuf_t inp; - gpgme_error_t err; - gpgme_key_t key; - struct keycache_s *c=NULL; - char keyid[16+1]; - - inp = gpg_iobuf_open (secring); - if (!inp) { - safe_free (pkt); - return; - } - gpg_iobuf_ioctl (inp, 3, 1, NULL); - gpg_init_packet (pkt); - while (gpg_parse_packet (inp, pkt) != -1) { - if (pkt->pkttype == PKT_SECRET_KEY) { - sk = pkt->pkt.secret_key; - /* XXX: key IDs of card public keys are wrong! */ - _snprintf (keyid, sizeof (keyid)-1, "%08lX", - gpg_keyid_from_sk (sk, NULL)); - if (kid && strcmp (kid, keyid) != 0) - goto next; - err = gpg_keycache_find_key2 (cache, keyid, 0, &key, &c); - if (err) - goto next; - c->gloflags.is_protected = sk->is_protected; - c->gloflags.divert_to_card = sk->protect.s2k.mode==1002? 1 : 0; - if (c->pubpart != NULL) { - c->pubpart->gloflags.is_protected = sk->is_protected; - c->pubpart->gloflags.divert_to_card = sk->protect.s2k.mode==1002? 1 : 0; - } - } -next: - gpg_free_packet (pkt); - gpg_init_packet (pkt); - } - safe_free (pkt); - gpg_iobuf_close (inp); -} - - -/* Merge the information from the keyrings into the key cache structure. */ -gpgme_error_t -keycache_prepare2 (gpg_keycache_t ctx, const char *kid, - const char *pubring, const char *secring) -{ - gpgme_error_t err; - gpgme_key_t key = NULL; - gpg_iobuf_t inp; - PACKET *pkt = (PACKET*)calloc (1, sizeof * pkt); - struct keycache_s *c; - const byte *sym_prefs; - char keyid[16+1], *id = NULL; - int key_seen = 0; - size_t nbytes = 0, nsym =0; - - if (secring) { - parse_secring (ctx, kid, secring); - if (!pubring) { - safe_free(pkt); - return 0; - } - } - inp = gpg_iobuf_open (pubring); - if (!inp) { - safe_free( pkt ); - return gpg_error (GPG_ERR_KEYRING_OPEN); - } - gpg_iobuf_ioctl( inp, 3, 1, NULL ); /* disable cache */ - - gpg_init_packet( pkt ); - while (gpg_parse_packet (inp, pkt) != -1) { - if (pkt->pkttype == PKT_PUBLIC_KEY) { - strcpy (keyid, ""); - key_seen = 1; - } - - if (pkt->pkttype == PKT_SIGNATURE && pkt->pkt.signature->sig_class == 0x1F) { - if (pkt->pkt.signature->numrevkeys == 0) - goto next; - _snprintf (keyid, sizeof keyid -1, "%08X", pkt->pkt.signature->keyid[1]); - if (kid && strcmp (kid, keyid) != 0) - goto next; - err = gpg_keycache_find_key2 (ctx, keyid, 0, &key, &c); - if (err) - goto next; - c->gloflags.has_desig_rev = 1; - } - if (pkt->pkttype == PKT_SIGNATURE && key_seen == 1 ) { - sym_prefs=gpg_parse_sig_subpkt (pkt->pkt.signature->hashed, - SIGSUBPKT_PREF_SYM, &nsym); - if (sym_prefs == NULL) - goto next; - _snprintf (keyid, sizeof keyid - 1, "%08X", pkt->pkt.signature->keyid[1]); - if (kid && strcmp (kid, keyid) != 0) - goto next; - err = gpg_keycache_find_key2 (ctx, keyid, 0, &key, &c); - if (err) - goto next; - else if (nsym > 0) { - c->sym_prefs = (unsigned char*)calloc (1, nsym+1); - if (!c->sym_prefs) - return gpg_error (GPG_ERR_ENOMEM); - memcpy (c->sym_prefs, sym_prefs, nsym); - } - } - if (pkt->pkttype == PKT_USER_ID) { - if (id) - free (id); - id = strdup (pkt->pkt.user_id->name); - if (!id) { - err = gpg_error (GPG_ERR_ENOMEM); - goto fail; - } - } - if ((pkt->pkttype == PKT_USER_ID || pkt->pkttype == PKT_ATTRIBUTE) - && pkt->pkt.user_id->attrib_data && key) { - PKT_user_id *id = pkt->pkt.user_id; - c->attrib.used = 1; - c->attrib.len = id->attrib_len; - c->attrib.d = (unsigned char*)calloc (1, id->attrib_len + 1); - if (!c->attrib.d) { - err = gpg_error (GPG_ERR_ENOMEM); - goto fail; - } - memcpy (c->attrib.d, id->attrib_data, id->attrib_len); - key = NULL; - c = NULL; - } -next: - gpg_free_packet (pkt); - gpg_init_packet(pkt); - } - -fail: - safe_free (id); - safe_free (pkt); - gpg_iobuf_close (inp); - return err; -} - - -gpgme_error_t -gpg_keycache_prepare (gpg_keycache_t ctx, const char *pubr, const char *secr) -{ - return keycache_prepare2 (ctx, NULL, pubr, secr); -} - -gpgme_error_t -gpg_keycache_prepare_single (gpg_keycache_t ctx, const char *keyid, - const char *pubr, const char *secr) -{ - if (!strncmp (keyid, "0x", 2)) - keyid += 2; - return keycache_prepare2 (ctx, keyid, pubr, secr); -} - - -/* Create new keycache object and return it in @r_ctx. - Return value: 0 on success. */ -gpgme_error_t -gpg_keycache_new (gpg_keycache_t *r_ctx) -{ - gpg_keycache_t ctx; - - if (!r_ctx) - return gpg_error (GPG_ERR_INV_ARG); - ctx = (gpg_keycache_t)calloc (1, sizeof *ctx); - if (!ctx) - return gpg_error (GPG_ERR_ENOMEM); - ctx->secret = 0; - ctx->pos = 0; - *r_ctx = ctx; - return 0; -} - - -/* Release keycache object @ctx. */ -void -gpg_keycache_release (gpg_keycache_t ctx) -{ - struct keycache_s *c, *c2; - - if (!ctx) - return; - - for (c = ctx->item; c; c = c2) { - c2 = c->next; - gpgme_key_release (c->key); - c->key = NULL; - if (c->sym_prefs) - free (c->sym_prefs); - c->sym_prefs = NULL; - if (c->attrib.d) - free (c->attrib.d); - c->attrib.d = NULL; - if (c->card_type) - free (c->card_type); - free (c); - } - if (ctx) - free (ctx); -} - - -/* Set (progress) callback for the given keycache object. - @ctx the keycache - @cb the callback function - @cb_value1 opaque value which is passed to the callback. - @cb_value2 see @cb_value1. */ -void -gpg_keycache_set_cb (gpg_keycache_t ctx, - void (*cb)(void *, const char *, int, int, int), - void * cb_value1, int cb_value2) -{ - if (!ctx) - return; - ctx->cb = cb; - ctx->cb_value = cb_value1; - ctx->cb_value2 = cb_value2; -} - - -/* Add @key to the cache @ctx. @opaque return the key cache context as a void*. - Return value: 0 on success. */ -gpgme_error_t -gpg_keycache_add_key (gpg_keycache_t ctx, gpgme_key_t key, void **opaque) -{ - struct keycache_s * c, * n1; - - if (!ctx) - return gpg_error (GPG_ERR_INV_ARG); - - c = (struct keycache_s*)calloc (1, sizeof *c); - if (!c) - return gpg_error (GPG_ERR_ENOMEM); - c->gloflags.is_protected = 1; /*default: assume protection. */ - c->key = key; - if (!ctx->item) - ctx->item = c; - else { - for (n1 = ctx->item; n1 && n1->next; n1 = n1->next) - ; - n1->next = c; - } - if (opaque) - *opaque = c; - return 0; -} - - - -#define has_keyid_len(pattern) (\ - strlen (pattern) == 8 || strlen (pattern) == 10 || \ - strlen (pattern) == 16 || strlen (pattern) == 18) - - -gpgme_error_t -gpg_keycache_find_key2 (gpg_keycache_t ctx, const char *pattern, int flags, - gpgme_key_t *r_key, struct keycache_s **r_item) -{ - struct keycache_s *c; - gpgme_subkey_t s; - gpgme_user_id_t u; - gpgme_key_t key; - const char *kid; - - if (!ctx || !r_key) - return gpg_error (GPG_ERR_INV_ARG); - - if (strstr (pattern, "0x")) - pattern += 2; - - /* Sometimes a subkey has no valid fpr. As a kludge we convert v4 - fingerprints into the 64-bit keyid. */ - if (strlen (pattern) == 40 && isxdigit (*pattern)) - pattern += 32; - - /* XXX: this code is very slow, revamp it and use hash tables whenever - it is possible. */ - for (c = ctx->item; c; c = c->next) { - key = c->key; - assert (key->_refs >= 1); - for (s = key->subkeys; s; s = s->next) { - for (u = key->uids; u; u = u->next) { - if (u->name && stristr (u->name, pattern)) { - if (r_item) - *r_item = c; - *r_key = flags? c->pubpart->key : c->key; - return 0; - } - } - if (has_keyid_len (pattern)) - kid = s->keyid; - else - kid = s->fpr; - if (kid && stristr (kid, pattern)) { - if (r_item) - *r_item = c; - *r_key = flags? c->pubpart->key : c->key; - return 0; - } - } - } - *r_key = NULL; - return gpg_error (GPG_ERR_INTERNAL); -} /* keycache_find_key */ - - -gpgme_error_t -gpg_keycache_find_key (gpg_keycache_t ctx, const char *pattern, - int flags, gpgme_key_t *r_key) -{ - return gpg_keycache_find_key2 (ctx, pattern, flags, r_key, NULL); -} - - -gpgme_error_t -gpg_keycache_update_key (gpg_keycache_t ctx, int is_sec, - void *opaque, const char *keyid) -{ - struct keycache_s *c = NULL, *c_new=NULL; - gpgme_key_t key=NULL, fndkey=NULL; - gpgme_error_t err; - gpgme_ctx_t gctx; - gpg_keycache_t pub = (gpg_keycache_t)opaque; - - err = gpgme_new (&gctx); - if (err) - return err; - err = gpgme_get_key (gctx, keyid, &key, is_sec); - gpgme_release (gctx); - if (err) - return err; - err = gpg_keycache_find_key2 (ctx, keyid, 0, &fndkey, &c); - if (!err && c != NULL) { - log_debug ("keycache update: keyid=%s %p\r\n", keyid, pub); - gpgme_key_release (fndkey); - c->key = key; - c->flags = 0; - if (is_sec && pub != NULL && - !gpg_keycache_find_key (pub, keyid, 0, &fndkey)) { - log_debug ("keycache update: set public part %p\r\n", fndkey); - c->pubpart->key = fndkey; - } - } - else { - log_debug ("keycache add: sync public part\r\n"); - if (is_sec) - gpg_keycache_find_key2 (pub, keyid, 0, &fndkey, &c_new); - gpg_keycache_add_key (ctx, key, (void **)&c); - if (c != NULL && is_sec) { - log_debug ("keycache add: keyid=%s %p %p\r\n", keyid, c, fndkey); - c->pubpart = c_new; - if (c_new != NULL) { - c->pubpart->key = fndkey; - c->gloflags.is_protected = c_new->gloflags.is_protected; - c->gloflags.divert_to_card = c_new->gloflags.divert_to_card; - } - } - } - return 0; -} - - -/* Delete a key from the cache @ctx with the pattern @pattern. - Return value: 0 on success. */ -gpgme_error_t -gpg_keycache_delete_key (gpg_keycache_t ctx, const char *pattern) -{ - struct keycache_s *itm = NULL, *c; - gpgme_key_t key; - gpgme_error_t rc; - - if (!ctx) - return gpg_error (GPG_ERR_INV_ARG); - rc = gpg_keycache_find_key2 (ctx, pattern, 0, &key, &itm); - if (rc) - return rc; - - c = ctx->item; - if (c->next == NULL) { - gpgme_key_release (itm->key); - if (itm) - free (itm); - ctx->item = NULL; - } - else { - while (c && c->next != itm) - c = c->next; - c->next = c->next->next; - gpgme_key_release (itm->key); - if (itm) - free (itm); - } - return 0; -} - - -/* Initialize the given cache @ctx. If @pattern is NULL, the entire keyring - will be added to the cache. @secret is 1 if the source is the secret keyring. - Return value: 0 on success. */ -gpgme_error_t -gpg_keycache_init (gpg_keycache_t ctx, const char *pattern, int secret) -{ - gpgme_error_t err; - gpgme_ctx_t c; - gpgme_key_t key; - int count = 0; - - if (!ctx) - return gpg_error (GPG_ERR_INV_ARG); - - err = gpgme_new (&c); - if (err) - return err; - - gpgme_set_keylist_mode (c, GPGME_KEYLIST_MODE_SIGS); - err = gpgme_op_keylist_start (c, pattern, secret); - while(!err) { - err = gpgme_op_keylist_next (c, &key); - if (!err) - err = gpg_keycache_add_key (ctx, key, NULL); - if (ctx->cb) - ctx->cb (ctx->cb_value, _("Load GPG Keyrings..."), 0, - count++, ctx->cb_value2); - } - if (gpgme_err_code (err) == GPG_ERR_EOF) - err = gpg_error (GPG_ERR_NO_ERROR); - /* XXX: make sure the progress dialog is closed. */ - gpgme_op_keylist_end (c); - gpgme_release (c); - return err; -} - - -/* XXX: kludge to see if the key is stored on a card. */ -static int -key_divert_to_card (gpgme_key_t key) -{ - gpgme_subkey_t k; - int n=0, n_alg=0, can_auth = 0; - - for (k = key->subkeys; k; k = k->next) { - n++; - if (k->pubkey_algo == GPGME_PK_RSA && k->length == 1024) - n_alg++; - if (k->can_authenticate) - can_auth++; - } - if (n == 3 && n_alg == 3 && can_auth == 1) - return 1; - return 0; -} - - -gpgme_error_t -gpg_keycache_sync (gpg_keycache_t pub, gpg_keycache_t sec) -{ - struct keycache_s *c, *c_sec; - gpgme_key_t key; - - if (!pub || !sec) - return gpg_error (GPG_ERR_INV_ARG); - - for (c=sec->item; c; c=c->next) { - if (!gpg_keycache_find_key2 (pub, c->key->subkeys->keyid, 0, &key, &c_sec)) { - c_sec->gloflags.is_protected = c->gloflags.is_protected; - c_sec->gloflags.divert_to_card = c->gloflags.divert_to_card; - if (!c->gloflags.divert_to_card) - c->gloflags.divert_to_card = key_divert_to_card (key); - c->pubpart = c_sec; - c->pubpart->key = key; - } - } - return 0; -} - - -/* Rewind the given cache @ctx to the begin. */ -void -gpg_keycache_rewind (gpg_keycache_t ctx) -{ - if (ctx) - ctx->pos = 0; -} - - - -/* Return the number of elements in the cache @ctx. */ -int -gpg_keycache_get_size (gpg_keycache_t ctx) -{ - struct keycache_s *c; - int count = 0; - - if (!ctx) - return 0; - for (c = ctx->item; c; c = c->next) - count++; - return count; -} - - -static gpgme_error_t -keycache_next_key (gpg_keycache_t ctx, int flags, - struct keycache_s **c, gpgme_key_t *r_key) -{ - if (!ctx || !r_key) - return gpg_error (GPG_ERR_INV_ARG); - - if (!ctx->pos) - ctx->tmp = ctx->item; - - if (!ctx->tmp || !ctx->tmp->key) { - ctx->pos = 0; - *r_key = NULL; - return gpg_error (GPG_ERR_EOF); - } - - *r_key = flags? ctx->tmp->pubpart->key : ctx->tmp->key; - *c = ctx->tmp = ctx->tmp->next; - ctx->pos++; - - return 0; -} - - -/* Return the next key from the cache @ctx. The key will be returned - in @r_key. @flags can contain additional flags. - Return value: 0 on success. */ -gpgme_error_t -gpg_keycache_next_key (gpg_keycache_t ctx, int flags, gpgme_key_t *r_key) -{ - struct keycache_s *c=NULL; - gpgme_error_t err = 0; - - err = keycache_next_key (ctx, flags, &c, r_key); - return err; -} +/* wptKeyCache.cpp- Caching for the pub- and the secring + * Copyright (C) 2001-2006 Timo Schulz + * + * This file is part of MyGPGME. + * + * MyGPGME 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 2 of the License, or + * (at your option) any later version. + * + * MyGPGME 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "wptKeyCache.h" +#include "openpgp.h" +#include "wptNLS.h" +#include "wptErrors.h" +#include "wptW32API.h" +#include "wptGPG.h" + + +/* Attribute list which holds the image data. */ +struct attr_list_s { + struct attr_list_s *next; + char *fpr; /* fingerprint of the key */ + unsigned char *d; /* actual JPEG data. */ + unsigned long octets; /* length of the data. */ + unsigned int flags; /* status of the attribute. */ +}; +typedef struct attr_list_s *attr_list_t; + + +void +free_attr_list (attr_list_t ctx) +{ + attr_list_t n; + while (ctx) { + n = ctx->next; + free (ctx->fpr); + free (ctx->d); + ctx = n; + } +} + +/* Parse the attribute list in @fp and store it into @ctx. + Return value: number of parsed items. */ +int +parse_attr_list (FILE *fp, const BYTE *data, DWORD datlen, attr_list_t *ctx) +{ + attr_list_t c, t; + char buf[512], *p, *buffer; + int pos, n=0; + + *ctx = NULL; + while (fgets (buf, 511, fp)) { + if (strstr (buf, "\r\n")) + buf[strlen (buf)-2]=0; + if (strstr (buf, "\n")) + buf[strlen (buf)-1]=0; + if (strlen (buf) < 2 || !strstr (buf, "ATTRIBUTE")) + continue; + buffer = buf+9+10; + pos = 0; + c = (attr_list_t)calloc (1, sizeof *c); + p = strtok (buffer, " "); + while (p != NULL) { + switch (pos) { + case 0: + c->fpr = strdup (p); + break; + + case 1: + c->octets = strtoul (p, NULL, 10); + break; + + case 7: + c->flags = strtoul (p, NULL, 10); + break; + + default: + break; + } + pos++; + p = strtok (NULL, " "); + } + /*printf ("id=%s octets=%d flags=%d\n", c->fpr, c->octets, c->flags);*/ + if (!*ctx) + *ctx = c; + else { + for (t = *ctx; t->next; t=t->next) + ; + t->next = c; + } + c->d = (unsigned char*)malloc (c->octets); + memcpy (c->d, data, c->octets); + data += c->octets; + datlen -= c->octets; + n++; + } + /*assert (datlen == 0); */ + return n; +} + + +/* Always use the $users temp folder. */ +static FILE* +w32_tmpfile (char *tmp, DWORD tmplen) +{ + char id[16]; + + if (!GetTempPath (tmplen-17, tmp)) + return NULL; + if (tmp[strlen (tmp)-1] != '\\') + strcat (tmp, "\\"); + _snprintf (id, sizeof (id)-1, "%lu", GetTickCount ()); + strcat (tmp, id); + return fopen (tmp, "w+b"); +} + + +static int +parse_attr_data (const char *keyid, attr_list_t *list) +{ + gpgme_error_t err; + FILE *tmp; + char *status, tmpnam[MAX_PATH+1]; + BYTE *data; + DWORD ndata; + + err = gpg_get_photoid_data (keyid, &status, &data, &ndata); + if (err) + return err; + + if (ndata > 0 && (tmp = w32_tmpfile (tmpnam, MAX_PATH)) != NULL) { + fwrite (status, 1, strlen (status), tmp); + fflush (tmp); + rewind (tmp); + + ndata = parse_attr_list (tmp, data, ndata, list); + fclose (tmp); + DeleteFile (tmpnam); + } + else + *list = NULL; + + safe_free (status); + safe_free (data); + return ndata; +} + + +/* Parse the secret keyring and retrieve some additional information + for each key which was found. */ +static void +parse_secring (gpg_keycache_t cache, const char *kid, const char *secring) +{ + PACKET *pkt; + PKT_secret_key *sk; + gpg_iobuf_t inp; + gpgme_error_t err; + gpgme_key_t key; + struct keycache_s *c=NULL; + char keyid[16+1]; + + inp = gpg_iobuf_open (secring); + if (!inp) + return; + + gpg_iobuf_ioctl (inp, 3, 1, NULL); + pkt = (PACKET*)calloc (1, sizeof *pkt); + gpg_init_packet (pkt); + while (gpg_parse_packet (inp, pkt) != -1) { + if (pkt->pkttype == PKT_SECRET_KEY) { + sk = pkt->pkt.secret_key; + /* XXX: key IDs of card public keys are wrong! */ + _snprintf (keyid, sizeof (keyid)-1, "%08lX", + gpg_keyid_from_sk (sk, NULL)); + if (kid && strcmp (kid, keyid) != 0) + goto next; + err = gpg_keycache_find_key2 (cache, keyid, 0, &key, &c); + if (err) + goto next; + c->gloflags.is_protected = sk->is_protected; + c->gloflags.divert_to_card = sk->protect.s2k.mode==1002? 1 : 0; + if (c->pubpart != NULL) { + c->pubpart->gloflags.is_protected = sk->is_protected; + c->pubpart->gloflags.divert_to_card = sk->protect.s2k.mode==1002? 1 : 0; + } + } +next: + gpg_free_packet (pkt); + gpg_init_packet (pkt); + } + safe_free (pkt); + gpg_iobuf_close (inp); +} + + +/* Update the photo image of a single key with the fingerprint + @fpr. The @dat struct contains the new item data. */ +static gpgme_error_t +keycache_update_photo (gpg_keycache_t ctx, const char *fpr, attr_list_t dat) +{ + struct keycache_s *fnd = NULL; + gpgme_key_t key; + + gpg_keycache_find_key2 (ctx, fpr, 0, &key, &fnd); + if (!fnd) + return gpg_error (GPG_ERR_NOT_FOUND); + safe_free (fnd->attrib.d); + fnd->attrib.flags = dat->flags; + fnd->attrib.len = dat->octets; + fnd->attrib.d = (unsigned char*)malloc (dat->octets); + memcpy (fnd->attrib.d, dat->d, dat->octets); + return 0; +} + + +/* Update all photo images in the cache. */ +static gpgme_error_t +keycache_update_photos (gpg_keycache_t ctx) +{ + attr_list_t list=NULL, n; + DWORD ndata; + + ndata = parse_attr_data (NULL, &list); + if (ndata < 1) { + free_attr_list (list); + return 0; + } + + for (n=list; n; n=n->next) + keycache_update_photo (ctx, n->fpr, n); + free_attr_list (list); + return 0; +} + + +/* Merge the information from the keyrings into the key cache structure. */ +gpgme_error_t +keycache_prepare2 (gpg_keycache_t ctx, const char *kid, + const char *pubring, const char *secring) +{ + gpgme_error_t err = gpg_error (GPG_ERR_NO_ERROR); + gpgme_key_t key = NULL; + gpg_iobuf_t inp; + PACKET *pkt; + struct keycache_s *c; + const byte *sym_prefs; + char keyid[16+1]; + int key_seen = 0; + size_t nsym =0; + + if (secring) { + parse_secring (ctx, kid, secring); + if (!pubring) + return 0; + } + inp = gpg_iobuf_open (pubring); + if (!inp) + return gpg_error (GPG_ERR_KEYRING_OPEN); + gpg_iobuf_ioctl (inp, 3, 1, NULL); /* disable cache */ + + pkt = (PACKET*)calloc (1, sizeof * pkt); + gpg_init_packet (pkt); + while (gpg_parse_packet (inp, pkt) != -1) { + if (pkt->pkttype == PKT_PUBLIC_KEY) { + strcpy (keyid, ""); + key_seen = 1; + } + + if (pkt->pkttype == PKT_SIGNATURE && + pkt->pkt.signature->sig_class == 0x1F) { + if (pkt->pkt.signature->numrevkeys == 0) + goto next; + _snprintf (keyid, sizeof (keyid) -1, "%08X", + pkt->pkt.signature->keyid[1]); + if (kid && strcmp (kid, keyid) != 0) + goto next; + err = gpg_keycache_find_key2 (ctx, keyid, 0, &key, &c); + if (err) + goto next; + c->gloflags.has_desig_rev = 1; + } + if (pkt->pkttype == PKT_SIGNATURE && key_seen == 1 ) { + sym_prefs = gpg_parse_sig_subpkt (pkt->pkt.signature->hashed, + SIGSUBPKT_PREF_SYM, &nsym); + if (!sym_prefs) + goto next; + _snprintf (keyid, sizeof (keyid) - 1, "%08X", + pkt->pkt.signature->keyid[1]); + if (kid && strcmp (kid, keyid) != 0) + goto next; + err = gpg_keycache_find_key2 (ctx, keyid, 0, &key, &c); + if (err) + goto next; + else if (nsym > 0) { + c->sym_prefs = (unsigned char*)calloc (1, nsym+1); + if (!c->sym_prefs) + return gpg_error (GPG_ERR_ENOMEM); + memcpy (c->sym_prefs, sym_prefs, nsym); + } + } +next: + gpg_free_packet (pkt); + gpg_init_packet(pkt); + } + + safe_free (pkt); + gpg_iobuf_close (inp); + return err; +} + + +gpgme_error_t +gpg_keycache_prepare (gpg_keycache_t ctx, const char *pubr, const char *secr) +{ + return keycache_prepare2 (ctx, NULL, pubr, secr); +} + +gpgme_error_t +gpg_keycache_prepare_single (gpg_keycache_t ctx, const char *keyid, + const char *pubr, const char *secr) +{ + if (!strncmp (keyid, "0x", 2)) + keyid += 2; + return keycache_prepare2 (ctx, keyid, pubr, secr); +} + + +/* Create new keycache object and return it in @r_ctx. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_new (gpg_keycache_t *r_ctx) +{ + gpg_keycache_t ctx; + + if (!r_ctx) + return gpg_error (GPG_ERR_INV_ARG); + ctx = (gpg_keycache_t)calloc (1, sizeof *ctx); + if (!ctx) + return gpg_error (GPG_ERR_ENOMEM); + ctx->secret = 0; + ctx->pos = 0; + *r_ctx = ctx; + return 0; +} + + +/* Release keycache object @ctx. */ +void +gpg_keycache_release (gpg_keycache_t ctx) +{ + struct keycache_s *c, *c2; + + if (!ctx) + return; + + for (c = ctx->item; c; c = c2) { + c2 = c->next; + gpgme_key_release (c->key); + c->key = NULL; + safe_free (c->sym_prefs); + safe_free (c->attrib.d); + safe_free (c->card_type); + free (c); + } + safe_free (ctx); +} + + +/* Set (progress) callback for the given keycache object. + @ctx the keycache + @cb the callback function + @cb_value1 opaque value which is passed to the callback. + @cb_value2 see @cb_value1. */ +void +gpg_keycache_set_cb (gpg_keycache_t ctx, + void (*cb)(void *, const char *, int, int, int), + void * cb_value1, int cb_value2) +{ + if (!ctx) + return; + ctx->cb = cb; + ctx->cb_value = cb_value1; + ctx->cb_value2 = cb_value2; +} + + +/* Add @key to the cache @ctx. @opaque return the key cache context as a void*. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_add_key (gpg_keycache_t ctx, gpgme_key_t key, void **opaque) +{ + struct keycache_s *c, *n1; + + if (!ctx) + return gpg_error (GPG_ERR_INV_ARG); + + c = (struct keycache_s*)calloc (1, sizeof *c); + if (!c) + return gpg_error (GPG_ERR_ENOMEM); + c->gloflags.is_protected = 1; /*default: assume protection. */ + c->key = key; + if (!ctx->item) + ctx->item = c; + else { + for (n1 = ctx->item; n1 && n1->next; n1 = n1->next) + ; + n1->next = c; + } + if (opaque) + *opaque = c; + return 0; +} + + + +#define has_keyid_len(pattern) (\ + strlen (pattern) == 8 || strlen (pattern) == 10 || \ + strlen (pattern) == 16 || strlen (pattern) == 18) + + +gpgme_error_t +gpg_keycache_find_key2 (gpg_keycache_t ctx, const char *pattern, int flags, + gpgme_key_t *r_key, struct keycache_s **r_item) +{ + struct keycache_s *c; + gpgme_subkey_t s; + gpgme_user_id_t u; + gpgme_key_t key; + const char *kid; + + if (!ctx || !r_key) + return gpg_error (GPG_ERR_INV_ARG); + + if (strstr (pattern, "0x")) + pattern += 2; + + /* Sometimes a subkey has no valid fpr. As a kludge we convert v4 + fingerprints into the 64-bit keyid. */ + if (strlen (pattern) == 40 && isxdigit (*pattern)) + pattern += 32; + + /* XXX: this code is very slow, revamp it and use hash tables whenever + it is possible. */ + for (c = ctx->item; c; c = c->next) { + key = c->key; + assert (key->_refs >= 1); + for (s = key->subkeys; s; s = s->next) { + for (u = key->uids; u; u = u->next) { + if (u->name && stristr (u->name, pattern)) { + if (r_item) + *r_item = c; + *r_key = flags? c->pubpart->key : c->key; + return 0; + } + } + if (has_keyid_len (pattern)) + kid = s->keyid; + else + kid = s->fpr; + if (kid && stristr (kid, pattern)) { + if (r_item) + *r_item = c; + *r_key = flags? c->pubpart->key : c->key; + return 0; + } + } + } + *r_key = NULL; + return gpg_error (GPG_ERR_INTERNAL); +} + + +gpgme_error_t +gpg_keycache_find_key (gpg_keycache_t ctx, const char *pattern, + int flags, gpgme_key_t *r_key) +{ + return gpg_keycache_find_key2 (ctx, pattern, flags, r_key, NULL); +} + + +/* Reload a photo image of a single key with the keyid @keyid. + Return value: 0 on success. */ +static gpgme_error_t +keycache_reload_photo (gpg_keycache_t ctx, const char *keyid) +{ + attr_list_t list; + + if (parse_attr_data (keyid, &list) < 1) { + free_attr_list (list); + return 0; + } + keycache_update_photo (ctx, list->fpr, list); + free_attr_list (list); + return 0; +} + + +/* Return the next key which was updated. Before it is + returned the update flag is cleared. + @r_status is 1 for a new key and 2 for an updated key. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_next_updated_key (gpg_keycache_t ctx, + struct keycache_s **r_obj, + int *r_status) +{ + struct keycache_s *c; + + for (c = ctx->item; c; c = c->next) { + if (c->flags != 0) { + *r_status = c->flags; + c->flags = 0; + *r_obj = c; + return 0; + } + } + return gpg_error (GPG_ERR_NOT_FOUND); +} + + + +gpgme_error_t +gpg_keycache_update_key (gpg_keycache_t ctx, int is_sec, + void *opaque, const char *keyid) +{ + struct keycache_s *c = NULL, *c_new=NULL; + gpgme_key_t key=NULL, fndkey=NULL; + gpgme_error_t err; + gpgme_ctx_t gctx; + gpg_keycache_t pub = (gpg_keycache_t)opaque; + + err = gpgme_new (&gctx); + if (err) + return err; + gpgme_set_keylist_mode (gctx, GPGME_KEYLIST_MODE_SIGS); + err = gpgme_get_key (gctx, keyid, &key, is_sec); + gpgme_release (gctx); + if (err) + return err; + err = gpg_keycache_find_key2 (ctx, keyid, 0, &fndkey, &c); + if (!err && c != NULL) { + log_debug ("keycache update: keyid=%s %p\r\n", keyid, pub); + gpgme_key_release (fndkey); + c->key = key; + c->flags = KC_FLAG_UPD; + if (is_sec && pub != NULL && + !gpg_keycache_find_key (pub, keyid, 0, &fndkey)) { + log_debug ("keycache update: set public part %p\r\n", fndkey); + c->pubpart->key = fndkey; + } + /* XXX: this is also called for keys without a photo-id. */ + keycache_reload_photo (ctx, keyid); + } + else { + log_debug ("keycache add: sync public part\r\n"); + if (is_sec) + gpg_keycache_find_key2 (pub, keyid, 0, &fndkey, &c_new); + gpg_keycache_add_key (ctx, key, (void **)&c); + if (c != NULL && is_sec) { + log_debug ("keycache add: keyid=%s %p %p\r\n", keyid, c, fndkey); + c->pubpart = c_new; + if (c_new != NULL) { + c->pubpart->key = fndkey; + c->gloflags.is_protected = c_new->gloflags.is_protected; + c->gloflags.divert_to_card = c_new->gloflags.divert_to_card; + } + } + if (c) + c->flags = KC_FLAG_ADD; + } + return 0; +} + + +/* Delete a key from the cache @ctx with the pattern @pattern. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_delete_key (gpg_keycache_t ctx, const char *pattern) +{ + struct keycache_s *itm = NULL, *c; + gpgme_key_t key; + gpgme_error_t rc; + + if (!ctx) + return gpg_error (GPG_ERR_INV_ARG); + rc = gpg_keycache_find_key2 (ctx, pattern, 0, &key, &itm); + if (rc) + return rc; + + c = ctx->item; + if (c->next == NULL) { + gpgme_key_release (itm->key); + if (itm) + free (itm); + ctx->item = NULL; + } + else { + while (c && c->next != itm) + c = c->next; + c->next = c->next->next; + gpgme_key_release (itm->key); + if (itm) + free (itm); + } + return 0; +} + + +/* Initialize the given cache @ctx. If @pattern is NULL, the entire keyring + will be added to the cache. @secret is 1 if the source is the secret keyring. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_init (gpg_keycache_t ctx, const char *pattern, int secret) +{ + gpgme_error_t err; + gpgme_ctx_t c; + gpgme_key_t key; + int count = 0; + + if (!ctx) + return gpg_error (GPG_ERR_INV_ARG); + + err = gpgme_new (&c); + if (err) + return err; + + gpgme_set_keylist_mode (c, GPGME_KEYLIST_MODE_SIGS); + err = gpgme_op_keylist_start (c, pattern, secret); + while(!err) { + err = gpgme_op_keylist_next (c, &key); + if (!err) + err = gpg_keycache_add_key (ctx, key, NULL); + if (ctx->cb) + ctx->cb (ctx->cb_value, _("Load GPG Keyrings..."), 0, + count++, ctx->cb_value2); + } + if (gpgme_err_code (err) == GPG_ERR_EOF) + err = gpg_error (GPG_ERR_NO_ERROR); + keycache_update_photos (ctx); + /* XXX: make sure the progress dialog is closed. */ + gpgme_op_keylist_end (c); + gpgme_release (c); + return err; +} + + +/* XXX: kludge to see if the key is stored on a card. */ +static int +key_divert_to_card (gpgme_key_t key) +{ + gpgme_subkey_t k; + int n=0, n_alg=0, can_auth = 0; + + for (k = key->subkeys; k; k = k->next) { + n++; + if (k->pubkey_algo == GPGME_PK_RSA && k->length == 1024) + n_alg++; + if (k->can_authenticate) + can_auth++; + } + if (n == 3 && n_alg == 3 && can_auth == 1) + return 1; + return 0; +} + + +static unsigned char* +copy_uid_prefs (const unsigned char *prefs) +{ + unsigned char *p; + size_t pos=0; + + while (prefs[pos] != 0) + pos++; + p = (unsigned char*)calloc (1, pos+1); + if (!p) + abort (); + memcpy (p, prefs, pos); + return p; +} + + +gpgme_error_t +gpg_keycache_sync (gpg_keycache_t pub, gpg_keycache_t sec) +{ + struct keycache_s *c, *c_sec; + gpgme_key_t key; + + if (!pub || !sec) + return gpg_error (GPG_ERR_INV_ARG); + + for (c=sec->item; c; c=c->next) { + if (!gpg_keycache_find_key2 (pub, c->key->subkeys->keyid, 0, &key, &c_sec)) { + c_sec->gloflags.is_protected = c->gloflags.is_protected; + c_sec->gloflags.divert_to_card = c->gloflags.divert_to_card; + if (!c->gloflags.divert_to_card) + c->gloflags.divert_to_card = key_divert_to_card (key); + if (c_sec->sym_prefs) + c->sym_prefs = copy_uid_prefs (c_sec->sym_prefs); + c->pubpart = c_sec; + c->pubpart->key = key; + } + } + return 0; +} + + +/* Rewind the given cache @ctx to the begin. */ +void +gpg_keycache_rewind (gpg_keycache_t ctx) +{ + if (ctx) + ctx->pos = 0; +} + + + +/* Return the number of elements in the cache @ctx. */ +int +gpg_keycache_get_size (gpg_keycache_t ctx) +{ + struct keycache_s *c; + int count = 0; + + if (!ctx) + return 0; + for (c = ctx->item; c; c = c->next) + count++; + return count; +} + + +static gpgme_error_t +keycache_next_key (gpg_keycache_t ctx, int flags, + struct keycache_s **c, gpgme_key_t *r_key) +{ + if (!ctx || !r_key) + return gpg_error (GPG_ERR_INV_ARG); + + if (!ctx->pos) + ctx->tmp = ctx->item; + + if (!ctx->tmp || !ctx->tmp->key) { + ctx->pos = 0; + *r_key = NULL; + return gpg_error (GPG_ERR_EOF); + } + if (ctx->tmp->flags != 0) + ctx->tmp->flags = 0; /* reset the 'updated' status. */ + /* it might be possible there is no public key. */ + if (flags && ctx->tmp->pubpart == NULL) + flags = 0; + *r_key = flags? ctx->tmp->pubpart->key : ctx->tmp->key; + *c = ctx->tmp = ctx->tmp->next; + ctx->pos++; + + return 0; +} + + +/* Return the next key from the cache @ctx. The key will be returned + in @r_key. @flags can contain additional flags. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_next_key (gpg_keycache_t ctx, int flags, gpgme_key_t *r_key) +{ + struct keycache_s *c=NULL; + gpgme_error_t err = 0; + + err = keycache_next_key (ctx, flags, &c, r_key); + return err; +} + +/* Search for a key with the pattern @pattern and mark + this key as the default signing key if found. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_set_default_key (gpg_keycache_t ctx, + const char *pattern) +{ + gpgme_error_t err; + gpgme_key_t key; + struct keycache_s *itm; + + err = gpg_keycache_find_key2 (ctx, pattern, 0, &key, &itm); + if (err) + return err; + + if (itm) + itm->default_key = 1; + return 0; +} + +/* Return the default key from the cache. If no was + marked before, NULL is returned in @r_key. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_get_default_key (gpg_keycache_t ctx, + gpgme_key_t *r_key) +{ + struct keycache_s *itm; + + *r_key = NULL; + for (itm = ctx->item; itm; itm = itm->next) { + if (itm->default_key) { + *r_key = itm->key; + break; + } + } + if (!*r_key) + return gpgme_error (GPG_ERR_NOT_FOUND); + return 0; +} + + +