auto updated version number.
[gpgme.git] / gpgme / key.c
index 92bb597..879be14 100644 (file)
@@ -1,5 +1,6 @@
 /* key.c - Key and keyList objects
  *     Copyright (C) 2000 Werner Koch (dd9jn)
+ *      Copyright (C) 2001, 2002 g10 Code GmbH
  *
  * This file is part of GPGME.
  *
 #include <config.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <assert.h>
+#include <ctype.h>
 
 #include "util.h"
 #include "ops.h"
 #include "key.h"
+#include "sema.h"
 
 #define ALLOC_CHUNK 1024
-#define my_isdigit(a) ( (a) >='0' && (a) <= '9' )
+#define my_isdigit(a) ((a) >='0' && (a) <= '9')
 
+#if SIZEOF_UNSIGNED_INT < 4
+#error unsigned int too short to be used as a hash value
+#endif
+
+struct key_cache_item_s
+{
+  struct key_cache_item_s *next;
+  GpgmeKey key;
+};
+
+/* Protects all key_cache_* variables.  */
+DEFINE_STATIC_LOCK (key_cache_lock);
+static int key_cache_initialized;
+static struct key_cache_item_s **key_cache;
+static size_t key_cache_size;
+static size_t key_cache_max_chain_length;
+static struct key_cache_item_s *key_cache_unused_items;
+
+/* Protects all reference counters in keys.  All other accesses to a
+   key are either read only or happen before the key is entered into
+   the cache.  */
+DEFINE_STATIC_LOCK (key_ref_lock);
+
+static int
+hextobyte (const byte *s)
+{
+  int c;
+
+  if (*s >= '0' && *s <= '9')
+    c = 16 * (*s - '0');
+  else if (*s >= 'A' && *s <= 'F')
+    c = 16 * (10 + *s - 'A');
+  else if (*s >= 'a' && *s <= 'f')
+    c = 16 * (10 + *s - 'a');
+  else
+    return -1;
+  s++;
+  if (*s >= '0' && *s <= '9')
+    c += *s - '0';
+  else if (*s >= 'A' && *s <= 'F')
+    c += 10 + *s - 'A';
+  else if (*s >= 'a' && *s <= 'f')
+    c += 10 + *s - 'a';
+  else
+    return -1;
+  return c;
+}
+
+static int
+hash_key (const char *fpr, unsigned int *rhash)
+{
+  unsigned int hash;
+  int c;
+
+  if (!fpr)
+    return -1;
+  if ((c = hextobyte (fpr)) == -1)
+    return -1;
+  hash = c;
+  if ((c = hextobyte (fpr+2)) == -1)
+    return -1;
+  hash |= c << 8;
+  if ((c = hextobyte (fpr+4)) == -1)
+    return -1;
+  hash |= c << 16;
+  if ((c = hextobyte (fpr+6)) == -1)
+    return -1;
+  hash |= c << 24;
+
+  *rhash = hash;
+  return 0;
+}
+
+void
+_gpgme_key_cache_init (void)
+{
+  LOCK (key_cache_lock);
+  if (!key_cache_initialized)
+    {
+      key_cache_size = 503;
+      key_cache = xtrycalloc (key_cache_size, sizeof *key_cache);
+      if (!key_cache)
+       {
+         key_cache_size = 0;
+         key_cache_initialized = 1;
+       }
+      else
+       {
+         /* The upper bound for our cache size is
+            key_cache_max_chain_length * key_cache_size.  */
+         key_cache_max_chain_length = 10;
+         key_cache_initialized = 1;
+       }
+    }
+  UNLOCK (key_cache_lock);
+}
+
+
+void
+_gpgme_key_cache_add (GpgmeKey key)
+{
+  struct subkey_s *k;
+
+  if (!key)
+    return;
+
+  _gpgme_key_cache_init ();
+
+  LOCK (key_cache_lock);
+  /* Check if cache was enabled.  */
+  if (!key_cache_size)
+    {
+      UNLOCK (key_cache_lock);
+      return;
+    }
+
+  /* Put the key under each fingerprint into the cache.  We use the
+     first 4 digits to calculate the hash.  */
+  for (k = &key->keys; k; k = k->next)
+    {
+      size_t n;
+      unsigned int hash;
+      struct key_cache_item_s *item;
+
+      if (hash_key (k->fingerprint, &hash))
+       continue;
+
+      hash %= key_cache_size;
+      for (item = key_cache[hash], n=0; item; item = item->next, n++)
+       {
+         struct subkey_s *k2;
+         if (item->key == key) 
+           /* Already in cache.  */
+           break;
+         /* Now do a deeper check.  */
+         for (k2 = &item->key->keys; k2; k2 = k2->next)
+           {
+             if (k2->fingerprint && !strcmp (k->fingerprint, k2->fingerprint))
+               {
+                 /* Okay, replace it with the new copy.  */
+                 gpgme_key_unref (item->key);
+                 item->key = key;
+                 gpgme_key_ref (item->key);
+                 UNLOCK (key_cache_lock);
+                 return;
+                }
+            }
+        }
+      if (item)
+       continue;
+        
+      if (n > key_cache_max_chain_length)
+       {
+         /* Remove the last entries.  */
+         struct key_cache_item_s *last = NULL;
+
+         for (item = key_cache[hash];
+              item && n < key_cache_max_chain_length;
+              last = item, item = item->next, n++)
+           ;
+         
+         if (last)
+           {
+             struct key_cache_item_s *next;
+
+             assert (last->next == item);
+             last->next = NULL;
+             for (; item; item = next)
+               {
+                 next = item->next;
+                 gpgme_key_unref (item->key);
+                 item->key = NULL;
+                 item->next = key_cache_unused_items;
+                 key_cache_unused_items = item;
+                }
+            }
+        }
+
+      item = key_cache_unused_items;
+      if (item)
+       {
+         key_cache_unused_items = item->next;
+         item->next = NULL;
+        }
+      else
+       {
+         item = xtrymalloc (sizeof *item);
+         if (!item)
+           {
+             UNLOCK (key_cache_lock);
+             return;
+           }
+        }
+
+      item->key = key;
+      gpgme_key_ref (key);
+      item->next = key_cache[hash];
+      key_cache[hash] = item;
+    }
+  UNLOCK (key_cache_lock);
+}
+
+
+GpgmeKey 
+_gpgme_key_cache_get (const char *fpr)
+{
+  struct key_cache_item_s *item;
+  unsigned int hash;
+
+  LOCK (key_cache_lock);
+  /* Check if cache is enabled already.  */
+  if (!key_cache_size)
+    {
+      UNLOCK (key_cache_lock);
+      return NULL;
+    }
+
+  if (hash_key (fpr, &hash))
+    {
+      UNLOCK (key_cache_lock);
+      return NULL;
+    }
+
+  hash %= key_cache_size;
+  for (item = key_cache[hash]; item; item = item->next)
+    {
+      struct subkey_s *k;
+
+      for (k = &item->key->keys; k; k = k->next)
+       {
+         if (k->fingerprint && !strcmp (k->fingerprint, fpr))
+           {
+             gpgme_key_ref (item->key);
+             UNLOCK (key_cache_lock);
+             return item->key;
+            }
+        }
+    }
+  UNLOCK (key_cache_lock);
+  return NULL;
+}
+
+
+static const char *
+pkalgo_to_string (int algo)
+{
+  switch (algo)
+    {
+    case 1: 
+    case 2:
+    case 3:
+      return "RSA";
+    case 16:
+    case 20:
+      return "ElG";
+    case 17:
+      return "DSA";
+    default:
+      return "Unknown";
+    }
+}
+
+
+static GpgmeError
+key_new (GpgmeKey *r_key, int secret)
+{
+  GpgmeKey key;
+
+  *r_key = NULL;
+  key = xtrycalloc (1, sizeof *key);
+  if (!key)
+    return mk_error (Out_Of_Core);
+  key->ref_count = 1;
+  *r_key = key;
+  if (secret)
+    key->secret = 1;
+  return 0;
+}
+
+GpgmeError
+_gpgme_key_new (GpgmeKey *r_key)
+{
+  return key_new (r_key, 0);
+}
 
 GpgmeError
-_gpgme_key_new( GpgmeKey *r_key )
+_gpgme_key_new_secret (GpgmeKey *r_key)
 {
-    GpgmeKey key;
+  return key_new (r_key, 1);
+}
 
-    *r_key = NULL;
-    key = xtrycalloc ( 1, sizeof *key );
-    if (!key)
-        return mk_error (Out_Of_Core);
 
-    *r_key = key;
-    return 0;
+/**
+ * gpgme_key_ref:
+ * @key: Key object
+ * 
+ * To safe memory the Key objects implements reference counting.
+ * Use this function to bump the reference counter.
+ **/
+void
+gpgme_key_ref (GpgmeKey key)
+{
+  return_if_fail (key);
+  LOCK (key_ref_lock);
+  key->ref_count++;
+  UNLOCK (key_ref_lock);
 }
 
+
+static struct subkey_s *
+add_subkey (GpgmeKey key, int secret)
+{
+  struct subkey_s *k, *kk;
+
+  k = xtrycalloc (1, sizeof *k);
+  if (!k)
+    return NULL;
+
+  if(!(kk = key->keys.next))
+    key->keys.next = k;
+  else
+    {
+      while (kk->next)
+       kk = kk->next;
+      kk->next = k;
+    }
+  if (secret)
+    k->secret = 1;
+  return k;
+}
+
+
+struct subkey_s *
+_gpgme_key_add_subkey (GpgmeKey key)
+{
+  return add_subkey (key, 0);
+}
+
+
+struct subkey_s *
+_gpgme_key_add_secret_subkey (GpgmeKey key)
+{
+  return add_subkey (key, 1);
+}
+
+
+/**
+ * gpgme_key_release:
+ * @key: Key Object or NULL
+ * 
+ * Release the key object. Note, that this function may not do an
+ * actual release if there are other shallow copies of the objects.
+ * You have to call this function for every newly created key object
+ * as well as for every gpgme_key_ref() done on the key object.
+ **/
 void
-_gpgme_key_release ( GpgmeKey key )
+gpgme_key_release (GpgmeKey key)
 {
-    struct user_id_s *u, *u2;
+  struct user_id_s *u, *u2;
+  struct subkey_s *k, *k2;
 
-    if (!key)
-        return;
+  if (!key)
+    return;
 
-    xfree (key->fingerprint);
-    for ( u = key->uids; u; u = u2 ) {
-        u2 = u->next;
-        xfree (u);
+  LOCK (key_ref_lock);
+  assert (key->ref_count);
+  if (--key->ref_count)
+    {
+      UNLOCK (key_ref_lock);
+      return;
     }
-    xfree (key);
+  UNLOCK (key_ref_lock);
+
+  xfree (key->keys.fingerprint);
+  for (k = key->keys.next; k; k = k2)
+    {
+      k2 = k->next;
+      xfree (k->fingerprint);
+      xfree (k);
+    }
+  for (u = key->uids; u; u = u2)
+    {
+      u2 = u->next;
+      xfree (u);
+    }
+  xfree (key->issuer_serial);
+  xfree (key->issuer_name);
+  xfree (key->chain_id);
+  xfree (key);
+}
+
+/**
+ * gpgme_key_unref:
+ * @key: Key Object
+ * 
+ * This is an alias for gpgme_key_release().
+ **/
+void
+gpgme_key_unref (GpgmeKey key)
+{
+  gpgme_key_release (key);
+}
+
+
+static char *
+set_user_id_part (char *tail, const char *buf, size_t len)
+{
+  while (len && (buf[len-1] == ' ' || buf[len-1] == '\t')) 
+    len--;
+  for (; len; len--)
+    *tail++ = *buf++;
+  *tail++ = 0;
+  return tail;
+}
+
+
+static void
+parse_user_id (struct user_id_s *uid, char *tail)
+{
+  const char *s, *start=NULL;
+  int in_name = 0;
+  int in_email = 0;
+  int in_comment = 0;
+
+    for (s = uid->name; *s; s++)
+      {
+        if (in_email)
+         {
+            if (*s == '<')
+             /* Not legal but anyway.  */
+             in_email++;
+            else if (*s == '>')
+             {
+                if (!--in_email)
+                 {
+                    if (!uid->email_part)
+                     {
+                        uid->email_part = tail;
+                        tail = set_user_id_part ( tail, start, s-start );
+                     }
+                 }
+             }
+         }
+        else if (in_comment)
+         {
+            if (*s == '(')
+             in_comment++;
+            else if (*s== ')')
+             {
+                if (!--in_comment)
+                 {
+                    if (!uid->comment_part)
+                     {
+                        uid->comment_part = tail;
+                        tail = set_user_id_part ( tail, start, s-start );
+                     }
+                 }
+             }
+         }
+        else if (*s == '<')
+         {
+            if (in_name)
+             {
+                if (!uid->name_part)
+                 {
+                    uid->name_part = tail;
+                    tail = set_user_id_part (tail, start, s-start);
+                 }
+                in_name = 0;
+             }
+            in_email = 1;
+            start = s+1;
+         }
+        else if (*s == '(')
+         {
+            if (in_name)
+             {
+                if (!uid->name_part)
+                 {
+                    uid->name_part = tail;
+                    tail = set_user_id_part (tail, start, s-start );
+                 }
+                in_name = 0;
+             }
+            in_comment = 1;
+            start = s+1;
+         }
+        else if (!in_name && *s != ' ' && *s != '\t')
+         {
+            in_name = 1;
+            start = s;
+         }    
+      }
+    if (in_name)
+      {
+        if (!uid->name_part)
+         {
+            uid->name_part = tail;
+            tail = set_user_id_part (tail, start, s-start);
+         }
+      }
+    /* Let unused parts point to an EOS.  */
+    tail--;
+    if (!uid->name_part)
+      uid->name_part = tail;
+    if (!uid->email_part)
+      uid->email_part = tail;
+    if (!uid->comment_part)
+      uid->comment_part = tail;
+}
+
+static void
+parse_x509_user_id (struct user_id_s *uid, char *tail)
+{
+  const char *s;
+
+  s=uid->name; 
+  if (*s == '<' && s[strlen (s) - 1] == '>')
+    uid->email_part = s;
+  
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!uid->name_part)
+    uid->name_part = tail;
+  if (!uid->email_part)
+    uid->email_part = tail;
+  if (!uid->comment_part)
+    uid->comment_part = tail;
 }
 
 /* 
@@ -66,70 +571,549 @@ _gpgme_key_release ( GpgmeKey key )
  * sequences and put it into the list of UIDs
  */
 GpgmeError
-_gpgme_key_append_name ( GpgmeKey key, const char *s )
-{
-    struct user_id_s *uid;
-    char *d;
-
-    assert (key);
-    /* we can malloc a buffer of the same length, because the converted
-     * string will never be larger */
-    uid = xtrymalloc ( sizeof *uid + strlen (s) );
-    if ( !uid )
-        return mk_error (Out_Of_Core);
-    uid->validity = 0;
-    d = uid->name;
-
-    while ( *s ) {
-        if ( *s != '\\' )
-            *d++ = *s++;
-        else if ( s[1] == '\\' ) {
-            s++;
-            *d++ = *s++; 
+_gpgme_key_append_name (GpgmeKey key, const char *s)
+{
+  struct user_id_s *uid;
+  char *d;
+
+  assert (key);
+  /* We can malloc a buffer of the same length, because the converted
+     string will never be larger. Actually we allocate it twice the
+     size, so that we are able to store the parsed stuff there too.  */
+  uid = xtrymalloc ( sizeof *uid + 2*strlen (s)+3);
+  if (!uid)
+    return mk_error (Out_Of_Core);
+  uid->revoked = 0;
+  uid->invalid = 0;
+  uid->validity = 0;
+  uid->name_part = NULL;
+  uid->email_part = NULL;
+  uid->comment_part = NULL;
+  uid->next = NULL;
+  d = uid->name;
+
+  while (*s)
+    {
+      if (*s != '\\')
+       *d++ = *s++;
+      else if (s[1] == '\\')
+       {
+         s++;
+         *d++ = *s++; 
         }
-        else if ( s[1] == 'n' ) {
-            s += 2;
-            *d++ = '\n'; 
+      else if (s[1] == 'n')
+       {
+         s += 2;
+         *d++ = '\n'; 
         }
-        else if ( s[1] == 'r' ) {
-            s += 2;
-            *d++ = '\r'; 
+      else if (s[1] == 'r')
+       {
+         s += 2;
+         *d++ = '\r'; 
         }
-        else if ( s[1] == 'v' ) {
-            s += 2;
-            *d++ = '\v'; 
+      else if (s[1] == 'v')
+       {
+         s += 2;
+         *d++ = '\v'; 
         }
-        else if ( s[1] == 'b' ) {
-            s += 2;
-            *d++ = '\b'; 
+      else if (s[1] == 'b')
+       {
+         s += 2;
+         *d++ = '\b'; 
         }
-        else if ( s[1] == '0' ) {
-            /* Hmmm: no way to express this */
-            s += 2;
-            *d++ = '\\';
-            *d++ = '\0'; 
+      else if (s[1] == '0')
+       {
+         /* Hmmm: no way to express this */
+         s += 2;
+         *d++ = '\\';
+         *d++ = '\0'; 
         }
-        else if ( s[1] == 'x' && my_isdigit (s[2]) && my_isdigit (s[3]) ) {
-            unsigned int val = (s[2]-'0')*16 + (s[3]-'0');
-            if ( !val ) {
-                *d++ = '\\';
-                *d++ = '\0'; 
-            }
-            else 
-                *(byte*)d++ = val;
-            s += 3;
+      else if (s[1] == 'x' && isxdigit (s[2]) && isxdigit (s[3]))
+       {
+         int val = hextobyte (&s[2]);
+         if (val == -1)
+           {
+             /* Should not happen.  */
+             *d++ = *s++;
+             *d++ = *s++;
+             *d++ = *s++;
+             *d++ = *s++;
+           }
+         else
+           {
+             if (!val)
+               {
+                 *d++ = '\\';
+                 *d++ = '\0'; 
+               }
+             else 
+               *(byte*)d++ = val;
+             s += 4;
+           }
         }
-        else { /* should not happen */
-            s++;
-            *d++ = '\\'; 
-            *d++ = *s++;
+      else
+       {
+         /* should not happen */
+         s++;
+         *d++ = '\\'; 
+         *d++ = *s++;
         } 
     }
+  *d++ = 0;
+  if (key->x509)
+    parse_x509_user_id (uid, d);
+  else
+    parse_user_id (uid, d);
 
-    uid->next = key->uids;
+  if (key->uids)
+    {
+      struct user_id_s *u = key->uids;
+      while (u->next)
+       u = u->next;
+      u->next = uid;
+    }
+  else
     key->uids = uid;
-    return 0;
+
+  return 0;
+}
+
+
+static void
+add_otag (GpgmeData d, const char *tag)
+{
+  _gpgme_data_append_string (d, "    <");
+  _gpgme_data_append_string (d, tag);
+  _gpgme_data_append_string (d, ">");
 }
 
+static void
+add_ctag (GpgmeData d, const char *tag)
+{
+  _gpgme_data_append_string (d, "</");
+  _gpgme_data_append_string (d, tag);
+  _gpgme_data_append_string (d, ">\n");
+}
 
+static void
+add_tag_and_string (GpgmeData d, const char *tag, const char *string)
+{
+  add_otag (d, tag);
+  _gpgme_data_append_string_for_xml (d, string);
+  add_ctag (d, tag); 
+}
 
+static void
+add_tag_and_uint (GpgmeData d, const char *tag, unsigned int val)
+{
+  char buf[30];
+  sprintf (buf, "%u", val);
+  add_tag_and_string (d, tag, buf);
+}
+
+static void
+add_tag_and_time (GpgmeData d, const char *tag, time_t val)
+{
+  char buf[30];
+
+  if (!val || val == (time_t) - 1)
+    return;
+  sprintf (buf, "%lu", (unsigned long) val);
+  add_tag_and_string (d, tag, buf);
+}
+
+static void
+one_uid_as_xml (GpgmeData d, struct user_id_s *u)
+{
+  _gpgme_data_append_string (d, "  <userid>\n");
+  if (u->invalid)
+    _gpgme_data_append_string (d, "    <invalid/>\n");
+  if (u->revoked)
+    _gpgme_data_append_string (d, "    <revoked/>\n");
+  add_tag_and_string (d, "raw", u->name);
+  if (*u->name_part)
+    add_tag_and_string (d, "name", u->name_part);
+  if (*u->email_part)
+    add_tag_and_string (d, "email", u->email_part);
+  if (*u->comment_part)
+    add_tag_and_string (d, "comment", u->comment_part);
+  _gpgme_data_append_string (d, "  </userid>\n");
+}
+
+
+/**
+ * gpgme_key_get_as_xml:
+ * @key: Key object
+ * 
+ * Return the key object as an XML string.  The classer has to free
+ * that string.
+ * 
+ * Return value:  An XML string or NULL in case of a memory problem or
+ *                a NULL passed as @key
+ **/
+char *
+gpgme_key_get_as_xml (GpgmeKey key)
+{
+  GpgmeData d;
+  struct user_id_s *u;
+  struct subkey_s *k;
+  
+  if (!key)
+    return NULL;
+  
+  if (gpgme_data_new (&d))
+    return NULL;
+  
+  _gpgme_data_append_string (d, "<GnupgKeyblock>\n"
+                            "  <mainkey>\n");
+  if (key->keys.secret)
+    _gpgme_data_append_string (d, "    <secret/>\n");
+  if (key->keys.flags.invalid)
+    _gpgme_data_append_string (d, "    <invalid/>\n");
+  if (key->keys.flags.revoked)
+    _gpgme_data_append_string (d, "    <revoked/>\n");
+  if (key->keys.flags.expired)
+    _gpgme_data_append_string (d, "    <expired/>\n");
+  if (key->keys.flags.disabled)
+    _gpgme_data_append_string (d, "    <disabled/>\n");
+  add_tag_and_string (d, "keyid", key->keys.keyid);
+  if (key->keys.fingerprint)
+    add_tag_and_string (d, "fpr", key->keys.fingerprint);
+  add_tag_and_uint (d, "algo", key->keys.key_algo);
+  add_tag_and_uint (d, "len", key->keys.key_len);
+  add_tag_and_time (d, "created", key->keys.timestamp);
+  add_tag_and_time (d, "expire", key->keys.expires_at);
+  if (key->issuer_serial)
+    add_tag_and_string (d, "serial", key->issuer_serial);
+  if (key->issuer_name)
+    add_tag_and_string (d, "issuer", key->issuer_name);
+  if (key->chain_id)
+    add_tag_and_string (d, "chainid", key->chain_id);
+  _gpgme_data_append_string (d, "  </mainkey>\n");
+
+  /* Now the user IDs.  */
+  for (u = key->uids; u; u = u->next)
+    one_uid_as_xml (d,u);
+  
+  /* And now the subkeys.  */
+  for (k = key->keys.next; k; k = k->next)
+    {
+      _gpgme_data_append_string (d, "  <subkey>\n");
+      if (k->secret)
+        _gpgme_data_append_string (d, "    <secret/>\n");
+      if (k->flags.invalid)
+        _gpgme_data_append_string (d, "    <invalid/>\n");
+      if (k->flags.revoked)
+        _gpgme_data_append_string (d, "    <revoked/>\n");
+      if (k->flags.expired)
+        _gpgme_data_append_string (d, "    <expired/>\n");
+      if (k->flags.disabled)
+        _gpgme_data_append_string (d, "    <disabled/>\n");
+      add_tag_and_string (d, "keyid", k->keyid);
+      if (k->fingerprint)
+        add_tag_and_string (d, "fpr", k->fingerprint);
+      add_tag_and_uint (d, "algo", k->key_algo);
+      add_tag_and_uint (d, "len", k->key_len);
+      add_tag_and_time (d, "created", k->timestamp);
+      add_tag_and_time (d, "expire", k->expires_at);
+      _gpgme_data_append_string (d, "  </subkey>\n");
+    }
+  _gpgme_data_append_string (d, "</GnupgKeyblock>\n");
+  
+  return _gpgme_data_release_and_return_string (d);
+}
+
+
+static const char *
+capabilities_to_string (struct subkey_s *k)
+{
+  static const char *const strings[8] =
+    {
+      "",
+      "c",
+      "s",
+      "sc",
+      "e",
+      "ec",
+      "es",
+      "esc"
+    };
+  return strings[(!!k->flags.can_encrypt << 2)
+                | (!!k->flags.can_sign    << 1)
+                | (!!k->flags.can_certify     )];
+}
+
+
+/**
+ * gpgme_key_get_string_attr:
+ * @key: Key Object
+ * @what: Attribute specifier
+ * @reserved: Must be 0
+ * @idx: Index counter
+ * 
+ * Return a attribute as specified by @what and @idx.  Note that not
+ * all attributes can be returned as a string, in which case NULL is
+ * returned.  @idx is used to iterate through attributes which do have
+ * more than one instance (e.g. user IDs or sub keys).
+ * 
+ * Return value: NULL or an const string which is only valid as long
+ * as the key object itself is valid.
+ **/
+const char *
+gpgme_key_get_string_attr (GpgmeKey key, GpgmeAttr what,
+                          const void *reserved, int idx)
+{
+  const char *val = NULL;
+  struct subkey_s *k;
+  struct user_id_s *u;
+
+  if (!key)
+    return NULL;
+  if (reserved)
+    return NULL;
+  if (idx < 0)
+    return NULL;
+
+  switch (what)
+    {
+    case GPGME_ATTR_KEYID:
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->keyid;
+      break;
+    case GPGME_ATTR_FPR:
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->fingerprint;
+      break;
+    case GPGME_ATTR_ALGO:    
+      for (k = &key->keys; k && idx; k=k->next, idx--)
+       ;
+      if (k) 
+       val = pkalgo_to_string (k->key_algo);
+      break;
+    case GPGME_ATTR_LEN:     
+    case GPGME_ATTR_CREATED: 
+    case GPGME_ATTR_EXPIRE:  
+      /* Use another get function.  */
+      break;
+    case GPGME_ATTR_OTRUST:  
+      switch (key->otrust)
+        {
+        case GPGME_VALIDITY_NEVER:     val = "n"; break;
+        case GPGME_VALIDITY_MARGINAL:  val = "m"; break;
+        case GPGME_VALIDITY_FULL:      val = "f"; break;
+        case GPGME_VALIDITY_ULTIMATE:  val = "u"; break;
+        default:   val = "?"; break;
+        }
+      break;
+    case GPGME_ATTR_USERID:  
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      val = u ? u->name : NULL;
+      break;
+    case GPGME_ATTR_NAME:   
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      val = u ? u->name_part : NULL;
+      break;
+    case GPGME_ATTR_EMAIL:
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      val = u ? u->email_part : NULL;
+      break;
+    case GPGME_ATTR_COMMENT:
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      val = u ? u->comment_part : NULL;
+      break;
+    case GPGME_ATTR_VALIDITY:
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      if (u)
+       {
+         switch (u->validity)
+           {
+           case GPGME_VALIDITY_UNKNOWN:
+             val = "?";
+             break;
+           case GPGME_VALIDITY_UNDEFINED:
+             val = "q";
+             break;
+           case GPGME_VALIDITY_NEVER:
+             val = "n";
+             break;
+           case GPGME_VALIDITY_MARGINAL:
+             val = "m";
+             break;
+           case GPGME_VALIDITY_FULL:
+             val = "f";
+             break;
+           case GPGME_VALIDITY_ULTIMATE:
+             val = "u";
+             break;
+            }
+        }
+      break;
+    case GPGME_ATTR_LEVEL:
+    case GPGME_ATTR_TYPE:
+    case GPGME_ATTR_KEY_REVOKED:
+    case GPGME_ATTR_KEY_INVALID:
+    case GPGME_ATTR_KEY_EXPIRED:
+    case GPGME_ATTR_KEY_DISABLED:
+    case GPGME_ATTR_UID_REVOKED:
+    case GPGME_ATTR_UID_INVALID:
+    case GPGME_ATTR_CAN_ENCRYPT:
+    case GPGME_ATTR_CAN_SIGN:
+    case GPGME_ATTR_CAN_CERTIFY:
+      /* Not used here.  */
+      break;
+    case GPGME_ATTR_IS_SECRET:
+      if (key->secret)
+       val = "1";
+      break;
+    case GPGME_ATTR_KEY_CAPS:    
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = capabilities_to_string (k);
+      break;
+    case GPGME_ATTR_SERIAL:
+      val = key->issuer_serial;
+      break;
+    case GPGME_ATTR_ISSUER:
+      val = idx? NULL : key->issuer_name;
+      break;
+    case GPGME_ATTR_CHAINID:
+      val = key->chain_id;
+      break;
+    case GPGME_ATTR_SIG_STATUS:
+    case GPGME_ATTR_ERRTOK:
+      /* Not of any use here.  */
+      break;
+    }
+  return val;
+}
+
+
+/**
+ * gpgme_key_get_ulong_attr:
+ * @key: 
+ * @what: 
+ * @reserved: 
+ * @idx: 
+ * 
+ * Return a attribute as specified by @what and @idx.  Note that not
+ * all attributes can be returned as an integer, in which case 0 is
+ * returned.  @idx is used to iterate through attributes which do have
+ * more than one instance (e.g. user IDs or sub keys).
+ *
+ * See gpgme.h for a list of attributes.
+ * 
+ * Return value: 0 or the requested value.
+ **/
+unsigned long
+gpgme_key_get_ulong_attr (GpgmeKey key, GpgmeAttr what,
+                         const void *reserved, int idx)
+{
+  unsigned long val = 0;
+  struct subkey_s *k;
+  struct user_id_s *u;
+
+  if (!key)
+    return 0;
+  if (reserved)
+    return 0;
+  if (idx < 0)
+    return 0;
+
+  switch (what)
+    {
+    case GPGME_ATTR_ALGO:    
+      for (k = &key->keys; k && idx; k=k->next, idx--)
+       ;
+      if (k) 
+       val = (unsigned long) k->key_algo;
+      break;
+    case GPGME_ATTR_LEN:     
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = (unsigned long) k->key_len;
+      break;
+    case GPGME_ATTR_CREATED: 
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->timestamp < 0 ? 0L : (unsigned long) k->timestamp;
+      break;
+    case GPGME_ATTR_EXPIRE: 
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->expires_at < 0 ? 0L : (unsigned long) k->expires_at;
+      break;
+    case GPGME_ATTR_VALIDITY:
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      if (u)
+       val = u->validity;
+      break;
+    case GPGME_ATTR_OTRUST:
+      val = key->otrust;
+      break;
+    case GPGME_ATTR_IS_SECRET:
+      val = !!key->secret;
+      break;
+    case GPGME_ATTR_KEY_REVOKED:
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->flags.revoked;
+      break;
+    case GPGME_ATTR_KEY_INVALID:
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->flags.invalid;
+      break;
+    case GPGME_ATTR_KEY_EXPIRED:
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->flags.expired;
+      break;
+    case GPGME_ATTR_KEY_DISABLED:
+      for (k = &key->keys; k && idx; k = k->next, idx--)
+       ;
+      if (k) 
+       val = k->flags.disabled;
+      break;
+    case GPGME_ATTR_UID_REVOKED:
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      if (u)
+       val = u->revoked;
+      break;
+    case GPGME_ATTR_UID_INVALID:
+      for (u = key->uids; u && idx; u = u->next, idx--)
+       ;
+      if (u)
+       val = u->invalid;
+      break;
+    case GPGME_ATTR_CAN_ENCRYPT:
+      val = key->gloflags.can_encrypt;
+      break;
+    case GPGME_ATTR_CAN_SIGN:
+      val = key->gloflags.can_sign;
+      break;
+    case GPGME_ATTR_CAN_CERTIFY:
+      val = key->gloflags.can_certify;
+      break;
+    default:
+      break;
+    }
+  return val;
+}