gpgme/
[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 /* 
463  * Take a name from the --with-colon listing, remove certain escape sequences
464  * sequences and put it into the list of UIDs
465  */
466 GpgmeError
467 _gpgme_key_append_name ( GpgmeKey key, const char *s )
468 {
469     struct user_id_s *uid;
470     char *d;
471
472     assert (key);
473     /* we can malloc a buffer of the same length, because the
474      * converted string will never be larger. Actually we allocate it
475      * twice the size, so that we are able to store the parsed stuff
476      * there too */
477     uid = xtrymalloc ( sizeof *uid + 2*strlen (s)+3 );
478     if ( !uid )
479         return mk_error (Out_Of_Core);
480     uid->revoked = 0;
481     uid->invalid = 0;
482     uid->validity = 0;
483     uid->name_part = NULL;
484     uid->email_part = NULL;
485     uid->comment_part = NULL;
486     d = uid->name;
487
488     while ( *s ) {
489         if ( *s != '\\' )
490             *d++ = *s++;
491         else if ( s[1] == '\\' ) {
492             s++;
493             *d++ = *s++; 
494         }
495         else if ( s[1] == 'n' ) {
496             s += 2;
497             *d++ = '\n'; 
498         }
499         else if ( s[1] == 'r' ) {
500             s += 2;
501             *d++ = '\r'; 
502         }
503         else if ( s[1] == 'v' ) {
504             s += 2;
505             *d++ = '\v'; 
506         }
507         else if ( s[1] == 'b' ) {
508             s += 2;
509             *d++ = '\b'; 
510         }
511         else if ( s[1] == '0' ) {
512             /* Hmmm: no way to express this */
513             s += 2;
514             *d++ = '\\';
515             *d++ = '\0'; 
516         }
517         else if ( s[1] == 'x' && my_isdigit (s[2]) && my_isdigit (s[3]) ) {
518             unsigned int val = (s[2]-'0')*16 + (s[3]-'0');
519             if ( !val ) {
520                 *d++ = '\\';
521                 *d++ = '\0'; 
522             }
523             else 
524                 *(byte*)d++ = val;
525             s += 3;
526         }
527         else { /* should not happen */
528             s++;
529             *d++ = '\\'; 
530             *d++ = *s++;
531         } 
532     }
533     *d++ = 0;
534     parse_user_id ( uid, d );
535
536     uid->next = key->uids;
537     key->uids = uid;
538     return 0;
539 }
540
541
542 static void
543 add_otag ( GpgmeData d, const char *tag )
544 {
545     _gpgme_data_append_string ( d, "    <" );
546     _gpgme_data_append_string ( d, tag );
547     _gpgme_data_append_string ( d, ">" );
548 }
549
550 static void
551 add_ctag ( GpgmeData d, const char *tag )
552 {
553     _gpgme_data_append_string ( d, "</" );
554     _gpgme_data_append_string ( d, tag );
555     _gpgme_data_append_string ( d, ">\n" );
556 }
557
558 static void
559 add_tag_and_string ( GpgmeData d, const char *tag, const char *string )
560 {
561     add_otag (d, tag);
562     _gpgme_data_append_string_for_xml ( d, string );
563     add_ctag (d, tag); 
564 }
565
566 static void
567 add_tag_and_uint ( GpgmeData d, const char *tag, unsigned int val )
568 {
569     char buf[30];
570     sprintf (buf, "%u", val );
571     add_tag_and_string ( d, tag, buf );
572 }
573
574 static void
575 add_tag_and_time ( GpgmeData d, const char *tag, time_t val )
576 {
577     char buf[30];
578
579     if (!val || val == (time_t)-1 )
580         return;
581     sprintf (buf, "%lu", (unsigned long)val );
582     add_tag_and_string ( d, tag, buf );
583 }
584
585 static void
586 one_uid_as_xml (GpgmeData d, struct user_id_s *u)
587 {
588     _gpgme_data_append_string (d, "  <userid>\n");
589     if ( u->invalid )
590         _gpgme_data_append_string ( d, "    <invalid/>\n");
591     if ( u->revoked )
592         _gpgme_data_append_string ( d, "    <revoked/>\n");
593     add_tag_and_string ( d, "raw", u->name );
594     if ( *u->name_part )
595         add_tag_and_string ( d, "name", u->name_part );
596     if ( *u->email_part )
597         add_tag_and_string ( d, "email", u->email_part );
598     if ( *u->comment_part )
599         add_tag_and_string ( d, "comment", u->comment_part );
600     _gpgme_data_append_string (d, "  </userid>\n");
601 }
602
603
604 /**
605  * gpgme_key_get_as_xml:
606  * @key: Key object
607  * 
608  * Return the key object as an XML string.  The classer has to free
609  * that string.
610  * 
611  * Return value:  An XML string or NULL in case of a memory problem or
612  *                a NULL passed as @key
613  **/
614 char *
615 gpgme_key_get_as_xml ( GpgmeKey key )
616 {
617     GpgmeData d;
618     struct user_id_s *u;
619     struct subkey_s *k;
620
621     if ( !key )
622         return NULL;
623     
624     if ( gpgme_data_new ( &d ) )
625         return NULL;
626     
627     _gpgme_data_append_string ( d, "<GnupgKeyblock>\n"
628                                    "  <mainkey>\n" );
629     if ( key->keys.secret )
630         _gpgme_data_append_string ( d, "    <secret/>\n");
631     if ( key->keys.flags.invalid )
632         _gpgme_data_append_string ( d, "    <invalid/>\n");
633     if ( key->keys.flags.revoked )
634         _gpgme_data_append_string ( d, "    <revoked/>\n");
635     if ( key->keys.flags.expired )
636         _gpgme_data_append_string ( d, "    <expired/>\n");
637     if ( key->keys.flags.disabled )
638         _gpgme_data_append_string ( d, "    <disabled/>\n");
639     add_tag_and_string (d, "keyid", key->keys.keyid );   
640     if (key->keys.fingerprint)
641         add_tag_and_string (d, "fpr", key->keys.fingerprint );
642     add_tag_and_uint (d, "algo", key->keys.key_algo );
643     add_tag_and_uint (d, "len", key->keys.key_len );
644     add_tag_and_time (d, "created", key->keys.timestamp );
645     /*add_tag_and_time (d, "expires", key->expires );*/
646     _gpgme_data_append_string (d, "  </mainkey>\n");
647
648     /* Now the user IDs.  We are listing the last one first because this is
649      * the primary one. */
650     for (u = key->uids; u && u->next; u = u->next )
651         ;
652     if (u) {
653         one_uid_as_xml (d,u);
654         for ( u = key->uids; u && u->next; u = u->next ) {
655             one_uid_as_xml (d,u);
656         }
657     }
658
659     /* and now the subkeys */
660     for (k=key->keys.next; k; k = k->next ) {
661         _gpgme_data_append_string (d, "  <subkey>\n");
662         if ( k->secret )
663             _gpgme_data_append_string ( d, "    <secret/>\n");
664         if ( k->flags.invalid )
665             _gpgme_data_append_string ( d, "    <invalid/>\n");
666         if ( k->flags.revoked )
667             _gpgme_data_append_string ( d, "    <revoked/>\n");
668         if ( k->flags.expired )
669             _gpgme_data_append_string ( d, "    <expired/>\n");
670         if ( k->flags.disabled )
671             _gpgme_data_append_string ( d, "    <disabled/>\n");
672         add_tag_and_string (d, "keyid", k->keyid );   
673         if (k->fingerprint)
674             add_tag_and_string (d, "fpr", k->fingerprint );
675         add_tag_and_uint (d, "algo", k->key_algo );
676         add_tag_and_uint (d, "len", k->key_len );
677         add_tag_and_time (d, "created", k->timestamp );
678         _gpgme_data_append_string (d, "  </subkey>\n");
679     }
680     _gpgme_data_append_string ( d, "</GnupgKeyblock>\n" );
681
682     return _gpgme_data_release_and_return_string (d);
683 }
684
685
686 static const char *
687 capabilities_to_string (struct subkey_s *k)
688 {
689     static char *strings[8] = {
690         "",
691         "c",
692         "s",
693         "sc",
694         "e",
695         "ec",
696         "es",
697         "esc"
698     };
699     return strings[  (!!k->flags.can_encrypt << 2)
700                    | (!!k->flags.can_sign    << 1)
701                    | (!!k->flags.can_certify     ) ];
702 }
703
704
705
706 /**
707  * gpgme_key_get_string_attr:
708  * @key: Key Object
709  * @what: Attribute specifier
710  * @reserved: Must be 0
711  * @idx: Index counter
712  * 
713  * Return a attribute as specified by @what and @idx.  Note that not
714  * all attributes can be returned as a string, in which case NULL is
715  * returned.  @idx is used to iterate through attributes which do have
716  * more than one instance (e.g. user IDs or sub keys).
717  * 
718  * Return value: NULL or an const string which is only valid as long
719  * as the key object itself is valid.
720  **/
721 const char *
722 gpgme_key_get_string_attr ( GpgmeKey key, GpgmeAttr what,
723                             const void *reserved, int idx )
724 {
725     const char *val = NULL;
726     struct subkey_s *k;
727     struct user_id_s *u;
728
729     if (!key)
730         return NULL;
731     if (reserved)
732         return NULL;
733     if (idx < 0)
734         return NULL;
735
736     switch (what) {
737       case GPGME_ATTR_KEYID:
738         for (k=&key->keys; k && idx; k=k->next, idx-- )
739             ;
740         if (k) 
741             val = k->keyid;
742         break;
743       case GPGME_ATTR_FPR:
744         for (k=&key->keys; k && idx; k=k->next, idx-- )
745             ;
746         if (k) 
747             val = k->fingerprint;
748         break;
749       case GPGME_ATTR_ALGO:    
750         for (k=&key->keys; k && idx; k=k->next, idx-- )
751             ;
752         if (k) 
753             val = pkalgo_to_string (k->key_algo);
754         break;
755       case GPGME_ATTR_LEN:     
756       case GPGME_ATTR_CREATED: 
757       case GPGME_ATTR_EXPIRE:  
758         break; /* use another get function */
759       case GPGME_ATTR_OTRUST:  
760         val = "[fixme]";
761         break;
762       case GPGME_ATTR_USERID:  
763         for (u=key->uids; u && idx; u=u->next, idx-- )
764             ;
765         val = u? u->name : NULL;
766         break;
767       case GPGME_ATTR_NAME:   
768         for (u=key->uids; u && idx; u=u->next, idx-- )
769             ;
770         val = u? u->name_part : NULL;
771         break;
772       case GPGME_ATTR_EMAIL:
773         for (u=key->uids; u && idx; u=u->next, idx-- )
774             ;
775         val = u? u->email_part : NULL;
776         break;
777       case GPGME_ATTR_COMMENT:
778         for (u=key->uids; u && idx; u=u->next, idx-- )
779             ;
780         val = u? u->comment_part : NULL;
781         break;
782       case GPGME_ATTR_VALIDITY:
783         for (u=key->uids; u && idx; u=u->next, idx-- )
784             ;
785         if (u) {
786             switch (u->validity) {
787               case GPGME_VALIDITY_UNKNOWN:   val = "?"; break;
788               case GPGME_VALIDITY_UNDEFINED: val = "q"; break;
789               case GPGME_VALIDITY_NEVER:     val = "n"; break;
790               case GPGME_VALIDITY_MARGINAL:  val = "m"; break;
791               case GPGME_VALIDITY_FULL:      val = "f"; break;
792               case GPGME_VALIDITY_ULTIMATE:  val = "u"; break;
793             }
794         }
795         break;
796       case GPGME_ATTR_LEVEL:  /* not used here */
797       case GPGME_ATTR_TYPE:
798       case GPGME_ATTR_KEY_REVOKED:
799       case GPGME_ATTR_KEY_INVALID:
800       case GPGME_ATTR_KEY_EXPIRED:
801       case GPGME_ATTR_KEY_DISABLED:
802       case GPGME_ATTR_UID_REVOKED:
803       case GPGME_ATTR_UID_INVALID:
804       case GPGME_ATTR_CAN_ENCRYPT:
805       case GPGME_ATTR_CAN_SIGN:
806       case GPGME_ATTR_CAN_CERTIFY:
807         break;
808       case GPGME_ATTR_IS_SECRET:
809         if (key->secret)
810             val = "1";
811         break;
812       case GPGME_ATTR_KEY_CAPS:    
813         for (k=&key->keys; k && idx; k=k->next, idx-- )
814             ;
815         if (k) 
816             val = capabilities_to_string (k);
817         break;
818     }
819     return val;
820 }
821
822
823 /**
824  * gpgme_key_get_ulong_attr:
825  * @key: 
826  * @what: 
827  * @reserved: 
828  * @idx: 
829  * 
830  * Return a attribute as specified by @what and @idx.  Note that not
831  * all attributes can be returned as an integer, in which case 0 is
832  * returned.  @idx is used to iterate through attributes which do have
833  * more than one instance (e.g. user IDs or sub keys).
834  *
835  * See gpgme.h for a list of attributes.
836  * 
837  * Return value: 0 or the requested value.
838  **/
839 unsigned long
840 gpgme_key_get_ulong_attr ( GpgmeKey key, GpgmeAttr what,
841                            const void *reserved, int idx )
842 {
843     unsigned long val = 0;
844     struct subkey_s *k;
845     struct user_id_s *u;
846
847     if (!key)
848         return 0;
849     if (reserved)
850         return 0;
851     if (idx < 0)
852         return 0;
853
854     switch (what) {
855       case GPGME_ATTR_ALGO:    
856         for (k=&key->keys; k && idx; k=k->next, idx-- )
857             ;
858         if (k) 
859             val = (unsigned long)k->key_algo;
860         break;
861       case GPGME_ATTR_LEN:     
862         for (k=&key->keys; k && idx; k=k->next, idx-- )
863             ;
864         if (k) 
865             val = (unsigned long)k->key_len;
866         break;
867       case GPGME_ATTR_CREATED: 
868         for (k=&key->keys; k && idx; k=k->next, idx-- )
869             ;
870         if (k) 
871             val = k->timestamp < 0? 0L:(unsigned long)k->timestamp;
872         break;
873       case GPGME_ATTR_VALIDITY:
874         for (u=key->uids; u && idx; u=u->next, idx-- )
875             ;
876         if (u)
877             val = u->validity;
878         break;
879       case GPGME_ATTR_IS_SECRET:
880         val = !!key->secret;
881         break;
882       case GPGME_ATTR_KEY_REVOKED:
883         for (k=&key->keys; k && idx; k=k->next, idx-- )
884             ;
885         if (k) 
886             val = k->flags.revoked;
887         break;
888       case GPGME_ATTR_KEY_INVALID:
889         for (k=&key->keys; k && idx; k=k->next, idx-- )
890             ;
891         if (k) 
892             val = k->flags.invalid;
893         break;
894       case GPGME_ATTR_KEY_EXPIRED:
895         for (k=&key->keys; k && idx; k=k->next, idx-- )
896             ;
897         if (k) 
898             val = k->flags.expired;
899         break;
900       case GPGME_ATTR_KEY_DISABLED:
901         for (k=&key->keys; k && idx; k=k->next, idx-- )
902             ;
903         if (k) 
904             val = k->flags.disabled;
905         break;
906       case GPGME_ATTR_UID_REVOKED:
907         for (u=key->uids; u && idx; u=u->next, idx-- )
908             ;
909         if (u)
910             val = u->revoked;
911         break;
912       case GPGME_ATTR_UID_INVALID:
913         for (u=key->uids; u && idx; u=u->next, idx-- )
914             ;
915         if (u)
916             val = u->invalid;
917         break;
918       case GPGME_ATTR_CAN_ENCRYPT:
919         val = key->gloflags.can_certify;
920         break;
921       case GPGME_ATTR_CAN_SIGN:
922         val = key->gloflags.can_sign;
923         break;
924       case GPGME_ATTR_CAN_CERTIFY:
925         val = key->gloflags.can_encrypt;
926         break;
927       default:
928         break;
929     }
930     return val;
931 }
932
933