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