8d34ab77ed3baf4d66870de9306c00dcefc8bb8a
[gnupg.git] / scd / sc-investigate.c
1 /* sc-investigate.c - A tool to look around on smartcards.
2  *      Copyright (C) 2003 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <unistd.h>
29 #ifdef USE_GNU_PTH
30 # include <pth.h>
31 #endif
32
33 #ifdef HAVE_READLINE_READLINE_H
34 #include <readline/readline.h>
35 #include <readline/history.h>
36 #endif
37
38 #define JNLIB_NEED_LOG_LOGV
39 #include "scdaemon.h"
40 #include <gcrypt.h>
41
42 #include "apdu.h" /* for open_reader */
43 #include "atr.h"
44 #include "app-common.h"
45 #include "iso7816.h"
46
47 #define _(a) (a)
48
49 #define CONTROL_D ('D' - 'A' + 1)
50
51
52 enum cmd_and_opt_values 
53
54   oInteractive    = 'i',
55   oVerbose        = 'v',
56   oQuiet          = 'q',
57   oReaderPort     = 500,
58   octapiDriver,
59
60   oDebug,
61   oDebugAll,
62
63   oDisableCCID,
64
65
66   oGenRandom,
67
68 aTest };
69
70
71 static ARGPARSE_OPTS opts[] = {
72   
73   { 301, NULL, 0, "@Options:\n " },
74
75   { oInteractive, "interactive", 0, "start in interactive explorer mode"},
76   { oQuiet,       "quiet", 0, "quiet" },
77   { oVerbose, "verbose",   0, "verbose" },
78   { oReaderPort, "reader-port", 2, "|N|connect to reader at port N"},
79   { octapiDriver, "ctapi-driver", 2, "NAME|use NAME as ctAPI driver"},
80   { oDisableCCID, "disable-ccid", 0,
81 #ifdef HAVE_LIBUSB
82                                     "do not use the internal CCID driver"
83 #else
84                                     "@"
85 #endif
86   },
87   { oDebug,     "debug"     ,4|16, "set debugging flags"},
88   { oDebugAll, "debug-all" ,0, "enable full debugging"},
89   { oGenRandom, "gen-random", 4, "|N|generate N bytes of random"},
90   {0}
91 };
92
93 #ifndef HAVE_OPENSC
94 #ifdef USE_GNU_PTH
95 /* Pth wrapper function definitions. */
96 GCRY_THREAD_OPTION_PTH_IMPL;
97 #endif /*USE_GNU_PTH*/
98 #endif /*!HAVE_OPENSC*/
99
100 static void interactive_shell (int slot);
101 static void dump_other_cards (int slot);
102
103 static const char *
104 my_strusage (int level)
105 {
106   const char *p;
107   switch (level)
108     {
109     case 11: p = "sc-investigate (GnuPG)";
110       break;
111     case 13: p = VERSION; break;
112     case 17: p = PRINTABLE_OS_NAME; break;
113     case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
114       break;
115     case 1:
116     case 40: p =  _("Usage: sc-investigate [options] (-h for help)\n");
117       break;
118     case 41: p =  _("Syntax: sc-investigate [options] [args]]\n"
119                     "Have a look at smartcards\n");
120     break;
121     
122     default: p = NULL;
123     }
124   return p;
125 }
126
127 /* Used by gcry for logging */
128 static void
129 my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
130 {
131   /* translate the log levels */
132   switch (level)
133     {
134     case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
135     case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
136     case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
137     case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
138     case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
139     case GCRY_LOG_BUG:  level = JNLIB_LOG_BUG; break;
140     case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
141     default:            level = JNLIB_LOG_ERROR; break;  
142     }
143   log_logv (level, fmt, arg_ptr);
144 }
145
146
147 int
148 main (int argc, char **argv )
149 {
150   ARGPARSE_ARGS pargs;
151   int slot, rc;
152   const char *reader_port = NULL;
153   unsigned long gen_random = 0;
154   int interactive = 0;
155
156   set_strusage (my_strusage);
157   gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
158   log_set_prefix ("sc-investigate", 1); 
159
160   /* Try to auto set the character set.  */
161   set_native_charset (NULL); 
162
163   /* Libgcrypt requires us to register the threading model first.  We
164      can't use pth at all if we are using OpenSC becuase OpenSC uses
165      ptreads.  Note that this will also do the pth_init. */
166 #ifndef HAVE_OPENSC
167 #ifdef USE_GNU_PTH
168   rc = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth);
169   if (rc)
170     {
171       log_fatal ("can't register GNU Pth with Libgcrypt: %s\n",
172                  gpg_strerror (rc));
173     }
174 #endif /*USE_GNU_PTH*/
175 #endif /*!HAVE_OPENSC*/
176
177   /* Check that the libraries are suitable.  Do it here because
178      the option parsing may need services of the library */
179   if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
180     {
181       log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
182                  NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
183     }
184
185
186   gcry_set_log_handler (my_gcry_logger, NULL);
187   /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/
188
189   pargs.argc = &argc;
190   pargs.argv = &argv;
191   pargs.flags=  1;  /* do not remove the args */
192   while (arg_parse (&pargs, opts) )
193     {
194       switch (pargs.r_opt)
195         {
196         case oVerbose: opt.verbose++; break;
197         case oQuiet: opt.quiet++; break;
198         case oDebug: opt.debug |= pargs.r.ret_ulong; break;
199         case oDebugAll: opt.debug = ~0; break;
200         case oReaderPort: reader_port = pargs.r.ret_str; break;
201         case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
202         case oDisableCCID: opt.disable_ccid = 1; break;
203         case oGenRandom: gen_random = pargs.r.ret_ulong; break;
204         case oInteractive: interactive = 1; break;
205         default : pargs.err = 2; break;
206         }
207     }
208   if (log_get_errorcount(0))
209     exit(2);
210
211   if (opt.verbose < 2)
212     opt.verbose = 2; /* Hack to let select_openpgp print some info. */
213
214   if (argc)
215     usage (1);
216
217   slot = apdu_open_reader (reader_port);
218   if (slot == -1)
219     exit (1);
220   
221   if (!gen_random && !opt.quiet)
222     {
223       rc = atr_dump (slot, stdout); 
224       if (rc)
225         log_error ("can't dump ATR: %s\n", gpg_strerror (rc));
226     }
227
228   if (interactive)
229     interactive_shell (slot);
230   else
231     {
232       struct app_ctx_s appbuf;
233
234       /* Fixme: We better use app.c directly. */
235       memset (&appbuf, 0, sizeof appbuf);
236       appbuf.slot = slot;
237       rc = app_select_openpgp (&appbuf);
238       if (rc)
239         {
240           if (!opt.quiet)
241             log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
242           memset (&appbuf, 0, sizeof appbuf);
243           appbuf.slot = slot;
244           rc = app_select_dinsig (&appbuf);
245           if (rc)
246             {
247               if (!opt.quiet)
248                 log_info ("selecting dinsig failed: %s\n", gpg_strerror (rc));
249               dump_other_cards (slot);
250             }
251           else
252             {
253               appbuf.initialized = 1;
254               log_info ("dinsig application selected\n");
255             }
256         }
257       else
258         {
259           appbuf.initialized = 1;
260           log_info ("openpgp application selected\n");
261
262           if (gen_random)
263             {
264               size_t nbytes;
265               unsigned char *buffer;
266           
267               buffer = xmalloc (4096);
268               do 
269                 {
270                   nbytes = gen_random > 4096? 4096 : gen_random;
271                   rc = app_get_challenge (&appbuf, nbytes, buffer);
272                   if (rc)
273                     log_error ("app_get_challenge failed: %s\n",gpg_strerror (rc));
274                   else
275                     {
276                       if (fwrite (buffer, nbytes, 1, stdout) != 1)
277                         log_error ("writing to stdout failed: %s\n",
278                                    strerror (errno));
279                       gen_random -= nbytes;
280                     }
281                 }
282               while (gen_random && !log_get_errorcount (0));
283               xfree (buffer);
284             }
285         }
286     }
287   
288   return log_get_errorcount (0)? 2:0;
289 }
290
291
292
293 void
294 send_status_info (CTRL ctrl, const char *keyword, ...)
295 {
296   /* DUMMY */
297 }
298
299
300
301 /* Dump BUFFER of length NBYTES in a nicely human readable format. */ 
302 static void
303 dump_buffer (const unsigned char *buffer, size_t nbytes)
304 {
305   int i;
306
307   while (nbytes)
308     {
309       for (i=0; i < 16 && i < nbytes; i++)
310         printf ("%02X%s ", buffer[i], i==8? " ":"");
311       for (; i < 16; i++)
312         printf ("  %s ", i==8? " ":"");
313       putchar (' ');
314       putchar (' ');
315       for (i=0; i < 16 && i < nbytes; i++)
316         if (isprint (buffer[i]))
317           putchar (buffer[i]);
318         else
319           putchar ('.');
320       nbytes -= i;
321       buffer += i;
322       for (; i < 16; i++)
323         putchar (' ');
324       putchar ('\n');
325     }
326 }
327
328
329 static void
330 dump_or_store_buffer (const char *arg,
331                       const unsigned char *buffer, size_t nbytes)
332 {
333   const char *s = strchr (arg, '>');
334   int append;
335   FILE *fp;
336
337   if (!s)
338     {
339       dump_buffer (buffer, nbytes);
340       return;
341     }
342   if ((append = (*++s == '>')))
343     s++;
344   fp = fopen (s, append? "ab":"wb");
345   if (!fp)
346     {
347       log_error ("failed to create `%s': %s\n", s, strerror (errno));
348       return;
349     }
350   if (nbytes && fwrite (buffer, nbytes, 1, fp) != 1)
351       log_error ("failed to write to `%s': %s\n", s, strerror (errno));
352   if (fclose (fp))
353       log_error ("failed to close `%s': %s\n", s, strerror (errno));
354 }
355
356
357 /* Convert STRING into a a newly allocated buffer and return the
358    length of the buffer in R_LENGTH.  Detect xx:xx:xx... sequence and
359    unhexify that one. */
360 static unsigned char *
361 pin_to_buffer (const char *string, size_t *r_length)
362 {
363   unsigned char *buffer = xmalloc (strlen (string)+1);
364   const char *s;
365   size_t n;
366
367   for (s=string, n=0; *s; s += 3)
368     {
369       if (hexdigitp (s) && hexdigitp (s+1) && (s[2]==':'||!s[2]))
370         {
371           buffer[n++] = xtoi_2 (s);
372           if (!s[2])
373             break;
374         }
375       else
376         {
377           memcpy (buffer, string, strlen (string));
378           *r_length = strlen (string);
379           return buffer;
380         }
381     }
382   *r_length = n;
383   return buffer;
384 }
385
386
387 static char *
388 my_read_line (int use_readline, char *prompt)
389 {
390   static char buf[256];
391
392 #ifdef HAVE_READLINE
393   if (use_readline)
394     {
395       char *line = readline (prompt);
396       if (line)
397         trim_spaces (line);
398       if (line && strlen (line) > 2 )
399         add_history (line);
400       return line;
401     }
402 #endif
403   /* Either we don't have readline or we are not running
404      interactively */
405 #ifndef HAVE_READLINE
406   printf ("%s", prompt );
407 #endif
408   fflush(stdout);
409   if (!fgets(buf, sizeof(buf), stdin))
410     return NULL;
411   if (!strlen(buf))
412     return NULL;
413   if (buf[strlen (buf)-1] == '\n')
414     buf[strlen (buf)-1] = 0;
415   trim_spaces (buf);
416   return buf;
417 }
418
419 /* Run a shell for interactive exploration of the card. */
420 static void
421 interactive_shell (int slot)
422 {
423   enum cmdids
424     {
425       cmdNOP = 0,
426       cmdQUIT, cmdHELP,
427       cmdSELECT,
428       cmdCHDIR,
429       cmdLS,
430       cmdAPP,
431       cmdREAD,
432       cmdREADREC,
433       cmdREADSHORTREC,
434       cmdDEBUG,
435       cmdVERIFY,
436       cmdCHANGEREF,
437       cmdREADPK,
438
439       cmdINVCMD
440     };
441   static struct 
442   {
443     const char *name;
444     enum cmdids id;
445     const char *desc;
446   } cmds[] = {
447     { "quit"   , cmdQUIT  , "quit this menu" },
448     { "q"      , cmdQUIT  , NULL   },
449     { "help"   , cmdHELP  , "show this help" },
450     { "?"      , cmdHELP  , NULL   },
451     { "debug"  , cmdDEBUG, "set debugging flags" },
452     { "select" , cmdSELECT, "select file (EF)" },
453     { "s"      , cmdSELECT, NULL },
454     { "chdir"  , cmdCHDIR, "change directory (select DF)"},
455     { "cd"     , cmdCHDIR,  NULL },
456     { "ls"     , cmdLS,    "list directory (some cards only)"},
457     { "app"    , cmdAPP,   "select application"},
458     { "read"   , cmdREAD,  "read binary" },
459     { "rb"     , cmdREAD,  NULL },
460     { "readrec", cmdREADREC,  "read record(s)" },
461     { "rr"     , cmdREADREC,  NULL },
462     { "rsr"    , cmdREADSHORTREC,  "readshortrec RECNO SHORT_EF" },
463     { "verify" , cmdVERIFY, "verify CHVNO PIN" },
464     { "ver"    , cmdVERIFY, NULL },
465     { "changeref", cmdCHANGEREF, "change reference data" },
466     { "readpk",    cmdREADPK,    "read a public key" },
467     { NULL, cmdINVCMD } 
468   };
469   enum cmdids cmd = cmdNOP;
470   int use_readline = isatty (fileno(stdin));
471   char *line;
472   gpg_error_t err = 0;
473   unsigned char *result = NULL;
474   size_t resultlen;
475
476 #ifdef HAVE_READLINE
477   if (use_readline)
478     using_history ();
479 #endif
480
481   for (;;)
482     {
483       int arg_number;
484       const char *arg_string = "";
485       const char *arg_next = "";
486       char *p;
487       int i;
488       
489       if (err)
490         printf ("command failed: %s\n", gpg_strerror (err));
491       err = 0;
492       xfree (result);
493       result = NULL;
494
495       printf ("\n");
496       do
497         {
498           line = my_read_line (use_readline, "cmd> ");
499         }
500       while ( line && *line == '#' );
501
502       arg_number = 0; 
503       if (!line || *line == CONTROL_D)
504         cmd = cmdQUIT; 
505       else if (!*line)
506         cmd = cmdNOP;
507       else {
508         if ((p=strchr (line,' ')))
509           {
510             char *endp;
511
512             *p++ = 0;
513             trim_spaces (line);
514             trim_spaces (p);
515             arg_number = strtol (p, &endp, 0);
516             arg_string = p;
517             if (endp != p)
518               {
519                 arg_next = endp;
520                 while ( spacep (arg_next) )
521                   arg_next++;
522               }
523           }
524
525         for (i=0; cmds[i].name; i++ )
526           if (!ascii_strcasecmp (line, cmds[i].name ))
527             break;
528         
529         cmd = cmds[i].id;
530       }
531       
532       switch (cmd)
533         {
534         case cmdHELP:
535           for (i=0; cmds[i].name; i++ )
536             if (cmds[i].desc)
537               printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
538           break;
539
540         case cmdQUIT:
541           goto leave;
542
543         case cmdNOP:
544           break;
545
546         case cmdDEBUG:
547           if (!*arg_string)
548             opt.debug = opt.debug? 0 : 2048;
549           else
550             opt.debug = arg_number;
551           break;
552
553         case cmdSELECT:
554           err = iso7816_select_file (slot, arg_number, 0, NULL, NULL);
555           break;
556
557         case cmdCHDIR:
558           err = iso7816_select_file (slot, arg_number, 1, NULL, NULL);
559           break;
560
561         case cmdLS:
562           err = iso7816_list_directory (slot, 1, &result, &resultlen);
563           if (!err || gpg_err_code (err) == GPG_ERR_ENOENT)
564             err = iso7816_list_directory (slot, 0, &result, &resultlen);
565           /* FIXME: Do something with RESULT. */
566           break;
567
568         case cmdAPP:
569           {
570             app_t app;
571
572             app = select_application (NULL, slot, *arg_string? arg_string:NULL);
573             if (app)
574               {
575                 char *sn;
576
577                 app_get_serial_and_stamp (app, &sn, NULL);
578                 log_info ("application `%s' ready; sn=%s\n",
579                           app->apptype?app->apptype:"?", sn? sn:"[none]");
580                 release_application (app);
581               }
582           }
583           break;
584
585         case cmdREAD:
586           err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
587           if (!err)
588             dump_or_store_buffer (arg_string, result, resultlen);
589           break;
590
591         case cmdREADREC:
592           if (*arg_string == '*' && (!arg_string[1] || arg_string[1] == ' '))
593             {
594               /* Fixme: Can't write to a file yet. */
595               for (i=1, err=0; !err; i++)
596                 {
597                   xfree (result); result = NULL;
598                   err = iso7816_read_record (slot, i, 1, 0,
599                                              &result, &resultlen);
600                   if (!err)
601                     dump_buffer (result, resultlen);
602                 }
603               if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
604                 err = 0;
605             }
606           else
607             {
608               err = iso7816_read_record (slot, arg_number, 1, 0,
609                                          &result, &resultlen);
610               if (!err)
611                 dump_or_store_buffer (arg_string, result, resultlen);
612             }
613           break;
614
615         case cmdREADSHORTREC:
616           {
617             int short_ef;
618
619             short_ef = strtol (arg_next, NULL, 0);
620             
621             if (short_ef < 1 || short_ef > 254)
622               printf ("error: short EF must be between 1 and 254\n");
623             else
624               {
625                 err = iso7816_read_record (slot, arg_number, 1, short_ef,
626                                            &result, &resultlen);
627                 if (!err)
628                   dump_or_store_buffer (arg_string, result, resultlen);
629               }
630           }
631           break;
632
633         case cmdVERIFY:
634           if (arg_number < 0 || arg_number > 255 || (arg_number & 127) > 31)
635             printf ("error: invalid CHVNO\n");
636           else 
637             {
638               unsigned char *pin;
639               size_t pinlen;
640
641               pin = pin_to_buffer (arg_next, &pinlen);
642               err = iso7816_verify (slot, arg_number, pin, pinlen);
643               xfree (pin);
644             }
645             break;
646
647         case cmdCHANGEREF:
648           {
649             const char *newpin = arg_next;
650             
651             while ( *newpin && !spacep (newpin) )
652               newpin++;
653             while ( spacep (newpin) )
654               newpin++;
655
656             if (arg_number < 0 || arg_number > 255 || (arg_number & 127) > 31)
657               printf ("error: invalid CHVNO\n");
658             else if (!*arg_next || !*newpin || newpin == arg_next)
659               printf ("usage: changeref CHVNO OLDPIN NEWPIN\n");
660             else
661               {
662                 char *oldpin = xstrdup (arg_next);
663                 unsigned char *oldpin_buf, *newpin_buf;
664                 size_t oldpin_len, newpin_len;
665
666                 for (p=oldpin; *p && !spacep (p); p++ )
667                   ;
668                 *p = 0;
669                 oldpin_buf = pin_to_buffer (oldpin, &oldpin_len);
670                 newpin_buf = pin_to_buffer (newpin, &newpin_len);
671
672                 err = iso7816_change_reference_data (slot, arg_number,
673                                                      oldpin_buf, oldpin_len,
674                                                      newpin_buf, newpin_len);
675
676                 xfree (newpin_buf);
677                 xfree (oldpin_buf);
678                 xfree (oldpin);
679               }
680           }
681           break;
682
683         case cmdREADPK:
684           if (arg_number < 1 || arg_number > 255)
685             printf ("usage: readpk CRTBYTE1\n");
686           else
687             {
688               unsigned char crt[2];
689             
690               crt[0] = arg_number;
691               crt[1] = 0;
692               err = iso7816_read_public_key(slot, crt, 2,
693                                             &result, &resultlen);
694               if (!err) 
695                 dump_or_store_buffer (arg_string, result, resultlen);
696             }
697             break;
698
699
700         case cmdINVCMD:
701         default:
702           printf ("\n");
703           printf ("Invalid command  (try \"help\")\n");
704           break;
705         } /* End command switch. */
706     } /* End of main menu loop. */
707
708  leave:
709   ;
710 }
711
712
713
714 /* Figure out whether the current card is a German Geldkarte and print
715    what we know about it. */
716 static int
717 dump_geldkarte (int slot)
718 {
719   unsigned char *r = NULL;
720   size_t rlen;
721   const char *t;
722
723   if (iso7816_read_record (slot, 1, 1, 0xbc, &r, &rlen))
724     return -1;
725   /* We require that the record is at least 24 bytes, the first byte
726      is 0x67 and the filler byte is correct. */
727   if (rlen < 24 || *r != 0x67 || r[22])
728     return -1;
729   
730   /* The short Bankleitzahl consists of 3 bytes at offset 1.  */
731   switch (r[1])
732     {
733     case 0x21: t = "Oeffentlich-rechtliche oder private Bank"; break;
734     case 0x22: t = "Privat- oder Geschäftsbank"; break;
735     case 0x25: t = "Sparkasse"; break;
736     case 0x26:
737     case 0x29: t = "Genossenschaftsbank"; break;
738     default: 
739       xfree (r);
740       return -1;  /* Probably not a Geldkarte. */
741     }
742
743   printf ("KBLZ .....: %02X-%02X%02X (%s)\n", r[1], r[2], r[3], t);
744   printf ("Card-No ..: %02X%02X%02X%02X%02X\n", r[4], r[5], r[6], r[7], r[8]);
745    
746 /*   Byte 10 enthält im linken Halbbyte eine Prüfziffer, die nach dem */
747 /*   Verfahren 'Luhn formula for computing modulus 10' über die Ziffern der */
748 /*   ersten 9 Byte berechnet ist. */
749   
750 /*   Das rechte Halbbyte wird zu 'D' gesetzt.  */
751   
752 /*   Für die Berechnung der Luhn-Prüfziffer sind die folgenden Schritte */
753 /*   durchzuführen: */
754   
755 /*   Schritt 1: Mit der rechtesten Ziffer beginnend ist einschließlich dieser */
756 /*   Ziffer jede übernächste Ziffer zu verdoppeln (mit 2 multiplizieren). */
757   
758 /*   Schritt 2: Die einzelnen Ziffern der Produkte aus Schritt 1 und die bei */
759 /*   diesen Multiplikationen unberührt gebliebenen Ziffern sind zu addieren. */
760   
761 /*   Schritt 3: Das Ergebnis der Addition aus Schritt 2 ist von dem auf die */
762 /*   nächst höhere Zahl mit der Einerstelle 0 aufgerundeten Ergebnis der */
763 /*   Addition aus Schritt 2 abzuziehen. Wenn das Ergebnis der Addition aus */
764 /*   Schritt 2 bereits eine Zahl mit der Einerstelle 0 ergibt (z.B. 30, 40, */
765 /*   usw.), ist die Prüfziffer 0. */
766   
767 /*   Beispiel:  Kartennummer ohne Prüfziffer: 992 839 871 */
768   
769 /*    9   9   2   8   3   9   8   7   1 */
770   
771 /*   x 2     x 2     x 2     x 2     x 2       Schritt 1 */
772   
773 /*   18       4       6      16       2 */
774   
775 /*   1+8 +9  +4  +8  +6  +9 +1+6 +7  +2 = 61   Schritt 2 */
776   
777 /*   70-61 = 9                                 Schritt 3 */
778   
779 /*   Prüfziffer zu 992 839 871 = 9 */
780
781
782   printf ("Expires at: %02X/%02X\n", r[11], r[10] );
783   printf ("Valid from: %02X.%02X.%02X\n", r[14], r[13], r[12]);
784   printf ("Country ..: %02X%02X\n", r[15], r[16]);
785   printf ("Currency .: %c%c%c\n", isascii (r[17])? r[17]:' ',
786           isascii (r[18])? r[18]:' ', isascii (r[19])? r[19]:' ');
787   printf ("Cur.-Mult : %s\n", 
788           r[20] == 0x01? "0.01":
789           r[20] == 0x02? "0.1":
790           r[20] == 0x04? "1":
791           r[20] == 0x08? "10":
792           r[20] == 0x10? "100":
793           r[20] == 0x20? "1000": "?");
794   printf ("ZKA ChipID: %02X\n", r[21]);
795   printf ("OS version: %02X\n", r[23]);
796
797   xfree (r);
798   return 0;
799
800
801
802
803 /* Try to figure out the type of teh card and dump its contents. */
804 static void
805 dump_other_cards (int slot)
806 {
807
808   if (!dump_geldkarte (slot))
809     return; 
810
811
812