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