* pinentry/pinentry.h (struct pinentry): Add TOUCH_FILE.
[pinentry.git] / pinentry / pinentry-curses.c
1 /* pinentry-curses.c - A secure curses dialog for PIN entry, library version
2    Copyright (C) 2002 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 #include <assert.h>
25 #include <curses.h>
26 #include <signal.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <locale.h>
31 #include <iconv.h>
32 #include <langinfo.h>
33 #include <limits.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <time.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #ifdef HAVE_UTIME_H
40 #include <utime.h>
41 #endif /*HAVE_UTIME_H*/
42
43 #include <memory.h>
44
45 #include "pinentry.h"
46
47 #define STRING_OK "<OK>"
48 #define STRING_CANCEL "<Cancel>"
49
50 static int init_screen;
51
52 typedef enum
53   {
54     DIALOG_POS_NONE,
55     DIALOG_POS_PIN,
56     DIALOG_POS_OK,
57     DIALOG_POS_CANCEL
58   }
59 dialog_pos_t;
60
61 struct dialog
62 {
63   dialog_pos_t pos;
64   int pin_y;
65   int pin_x;
66   /* Width of the PIN field.  */
67   int pin_size;
68   /* Cursor location in PIN field.  */
69   int pin_loc;
70   char *pin;
71   int pin_max;
72   /* Length of PIN.  */
73   int pin_len;
74
75   int ok_y;
76   int ok_x;
77   char *ok;
78   int cancel_y;
79   int cancel_x;
80   char *cancel;
81 };
82 typedef struct dialog *dialog_t;
83
84 \f
85 /* Return the next line up to MAXLEN columns wide in START and LEN.
86    The first invocation should have 0 as *LEN.  If the line ends with
87    a \n, it is a normal line that will be continued.  If it is a '\0'
88    the end of the text is reached after this line.  In all other cases
89    there is a forced line break.  A full line is returned and will be
90    continued in the next line.  */
91 static void
92 collect_line (int maxlen, char **start_p, int *len_p)
93 {
94   int last_space = 0;
95   int len = *len_p;
96   char *end;
97
98   /* Skip to next line.  */
99   *start_p += len;
100   /* Skip leading space.  */
101   while (**start_p == ' ')
102     (*start_p)++;
103
104   end = *start_p;
105   len = 0;
106
107   while (len < maxlen - 1 && *end && *end != '\n')
108     {
109       len++;
110       end++;
111       if (*end == ' ')
112         last_space = len;
113     }
114
115   if (*end && *end != '\n' && last_space != 0)
116     {
117       /* We reached the end of the available space, but still have
118          characters to go in this line.  We can break the line into
119          two parts at a space.  */
120       len = last_space;
121       (*start_p)[len] = '\n';
122     }
123   *len_p = len + 1;
124 }
125
126
127 static int
128 dialog_create (pinentry_t pinentry, dialog_t dialog)
129 {
130   int err = 0;
131   int size_y;
132   int size_x;
133   int y;
134   int x;
135   int ypos;
136   int xpos;
137   int description_x = 0;
138   int error_x = 0;
139   char *description = NULL;
140   char *error = NULL;
141   char *prompt = NULL;
142
143 #define COPY_OUT(what)                                                  \
144   do                                                                    \
145     if (pinentry->what)                                                 \
146       {                                                                 \
147         what = pinentry_utf8_to_local (pinentry->lc_ctype,              \
148                                        pinentry->what);                 \
149         if (!what)                                                      \
150           {                                                             \
151             err = 1;                                                    \
152             goto out;                                                   \
153           }                                                             \
154       }                                                                 \
155   while (0)
156     
157   COPY_OUT (description);
158   COPY_OUT (error);
159   COPY_OUT (prompt);
160
161 #define MAKE_BUTTON(which,default)                                      \
162   do                                                                    \
163     {                                                                   \
164       char *new = NULL;                                                 \
165       if (pinentry->which)                                              \
166         {                                                               \
167           int len = strlen (pinentry->which);                           \
168           new = malloc (len + 3);                                       \
169           if (!new)                                                     \
170             {                                                           \
171               err = 1;                                                  \
172               goto out;                                                 \
173             }                                                           \
174           new[0] = '<';                                                 \
175           memcpy (&new[1], pinentry->which, len);                       \
176           new[len + 1] = '>';                                           \
177           new[len + 2] = '\0';                                          \
178         }                                                               \
179       dialog->which = pinentry_utf8_to_local (pinentry->lc_ctype,       \
180                                               new ? new : default);     \
181       if (!dialog->which)                                               \
182         {                                                               \
183           err = 1;                                                      \
184           goto out;                                                     \
185         }                                                               \
186     }                                                                   \
187   while (0)
188
189   MAKE_BUTTON (ok, STRING_OK);
190   if (!pinentry->one_button)
191     MAKE_BUTTON (cancel, STRING_CANCEL);
192   else
193     dialog->cancel = NULL;
194
195   getmaxyx (stdscr, size_y, size_x);
196
197   /* Check if all required lines fit on the screen.  */
198   y = 1;                /* Top frame.  */
199   if (description)
200     {
201       char *start = description;
202       int len = 0;
203
204       do
205         {
206           collect_line (size_x - 4, &start, &len);
207           if (len > description_x)
208             description_x = len;
209           y++;
210         }
211       while (start[len - 1]);
212       y++;
213     }
214       
215   if (pinentry->pin)
216     {
217       if (error)
218         {
219           char *p = error;
220           int err_x = 0;
221
222           while (*p)
223             {
224               if (*(p++) == '\n')
225                 {
226                   if (err_x > error_x)
227                     error_x = err_x;
228                   y++;
229                   err_x = 0;
230                 }
231               else
232                 err_x++;
233             }
234           if (err_x > error_x)
235             error_x = err_x;
236           y += 2;       /* Error message.  */
237         }
238       y += 2;           /* Pin entry field.  */
239     }
240   y += 2;               /* OK/Cancel and bottom frame.  */
241   
242   if (y > size_y)
243     {
244       err = 1;
245       goto out;
246     }
247
248   /* Check if all required columns fit on the screen.  */
249   x = 0;
250   if (description)
251     {
252       int new_x = description_x;
253       if (new_x > size_x - 4)
254         new_x = size_x - 4;
255       if (new_x > x)
256         x = new_x;
257     }
258   if (pinentry->pin)
259     {
260 #define MIN_PINENTRY_LENGTH 40
261       int new_x;
262
263       if (error)
264         {
265           new_x = error_x;
266           if (new_x > size_x - 4)
267             new_x = size_x - 4;
268           if (new_x > x)
269             x = new_x;
270         }
271
272       new_x = MIN_PINENTRY_LENGTH;
273       if (prompt)
274         new_x += strlen (prompt) + 1;   /* One space after prompt.  */
275       if (new_x > size_x - 4)
276         new_x = size_x - 4;
277       if (new_x > x)
278         x = new_x;
279     }
280   /* We position the buttons after the first and second third of the
281      width.  Account for rounding.  */
282   if (x < 2 * strlen (dialog->ok))
283     x = 2 * strlen (dialog->ok);
284   if (dialog->cancel)
285     if (x < 2 * strlen (dialog->cancel))
286       x = 2 * strlen (dialog->cancel);
287
288   /* Add the frame.  */
289   x += 4;
290
291   if (x > size_x)
292     {
293       err = 1;
294       goto out;
295     }
296
297   dialog->pos = DIALOG_POS_NONE;
298   dialog->pin = pinentry->pin;
299   dialog->pin_max = pinentry->pin_len;
300   dialog->pin_loc = 0;
301   dialog->pin_len = 0;
302   ypos = (size_y - y) / 2;
303   xpos = (size_x - x) / 2;
304   move (ypos, xpos);
305   addch (ACS_ULCORNER);
306   hline (0, x - 2);
307   move (ypos, xpos + x - 1);
308   addch (ACS_URCORNER);
309   move (ypos + 1, xpos + x - 1);
310   vline (0, y - 2);
311   move (ypos + y - 1, xpos);
312   addch (ACS_LLCORNER);
313   hline (0, x - 2);
314   move (ypos + y - 1, xpos + x - 1);
315   addch (ACS_LRCORNER);
316   ypos++;
317   if (description)
318     {
319       char *start = description;
320       int len = 0;
321
322       do
323         {
324           int i;
325
326           move (ypos, xpos);
327           addch (ACS_VLINE);
328           addch (' ');
329           collect_line (size_x - 4, &start, &len);
330           for (i = 0; i < len - 1; i++)
331             addch ((unsigned char) start[i]);
332           if (start[len - 1] && start[len - 1] != '\n')
333             addch ((unsigned char) start[len - 1]);
334           ypos++;
335         }
336       while (start[len - 1]);
337       move (ypos, xpos);
338       addch (ACS_VLINE);
339       ypos++;
340     }
341   if (pinentry->pin)
342     {
343       int i;
344
345       if (error)
346         {
347           char *p = error;
348           i = 0;
349
350           while (*p)
351             {
352               move (ypos, xpos);
353               addch (ACS_VLINE);
354               addch (' ');
355               if (has_colors () && COLOR_PAIRS >= 1)
356                 attron (COLOR_PAIR(1) | A_BOLD);
357               else
358                 standout ();
359               for (;*p && *p != '\n'; p++)
360                 if (i < x - 4)
361                   {
362                     i++;
363                     addch ((unsigned char) *p);
364                   }
365               if (has_colors () && COLOR_PAIRS >= 1)
366                 attroff (COLOR_PAIR(1) | A_BOLD);
367               else
368                 standend ();
369               if (*p == '\n')
370                 p++;
371               i = 0;
372               ypos++;
373             }
374           move (ypos, xpos);
375           addch (ACS_VLINE);
376           ypos++;
377         }
378
379       move (ypos, xpos);
380       addch (ACS_VLINE);
381       addch (' ');
382
383       dialog->pin_y = ypos;
384       dialog->pin_x = xpos + 2;
385       dialog->pin_size = x - 4;
386       if (prompt)
387         {
388           char *p = prompt;
389           i = strlen (prompt);
390           if (i > x - 4 - MIN_PINENTRY_LENGTH)
391             i = x - 4 - MIN_PINENTRY_LENGTH;
392           dialog->pin_x += i + 1;
393           dialog->pin_size -= i + 1;
394           while (i-- > 0)
395             addch ((unsigned char) *(p++));
396           addch (' ');
397         }
398       for (i = 0; i < dialog->pin_size; i++)
399         addch ('_');
400       ypos++;
401       move (ypos, xpos);
402       addch (ACS_VLINE);
403       ypos++;
404     }
405   move (ypos, xpos);
406   addch (ACS_VLINE);
407   dialog->ok_y = ypos;
408   /* Calculating the left edge of the left button, rounding down.  */
409   dialog->ok_x = xpos + 2 + ((x - 4) / 2 - strlen (dialog->ok)) / 2;
410   move (dialog->ok_y, dialog->ok_x);
411   addstr (dialog->ok);
412   if ( dialog->cancel)
413     {
414       dialog->cancel_y = ypos;
415       /* Calculating the left edge of the right button, rounding up.  */
416       move (dialog->cancel_y, dialog->cancel_x);
417       addstr (dialog->cancel);
418     }
419
420  out:
421   if (description)
422     free (description);
423   if (error)
424     free (error);
425   if (prompt)
426     free (prompt);
427   return err;
428 }
429
430 \f
431 static void
432 set_cursor_state (int on)
433 {
434   static int normal_state = -1;
435   static int on_last;
436
437   if (normal_state < 0 && !on)
438     {
439       normal_state = curs_set (0);
440       on_last = on;
441     }
442   else if (on != on_last)
443     {
444       curs_set (on ? normal_state : 0);
445       on_last = on;
446     }
447 }
448
449 static int
450 dialog_switch_pos (dialog_t diag, dialog_pos_t new_pos)
451 {
452   if (new_pos != diag->pos)
453     {
454       switch (diag->pos)
455         {
456         case DIALOG_POS_OK:
457           move (diag->ok_y, diag->ok_x);
458           addstr (diag->ok);
459           break;
460         case DIALOG_POS_CANCEL:
461           if (diag->cancel)
462             {
463               move (diag->cancel_y, diag->cancel_x);
464               addstr (diag->cancel);
465             }
466           break;
467         default:
468           break;
469         }
470       diag->pos = new_pos;
471       switch (diag->pos)
472         {
473         case DIALOG_POS_PIN:
474           move (diag->pin_y, diag->pin_x + diag->pin_loc);
475           set_cursor_state (1);
476           break;
477         case DIALOG_POS_OK:
478           set_cursor_state (0);
479           move (diag->ok_y, diag->ok_x);
480           standout ();
481           addstr (diag->ok);
482           standend ();
483           move (diag->ok_y, diag->ok_x);
484           break;
485         case DIALOG_POS_CANCEL:
486           if (diag->cancel)
487             {
488               set_cursor_state (0);
489               move (diag->cancel_y, diag->cancel_x);
490               standout ();
491               addstr (diag->cancel);
492               standend ();
493               move (diag->cancel_y, diag->cancel_x);
494             }
495           break;
496         case DIALOG_POS_NONE:
497           set_cursor_state (0);
498           break;
499         }
500       refresh ();
501     }
502   return 0;
503 }
504
505 /* XXX Assume that field width is at least > 5.  */
506 static void
507 dialog_input (dialog_t diag, int chr)
508 {
509   int old_loc = diag->pin_loc;
510   assert (diag->pin);
511   assert (diag->pos == DIALOG_POS_PIN);
512
513   switch (chr)
514     {
515     case KEY_BACKSPACE:
516       if (diag->pin_len > 0)
517         {
518           diag->pin_len--;
519           diag->pin_loc--;
520           if (diag->pin_loc == 0 && diag->pin_len > 0)
521             {
522               diag->pin_loc = diag->pin_size - 5;
523               if (diag->pin_loc > diag->pin_len)
524                 diag->pin_loc = diag->pin_len;
525             }
526         }
527       break;
528
529     default:
530       if (chr > 0 && chr < 256 && diag->pin_len < diag->pin_max)
531         {
532           diag->pin[diag->pin_len] = (char) chr;
533           diag->pin_len++;
534           diag->pin_loc++;
535           if (diag->pin_loc == diag->pin_size && diag->pin_len < diag->pin_max)
536             {
537               diag->pin_loc = 5;
538               if (diag->pin_loc < diag->pin_size - (diag->pin_max + 1 - diag->pin_len))
539                 diag->pin_loc = diag->pin_size - (diag->pin_max + 1 - diag->pin_len);
540             }
541         }
542       break;
543     }
544
545   if (old_loc < diag->pin_loc)
546     {
547       move (diag->pin_y, diag->pin_x + old_loc);
548       while (old_loc++ < diag->pin_loc)
549         addch ('*');
550     }
551   else if (old_loc > diag->pin_loc)
552     {
553       move (diag->pin_y, diag->pin_x + diag->pin_loc);
554       while (old_loc-- > diag->pin_loc)
555         addch ('_');
556     }
557   move (diag->pin_y, diag->pin_x + diag->pin_loc);
558 }
559
560 static int
561 dialog_run (pinentry_t pinentry, const char *tty_name, const char *tty_type)
562 {
563   struct dialog diag;
564   FILE *ttyfi = NULL;
565   FILE *ttyfo = NULL;
566   SCREEN *screen = 0;
567   int done = 0;
568   char *pin_utf8;
569
570   /* Open the desired terminal if necessary.  */
571   if (tty_name)
572     {
573       ttyfi = fopen (tty_name, "r");
574       if (!ttyfi)
575         return -1;
576       ttyfo = fopen (tty_name, "w");
577       if (!ttyfo)
578         {
579           int err = errno;
580           fclose (ttyfi);
581           errno = err;
582           return -1;
583         }
584       screen = newterm (tty_type, ttyfo, ttyfi);
585       set_term (screen);
586     }
587   else
588     {
589       if (!init_screen)
590         {
591           init_screen = 1;
592           initscr ();
593         }
594       else
595         clear ();
596     }
597   
598   keypad (stdscr, TRUE); /* Enable keyboard mapping.  */
599   nonl ();              /* Tell curses not to do NL->CR/NL on output.  */
600   cbreak ();            /* Take input chars one at a time, no wait for \n.  */
601   noecho ();            /* Don't echo input - in color.  */
602   refresh ();
603
604   if (has_colors ())
605     {
606       start_color ();
607
608       if (COLOR_PAIRS >= 1)
609         init_pair (1, COLOR_RED, COLOR_BLACK);
610     }
611
612   /* XXX */
613   if (dialog_create (pinentry, &diag))
614     return -2;
615   dialog_switch_pos (&diag, diag.pin ? DIALOG_POS_PIN : DIALOG_POS_OK);
616
617   do
618     {
619       int c;
620
621       c = getch ();     /* Refresh, accept single keystroke of input.  */
622
623       switch (c)
624         {
625         case KEY_LEFT:
626         case KEY_UP:
627           switch (diag.pos)
628             {
629             case DIALOG_POS_OK:
630               if (diag.pin)
631                 dialog_switch_pos (&diag, DIALOG_POS_PIN);
632               break;
633             case DIALOG_POS_CANCEL:
634               dialog_switch_pos (&diag, DIALOG_POS_OK);
635               break;
636             default:
637               break;
638             }
639           break;
640
641         case KEY_RIGHT:
642         case KEY_DOWN:
643           switch (diag.pos)
644             {
645             case DIALOG_POS_PIN:
646               dialog_switch_pos (&diag, DIALOG_POS_OK);
647               break;
648             case DIALOG_POS_OK:
649               dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
650               break;
651             default:
652               break;
653             }
654           break;
655
656         case '\t':
657           switch (diag.pos)
658             {
659             case DIALOG_POS_PIN:
660               dialog_switch_pos (&diag, DIALOG_POS_OK);
661               break;
662             case DIALOG_POS_OK:
663               dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
664               break;
665             case DIALOG_POS_CANCEL:
666               if (diag.pin)
667                 dialog_switch_pos (&diag, DIALOG_POS_PIN);
668               else
669                 dialog_switch_pos (&diag, DIALOG_POS_OK);
670               break;
671             default:
672               break;
673             }
674           break;
675   
676         case '\e':
677           done = -2;
678           break;
679
680         case '\r':
681           switch (diag.pos)
682             {
683             case DIALOG_POS_PIN:
684             case DIALOG_POS_OK:
685               done = 1;
686               break;
687             case DIALOG_POS_CANCEL:
688               done = -2;
689               break;
690             case DIALOG_POS_NONE:
691               break;
692             }
693           break;
694
695         default:
696           if (diag.pos == DIALOG_POS_PIN)
697             dialog_input (&diag, c);
698         }
699     }
700   while (!done);
701
702   set_cursor_state (1);
703   endwin ();
704   if (screen)
705     delscreen (screen);
706
707   if (ttyfi)
708     fclose (ttyfi);
709   if (ttyfo)
710     fclose (ttyfo);
711   /* XXX Factor out into dialog_release or something.  */
712   free (diag.ok);
713   free (diag.cancel);
714
715   if (pinentry->pin)
716     {
717       pinentry->locale_err = 1;
718       pin_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, pinentry->pin, 1);
719       if (pin_utf8)
720         {
721           pinentry_setbufferlen (pinentry, strlen (pin_utf8) + 1);
722           if (pinentry->pin)
723             strcpy (pinentry->pin, pin_utf8);
724           secmem_free (pin_utf8);
725           pinentry->locale_err = 0;
726         }
727     }
728
729   return diag.pin ? (done < 0 ? -1 : diag.pin_len) : (done < 0 ? 0 : 1);
730 }
731
732
733 /* If a touch has been registered, touch that file.  */
734 static void
735 do_touch_file (pinentry_t pinentry)
736 {
737 #ifdef HAVE_UTIME_H
738   struct stat st;
739   time_t tim;
740
741   if (!pinentry->touch_file || !*pinentry->touch_file)
742     return;
743
744   if (stat (pinentry->touch_file, &st))
745     return; /* Oops.  */
746
747   /* Make sure that we actually update the mtime.  */
748   while ( (tim = time (NULL)) == st.st_mtime )
749     sleep (1);
750
751   /* Update but ignore errors as we can't do anything in that case.
752      Printing error messages may even clubber the display further. */
753   utime (pinentry->touch_file, NULL);
754 #endif /*HAVE_UTIME_H*/
755 }
756
757
758 int
759 curses_cmd_handler (pinentry_t pinentry)
760 {
761   int rc;
762
763   rc = dialog_run (pinentry, pinentry->ttyname, pinentry->ttytype);
764   do_touch_file (pinentry);
765   return rc;
766 }