* call-agent.c (agent_scd_getattr): Don't clear the passed info
[gnupg.git] / g10 / card-util.c
1 /* card-util.c - Utility functions for the OpenPGP card.
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 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <assert.h>
27
28 #if GNUPG_MAJOR_VERSION != 1
29 #include "gpg.h"
30 #endif
31 #include "util.h"
32 #include "i18n.h"
33 #include "ttyio.h"
34 #include "status.h"
35 #include "options.h"
36 #include "main.h"
37 #if GNUPG_MAJOR_VERSION == 1
38 #include "cardglue.h"
39 #else
40 #include "call-agent.h"
41 #endif
42
43 #define CONTROL_D ('D' - 'A' + 1)
44
45
46 /* Change the PIN of a an OpenPGP card.  This is an interactive
47    function. */
48 void
49 change_pin (int chvno)
50 {
51   struct agent_card_info_s info;
52   int rc;
53
54   rc = agent_learn (&info);
55   if (rc)
56     {
57       log_error (_("OpenPGP card not available: %s\n"),
58                   gpg_strerror (rc));
59       return;
60     }
61   
62   log_info (_("OpenPGP card no. %s detected\n"),
63               info.serialno? info.serialno : "[none]");
64
65   agent_release_card_info (&info);
66
67   if (opt.batch)
68     {
69       log_error (_("sorry, can't do this in batch mode\n"));
70       return;
71     }
72
73   for (;;)
74     {
75       char *answer;
76
77       tty_printf ("\n");
78       tty_printf ("1 - change PIN\n"
79                   "2 - unblock PIN\n"
80                   "3 - change Admin PIN\n"
81                   "Q - quit\n");
82       tty_printf ("\n");
83
84       answer = cpr_get("cardutil.change_pin.menu",_("Your selection? "));
85       cpr_kill_prompt();
86       if (strlen (answer) != 1)
87         continue;
88
89       rc = 0;
90       if (*answer == '1')
91         {
92           rc = agent_scd_change_pin (1);
93           if (rc)
94             tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
95           else
96             tty_printf ("PIN changed.\n");
97         }
98       else if (*answer == '2')
99         {
100           rc = agent_scd_change_pin (101);
101           if (rc)
102             tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc));
103           else
104             tty_printf ("PIN unblocked and new PIN set.\n");
105         }
106       else if (*answer == '3')
107         {
108           rc = agent_scd_change_pin (3);
109           if (rc)
110             tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
111           else
112             tty_printf ("PIN changed.\n");
113         }
114       else if (*answer == 'q' || *answer == 'Q')
115         {
116           break;
117         }
118     }
119 }
120
121 static const char *
122 get_manufacturer (unsigned int no)
123 {
124   /* Note:  Make sure that there is no colon or linefeed in the string. */
125   switch (no)
126     {
127     case 0:
128     case 0xffff: return "test card";
129     case 0x0001: return "PPC Card Systems";
130     default: return "unknown";
131     }
132 }
133
134
135 static void
136 print_sha1_fpr (FILE *fp, const unsigned char *fpr)
137 {
138   int i;
139
140   if (fpr)
141     {
142       for (i=0; i < 20 ; i+=2, fpr += 2 )
143         {
144           if (i == 10 )
145             tty_fprintf (fp, " ");
146           tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
147         }
148     }
149   else
150     tty_fprintf (fp, " [none]");
151   tty_fprintf (fp, "\n");
152 }
153
154
155 static void
156 print_sha1_fpr_colon (FILE *fp, const unsigned char *fpr)
157 {
158   int i;
159
160   if (fpr)
161     {
162       for (i=0; i < 20 ; i++, fpr++)
163         fprintf (fp, "%02X", *fpr);
164     }
165   putc (':', fp);
166 }
167
168
169 static void
170 print_name (FILE *fp, const char *text, const char *name)
171 {
172   tty_fprintf (fp, text);
173
174
175   /* FIXME: tty_printf_utf8_string2 eats everything after and
176      including an @ - e.g. when printing an url. */
177   if (name && *name)
178     {
179       if (fp)
180         print_utf8_string2 (fp, name, strlen (name), '\n');
181       else
182         tty_print_utf8_string2 (name, strlen (name), 0);
183     }
184   else
185     tty_fprintf (fp, _("[not set]"));
186   tty_fprintf (fp, "\n");
187 }
188
189 static void
190 print_isoname (FILE *fp, const char *text, const char *tag, const char *name)
191 {
192   if (opt.with_colons)
193     fprintf (fp, "%s:", tag);
194   else
195     tty_fprintf (fp, text);
196
197   if (name && *name)
198     {
199       char *p, *given, *buf = xstrdup (name);
200
201       given = strstr (buf, "<<");
202       for (p=buf; *p; p++)
203         if (*p == '<')
204           *p = ' ';
205       if (given && given[2])
206         {
207           *given = 0;
208           given += 2;
209           if (opt.with_colons)
210             print_string (fp, given, strlen (given), ':');
211           else if (fp)
212             print_utf8_string2 (fp, given, strlen (given), '\n');
213           else
214             tty_print_utf8_string2 (given, strlen (given), 0);
215
216           if (opt.with_colons)
217             putc (':', fp);
218           else if (*buf)
219             tty_fprintf (fp, " ");
220         }
221
222       if (opt.with_colons)
223         print_string (fp, buf, strlen (buf), ':');
224       else if (fp)
225         print_utf8_string2 (fp, buf, strlen (buf), '\n');
226       else
227         tty_print_utf8_string2 (buf, strlen (buf), 0);
228       xfree (buf);
229     }
230   else
231     {
232       if (opt.with_colons)
233         putc (':', fp);
234       else
235         tty_fprintf (fp, _("[not set]"));
236     }
237
238   if (opt.with_colons)
239     fputs (":\n", fp);
240   else
241     tty_fprintf (fp, "\n");
242 }
243
244 /* Return true if the SHA1 fingerprint FPR consists only of zeroes. */
245 static int
246 fpr_is_zero (const char *fpr)
247 {
248   int i;
249
250   for (i=0; i < 20 && !fpr[i]; i++)
251     ;
252   return (i == 20);
253 }
254
255
256 /* Print all available information about the current card. */
257 void
258 card_status (FILE *fp)
259 {
260   struct agent_card_info_s info;
261   PKT_public_key *pk = xcalloc (1, sizeof *pk);
262   int rc;
263   unsigned int uval;
264
265   rc = agent_learn (&info);
266   if (rc)
267     {
268       if (opt.with_colons)
269         fputs ("AID:::\n", fp);
270       log_error (_("OpenPGP card not available: %s\n"),
271                   gpg_strerror (rc));
272       xfree (pk);
273       return;
274     }
275
276   if (opt.with_colons)
277     fprintf (fp, "AID:%s:", info.serialno? info.serialno : "");
278   else
279     tty_fprintf (fp, "Application ID ...: %s\n",
280                  info.serialno? info.serialno : "[none]");
281   if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) 
282       || strlen (info.serialno) != 32 )
283     {
284       if (opt.with_colons)
285         fputs ("unknown:\n", fp);
286       log_info ("not an OpenPGP card\n");
287       agent_release_card_info (&info);
288       xfree (pk);
289       return;
290     }
291
292   if (opt.with_colons)
293     fputs ("openpgp-card:\n", fp);
294
295
296   if (opt.with_colons)
297     {
298       fprintf (fp, "version:%.4s:\n", info.serialno+12);
299       uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18);
300       fprintf (fp, "vendor:%04x:%s:\n", uval, get_manufacturer (uval));
301       fprintf (fp, "serial:%.8s:\n", info.serialno+20);
302       
303       print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
304
305       fputs ("lang:", fp);
306       if (info.disp_lang)
307         print_string (fp, info.disp_lang, strlen (info.disp_lang), ':');
308       fputs (":\n", fp);
309
310       fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm':
311                                  info.disp_sex == 2? 'f' : 'u'));
312
313       fputs ("url:", fp);
314       if (info.pubkey_url)
315         print_string (fp, info.pubkey_url, strlen (info.pubkey_url), ':');
316       fputs (":\n", fp);
317
318       fputs ("login:", fp);
319       if (info.login_data)
320         print_string (fp, info.login_data, strlen (info.login_data), ':');
321       fputs (":\n", fp);
322
323       fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached);
324       fprintf (fp, "maxpinlen:%d:%d:%d:\n",
325                    info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
326       fprintf (fp, "pinretry:%d:%d:%d:\n",
327                    info.chvretry[0], info.chvretry[1], info.chvretry[2]);
328       fprintf (fp, "sigcount:%lu:::\n", info.sig_counter);
329
330       fputs ("fpr:", fp);
331       print_sha1_fpr_colon (fp, info.fpr1valid? info.fpr1:NULL);
332       print_sha1_fpr_colon (fp, info.fpr2valid? info.fpr2:NULL);
333       print_sha1_fpr_colon (fp, info.fpr3valid? info.fpr3:NULL);
334       putc ('\n', fp);
335
336     }
337   else 
338     {
339       tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n",
340                    info.serialno[12] == '0'?"":info.serialno+12,
341                    info.serialno[13],
342                    info.serialno[14] == '0'?"":info.serialno+14,
343                    info.serialno[15]);
344       tty_fprintf (fp, "Manufacturer .....: %s\n", 
345                    get_manufacturer (xtoi_2(info.serialno+16)*256
346                                      + xtoi_2 (info.serialno+18)));
347       tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20);
348       
349       print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
350       print_name (fp, "Language prefs ...: ", info.disp_lang);
351       tty_fprintf (fp,    "Sex ..............: %s\n",
352                    info.disp_sex == 1? _("male"):
353                    info.disp_sex == 2? _("female") : _("unspecified"));
354       print_name (fp, "URL of public key : ", info.pubkey_url);
355       print_name (fp, "Login data .......: ", info.login_data);
356       tty_fprintf (fp,    "Signature PIN ....: %s\n",
357                    info.chv1_cached? _("not forced"): _("forced"));
358       tty_fprintf (fp,    "Max. PIN lengths .: %d %d %d\n",
359                    info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
360       tty_fprintf (fp,    "PIN retry counter : %d %d %d\n",
361                    info.chvretry[0], info.chvretry[1], info.chvretry[2]);
362       tty_fprintf (fp,    "Signature counter : %lu\n", info.sig_counter);
363       tty_fprintf (fp, "Signature key ....:");
364       print_sha1_fpr (fp, info.fpr1valid? info.fpr1:NULL);
365       tty_fprintf (fp, "Encryption key....:");
366       print_sha1_fpr (fp, info.fpr2valid? info.fpr2:NULL);
367       tty_fprintf (fp, "Authentication key:");
368       print_sha1_fpr (fp, info.fpr3valid? info.fpr3:NULL);
369       tty_fprintf (fp, "General key info..: "); 
370       if (info.fpr1valid && !get_pubkey_byfprint (pk, info.fpr1, 20))
371         print_pubkey_info (fp, pk);
372       else
373         tty_fprintf (fp, "[none]\n");
374     }
375       
376   free_public_key (pk);
377   agent_release_card_info (&info);
378 }
379
380
381 static char *
382 get_one_name (const char *prompt1, const char *prompt2)
383 {
384   char *name;
385   int i;
386
387   for (;;)
388     {
389       name = cpr_get (prompt1, prompt2);
390       if (!name)
391         return NULL;
392       trim_spaces (name);
393       cpr_kill_prompt ();
394       for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
395         ;
396
397       /* The name must be in Latin-1 and not UTF-8 - lacking the code
398          to ensure this we restrict it to ASCII. */
399       if (name[i])
400         tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
401       else if (strchr (name, '<'))
402         tty_printf (_("Error: The \"<\" character may not be used.\n"));
403       else if (strstr (name, "  "))
404         tty_printf (_("Error: Double spaces are not allowed.\n"));    
405       else
406         return name;
407       xfree (name);
408     }
409 }
410
411
412
413 static int
414 change_name (void)
415 {
416   char *surname = NULL, *givenname = NULL;
417   char *isoname, *p;
418   int rc;
419
420   surname = get_one_name ("keygen.smartcard.surname",
421                                     _("Cardholder's surname: "));
422   givenname = get_one_name ("keygen.smartcard.givenname",
423                                        _("Cardholder's given name: "));
424   if (!surname || !givenname || (!*surname && !*givenname))
425     {
426       xfree (surname);
427       xfree (givenname);
428       return -1; /*canceled*/
429     }
430
431   isoname = xmalloc ( strlen (surname) + 2 + strlen (givenname) + 1);
432   strcpy (stpcpy (stpcpy (isoname, surname), "<<"), givenname);
433   xfree (surname);
434   xfree (givenname);
435   for (p=isoname; *p; p++)
436     if (*p == ' ')
437       *p = '<';
438
439   log_debug ("setting Name to `%s'\n", isoname);
440   rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname) );
441   if (rc)
442     log_error ("error setting Name: %s\n", gpg_strerror (rc));
443
444   xfree (isoname);
445   return rc;
446 }
447
448
449 static int
450 change_url (void)
451 {
452   char *url;
453   int rc;
454
455   url = cpr_get ("cardedit.change_url", _("URL to retrieve public key: "));
456   if (!url)
457     return -1;
458   trim_spaces (url);
459   cpr_kill_prompt ();
460
461   rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url) );
462   if (rc)
463     log_error ("error setting URL: %s\n", gpg_strerror (rc));
464   xfree (url);
465   return rc;
466 }
467
468 static int
469 change_login (void)
470 {
471   char *data;
472   int rc;
473
474   data = cpr_get ("cardedit.change_login",
475                   _("Login data (account name): "));
476   if (!data)
477     return -1;
478   trim_spaces (data);
479   cpr_kill_prompt ();
480
481   rc = agent_scd_setattr ("LOGIN-DATA", data, strlen (data) );
482   if (rc)
483     log_error ("error setting login data: %s\n", gpg_strerror (rc));
484   xfree (data);
485   return rc;
486 }
487
488 static int
489 change_lang (void)
490 {
491   char *data, *p;
492   int rc;
493
494   data = cpr_get ("cardedit.change_lang",
495                   _("Language preferences: "));
496   if (!data)
497     return -1;
498   trim_spaces (data);
499   cpr_kill_prompt ();
500
501   if (strlen (data) > 8 || (strlen (data) & 1))
502     {
503       tty_printf (_("Error: invalid length of preference string.\n"));
504       xfree (data);
505       return -1;
506     }
507
508   for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
509     ;
510   if (*p)
511     {
512       tty_printf (_("Error: invalid characters in preference string.\n"));
513       xfree (data);
514       return -1;
515     }
516
517   rc = agent_scd_setattr ("DISP-LANG", data, strlen (data) );
518   if (rc)
519     log_error ("error setting lang: %s\n", gpg_strerror (rc));
520   xfree (data);
521   return rc;
522 }
523
524
525 static int
526 change_sex (void)
527 {
528   char *data;
529   const char *str;
530   int rc;
531
532   data = cpr_get ("cardedit.change_sex",
533                   _("Sex ((M)ale, (F)emale or space): "));
534   if (!data)
535     return -1;
536   trim_spaces (data);
537   cpr_kill_prompt ();
538
539   if (!*data)
540     str = "9";
541   else if ((*data == 'M' || *data == 'm') && !data[1])
542     str = "1";
543   else if ((*data == 'F' || *data == 'f') && !data[1])
544     str = "2";
545   else 
546     {
547       tty_printf (_("Error: invalid response.\n"));
548       xfree (data);
549       return -1;
550     }
551      
552   rc = agent_scd_setattr ("DISP-SEX", str, 1 );
553   if (rc)
554     log_error ("error setting sex: %s\n", gpg_strerror (rc));
555   xfree (data);
556   return rc;
557 }
558
559
560 static void
561 toggle_forcesig (void)
562 {
563   struct agent_card_info_s info;
564   int rc;
565   int newstate;
566
567   memset (&info, 0, sizeof info);
568   rc = agent_scd_getattr ("CHV-STATUS", &info);
569   if (rc)
570     {
571       log_error ("error getting current status: %s\n", gpg_strerror (rc));
572       return;
573     }
574   newstate = !info.chv1_cached;
575   agent_release_card_info (&info);
576
577   rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
578   if (rc)
579     log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc));
580 }
581
582
583 static void
584 generate_card_keys (void)
585 {
586   struct agent_card_info_s info;
587   int rc;
588   int forced_chv1;
589
590   memset (&info, 0, sizeof info);
591   rc = agent_scd_getattr ("KEY-FPR", &info);
592   if (!rc)
593     rc = agent_scd_getattr ("SERIALNO", &info);
594   if (!rc)
595     rc = agent_scd_getattr ("CHV-STATUS", &info);
596   if (!rc)
597     rc = agent_scd_getattr ("DISP-NAME", &info);
598   if (rc)
599     {
600       log_error ("error getting current key info: %s\n", gpg_strerror (rc));
601       return;
602     }
603   if ( (info.fpr1valid && !fpr_is_zero (info.fpr1))
604        || (info.fpr2valid && !fpr_is_zero (info.fpr2))
605        || (info.fpr3valid && !fpr_is_zero (info.fpr3)))
606     {
607       tty_printf ("\n");
608       log_info ("NOTE: keys are already stored on the card!\n");
609       tty_printf ("\n");
610       if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_keys",
611                                   _("Replace existing keys? ")))
612         {
613           agent_release_card_info (&info);
614           return;
615         }
616     }
617   else if (!info.disp_name || !*info.disp_name)
618     {
619       tty_printf ("\n");
620       tty_printf (_("Please note that the factory settings of the PINs are\n"
621                     "   PIN = \"%s\"     Admin PIN = \"%s\"\n"
622                     "You should change them using the command --change-pin\n"),
623                   "123456", "12345678");
624       tty_printf ("\n");
625     }
626
627   forced_chv1 = !info.chv1_cached;
628   if (forced_chv1)
629     { /* Switch of the forced mode so that during key generation we
630          don't get bothered with PIN queries for each
631          self-signature. */
632       rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1);
633       if (rc)
634         {
635           log_error ("error clearing forced signature PIN flag: %s\n",
636                      gpg_strerror (rc));
637           return;
638         }
639     }
640   generate_keypair (NULL, info.serialno);
641   agent_release_card_info (&info);
642   if (forced_chv1)
643     { /* Switch back to forced state. */
644       rc = agent_scd_setattr ("CHV-STATUS-1", "", 1);
645       if (rc)
646         {
647           log_error ("error setting forced signature PIN flag: %s\n",
648                      gpg_strerror (rc));
649           return;
650         }
651     }
652 }
653
654 /* Menu to edit all user changeable values on an OpenPGP card.  Only
655    Key creation is not handled here. */
656 void
657 card_edit (STRLIST commands)
658 {
659   enum cmdids {
660     cmdNOP = 0,
661     cmdQUIT, cmdHELP, cmdLIST, cmdDEBUG,
662     cmdNAME, cmdURL, cmdLOGIN, cmdLANG, cmdSEX,
663     cmdFORCESIG, cmdGENERATE,
664     cmdINVCMD
665   };
666
667   static struct {
668     const char *name;
669     enum cmdids id;
670     const char *desc;
671   } cmds[] = {
672     { N_("quit")  , cmdQUIT  , N_("quit this menu") },
673     { N_("q")     , cmdQUIT  , NULL   },
674     { N_("help")  , cmdHELP  , N_("show this help") },
675     {    "?"      , cmdHELP  , NULL   },
676     { N_("list")  , cmdLIST  , N_("list all available data") },
677     { N_("l")     , cmdLIST  , NULL   },
678     { N_("debug") , cmdDEBUG , NULL },
679     { N_("name")  , cmdNAME  , N_("change card holder's name") },
680     { N_("url")   , cmdURL   , N_("change URL to retrieve key") },
681     { N_("login") , cmdLOGIN , N_("change the login name") },
682     { N_("lang")  , cmdLANG  , N_("change the language preferences") },
683     { N_("sex")   , cmdSEX   , N_("change card holder's sex") },
684     { N_("forcesig"), cmdFORCESIG, N_("toggle the signature force PIN flag") },
685     { N_("generate"), cmdGENERATE, N_("generate new keys") },
686     { NULL, cmdINVCMD } 
687   };
688  
689   enum cmdids cmd = cmdNOP;
690   int have_commands = !!commands;
691   int redisplay = 1;
692   char *answer = NULL;
693
694   if (opt.command_fd != -1)
695     ;
696   else if (opt.batch && !have_commands)
697     {
698       log_error(_("can't do that in batchmode\n"));
699       goto leave;
700     }
701
702   for (;;)
703     {
704       int arg_number;
705       const char *arg_string = "";
706       char *p;
707       int i;
708
709       tty_printf("\n");
710       if (redisplay )
711         {
712           if (opt.with_colons)
713             {
714               card_status (stdout);
715               fflush (stdout);
716             }
717           else
718             {
719               card_status (NULL);
720               tty_printf("\n");
721             }
722           redisplay = 0;
723         }
724
725       do
726         {
727           xfree (answer);
728           if (have_commands)
729             {
730               if (commands)
731                 {
732                   answer = xstrdup (commands->d);
733                   commands = commands->next;
734                 }
735               else if (opt.batch)
736                 {
737                   answer = xstrdup ("quit");
738                 }
739               else
740                 have_commands = 0;
741             }
742
743             if (!have_commands)
744               {
745                 answer = cpr_get_no_help("cardedit.prompt", _("Command> "));
746                 cpr_kill_prompt();
747             }
748             trim_spaces(answer);
749         }
750       while( *answer == '#' );
751
752       arg_number = 0; /* Yes, here is the init which egcc complains about */
753       if (!*answer)
754         cmd = cmdLIST; /* Default to the list command */
755       else if (*answer == CONTROL_D)
756         cmd = cmdQUIT;
757       else {
758         if ((p=strchr (answer,' ')))
759           {
760             *p++ = 0;
761             trim_spaces (answer);
762             trim_spaces (p);
763             arg_number = atoi(p);
764             arg_string = p;
765           }
766
767         for (i=0; cmds[i].name; i++ )
768           if (!ascii_strcasecmp (answer, cmds[i].name ))
769             break;
770
771         cmd = cmds[i].id;
772       }
773
774       switch (cmd)
775         {
776         case cmdHELP:
777           for (i=0; cmds[i].name; i++ )
778             if (cmds[i].desc)
779               tty_printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
780           break;
781
782         case cmdLIST:
783           redisplay = 1;
784           break;
785
786         case cmdNAME:
787           change_name ();
788           break;
789
790         case cmdURL:
791           change_url ();
792           break;
793
794         case cmdLOGIN:
795           change_login ();
796           break;
797
798         case cmdLANG:
799           change_lang ();
800           break;
801
802         case cmdSEX:
803           change_sex ();
804           break;
805
806         case cmdFORCESIG:
807           toggle_forcesig ();
808           break;
809
810         case cmdGENERATE:
811           generate_card_keys ();
812           break;
813
814         case cmdQUIT:
815           goto leave;
816
817         case cmdNOP:
818           break;
819
820         case cmdINVCMD:
821         default:
822           tty_printf ("\n");
823           tty_printf (_("Invalid command  (try \"help\")\n"));
824           break;
825         } /* End command switch. */
826     } /* End of main menu loop. */
827
828  leave:
829   xfree (answer);
830 }
831