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