a3564803787a5b0f7751a7cdef1845b0b762ea5d
[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   if (!pinentry->one_button)
185     MAKE_BUTTON (cancel, STRING_CANCEL);
186   else
187     dialog->cancel = NULL;
188
189   getmaxyx (stdscr, size_y, size_x);
190
191   /* Check if all required lines fit on the screen.  */
192   y = 1;                /* Top frame.  */
193   if (description)
194     {
195       char *start = description;
196       int len = 0;
197
198       do
199         {
200           collect_line (size_x - 4, &start, &len);
201           if (len > description_x)
202             description_x = len;
203           y++;
204         }
205       while (start[len - 1]);
206       y++;
207     }
208       
209   if (pinentry->pin)
210     {
211       if (error)
212         {
213           char *p = error;
214           int err_x = 0;
215
216           while (*p)
217             {
218               if (*(p++) == '\n')
219                 {
220                   if (err_x > error_x)
221                     error_x = err_x;
222                   y++;
223                   err_x = 0;
224                 }
225               else
226                 err_x++;
227             }
228           if (err_x > error_x)
229             error_x = err_x;
230           y += 2;       /* Error message.  */
231         }
232       y += 2;           /* Pin entry field.  */
233     }
234   y += 2;               /* OK/Cancel and bottom frame.  */
235   
236   if (y > size_y)
237     {
238       err = 1;
239       goto out;
240     }
241
242   /* Check if all required columns fit on the screen.  */
243   x = 0;
244   if (description)
245     {
246       int new_x = description_x;
247       if (new_x > size_x - 4)
248         new_x = size_x - 4;
249       if (new_x > x)
250         x = new_x;
251     }
252   if (pinentry->pin)
253     {
254 #define MIN_PINENTRY_LENGTH 40
255       int new_x;
256
257       if (error)
258         {
259           new_x = error_x;
260           if (new_x > size_x - 4)
261             new_x = size_x - 4;
262           if (new_x > x)
263             x = new_x;
264         }
265
266       new_x = MIN_PINENTRY_LENGTH;
267       if (prompt)
268         new_x += strlen (prompt) + 1;   /* One space after prompt.  */
269       if (new_x > size_x - 4)
270         new_x = size_x - 4;
271       if (new_x > x)
272         x = new_x;
273     }
274   /* We position the buttons after the first and second third of the
275      width.  Account for rounding.  */
276   if (x < 2 * strlen (dialog->ok))
277     x = 2 * strlen (dialog->ok);
278   if (dialog->cancel)
279     if (x < 2 * strlen (dialog->cancel))
280       x = 2 * strlen (dialog->cancel);
281
282   /* Add the frame.  */
283   x += 4;
284
285   if (x > size_x)
286     {
287       err = 1;
288       goto out;
289     }
290
291   dialog->pos = DIALOG_POS_NONE;
292   dialog->pin = pinentry->pin;
293   dialog->pin_max = pinentry->pin_len;
294   dialog->pin_loc = 0;
295   dialog->pin_len = 0;
296   ypos = (size_y - y) / 2;
297   xpos = (size_x - x) / 2;
298   move (ypos, xpos);
299   addch (ACS_ULCORNER);
300   hline (0, x - 2);
301   move (ypos, xpos + x - 1);
302   addch (ACS_URCORNER);
303   move (ypos + 1, xpos + x - 1);
304   vline (0, y - 2);
305   move (ypos + y - 1, xpos);
306   addch (ACS_LLCORNER);
307   hline (0, x - 2);
308   move (ypos + y - 1, xpos + x - 1);
309   addch (ACS_LRCORNER);
310   ypos++;
311   if (description)
312     {
313       char *start = description;
314       int len = 0;
315
316       do
317         {
318           int i;
319
320           move (ypos, xpos);
321           addch (ACS_VLINE);
322           addch (' ');
323           collect_line (size_x - 4, &start, &len);
324           for (i = 0; i < len - 1; i++)
325             addch ((unsigned char) start[i]);
326           if (start[len - 1] && start[len - 1] != '\n')
327             addch ((unsigned char) start[len - 1]);
328           ypos++;
329         }
330       while (start[len - 1]);
331       move (ypos, xpos);
332       addch (ACS_VLINE);
333       ypos++;
334     }
335   if (pinentry->pin)
336     {
337       int i;
338
339       if (error)
340         {
341           char *p = error;
342           i = 0;
343
344           while (*p)
345             {
346               move (ypos, xpos);
347               addch (ACS_VLINE);
348               addch (' ');
349               if (has_colors () && COLOR_PAIRS >= 1)
350                 attron (COLOR_PAIR(1) | A_BOLD);
351               else
352                 standout ();
353               for (;*p && *p != '\n'; p++)
354                 if (i < x - 4)
355                   {
356                     i++;
357                     addch ((unsigned char) *p);
358                   }
359               if (has_colors () && COLOR_PAIRS >= 1)
360                 attroff (COLOR_PAIR(1) | A_BOLD);
361               else
362                 standend ();
363               if (*p == '\n')
364                 p++;
365               i = 0;
366               ypos++;
367             }
368           move (ypos, xpos);
369           addch (ACS_VLINE);
370           ypos++;
371         }
372
373       move (ypos, xpos);
374       addch (ACS_VLINE);
375       addch (' ');
376
377       dialog->pin_y = ypos;
378       dialog->pin_x = xpos + 2;
379       dialog->pin_size = x - 4;
380       if (prompt)
381         {
382           char *p = prompt;
383           i = strlen (prompt);
384           if (i > x - 4 - MIN_PINENTRY_LENGTH)
385             i = x - 4 - MIN_PINENTRY_LENGTH;
386           dialog->pin_x += i + 1;
387           dialog->pin_size -= i + 1;
388           while (i-- > 0)
389             addch ((unsigned char) *(p++));
390           addch (' ');
391         }
392       for (i = 0; i < dialog->pin_size; i++)
393         addch ('_');
394       ypos++;
395       move (ypos, xpos);
396       addch (ACS_VLINE);
397       ypos++;
398     }
399   move (ypos, xpos);
400   addch (ACS_VLINE);
401   dialog->ok_y = ypos;
402   /* Calculating the left edge of the left button, rounding down.  */
403   dialog->ok_x = xpos + 2 + ((x - 4) / 2 - strlen (dialog->ok)) / 2;
404   move (dialog->ok_y, dialog->ok_x);
405   addstr (dialog->ok);
406   if ( dialog->cancel)
407     {
408       dialog->cancel_y = ypos;
409       /* Calculating the left edge of the right button, rounding up.  */
410       move (dialog->cancel_y, dialog->cancel_x);
411       addstr (dialog->cancel);
412     }
413
414  out:
415   if (description)
416     free (description);
417   if (error)
418     free (error);
419   if (prompt)
420     free (prompt);
421   return err;
422 }
423
424 \f
425 static void
426 set_cursor_state (int on)
427 {
428   static int normal_state = -1;
429   static int on_last;
430
431   if (normal_state < 0 && !on)
432     {
433       normal_state = curs_set (0);
434       on_last = on;
435     }
436   else if (on != on_last)
437     {
438       curs_set (on ? normal_state : 0);
439       on_last = on;
440     }
441 }
442
443 static int
444 dialog_switch_pos (dialog_t diag, dialog_pos_t new_pos)
445 {
446   if (new_pos != diag->pos)
447     {
448       switch (diag->pos)
449         {
450         case DIALOG_POS_OK:
451           move (diag->ok_y, diag->ok_x);
452           addstr (diag->ok);
453           break;
454         case DIALOG_POS_CANCEL:
455           if (diag->cancel)
456             {
457               move (diag->cancel_y, diag->cancel_x);
458               addstr (diag->cancel);
459             }
460           break;
461         default:
462           break;
463         }
464       diag->pos = new_pos;
465       switch (diag->pos)
466         {
467         case DIALOG_POS_PIN:
468           move (diag->pin_y, diag->pin_x + diag->pin_loc);
469           set_cursor_state (1);
470           break;
471         case DIALOG_POS_OK:
472           set_cursor_state (0);
473           move (diag->ok_y, diag->ok_x);
474           standout ();
475           addstr (diag->ok);
476           standend ();
477           move (diag->ok_y, diag->ok_x);
478           break;
479         case DIALOG_POS_CANCEL:
480           if (diag->cancel)
481             {
482               set_cursor_state (0);
483               move (diag->cancel_y, diag->cancel_x);
484               standout ();
485               addstr (diag->cancel);
486               standend ();
487               move (diag->cancel_y, diag->cancel_x);
488             }
489           break;
490         case DIALOG_POS_NONE:
491           set_cursor_state (0);
492           break;
493         }
494       refresh ();
495     }
496   return 0;
497 }
498
499 /* XXX Assume that field width is at least > 5.  */
500 static void
501 dialog_input (dialog_t diag, int chr)
502 {
503   int old_loc = diag->pin_loc;
504   assert (diag->pin);
505   assert (diag->pos == DIALOG_POS_PIN);
506
507   switch (chr)
508     {
509     case KEY_BACKSPACE:
510       if (diag->pin_len > 0)
511         {
512           diag->pin_len--;
513           diag->pin_loc--;
514           if (diag->pin_loc == 0 && diag->pin_len > 0)
515             {
516               diag->pin_loc = diag->pin_size - 5;
517               if (diag->pin_loc > diag->pin_len)
518                 diag->pin_loc = diag->pin_len;
519             }
520         }
521       break;
522
523     default:
524       if (chr > 0 && chr < 256 && diag->pin_len < diag->pin_max)
525         {
526           diag->pin[diag->pin_len] = (char) chr;
527           diag->pin_len++;
528           diag->pin_loc++;
529           if (diag->pin_loc == diag->pin_size && diag->pin_len < diag->pin_max)
530             {
531               diag->pin_loc = 5;
532               if (diag->pin_loc < diag->pin_size - (diag->pin_max + 1 - diag->pin_len))
533                 diag->pin_loc = diag->pin_size - (diag->pin_max + 1 - diag->pin_len);
534             }
535         }
536       break;
537     }
538
539   if (old_loc < diag->pin_loc)
540     {
541       move (diag->pin_y, diag->pin_x + old_loc);
542       while (old_loc++ < diag->pin_loc)
543         addch ('*');
544     }
545   else if (old_loc > diag->pin_loc)
546     {
547       move (diag->pin_y, diag->pin_x + diag->pin_loc);
548       while (old_loc-- > diag->pin_loc)
549         addch ('_');
550     }
551   move (diag->pin_y, diag->pin_x + diag->pin_loc);
552 }
553
554 static int
555 dialog_run (pinentry_t pinentry, const char *tty_name, const char *tty_type)
556 {
557   struct dialog diag;
558   FILE *ttyfi = NULL;
559   FILE *ttyfo = NULL;
560   SCREEN *screen = 0;
561   int done = 0;
562   char *pin_utf8;
563
564   /* Open the desired terminal if necessary.  */
565   if (tty_name)
566     {
567       ttyfi = fopen (tty_name, "r");
568       if (!ttyfi)
569         return -1;
570       ttyfo = fopen (tty_name, "w");
571       if (!ttyfo)
572         {
573           int err = errno;
574           fclose (ttyfi);
575           errno = err;
576           return -1;
577         }
578       screen = newterm (tty_type, ttyfo, ttyfi);
579       set_term (screen);
580     }
581   else
582     {
583       if (!init_screen)
584         {
585           init_screen = 1;
586           initscr ();
587         }
588       else
589         clear ();
590     }
591   
592   keypad (stdscr, TRUE); /* Enable keyboard mapping.  */
593   nonl ();              /* Tell curses not to do NL->CR/NL on output.  */
594   cbreak ();            /* Take input chars one at a time, no wait for \n.  */
595   noecho ();            /* Don't echo input - in color.  */
596   refresh ();
597
598   if (has_colors ())
599     {
600       start_color ();
601
602       if (COLOR_PAIRS >= 1)
603         init_pair (1, COLOR_RED, COLOR_BLACK);
604     }
605
606   /* XXX */
607   if (dialog_create (pinentry, &diag))
608     return -2;
609   dialog_switch_pos (&diag, diag.pin ? DIALOG_POS_PIN : DIALOG_POS_OK);
610
611   do
612     {
613       int c;
614
615       c = getch ();     /* Refresh, accept single keystroke of input.  */
616
617       switch (c)
618         {
619         case KEY_LEFT:
620         case KEY_UP:
621           switch (diag.pos)
622             {
623             case DIALOG_POS_OK:
624               if (diag.pin)
625                 dialog_switch_pos (&diag, DIALOG_POS_PIN);
626               break;
627             case DIALOG_POS_CANCEL:
628               dialog_switch_pos (&diag, DIALOG_POS_OK);
629               break;
630             default:
631               break;
632             }
633           break;
634
635         case KEY_RIGHT:
636         case KEY_DOWN:
637           switch (diag.pos)
638             {
639             case DIALOG_POS_PIN:
640               dialog_switch_pos (&diag, DIALOG_POS_OK);
641               break;
642             case DIALOG_POS_OK:
643               dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
644               break;
645             default:
646               break;
647             }
648           break;
649
650         case '\t':
651           switch (diag.pos)
652             {
653             case DIALOG_POS_PIN:
654               dialog_switch_pos (&diag, DIALOG_POS_OK);
655               break;
656             case DIALOG_POS_OK:
657               dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
658               break;
659             case DIALOG_POS_CANCEL:
660               if (diag.pin)
661                 dialog_switch_pos (&diag, DIALOG_POS_PIN);
662               else
663                 dialog_switch_pos (&diag, DIALOG_POS_OK);
664               break;
665             default:
666               break;
667             }
668           break;
669   
670         case '\e':
671           done = -2;
672           break;
673
674         case '\r':
675           switch (diag.pos)
676             {
677             case DIALOG_POS_PIN:
678             case DIALOG_POS_OK:
679               done = 1;
680               break;
681             case DIALOG_POS_CANCEL:
682               done = -2;
683               break;
684             case DIALOG_POS_NONE:
685               break;
686             }
687           break;
688
689         default:
690           if (diag.pos == DIALOG_POS_PIN)
691             dialog_input (&diag, c);
692         }
693     }
694   while (!done);
695
696   set_cursor_state (1);
697   endwin ();
698   if (screen)
699     delscreen (screen);
700
701   if (ttyfi)
702     fclose (ttyfi);
703   if (ttyfo)
704     fclose (ttyfo);
705   /* XXX Factor out into dialog_release or something.  */
706   free (diag.ok);
707   free (diag.cancel);
708
709   if (pinentry->pin)
710     {
711       pinentry->locale_err = 1;
712       pin_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, pinentry->pin, 1);
713       if (pin_utf8)
714         {
715           pinentry_setbufferlen (pinentry, strlen (pin_utf8) + 1);
716           if (pinentry->pin)
717             strcpy (pinentry->pin, pin_utf8);
718           secmem_free (pin_utf8);
719           pinentry->locale_err = 0;
720         }
721     }
722
723   return diag.pin ? (done < 0 ? -1 : diag.pin_len) : (done < 0 ? 0 : 1);
724 }
725
726 int
727 curses_cmd_handler (pinentry_t pinentry)
728 {
729   return dialog_run (pinentry, pinentry->ttyname, pinentry->ttytype);
730 }