doc/
[gpgme.git] / gpgme / key.c
index 92bb597..3c0d736 100644 (file)
-/* key.c - Key and keyList objects
- *     Copyright (C) 2000 Werner Koch (dd9jn)
- *
- * This file is part of GPGME.
- *
- * GPGME 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.
- *
- * GPGME 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
- */
+/* key.c - Key objects.
+   Copyright (C) 2000 Werner Koch (dd9jn)
+   Copyright (C) 2001, 2002, 2003 g10 Code GmbH
 
+   This file is part of GPGME.
+   GPGME 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.
+   GPGME 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 GPGME; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
 #include <config.h>
-#include <stdio.h>
+#endif
 #include <stdlib.h>
+#include <string.h>
 #include <assert.h>
+#include <errno.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' )
+\f
+/* Protects all reference counters in keys.  All other accesses to a
+   key are read only.  */
+DEFINE_STATIC_LOCK (key_ref_lock);
 
 
-GpgmeError
-_gpgme_key_new( GpgmeKey *r_key )
+/* Create a new key.  */
+gpgme_error_t
+_gpgme_key_new (gpgme_key_t *r_key)
 {
-    GpgmeKey key;
+  gpgme_key_t key;
 
-    *r_key = NULL;
-    key = xtrycalloc ( 1, sizeof *key );
-    if (!key)
-        return mk_error (Out_Of_Core);
+  key = calloc (1, sizeof *key);
+  if (!key)
+    return gpg_error_from_errno (errno);
+  key->_refs = 1;
 
-    *r_key = key;
-    return 0;
+  *r_key = key;
+  return 0;
+}
+
+
+gpgme_error_t
+_gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey)
+{
+  gpgme_subkey_t subkey;
+
+  subkey = calloc (1, sizeof *subkey);
+  if (!subkey)
+    return gpg_error_from_errno (errno);
+  subkey->keyid = subkey->_keyid;
+  subkey->_keyid[16] = '\0';
+
+  if (!key->subkeys)
+    key->subkeys = subkey;
+  if (key->_last_subkey)
+    key->_last_subkey->next = subkey;
+  key->_last_subkey = subkey;
+
+  *r_subkey = subkey;
+  return 0;
+}
+
+
+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 (char *src, char **name, char **email,
+              char **comment, char *tail)
+{
+  const char *start = NULL;
+  int in_name = 0;
+  int in_email = 0;
+  int in_comment = 0;
+
+  while (*src)
+    {
+      if (in_email)
+       {
+         if (*src == '<')
+           /* Not legal but anyway.  */
+           in_email++;
+         else if (*src == '>')
+           {
+             if (!--in_email && !*email)
+               {
+                 *email = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+           }
+       }
+      else if (in_comment)
+       {
+         if (*src == '(')
+           in_comment++;
+         else if (*src == ')')
+           {
+             if (!--in_comment && !*comment)
+               {
+                 *comment = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+           }
+       }
+      else if (*src == '<')
+       {
+         if (in_name)
+           {
+             if (!*name)
+               {
+                 *name = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+             in_name = 0;
+           }
+         in_email = 1;
+         start = src + 1;
+       }
+      else if (*src == '(')
+       {
+         if (in_name)
+           {
+             if (!*name)
+               {
+                 *name = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+             in_name = 0;
+           }
+         in_comment = 1;
+         start = src + 1;
+       }
+      else if (!in_name && *src != ' ' && *src != '\t')
+       {
+         in_name = 1;
+         start = src;
+       }    
+      src++;
+    }
+  if (in_name)
+    {
+      if (!*name)
+       {
+         *name = tail;
+         tail = set_user_id_part (tail, start, src - start);
+       }
+    }
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!*name)
+    *name = tail;
+  if (!*email)
+    *email = tail;
+  if (!*comment)
+    *comment = tail;
+}
+
+
+static void
+parse_x509_user_id (char *src, char **name, char **email,
+                   char **comment, char *tail)
+{
+  if (*src == '<' && src[strlen (src) - 1] == '>')
+    *email = src;
+  
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!*name)
+    *name = tail;
+  if (!*email)
+    *email = tail;
+  if (!*comment)
+    *comment = tail;
+}
+
+
+/* Take a name from the --with-colon listing, remove certain escape
+   sequences sequences and put it into the list of UIDs.  */
+gpgme_error_t
+_gpgme_key_append_name (gpgme_key_t key, char *src)
+{
+  gpgme_user_id_t uid;
+  char *dst;
+  int src_len = strlen (src);
+
+  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 = malloc (sizeof (*uid) + 2 * src_len + 3);
+  if (!uid)
+    return gpg_error_from_errno (errno);
+  memset (uid, 0, sizeof *uid);
+
+  uid->uid = ((char *) uid) + sizeof (*uid);
+  dst = uid->uid;
+  _gpgme_decode_c_string (src, &dst, src_len + 1);
+
+  dst += src_len + 1;
+  if (key->protocol == GPGME_PROTOCOL_CMS)
+    parse_x509_user_id (uid->uid, &uid->name, &uid->email,
+                       &uid->comment, dst);
+  else
+    parse_user_id (uid->uid, &uid->name, &uid->email,
+                  &uid->comment, dst);
+
+  if (!key->uids)
+    key->uids = uid;
+  if (key->_last_uid)
+    key->_last_uid->next = uid;
+  key->_last_uid = uid;
+
+  return 0;
+}
+
+
+gpgme_key_sig_t
+_gpgme_key_add_sig (gpgme_key_t key, char *src)
+{
+  int src_len = src ? strlen (src) : 0;
+  gpgme_user_id_t uid;
+  gpgme_key_sig_t sig;
+
+  assert (key);        /* XXX */
+
+  uid = key->_last_uid;
+  assert (uid);        /* XXX */
+
+  /* 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.  */
+  sig = calloc (1, sizeof (*sig) + 2 * src_len + 3);
+  if (!sig)
+    return NULL;
+  sig->keyid = sig->_keyid;
+  sig->_keyid[16] = '\0';
+  sig->uid = ((char *) sig) + sizeof (*sig);
+
+  if (src)
+    {
+      char *dst = sig->uid;
+      _gpgme_decode_c_string (src, &dst, src_len + 1);
+      dst += src_len + 1;
+      if (key->protocol == GPGME_PROTOCOL_CMS)
+       parse_x509_user_id (sig->uid, &sig->name, &sig->email,
+                           &sig->comment, dst);
+      else
+       parse_user_id (sig->uid, &sig->name, &sig->email,
+                      &sig->comment, dst);
+    }
+
+  if (!uid->signatures)
+    uid->signatures = sig;
+  if (uid->_last_keysig)
+    uid->_last_keysig->next = sig;
+  uid->_last_keysig = sig;
+
+  return sig;
 }
 
+\f
+/* Acquire a reference to KEY.  */
 void
-_gpgme_key_release ( GpgmeKey key )
-{
-    struct user_id_s *u, *u2;
-
-    if (!key)
-        return;
-
-    xfree (key->fingerprint);
-    for ( u = key->uids; u; u = u2 ) {
-        u2 = u->next;
-        xfree (u);
-    }
-    xfree (key);
-}
-
-/* 
- * Take a name from the --with-colon listing, remove certain escape sequences
- * 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++; 
-        }
-        else if ( s[1] == 'n' ) {
-            s += 2;
-            *d++ = '\n'; 
-        }
-        else if ( s[1] == 'r' ) {
-            s += 2;
-            *d++ = '\r'; 
-        }
-        else if ( s[1] == 'v' ) {
-            s += 2;
-            *d++ = '\v'; 
-        }
-        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] == '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;
+gpgme_key_ref (gpgme_key_t key)
+{
+  LOCK (key_ref_lock);
+  key->_refs++;
+  UNLOCK (key_ref_lock);
+}
+
+
+/* gpgme_key_unref releases 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_unref (gpgme_key_t key)
+{
+  gpgme_user_id_t uid;
+  gpgme_subkey_t subkey;
+
+  LOCK (key_ref_lock);
+  assert (key->_refs > 0);
+  if (--key->_refs)
+    {
+      UNLOCK (key_ref_lock);
+      return;
+    }
+  UNLOCK (key_ref_lock);
+
+  subkey = key->subkeys;
+  while (subkey)
+    {
+      gpgme_subkey_t next = subkey->next;
+      if (subkey->fpr)
+       free (subkey->fpr);
+      free (subkey);
+      subkey = next;
+    }
+
+  uid = key->uids;
+  while (uid)
+    {
+      gpgme_user_id_t next_uid = uid->next;
+      gpgme_key_sig_t keysig = uid->signatures;
+
+      while (keysig)
+       {
+         gpgme_key_sig_t next = keysig->next;
+          free (keysig);
+         keysig = next;
         }
-        else { /* should not happen */
-            s++;
-            *d++ = '\\'; 
-            *d++ = *s++;
-        } 
+      free (uid);
+      uid = next_uid;
     }
+  
+  if (key->issuer_serial)
+    free (key->issuer_serial);
+  if (key->issuer_name)
+    free (key->issuer_name);
 
-    uid->next = key->uids;
-    key->uids = uid;
+  if (key->chain_id)
+    free (key->chain_id);
+
+  free (key);
+}
+
+\f
+/* Compatibility interfaces.  */
+
+void
+gpgme_key_release (gpgme_key_t key)
+{
+  gpgme_key_unref (key);
+}
+
+
+static const char *
+otrust_to_string (int otrust)
+{
+  switch (otrust)
+    {
+    case GPGME_VALIDITY_NEVER:
+      return "n";
+
+    case GPGME_VALIDITY_MARGINAL:
+      return "m";
+
+    case GPGME_VALIDITY_FULL:
+      return "f";
+
+    case GPGME_VALIDITY_ULTIMATE:
+      return "u";
+
+    default:
+      return "?";
+    }
+}
+
+
+static const char *
+validity_to_string (int validity)
+{
+  switch (validity)
+    {
+    case GPGME_VALIDITY_UNDEFINED:
+      return "q";
+
+    case GPGME_VALIDITY_NEVER:
+      return "n";
+
+    case GPGME_VALIDITY_MARGINAL:
+      return "m";
+
+    case GPGME_VALIDITY_FULL:
+      return "f";
+
+    case GPGME_VALIDITY_ULTIMATE:
+      return "u";
+
+    case GPGME_VALIDITY_UNKNOWN:
+    default:
+      return "?";
+    }
+}
+
+
+static const char *
+capabilities_to_string (gpgme_subkey_t subkey)
+{
+  static const char *const strings[8] =
+    {
+      "",
+      "c",
+      "s",
+      "sc",
+      "e",
+      "ec",
+      "es",
+      "esc"
+    };
+  return strings[(!!subkey->can_encrypt << 2)
+                | (!!subkey->can_sign << 1)
+                | (!!subkey->can_certify)];
+}
+
+
+/* Return the value of the attribute WHAT of ITEM, which has to be
+   representable by a string.  */
+const char *
+gpgme_key_get_string_attr (gpgme_key_t key, _gpgme_attr_t what,
+                          const void *reserved, int idx)
+{
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
+
+  if (!key || reserved || idx < 0)
+    return NULL;
+
+  /* Select IDXth subkey.  */
+  subkey = key->subkeys;
+  for (i = 0; i < idx; i++)
+    {
+      subkey = subkey->next;
+      if (!subkey)
+       break;
+    }
+
+  /* Select the IDXth user ID.  */
+  uid = key->uids;
+  for (i = 0; i < idx; i++)
+    {
+      uid = uid->next;
+      if (!uid)
+       break;
+    }
+
+  switch (what)
+    {
+    case GPGME_ATTR_KEYID:
+      return subkey ? subkey->keyid : NULL;
+
+    case GPGME_ATTR_FPR:
+      return subkey ? subkey->fpr : NULL;
+
+    case GPGME_ATTR_ALGO:    
+      return subkey ? gpgme_pubkey_algo_name (subkey->pubkey_algo) : NULL;
+
+    case GPGME_ATTR_TYPE:
+      return key->protocol == GPGME_PROTOCOL_CMS ? "X.509" : "PGP";
+
+    case GPGME_ATTR_OTRUST:
+      return otrust_to_string (key->owner_trust);
+
+    case GPGME_ATTR_USERID:  
+      return uid ? uid->uid : NULL;
+
+    case GPGME_ATTR_NAME:   
+      return uid ? uid->name : NULL;
+
+    case GPGME_ATTR_EMAIL:
+      return uid ? uid->email : NULL;
+
+    case GPGME_ATTR_COMMENT:
+      return uid ? uid->comment : NULL;
+
+    case GPGME_ATTR_VALIDITY:
+      return uid ? validity_to_string (uid->validity) : NULL;
+
+    case GPGME_ATTR_KEY_CAPS:    
+      return subkey ? capabilities_to_string (subkey) : NULL;
+
+    case GPGME_ATTR_SERIAL:
+      return key->issuer_serial;
+
+    case GPGME_ATTR_ISSUER:
+      return idx ? NULL : key->issuer_name;
+
+    case GPGME_ATTR_CHAINID:
+      return key->chain_id;
+
+    default:
+      return NULL;
+    }
+}
+
+
+unsigned long
+gpgme_key_get_ulong_attr (gpgme_key_t key, _gpgme_attr_t what,
+                         const void *reserved, int idx)
+{
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
+
+  if (!key || reserved || idx < 0)
     return 0;
+
+  /* Select IDXth subkey.  */
+  subkey = key->subkeys;
+  for (i = 0; i < idx; i++)
+    {
+      subkey = subkey->next;
+      if (!subkey)
+       break;
+    }
+
+  /* Select the IDXth user ID.  */
+  uid = key->uids;
+  for (i = 0; i < idx; i++)
+    {
+      uid = uid->next;
+      if (!uid)
+       break;
+    }
+
+  switch (what)
+    {
+    case GPGME_ATTR_ALGO:
+      return subkey ? (unsigned long) subkey->pubkey_algo : 0;
+
+    case GPGME_ATTR_LEN:
+      return subkey ? (unsigned long) subkey->length : 0;
+
+    case GPGME_ATTR_TYPE:
+      return key->protocol == GPGME_PROTOCOL_CMS ? 1 : 0;
+
+    case GPGME_ATTR_CREATED:
+      return (subkey && subkey->timestamp >= 0)
+       ? (unsigned long) subkey->timestamp : 0;
+
+    case GPGME_ATTR_EXPIRE: 
+      return (subkey && subkey->expires >= 0)
+       ? (unsigned long) subkey->expires : 0;
+
+    case GPGME_ATTR_VALIDITY:
+      return uid ? uid->validity : 0;
+
+    case GPGME_ATTR_OTRUST:
+      return key->owner_trust;
+
+    case GPGME_ATTR_IS_SECRET:
+      return !!key->secret;
+
+    case GPGME_ATTR_KEY_REVOKED:
+      return subkey ? subkey->revoked : 0;
+
+    case GPGME_ATTR_KEY_INVALID:
+      return subkey ? subkey->invalid : 0;
+
+    case GPGME_ATTR_KEY_EXPIRED:
+      return subkey ? subkey->expired : 0;
+
+    case GPGME_ATTR_KEY_DISABLED:
+      return subkey ? subkey->disabled : 0;
+
+    case GPGME_ATTR_UID_REVOKED:
+      return uid ? uid->revoked : 0;
+
+    case GPGME_ATTR_UID_INVALID:
+      return uid ? uid->invalid : 0;
+
+    case GPGME_ATTR_CAN_ENCRYPT:
+      return key->can_encrypt;
+
+    case GPGME_ATTR_CAN_SIGN:
+      return key->can_sign;
+
+    case GPGME_ATTR_CAN_CERTIFY:
+      return key->can_certify;
+
+    default:
+      return 0;
+    }
 }
 
 
+static gpgme_key_sig_t
+get_keysig (gpgme_key_t key, int uid_idx, int idx)
+{
+  gpgme_user_id_t uid;
+  gpgme_key_sig_t sig;
+
+  if (!key || uid_idx < 0 || idx < 0)
+    return NULL;
+
+  uid = key->uids;
+  while (uid && uid_idx > 0)
+    {
+      uid = uid->next;
+      uid_idx--;
+    }
+  if (!uid)
+    return NULL;
+
+  sig = uid->signatures;
+  while (sig && idx > 0)
+    {
+      sig = sig->next;
+      idx--;
+    }
+  return sig;
+}
+
+
+const char *
+gpgme_key_sig_get_string_attr (gpgme_key_t key, int uid_idx,
+                              _gpgme_attr_t what,
+                              const void *reserved, int idx)
+{
+  gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx);
+
+  if (!certsig || reserved)
+    return NULL;
+
+  switch (what)
+    {
+    case GPGME_ATTR_KEYID:
+      return certsig->keyid;
 
+    case GPGME_ATTR_ALGO:    
+      return gpgme_pubkey_algo_name (certsig->pubkey_algo);
+
+    case GPGME_ATTR_USERID:
+      return certsig->uid;
+
+    case GPGME_ATTR_NAME:   
+      return certsig->name;
+
+    case GPGME_ATTR_EMAIL:
+      return certsig->email;
+
+    case GPGME_ATTR_COMMENT:
+      return certsig->comment;
+   
+    default:
+      return NULL;
+    }
+}
+
+
+unsigned long
+gpgme_key_sig_get_ulong_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what,
+                             const void *reserved, int idx)
+{
+  gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx);
+
+  if (!certsig || reserved)
+    return 0;
+
+  switch (what)
+    {
+    case GPGME_ATTR_ALGO:    
+      return (unsigned long) certsig->pubkey_algo;
+
+    case GPGME_ATTR_CREATED: 
+      return certsig->timestamp < 0 ? 0L : (unsigned long) certsig->timestamp;
+
+    case GPGME_ATTR_EXPIRE: 
+      return certsig->expires < 0 ? 0L : (unsigned long) certsig->expires;
+
+    case GPGME_ATTR_KEY_REVOKED:
+      return certsig->revoked;
+
+    case GPGME_ATTR_KEY_INVALID:
+      return certsig->invalid;
+
+    case GPGME_ATTR_KEY_EXPIRED:
+      return certsig->expired;
+
+    case GPGME_ATTR_SIG_CLASS:
+      return certsig->class;
+
+    case GPGME_ATTR_SIG_STATUS:
+      return certsig->status;
+
+    default:
+      return 0;
+    }
+}