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