2001-12-18 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / key.c
1 /* key.c - Key and keyList objects
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *      Copyright (C) 2001 g10 Code GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <assert.h>
27
28 #include "util.h"
29 #include "ops.h"
30 #include "key.h"
31
32 #define ALLOC_CHUNK 1024
33 #define my_isdigit(a) ( (a) >='0' && (a) <= '9' )
34
35 #if SIZEOF_UNSIGNED_INT < 4
36 #error unsigned int too short to be used as a hash value
37 #endif
38
39 struct key_cache_item_s {
40     struct key_cache_item_s *next;
41     GpgmeKey key;
42 };
43
44 static int key_cache_initialized;
45 static struct key_cache_item_s **key_cache;
46 static size_t key_cache_size;
47 static size_t key_cache_max_chain_length;
48 static struct key_cache_item_s *key_cache_unused_items;
49
50 static int
51 hextobyte ( const byte *s )
52 {
53     int c;
54
55     if ( *s >= '0' && *s <= '9' )
56         c = 16 * (*s - '0');
57     else if ( *s >= 'A' && *s <= 'F' )
58         c = 16 * (10 + *s - 'A');
59     else if ( *s >= 'a' && *s <= 'f' )
60         c = 16 * (10 + *s - 'a');
61     else
62         return -1;
63     s++;
64     if ( *s >= '0' && *s <= '9' )
65         c += *s - '0';
66     else if ( *s >= 'A' && *s <= 'F' )
67         c += 10 + *s - 'A';
68     else if ( *s >= 'a' && *s <= 'f' )
69         c += 10 + *s - 'a';
70     else
71         return -1;
72     return c;
73 }
74
75 static int
76 hash_key (const char *fpr, unsigned int *rhash)
77 {
78     unsigned int hash;
79     int c;
80
81     if ( !fpr )                         return -1;
82     if ( (c = hextobyte(fpr)) == -1 )   return -1;
83     hash = c;
84     if ( (c = hextobyte(fpr+2)) == -1 ) return -1;
85     hash |= c << 8;
86     if ( (c = hextobyte(fpr+4)) == -1 ) return -1;
87     hash |= c << 16;
88     if ( (c = hextobyte(fpr+6)) == -1 ) return -1;
89     hash |= c << 24;
90
91     *rhash = hash;
92     return 0;
93 }
94
95 void
96 _gpgme_key_cache_init (void)
97 {
98     if (key_cache_initialized)
99         return;
100     key_cache_size = 503;
101     key_cache = xtrycalloc (key_cache_size, sizeof *key_cache);
102     if (!key_cache) {
103         key_cache_size = 0;
104         key_cache_initialized = 1;
105         return;
106     }
107     /*
108      * The upper bound for our cache size is 
109      * key_cache_max_chain_length * key_cache_size 
110      */
111     key_cache_max_chain_length = 10;
112     key_cache_initialized = 1;
113 }
114
115
116 void
117 _gpgme_key_cache_add (GpgmeKey key)
118 {
119     struct subkey_s *k;
120
121     if (!key)
122         return;
123
124     /* FIXME: add locking */
125     if (!key_cache_initialized)
126         _gpgme_key_cache_init ();
127     if (!key_cache_size)
128         return; /* cache was not enabled */
129
130     /* put the key under each fingerprint into the cache.  We use the
131      * first 4 digits to calculate the hash */
132     for (k=&key->keys; k; k = k->next ) {
133         size_t n;
134         unsigned int hash;
135         struct key_cache_item_s *item;
136
137         if ( hash_key (k->fingerprint, &hash) )
138             continue;
139
140         hash %= key_cache_size;
141         for (item=key_cache[hash],n=0; item; item = item->next, n++) {
142             struct subkey_s *k2;
143             if (item->key == key) 
144                 break; /* already in cache */
145             /* now do a deeper check */
146             for (k2=&item->key->keys; k2; k2 = k2->next ) {
147                 if( k2->fingerprint
148                     && !strcmp (k->fingerprint, k2->fingerprint) ) {
149                     /* okay, replace it with the new copy */
150                     gpgme_key_unref (item->key);
151                     item->key = key;
152                     gpgme_key_ref (item->key);
153                     return;
154                 }
155             }
156         }
157         if (item)
158             continue; 
159         
160         if (n > key_cache_max_chain_length ) { /* remove the last entries */
161             struct key_cache_item_s *last = NULL;
162
163             for (item=key_cache[hash];
164                  item && n < key_cache_max_chain_length;
165                  last = item, item = item->next, n++ ) {
166                 ;
167             }
168             if (last) {
169                 struct key_cache_item_s *next;
170
171                 assert (last->next == item);
172                 last->next = NULL;
173                 for ( ;item; item=next) {
174                     next = item->next;
175                     gpgme_key_unref (item->key);
176                     item->key = NULL;
177                     item->next = key_cache_unused_items;
178                     key_cache_unused_items = item;
179                 }
180             }
181         }
182
183         item = key_cache_unused_items;
184         if (item) {
185             key_cache_unused_items = item->next;
186             item->next = NULL;
187         }
188         else {
189             item = xtrymalloc (sizeof *item);
190             if (!item)
191                 return; /* out of core */
192         }
193         
194         item->key = key;
195         gpgme_key_ref (key);
196         item->next = key_cache[hash];
197         key_cache[hash] = item;
198     }
199 }
200
201
202 GpgmeKey 
203 _gpgme_key_cache_get (const char *fpr)
204 {
205     struct key_cache_item_s *item;
206     unsigned int hash;
207
208     if (!key_cache_size)
209         return NULL; /* cache not (yet) enabled */
210
211     if (hash_key (fpr, &hash))
212         return NULL;
213
214     hash %= key_cache_size;
215     for (item=key_cache[hash]; item; item = item->next) {
216         struct subkey_s *k;
217
218         for (k=&item->key->keys; k; k = k->next ) {
219             if( k->fingerprint && !strcmp (k->fingerprint, fpr) ) {
220                 gpgme_key_ref (item->key);
221                 return item->key;
222             }
223         }
224     }
225     return NULL;
226 }
227
228
229 static const char *
230 pkalgo_to_string ( int algo )
231 {
232     switch (algo) {
233       case 1: 
234       case 2:
235       case 3: return "RSA";
236       case 16:
237       case 20: return "ElG";
238       case 17: return "DSA";
239       default: return "Unknown";
240     }
241 }
242
243
244 static GpgmeError
245 key_new ( GpgmeKey *r_key, int secret )
246 {
247     GpgmeKey key;
248
249     *r_key = NULL;
250     key = xtrycalloc ( 1, sizeof *key );
251     if (!key)
252         return mk_error (Out_Of_Core);
253     key->ref_count = 1;
254     *r_key = key;
255     if (secret)
256         key->secret = 1;
257     return 0;
258 }
259
260 GpgmeError
261 _gpgme_key_new ( GpgmeKey *r_key )
262 {
263     return key_new ( r_key, 0 );
264 }
265
266 GpgmeError
267 _gpgme_key_new_secret ( GpgmeKey *r_key )
268 {
269     return key_new ( r_key, 1 );
270 }
271
272
273 /**
274  * gpgme_key_ref:
275  * @key: Key object
276  * 
277  * To safe memory the Key objects implement reference counting.
278  * Use this function to bump the reference counter.
279  **/
280 void
281 gpgme_key_ref ( GpgmeKey key )
282 {
283     return_if_fail (key);
284     key->ref_count++;
285 }
286
287
288 static struct subkey_s *
289 add_subkey (GpgmeKey key, int secret)
290 {
291     struct subkey_s *k, *kk;
292
293     k = xtrycalloc (1, sizeof *k);
294     if (!k)
295         return NULL;
296
297     if( !(kk=key->keys.next) )
298         key->keys.next = k;
299     else {
300         while ( kk->next )
301             kk = kk->next;
302         kk->next = k;
303     }
304     if (secret)
305         k->secret = 1;
306     return k;
307 }
308
309 struct subkey_s *
310 _gpgme_key_add_subkey (GpgmeKey key)
311 {
312     return add_subkey (key, 0);
313 }
314
315 struct subkey_s *
316 _gpgme_key_add_secret_subkey (GpgmeKey key)
317 {
318     return add_subkey (key, 1);
319 }
320
321
322
323 /**
324  * gpgme_key_release:
325  * @key: Key Object or NULL
326  * 
327  * Release the key object. Note, that this function may not do an
328  * actual release if there are other shallow copies of the objects.
329  * You have to call this function for every newly created key object
330  * as well as for every gpgme_key_ref() done on the key object.
331  **/
332 void
333 gpgme_key_release ( GpgmeKey key )
334 {
335     struct user_id_s *u, *u2;
336     struct subkey_s *k, *k2;
337
338     if (!key)
339         return;
340
341     assert (key->ref_count);
342     if ( --key->ref_count )
343         return;
344
345     xfree (key->keys.fingerprint);
346     for (k = key->keys.next; k; k = k2 ) {
347         k2 = k->next;
348         xfree (k->fingerprint);
349         xfree (k);
350     }
351     for (u = key->uids; u; u = u2 ) {
352         u2 = u->next;
353         xfree (u);
354     }
355     xfree (key);
356 }
357
358 /**
359  * gpgme_key_unref:
360  * @key: Key Object
361  * 
362  * This is an alias for gpgme_key_release().
363  **/
364 void
365 gpgme_key_unref (GpgmeKey key)
366 {
367     gpgme_key_release (key);
368 }
369
370
371 static char *
372 set_user_id_part ( char *tail, const char *buf, size_t len )
373 {
374     while ( len && (buf[len-1] == ' ' || buf[len-1] == '\t') ) 
375         len--;
376     for ( ; len; len--)
377         *tail++ = *buf++;
378     *tail++ = 0;
379     return tail;
380 }
381
382
383 static void
384 parse_user_id ( struct user_id_s *uid, char *tail )
385 {
386     const char *s, *start=NULL;
387     int in_name = 0;
388     int in_email = 0;
389     int in_comment = 0;
390
391     for (s=uid->name; *s; s++ ) {
392         if ( in_email ) {
393             if ( *s == '<' )
394                 in_email++; /* not legal but anyway */
395             else if (*s== '>') {
396                 if ( !--in_email ) {
397                     if (!uid->email_part) {
398                         uid->email_part = tail;
399                         tail = set_user_id_part ( tail, start, s-start );
400                     }
401                 }
402             }
403         }
404         else if ( in_comment ) {
405             if ( *s == '(' )
406                 in_comment++;
407             else if (*s== ')') {
408                 if ( !--in_comment ) {
409                     if (!uid->comment_part) {
410                         uid->comment_part = tail;
411                         tail = set_user_id_part ( tail, start, s-start );
412                     }
413                 }
414             }
415         }
416         else if ( *s == '<' ) {
417             if ( in_name ) {
418                 if ( !uid->name_part ) {
419                     uid->name_part = tail;
420                     tail = set_user_id_part (tail, start, s-start );
421                 }
422                 in_name = 0;
423             }
424             in_email = 1;
425             start = s+1;
426         }
427         else if ( *s == '(' ) {
428             if ( in_name ) {
429                 if ( !uid->name_part ) {
430                     uid->name_part = tail;
431                     tail = set_user_id_part (tail, start, s-start );
432                 }
433                 in_name = 0;
434             }
435             in_comment = 1;
436             start = s+1;
437         }
438         else if ( !in_name && *s != ' ' && *s != '\t' ) {
439             in_name = 1;
440             start = s;
441         }    
442     }
443
444     if ( in_name ) {
445         if ( !uid->name_part ) {
446             uid->name_part = tail;
447             tail = set_user_id_part (tail, start, s-start );
448         }
449     }
450
451     /* let unused parts point to an EOS */ 
452     tail--;
453     if (!uid->name_part)
454         uid->name_part = tail;
455     if (!uid->email_part)
456         uid->email_part = tail;
457     if (!uid->comment_part)
458         uid->comment_part = tail;
459
460 }
461
462 static void
463 parse_x509_user_id ( struct user_id_s *uid, char *tail )
464 {
465   const char *s;
466
467   s=uid->name; 
468   if (*s == '<' && s[strlen(s)-1] == '>')
469     uid->email_part = s;
470   
471   /* let unused parts point to an EOS */ 
472   tail--;
473   if (!uid->name_part)
474     uid->name_part = tail;
475   if (!uid->email_part)
476     uid->email_part = tail;
477   if (!uid->comment_part)
478     uid->comment_part = tail;
479 }
480
481 /* 
482  * Take a name from the --with-colon listing, remove certain escape sequences
483  * sequences and put it into the list of UIDs
484  */
485 GpgmeError
486 _gpgme_key_append_name (GpgmeKey key, const char *s)
487 {
488   struct user_id_s *uid;
489   char *d;
490
491   assert (key);
492   /* We can malloc a buffer of the same length, because the converted
493      string will never be larger. Actually we allocate it twice the
494      size, so that we are able to store the parsed stuff there too.  */
495   uid = xtrymalloc ( sizeof *uid + 2*strlen (s)+3);
496   if (!uid)
497     return mk_error (Out_Of_Core);
498   uid->revoked = 0;
499   uid->invalid = 0;
500   uid->validity = 0;
501   uid->name_part = NULL;
502   uid->email_part = NULL;
503   uid->comment_part = NULL;
504   uid->next = NULL;
505   d = uid->name;
506
507   while (*s)
508     {
509       if (*s != '\\')
510         *d++ = *s++;
511       else if (s[1] == '\\')
512         {
513           s++;
514           *d++ = *s++; 
515         }
516       else if (s[1] == 'n')
517         {
518           s += 2;
519           *d++ = '\n'; 
520         }
521       else if (s[1] == 'r')
522         {
523           s += 2;
524           *d++ = '\r'; 
525         }
526       else if (s[1] == 'v')
527         {
528           s += 2;
529           *d++ = '\v'; 
530         }
531       else if (s[1] == 'b')
532         {
533           s += 2;
534           *d++ = '\b'; 
535         }
536       else if (s[1] == '0')
537         {
538           /* Hmmm: no way to express this */
539           s += 2;
540           *d++ = '\\';
541           *d++ = '\0'; 
542         }
543       else if (s[1] == 'x' && my_isdigit (s[2]) && my_isdigit (s[3]))
544         {
545           unsigned int val = (s[2]-'0')*16 + (s[3]-'0');
546           if (!val)
547             {
548               *d++ = '\\';
549               *d++ = '\0'; 
550             }
551           else 
552             *(byte*)d++ = val;
553           s += 3;
554         }
555       else
556         {
557           /* should not happen */
558           s++;
559           *d++ = '\\'; 
560           *d++ = *s++;
561         } 
562     }
563   *d++ = 0;
564   if (key->x509)
565     parse_x509_user_id (uid, d);
566   else
567     parse_user_id (uid, d);
568
569   if (key->uids)
570     {
571       struct user_id_s *u = key->uids;
572       while (u->next)
573         u = u->next;
574       u->next = uid;
575     }
576   else
577     key->uids = uid;
578
579   return 0;
580 }
581
582
583 static void
584 add_otag ( GpgmeData d, const char *tag )
585 {
586     _gpgme_data_append_string ( d, "    <" );
587     _gpgme_data_append_string ( d, tag );
588     _gpgme_data_append_string ( d, ">" );
589 }
590
591 static void
592 add_ctag ( GpgmeData d, const char *tag )
593 {
594     _gpgme_data_append_string ( d, "</" );
595     _gpgme_data_append_string ( d, tag );
596     _gpgme_data_append_string ( d, ">\n" );
597 }
598
599 static void
600 add_tag_and_string ( GpgmeData d, const char *tag, const char *string )
601 {
602     add_otag (d, tag);
603     _gpgme_data_append_string_for_xml ( d, string );
604     add_ctag (d, tag); 
605 }
606
607 static void
608 add_tag_and_uint ( GpgmeData d, const char *tag, unsigned int val )
609 {
610     char buf[30];
611     sprintf (buf, "%u", val );
612     add_tag_and_string ( d, tag, buf );
613 }
614
615 static void
616 add_tag_and_time ( GpgmeData d, const char *tag, time_t val )
617 {
618     char buf[30];
619
620     if (!val || val == (time_t)-1 )
621         return;
622     sprintf (buf, "%lu", (unsigned long)val );
623     add_tag_and_string ( d, tag, buf );
624 }
625
626 static void
627 one_uid_as_xml (GpgmeData d, struct user_id_s *u)
628 {
629     _gpgme_data_append_string (d, "  <userid>\n");
630     if ( u->invalid )
631         _gpgme_data_append_string ( d, "    <invalid/>\n");
632     if ( u->revoked )
633         _gpgme_data_append_string ( d, "    <revoked/>\n");
634     add_tag_and_string ( d, "raw", u->name );
635     if ( *u->name_part )
636         add_tag_and_string ( d, "name", u->name_part );
637     if ( *u->email_part )
638         add_tag_and_string ( d, "email", u->email_part );
639     if ( *u->comment_part )
640         add_tag_and_string ( d, "comment", u->comment_part );
641     _gpgme_data_append_string (d, "  </userid>\n");
642 }
643
644
645 /**
646  * gpgme_key_get_as_xml:
647  * @key: Key object
648  * 
649  * Return the key object as an XML string.  The classer has to free
650  * that string.
651  * 
652  * Return value:  An XML string or NULL in case of a memory problem or
653  *                a NULL passed as @key
654  **/
655 char *
656 gpgme_key_get_as_xml ( GpgmeKey key )
657 {
658     GpgmeData d;
659     struct user_id_s *u;
660     struct subkey_s *k;
661
662     if ( !key )
663         return NULL;
664     
665     if ( gpgme_data_new ( &d ) )
666         return NULL;
667     
668     _gpgme_data_append_string ( d, "<GnupgKeyblock>\n"
669                                    "  <mainkey>\n" );
670     if ( key->keys.secret )
671         _gpgme_data_append_string ( d, "    <secret/>\n");
672     if ( key->keys.flags.invalid )
673         _gpgme_data_append_string ( d, "    <invalid/>\n");
674     if ( key->keys.flags.revoked )
675         _gpgme_data_append_string ( d, "    <revoked/>\n");
676     if ( key->keys.flags.expired )
677         _gpgme_data_append_string ( d, "    <expired/>\n");
678     if ( key->keys.flags.disabled )
679         _gpgme_data_append_string ( d, "    <disabled/>\n");
680     add_tag_and_string (d, "keyid", key->keys.keyid );   
681     if (key->keys.fingerprint)
682         add_tag_and_string (d, "fpr", key->keys.fingerprint );
683     add_tag_and_uint (d, "algo", key->keys.key_algo );
684     add_tag_and_uint (d, "len", key->keys.key_len );
685     add_tag_and_time (d, "created", key->keys.timestamp );
686     /*add_tag_and_time (d, "expires", key->expires );*/
687     _gpgme_data_append_string (d, "  </mainkey>\n");
688
689     /* Now the user IDs.  */
690     for (u = key->uids; u; u = u->next)
691       one_uid_as_xml (d,u);
692
693     /* and now the subkeys */
694     for (k=key->keys.next; k; k = k->next ) {
695         _gpgme_data_append_string (d, "  <subkey>\n");
696         if ( k->secret )
697             _gpgme_data_append_string ( d, "    <secret/>\n");
698         if ( k->flags.invalid )
699             _gpgme_data_append_string ( d, "    <invalid/>\n");
700         if ( k->flags.revoked )
701             _gpgme_data_append_string ( d, "    <revoked/>\n");
702         if ( k->flags.expired )
703             _gpgme_data_append_string ( d, "    <expired/>\n");
704         if ( k->flags.disabled )
705             _gpgme_data_append_string ( d, "    <disabled/>\n");
706         add_tag_and_string (d, "keyid", k->keyid );   
707         if (k->fingerprint)
708             add_tag_and_string (d, "fpr", k->fingerprint );
709         add_tag_and_uint (d, "algo", k->key_algo );
710         add_tag_and_uint (d, "len", k->key_len );
711         add_tag_and_time (d, "created", k->timestamp );
712         _gpgme_data_append_string (d, "  </subkey>\n");
713     }
714     _gpgme_data_append_string ( d, "</GnupgKeyblock>\n" );
715
716     return _gpgme_data_release_and_return_string (d);
717 }
718
719
720 static const char *
721 capabilities_to_string (struct subkey_s *k)
722 {
723     static char *strings[8] = {
724         "",
725         "c",
726         "s",
727         "sc",
728         "e",
729         "ec",
730         "es",
731         "esc"
732     };
733     return strings[  (!!k->flags.can_encrypt << 2)
734                    | (!!k->flags.can_sign    << 1)
735                    | (!!k->flags.can_certify     ) ];
736 }
737
738
739
740 /**
741  * gpgme_key_get_string_attr:
742  * @key: Key Object
743  * @what: Attribute specifier
744  * @reserved: Must be 0
745  * @idx: Index counter
746  * 
747  * Return a attribute as specified by @what and @idx.  Note that not
748  * all attributes can be returned as a string, in which case NULL is
749  * returned.  @idx is used to iterate through attributes which do have
750  * more than one instance (e.g. user IDs or sub keys).
751  * 
752  * Return value: NULL or an const string which is only valid as long
753  * as the key object itself is valid.
754  **/
755 const char *
756 gpgme_key_get_string_attr ( GpgmeKey key, GpgmeAttr what,
757                             const void *reserved, int idx )
758 {
759     const char *val = NULL;
760     struct subkey_s *k;
761     struct user_id_s *u;
762
763     if (!key)
764         return NULL;
765     if (reserved)
766         return NULL;
767     if (idx < 0)
768         return NULL;
769
770     switch (what) {
771       case GPGME_ATTR_KEYID:
772         for (k=&key->keys; k && idx; k=k->next, idx-- )
773             ;
774         if (k) 
775             val = k->keyid;
776         break;
777       case GPGME_ATTR_FPR:
778         for (k=&key->keys; k && idx; k=k->next, idx-- )
779             ;
780         if (k) 
781             val = k->fingerprint;
782         break;
783       case GPGME_ATTR_ALGO:    
784         for (k=&key->keys; k && idx; k=k->next, idx-- )
785             ;
786         if (k) 
787             val = pkalgo_to_string (k->key_algo);
788         break;
789       case GPGME_ATTR_LEN:     
790       case GPGME_ATTR_CREATED: 
791       case GPGME_ATTR_EXPIRE:  
792         break; /* use another get function */
793       case GPGME_ATTR_OTRUST:  
794         val = "[fixme]";
795         break;
796       case GPGME_ATTR_USERID:  
797         for (u=key->uids; u && idx; u=u->next, idx-- )
798             ;
799         val = u? u->name : NULL;
800         break;
801       case GPGME_ATTR_NAME:   
802         for (u=key->uids; u && idx; u=u->next, idx-- )
803             ;
804         val = u? u->name_part : NULL;
805         break;
806       case GPGME_ATTR_EMAIL:
807         for (u=key->uids; u && idx; u=u->next, idx-- )
808             ;
809         val = u? u->email_part : NULL;
810         break;
811       case GPGME_ATTR_COMMENT:
812         for (u=key->uids; u && idx; u=u->next, idx-- )
813             ;
814         val = u? u->comment_part : NULL;
815         break;
816       case GPGME_ATTR_VALIDITY:
817         for (u=key->uids; u && idx; u=u->next, idx-- )
818             ;
819         if (u) {
820             switch (u->validity) {
821               case GPGME_VALIDITY_UNKNOWN:   val = "?"; break;
822               case GPGME_VALIDITY_UNDEFINED: val = "q"; break;
823               case GPGME_VALIDITY_NEVER:     val = "n"; break;
824               case GPGME_VALIDITY_MARGINAL:  val = "m"; break;
825               case GPGME_VALIDITY_FULL:      val = "f"; break;
826               case GPGME_VALIDITY_ULTIMATE:  val = "u"; break;
827             }
828         }
829         break;
830       case GPGME_ATTR_LEVEL:  /* not used here */
831       case GPGME_ATTR_TYPE:
832       case GPGME_ATTR_KEY_REVOKED:
833       case GPGME_ATTR_KEY_INVALID:
834       case GPGME_ATTR_KEY_EXPIRED:
835       case GPGME_ATTR_KEY_DISABLED:
836       case GPGME_ATTR_UID_REVOKED:
837       case GPGME_ATTR_UID_INVALID:
838       case GPGME_ATTR_CAN_ENCRYPT:
839       case GPGME_ATTR_CAN_SIGN:
840       case GPGME_ATTR_CAN_CERTIFY:
841         break;
842       case GPGME_ATTR_IS_SECRET:
843         if (key->secret)
844             val = "1";
845         break;
846       case GPGME_ATTR_KEY_CAPS:    
847         for (k=&key->keys; k && idx; k=k->next, idx-- )
848             ;
849         if (k) 
850             val = capabilities_to_string (k);
851         break;
852     }
853     return val;
854 }
855
856
857 /**
858  * gpgme_key_get_ulong_attr:
859  * @key: 
860  * @what: 
861  * @reserved: 
862  * @idx: 
863  * 
864  * Return a attribute as specified by @what and @idx.  Note that not
865  * all attributes can be returned as an integer, in which case 0 is
866  * returned.  @idx is used to iterate through attributes which do have
867  * more than one instance (e.g. user IDs or sub keys).
868  *
869  * See gpgme.h for a list of attributes.
870  * 
871  * Return value: 0 or the requested value.
872  **/
873 unsigned long
874 gpgme_key_get_ulong_attr ( GpgmeKey key, GpgmeAttr what,
875                            const void *reserved, int idx )
876 {
877     unsigned long val = 0;
878     struct subkey_s *k;
879     struct user_id_s *u;
880
881     if (!key)
882         return 0;
883     if (reserved)
884         return 0;
885     if (idx < 0)
886         return 0;
887
888     switch (what) {
889       case GPGME_ATTR_ALGO:    
890         for (k=&key->keys; k && idx; k=k->next, idx-- )
891             ;
892         if (k) 
893             val = (unsigned long)k->key_algo;
894         break;
895       case GPGME_ATTR_LEN:     
896         for (k=&key->keys; k && idx; k=k->next, idx-- )
897             ;
898         if (k) 
899             val = (unsigned long)k->key_len;
900         break;
901       case GPGME_ATTR_CREATED: 
902         for (k=&key->keys; k && idx; k=k->next, idx-- )
903             ;
904         if (k) 
905             val = k->timestamp < 0? 0L:(unsigned long)k->timestamp;
906         break;
907       case GPGME_ATTR_VALIDITY:
908         for (u=key->uids; u && idx; u=u->next, idx-- )
909             ;
910         if (u)
911             val = u->validity;
912         break;
913       case GPGME_ATTR_IS_SECRET:
914         val = !!key->secret;
915         break;
916       case GPGME_ATTR_KEY_REVOKED:
917         for (k=&key->keys; k && idx; k=k->next, idx-- )
918             ;
919         if (k) 
920             val = k->flags.revoked;
921         break;
922       case GPGME_ATTR_KEY_INVALID:
923         for (k=&key->keys; k && idx; k=k->next, idx-- )
924             ;
925         if (k) 
926             val = k->flags.invalid;
927         break;
928       case GPGME_ATTR_KEY_EXPIRED:
929         for (k=&key->keys; k && idx; k=k->next, idx-- )
930             ;
931         if (k) 
932             val = k->flags.expired;
933         break;
934       case GPGME_ATTR_KEY_DISABLED:
935         for (k=&key->keys; k && idx; k=k->next, idx-- )
936             ;
937         if (k) 
938             val = k->flags.disabled;
939         break;
940       case GPGME_ATTR_UID_REVOKED:
941         for (u=key->uids; u && idx; u=u->next, idx-- )
942             ;
943         if (u)
944             val = u->revoked;
945         break;
946       case GPGME_ATTR_UID_INVALID:
947         for (u=key->uids; u && idx; u=u->next, idx-- )
948             ;
949         if (u)
950             val = u->invalid;
951         break;
952       case GPGME_ATTR_CAN_ENCRYPT:
953         val = key->gloflags.can_certify;
954         break;
955       case GPGME_ATTR_CAN_SIGN:
956         val = key->gloflags.can_sign;
957         break;
958       case GPGME_ATTR_CAN_CERTIFY:
959         val = key->gloflags.can_encrypt;
960         break;
961       default:
962         break;
963     }
964     return val;
965 }
966
967