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