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