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