5526131b61e5fdb32f9338ba63162ad19c49858b
[pinentry.git] / pinentry / pinentry.c
1 /* pinentry.c - The PIN entry support library
2    Copyright (C) 2002, 2003 g10 Code GmbH
3    
4    This file is part of PINENTRY.
5    
6    PINENTRY is free software; you can redistribute it and/or modify it
7    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    PINENTRY is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    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
19    02111-1307, USA  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <getopt.h>
29 #include <unistd.h>
30 #include <locale.h>
31 #include <iconv.h>
32 #include <langinfo.h>
33 #include <limits.h>
34
35 #include "assuan.h"
36 #include "memory.h"
37 #include "secmem-util.h"
38 #include "pinentry.h"
39
40
41 /* Keep the name of our program here. */
42 static char this_pgmname[50]; 
43
44
45 struct pinentry pinentry =
46   {
47     NULL,       /* Description.  */
48     NULL,       /* Error.  */
49     NULL,       /* Prompt.  */
50     NULL,       /* Ok button.  */
51     NULL,       /* Cancel button.  */
52     NULL,       /* PIN.  */
53     2048,       /* PIN length.  */
54     0,          /* Display.  */
55     0,          /* TTY name.  */
56     0,          /* TTY type.  */
57     0,          /* TTY LC_CTYPE.  */
58     0,          /* TTY LC_MESSAGES.  */
59     0,          /* Debug mode.  */
60     0,          /* Enhanced mode.  */
61     1,          /* Global grab.  */
62     0,          /* Parent Window ID.  */
63     0,          /* Result.  */
64     0           /* Locale error flag. */
65   };
66
67 \f
68 char *
69 pinentry_utf8_to_local (char *lc_ctype, char *text)
70 {
71   iconv_t cd;
72   char *input = text;
73   size_t input_len = strlen (text) + 1;
74   char *output;
75   size_t output_len;
76   char *output_buf;
77   size_t processed;
78
79   /* If no locale setting could be determined, simply copy the
80      string.  */
81   if (!lc_ctype)
82     {
83       fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
84                this_pgmname);
85       return strdup (text);
86     }
87
88   /* This is overkill, but simplifies the iconv invocation greatly.  */
89   output_len = input_len * MB_LEN_MAX;
90   output_buf = output = malloc (output_len);
91   if (!output)
92     return NULL;
93
94   cd = iconv_open (lc_ctype, "UTF-8");
95   if (cd == (iconv_t) -1)
96     {
97       fprintf (stderr, "%s: can't convert from UTF-8 to local code set: %s\n",
98                this_pgmname, strerror (errno));
99       free (output_buf);
100       return NULL;
101     }
102   processed = iconv (cd, &input, &input_len, &output, &output_len);
103   iconv_close (cd);
104   if (processed == (size_t) -1 || input_len)
105     {
106       fprintf (stderr, "%s: error converting from UTF-8 to local code set: %s\n"
107                , this_pgmname, strerror (errno));
108       free (output_buf);
109       return NULL;
110     }
111   return output_buf;
112 }
113
114 /* Convert TEXT which is encoded according to LC_CTYPE to UTF-8.  With
115    SECURE set to true, use secure memory for the returned buffer.
116    Return NULL on error. */
117 char *
118 pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure)
119 {
120   char *old_ctype;
121   char *source_encoding;
122   iconv_t cd;
123   char *input = text;
124   size_t input_len = strlen (text) + 1;
125   char *output;
126   size_t output_len;
127   char *output_buf;
128   size_t processed;
129
130   /* If no locale setting could be determined, simply copy the
131      string.  */
132   if (!lc_ctype)
133     {
134       fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
135                this_pgmname);
136       output_buf = secure? secmem_malloc (input_len) : malloc (input_len);
137       if (output_buf)
138         strcpy (output_buf, input);
139       return output_buf;
140     }
141
142   old_ctype = strdup (setlocale (LC_CTYPE, NULL));
143   if (!old_ctype)
144     return NULL;
145   setlocale (LC_CTYPE, lc_ctype);
146   source_encoding = nl_langinfo (CODESET);
147   setlocale (LC_CTYPE, old_ctype);
148   free (old_ctype);
149
150   /* This is overkill, but simplifies the iconv invocation greatly.  */
151   output_len = input_len * MB_LEN_MAX;
152   output_buf = output = secure? secmem_malloc (output_len):malloc (output_len);
153   if (!output)
154     return NULL;
155
156   cd = iconv_open ("UTF-8", source_encoding);
157   if (cd == (iconv_t) -1)
158     {
159       fprintf (stderr, "%s: can't convert from %s to UTF-8: %s\n",
160                this_pgmname, source_encoding? source_encoding : "?",
161                strerror (errno));
162       if (secure)
163         secmem_free (output_buf);
164       else
165         free (output_buf);
166       return NULL;
167     }
168   processed = iconv (cd, &input, &input_len, &output, &output_len);
169   iconv_close (cd);
170   if (processed == (size_t) -1 || input_len)
171     {
172       fprintf (stderr, "%s: error converting from %s to UTF-8: %s\n",
173                this_pgmname, source_encoding? source_encoding : "?",
174                strerror (errno));
175       if (secure)
176         secmem_free (output_buf);
177       else
178         free (output_buf);
179       return NULL;
180     }
181   return output_buf;
182 }
183
184 /* Try to make room for at least LEN bytes in the pinentry.  Returns
185    new buffer on success and 0 on failure or when the old buffer is
186    sufficient.  */
187 char *
188 pinentry_setbufferlen (pinentry_t pin, int len)
189 {
190   char *newp;
191   if (len < pinentry.pin_len)
192     return NULL;
193   newp = secmem_realloc (pin->pin, 2 * pin->pin_len);
194   if (newp)
195     {
196       pin->pin = newp;
197       pin->pin_len *= 2;
198     }
199   else
200     {
201       secmem_free (pin->pin);
202       pin->pin = 0;
203       pin->pin_len = 0;
204     }
205   return newp;
206 }
207
208
209 /* Initialize the secure memory subsystem, drop privileges and return.
210    Must be called early. */
211 void
212 pinentry_init (const char *pgmname)
213 {
214   /* Store away our name. */
215   if (strlen (pgmname) > sizeof this_pgmname - 2)
216     abort ();
217   strcpy (this_pgmname, pgmname);
218
219   /* Initialize secure memory.  1 is too small, so the default size
220      will be used.  */
221   secmem_init (1);
222   secmem_set_flags (SECMEM_WARN);
223   drop_privs ();
224
225   if (atexit (secmem_term))
226     /* FIXME: Could not register at-exit function, bail out.  */
227     ;
228
229   assuan_set_malloc_hooks (secmem_malloc, secmem_realloc, secmem_free);
230 }
231
232 /* Simple test to check whether DISPLAY is set or the option --display
233    was given.  Used to decide whether the GUI or curses should be
234    initialized.  */
235 int
236 pinentry_have_display (int argc, char **argv)
237 {
238   if (getenv ("DISPLAY"))
239     return 1;
240   for (; argc; argc--, argv++)
241     if (!strcmp (*argv, "--display"))
242       return 1;
243   return 0;
244 }
245
246
247 \f
248 static void 
249 usage (void)
250 {
251   fprintf (stderr, "Usage: %s [OPTION]...\n\
252 Ask securely for a secret and print it to stdout.\n\
253 \n\
254       --display DISPLAY Set the X display\n\
255       --ttyname PATH    Set the tty terminal node name\n\
256       --ttytype NAME    Set the tty terminal type\n\
257       --lc-ctype        Set the tty LC_CTYPE value\n\
258       --lc-messages     Set the tty LC_MESSAGES value\n\
259   -e, --enhanced        Ask for timeout and insurance, too\n\
260   -g, --no-global-grab  Grab keyboard only while window is focused\n\
261       --parent-wid      Parent window ID (for positioning)\n\
262   -d, --debug           Turn on debugging output\n\
263       --help            Display this help and exit\n\
264       --version         Output version information and exit\n", this_pgmname);
265 }
266
267
268 /* Parse the command line options.  Returns 1 if user should print
269    version and exit.  Can exit the program if only help output is
270    requested.  */
271 int
272 pinentry_parse_opts (int argc, char *argv[])
273 {
274   int opt;
275   int opt_help = 0;
276   int opt_version = 0;
277   struct option opts[] =
278     {{ "debug", no_argument, &pinentry.debug, 1 },
279      { "display", required_argument, 0, 'D' },
280      { "ttyname", required_argument, 0, 'T' },
281      { "ttytype", required_argument, 0, 'N' },
282      { "lc-ctype", required_argument, 0, 'C' },
283      { "lc-messages", required_argument, 0, 'M' },
284      { "enhanced", no_argument, &pinentry.enhanced, 1 },
285      { "no-global-grab", no_argument, &pinentry.grab, 0 },
286      { "parent-wid", required_argument, 0, 'W' },
287      { "help", no_argument, &opt_help, 1 },
288      { "version", no_argument, &opt_version, 1 },
289      { NULL, 0, NULL, 0 }};
290   
291   while ((opt = getopt_long (argc, argv, "deg", opts, NULL)) != -1)
292     {
293       switch (opt)
294         {
295         case 0:
296         case '?':
297           break;
298         case 'D':
299           pinentry.display = strdup (optarg);
300           if (!pinentry.display)
301             {
302               fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
303               exit (EXIT_FAILURE);
304             }
305           break;
306         case 'T':
307           pinentry.ttyname = strdup (optarg);
308           if (!pinentry.ttyname)
309             {
310               fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
311               exit (EXIT_FAILURE);
312             }
313           break;
314         case 'N':
315           pinentry.ttytype = strdup (optarg);
316           if (!pinentry.ttytype)
317             {
318               fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
319               exit (EXIT_FAILURE);
320             }
321           break;
322         case 'C':
323           pinentry.lc_ctype = strdup (optarg);
324           if (!pinentry.lc_ctype)
325             {
326               fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
327               exit (EXIT_FAILURE);
328             }
329           break;
330         case 'M':
331           pinentry.lc_messages = strdup (optarg);
332           if (!pinentry.lc_messages)
333             {
334               fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
335               exit (EXIT_FAILURE);
336             }
337           break;
338         case 'W':
339           pinentry.parent_wid = atoi (optarg);
340           /* FIXME: Add some error handling.  Use strtol.  */
341           break;
342         default:
343           /* XXX Should never happen.  */
344           break;
345         }
346     }
347   if (opt_version) 
348     return 1;
349   if (opt_help) 
350     {
351       usage ();
352       exit (EXIT_SUCCESS);
353     }
354   return 0;
355 }
356
357 \f
358 static int
359 option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
360 {
361   if (!strcmp (key, "no-grab") && !*value)
362     pinentry.grab = 0;
363   else if (!strcmp (key, "grab") && !*value)
364     pinentry.grab = 1;
365   else if (!strcmp (key, "debug-wait"))
366     {
367       fprintf (stderr, "%s: waiting for debugger - my pid is %u ...\n",
368                this_pgmname, (unsigned int) getpid());
369       sleep (*value?atoi (value):5);
370       fprintf (stderr, "%s: ... okay\n", this_pgmname);
371     }
372   else if (!strcmp (key, "display"))
373     {
374       if (pinentry.display)
375         free (pinentry.display);
376       pinentry.display = strdup (value);
377       if (!pinentry.display)
378         return ASSUAN_Out_Of_Core;
379     }
380   else if (!strcmp (key, "ttyname"))
381     {
382       if (pinentry.ttyname)
383         free (pinentry.ttyname);
384       pinentry.ttyname = strdup (value);
385       if (!pinentry.ttyname)
386         return ASSUAN_Out_Of_Core;
387     }
388   else if (!strcmp (key, "ttytype"))
389     {
390       if (pinentry.ttytype)
391         free (pinentry.ttytype);
392       pinentry.ttytype = strdup (value);
393       if (!pinentry.ttytype)
394         return ASSUAN_Out_Of_Core;
395     }
396   else if (!strcmp (key, "lc-ctype"))
397     {
398       if (pinentry.lc_ctype)
399         free (pinentry.lc_ctype);
400       pinentry.lc_ctype = strdup (value);
401       if (!pinentry.lc_ctype)
402         return ASSUAN_Out_Of_Core;
403     }
404   else if (!strcmp (key, "lc-messages"))
405     {
406       if (pinentry.lc_messages)
407         free (pinentry.lc_messages);
408       pinentry.lc_messages = strdup (value);
409       if (!pinentry.lc_messages)
410         return ASSUAN_Out_Of_Core;
411     }
412   else if (!strcmp (key, "parent-wid"))
413     {
414       pinentry.parent_wid = atoi (value);
415       /* FIXME: Use strtol and add some error handling.  */
416     }
417   else
418     return ASSUAN_Invalid_Option;
419   return 0;
420 }
421
422
423 /* note, that it is sufficient to allocate the target string D as
424    long as the source string S, i.e.: strlen(s)+1; */
425 static void
426 strcpy_escaped (char *d, const unsigned char *s)
427 {
428   while (*s)
429     {
430       if (*s == '%' && s[1] && s[2])
431         { 
432           s++;
433           *d++ = xtoi_2 ( s);
434           s += 2;
435         }
436       else
437         *d++ = *s++;
438     }
439   *d = 0; 
440 }
441
442
443 static int
444 cmd_setdesc (ASSUAN_CONTEXT ctx, char *line)
445 {
446   char *newd;
447   newd = malloc (strlen (line) + 1);
448
449   if (!newd)
450     return ASSUAN_Out_Of_Core;
451
452   strcpy_escaped (newd, line);
453   if (pinentry.description)
454     free (pinentry.description);
455   pinentry.description = newd;
456   return 0;
457 }
458
459
460 static int
461 cmd_setprompt (ASSUAN_CONTEXT ctx, char *line)
462 {
463   char *newp;
464   newp = malloc (strlen (line) + 1);
465
466   if (!newp)
467     return ASSUAN_Out_Of_Core;
468
469   strcpy_escaped (newp, line);
470   if (pinentry.prompt)
471     free (pinentry.prompt);
472   pinentry.prompt = newp;
473   return 0;
474 }
475
476
477 static int
478 cmd_seterror (ASSUAN_CONTEXT ctx, char *line)
479 {
480   char *newe;
481   newe = malloc (strlen (line) + 1);
482
483   if (!newe)
484     return ASSUAN_Out_Of_Core;
485
486   strcpy_escaped (newe, line);
487   if (pinentry.error)
488     free (pinentry.error);
489   pinentry.error = newe;
490   return 0;
491 }
492
493
494 static int
495 cmd_setok (ASSUAN_CONTEXT ctx, char *line)
496 {
497   char *newo;
498   newo = malloc (strlen (line) + 1);
499
500   if (!newo)
501     return ASSUAN_Out_Of_Core;
502
503   strcpy_escaped (newo, line);
504   if (pinentry.ok)
505     free (pinentry.ok);
506   pinentry.ok = newo;
507   return 0;
508 }
509
510
511 static int
512 cmd_setcancel (ASSUAN_CONTEXT ctx, char *line)
513 {
514   char *newc;
515   newc = malloc (strlen (line) + 1);
516
517   if (!newc)
518     return ASSUAN_Out_Of_Core;
519
520   strcpy_escaped (newc, line);
521   if (pinentry.cancel)
522     free (pinentry.cancel);
523   pinentry.cancel = newc;
524   return 0;
525 }
526
527
528 static int
529 cmd_getpin (ASSUAN_CONTEXT ctx, char *line)
530 {
531   int result;
532   int set_prompt = 0;
533
534   pinentry.pin = secmem_malloc (pinentry.pin_len);
535   if (!pinentry.pin)
536     return ASSUAN_Out_Of_Core;
537   if (!pinentry.prompt)
538     {
539       pinentry.prompt = "PIN:";
540       set_prompt = 1;
541     }
542   pinentry.locale_err = 0;
543
544   result = (*pinentry_cmd_handler) (&pinentry);
545   if (pinentry.error)
546     {
547       free (pinentry.error);
548       pinentry.error = NULL;
549     }
550   if (set_prompt)
551     pinentry.prompt = NULL;
552
553   if (result < 0)
554     {
555       if (pinentry.pin)
556         {
557           secmem_free (pinentry.pin);
558           pinentry.pin = NULL;
559         }
560       return pinentry.locale_err? ASSUAN_Locale_Problem: ASSUAN_Canceled;
561     }
562
563   if (result)
564     {
565       result = assuan_send_data (ctx, pinentry.pin, result);
566       if (!result)
567         result = assuan_send_data (ctx, NULL, 0);
568     }
569
570   if (pinentry.pin)
571     {
572       secmem_free (pinentry.pin);
573       pinentry.pin = NULL;
574     }
575
576   return result;
577 }
578
579
580 static int
581 cmd_confirm (ASSUAN_CONTEXT ctx, char *line)
582 {
583   int result;
584
585   pinentry.locale_err = 0;
586   result = (*pinentry_cmd_handler) (&pinentry);
587   if (pinentry.error)
588     {
589       free (pinentry.error);
590       pinentry.error = NULL;
591     }
592
593   return result ? 0
594                 : (pinentry.locale_err? ASSUAN_Locale_Problem
595                                       : ASSUAN_Not_Confirmed);
596 }
597
598
599 /* Tell the assuan library about our commands.  */
600 static int
601 register_commands (ASSUAN_CONTEXT ctx)
602 {
603   static struct
604   {
605     const char *name;
606     int cmd_id;
607     int (*handler) (ASSUAN_CONTEXT, char *line);
608   } table[] =
609     {
610       { "SETDESC",    0,  cmd_setdesc },
611       { "SETPROMPT",  0,  cmd_setprompt },
612       { "SETERROR",   0,  cmd_seterror },
613       { "SETOK",      0,  cmd_setok },
614       { "SETCANCEL",  0,  cmd_setcancel },
615       { "GETPIN",     0,  cmd_getpin },
616       { "CONFIRM",    0,  cmd_confirm },
617       { NULL }
618     };
619   int i, j, rc;
620
621   for (i = j = 0; table[i].name; i++)
622     {
623       rc = assuan_register_command (ctx,
624                                     table[i].cmd_id ? table[i].cmd_id
625                                                    : (ASSUAN_CMD_USER + j++),
626                                     table[i].name, table[i].handler);
627       if (rc)
628         return rc;
629     } 
630   return 0;
631 }
632
633
634 /* Start the pinentry event loop.  The program will start to process
635    Assuan commands until it is finished or an error occurs.  If an
636    error occurs, -1 is returned.  Otherwise, 0 is returned.  */
637 int
638 pinentry_loop (void)
639 {
640   int rc;
641   int filedes[2];
642   ASSUAN_CONTEXT ctx;
643
644   /* Extra check to make sure we have dropped privs. */
645   if (getuid() != geteuid())
646     abort ();
647
648   /* For now we use a simple pipe based server so that we can work
649      from scripts.  We will later add options to run as a daemon and
650      wait for requests on a Unix domain socket.  */
651   filedes[0] = 0;
652   filedes[1] = 1;
653   rc = assuan_init_pipe_server (&ctx, filedes);
654   if (rc)
655     {
656       fprintf (stderr, "%s: failed to initialize the server: %s\n",
657                this_pgmname, assuan_strerror(rc));
658       return -1;
659     }
660   rc = register_commands (ctx);
661   if (rc)
662     {
663       fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n",
664                this_pgmname, assuan_strerror(rc));
665       return -1;
666     }
667
668   assuan_register_option_handler (ctx, option_handler);
669 #if 0
670   assuan_set_log_stream (ctx, stderr);
671 #endif
672   
673   for (;;)
674     {
675       rc = assuan_accept (ctx);
676       if (rc == -1)
677           break;
678       else if (rc)
679         {
680           fprintf (stderr, "%s: Assuan accept problem: %s\n",
681                    this_pgmname, assuan_strerror (rc));
682           break;
683         }
684       
685       rc = assuan_process (ctx);
686       if (rc)
687         {
688           fprintf (stderr, "%s: Assuan processing failed: %s\n",
689                    this_pgmname, assuan_strerror (rc));
690           continue;
691         }
692     }
693
694   assuan_deinit_server (ctx);
695   return 0;
696 }