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