tty: Handle the case where the user needs to repeat the passphrase.
[pinentry.git] / tty / pinentry-tty.c
1 /* pinentry-curses.c - A secure curses dialog for PIN entry, library version
2    Copyright (C) 2014 Serge Voilokov
3    Copyright (C) 2015 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
4    Copyright (C) 2015 g10 Code GmbH
5
6    This file is part of PINENTRY.
7
8    PINENTRY is free software; you can redistribute it and/or modify it
9    under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    PINENTRY is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <signal.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <time.h>
33 #include <termios.h>
34 #ifdef HAVE_UTIME_H
35 #include <utime.h>
36 #endif /*HAVE_UTIME_H*/
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <ctype.h>
40
41 #include "pinentry.h"
42 #include "memory.h"
43
44 #ifndef HAVE_DOSISH_SYSTEM
45 static int timed_out;
46 #endif
47
48 static struct termios n_term;
49 static struct termios o_term;
50
51 static int
52 cbreak (int fd)
53 {
54   if ((tcgetattr(fd, &o_term)) == -1)
55     return -1;
56   n_term = o_term;
57   n_term.c_lflag = n_term.c_lflag & ~(ECHO|ICANON);
58   n_term.c_cc[VMIN] = 1;
59   n_term.c_cc[VTIME]= 0;
60   if ((tcsetattr(fd, TCSAFLUSH, &n_term)) == -1)
61     return -1;
62   return 1;
63 }
64
65 #define UNDERLINE_START "\033[4m"
66 /* Bold, red.  */
67 #define ALERT_START "\033[1;31m"
68 #define NORMAL_RESTORE "\033[0m"
69
70 static char
71 button (char *text, char *default_text, FILE *ttyfo)
72 {
73   char *highlight;
74
75   if (! text)
76     return 0;
77
78   /* Skip any leading white space.  */
79   while (*text == ' ')
80     text ++;
81
82   highlight = text;
83   while ((highlight = strchr (highlight, '_')))
84     {
85       highlight = highlight + 1;
86       if (*highlight == '_')
87         /* Escaped underscore.  */
88         continue;
89       else
90         break;
91     }
92
93   if (! highlight)
94     /* Not accelerator.  Take the first alpha-numeric character.  */
95     {
96       highlight = text;
97       while (*highlight && !isalnum (*highlight))
98         highlight ++;
99     }
100
101   if (! highlight)
102     /* Hmm, no alpha-num characters.  */
103     {
104       if (! default_text)
105         return 0;
106       text = highlight = default_text;
107     }
108
109   fputs ("  ", ttyfo);
110   for (; *text; text ++)
111     {
112       /* Skip accelerator prefix.  */
113       if (*text == '_')
114         continue;
115
116       if (text == highlight)
117         fputs (UNDERLINE_START, ttyfo);
118       fputc (*text, ttyfo);
119       if (text == highlight)
120         fputs (NORMAL_RESTORE, ttyfo);
121     }
122   fputc ('\n', ttyfo);
123
124   return *highlight;
125 }
126
127 static int
128 confirm (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
129 {
130   char *msg;
131
132   char ok = 0;
133   char notok = 0;
134   char cancel = 0;
135
136   int ret;
137
138   if (pinentry->error)
139     fprintf (ttyfo, "*** %s%s%s ***\n",
140              ALERT_START, pinentry->error, NORMAL_RESTORE);
141
142   msg = pinentry->description;
143   if (! msg)
144     /* If there is no description, fallback to the title.  */
145     msg = pinentry->title;
146   if (! msg)
147     msg = "Confirm:";
148
149   if (msg)
150     {
151       fputs (msg, ttyfo);
152       fputc ('\n', ttyfo);
153     }
154
155   fflush (ttyfo);
156
157   if (pinentry->default_ok)
158     ok = button (pinentry->default_ok, "OK", ttyfo);
159   else if (pinentry->ok)
160     ok = button (pinentry->ok, "OK", ttyfo);
161   else
162     ok = button ("OK", NULL, ttyfo);
163
164   if (! pinentry->one_button)
165     {
166       if (pinentry->default_cancel)
167         cancel = button (pinentry->default_cancel, "Cancel", ttyfo);
168       else if (pinentry->cancel)
169         cancel = button (pinentry->cancel, "Cancel", ttyfo);
170
171       if (pinentry->notok)
172         notok = button (pinentry->notok, NULL, ttyfo);
173     }
174
175   if (cbreak (fileno (ttyfi)) == -1)
176     {
177       int err = errno;
178       fprintf (stderr, "cbreak failure, exiting\n");
179       errno = err;
180       return -1;
181     }
182
183   if (pinentry->one_button)
184     fprintf (ttyfo, "Press any key to continue.");
185   else
186     {
187       fputc ('[', ttyfo);
188       if (ok)
189         fputc (tolower (ok), ttyfo);
190       if (cancel)
191         fputc (tolower (cancel), ttyfo);
192       if (notok)
193         fputc (tolower (notok), ttyfo);
194       fputs("]? ", ttyfo);
195     }
196
197   while (1)
198     {
199       int input = fgetc (ttyfi);
200       if (input == EOF || input == 0x4)
201         /* End of file or control-d (= end of file).  */
202         {
203           pinentry->close_button = 1;
204
205           pinentry->canceled = 1;
206           ret = 0;
207           break;
208         }
209
210       if (pinentry->one_button)
211         {
212           ret = 1;
213           break;
214         }
215
216       if (cancel && (input == toupper (cancel) || input == tolower (cancel)))
217         {
218           pinentry->canceled = 1;
219           ret = 0;
220           break;
221         }
222       else if (notok && (input == toupper (notok) || input == tolower (notok)))
223         {
224           ret = 0;
225           break;
226         }
227       else if (ok && (input == toupper (ok) || input == tolower (ok)))
228         {
229           ret = 1;
230           break;
231         }
232     }
233
234   fputc('\n', ttyfo);
235
236   tcsetattr (fileno(ttyfi), TCSANOW, &o_term);
237
238   return ret;
239 }
240
241 static char *
242 read_password (FILE *ttyfi, FILE *ttyfo)
243 {
244   int done = 0;
245   int len = 128;
246   int count = 0;
247   char *buffer;
248
249   if (cbreak (fileno (ttyfi)) == -1)
250     {
251       int err = errno;
252       fprintf (stderr, "cbreak failure, exiting\n");
253       errno = err;
254       return NULL;
255     }
256
257   buffer = secmem_malloc (len);
258   if (! buffer)
259     return NULL;
260
261   while (!done)
262     {
263       int c;
264
265       if (count == len - 1)
266         /* Double the buffer's size.  Note: we check if count is len -
267            1 and not len so that we always have space for the NUL
268            character.  */
269         {
270           char *tmp = secmem_realloc (buffer, 2 * len);
271           if (! tmp)
272             {
273               secmem_free (tmp);
274               return NULL;
275             }
276           buffer = tmp;
277         }
278
279       c = fgetc (ttyfi);
280       switch (c)
281         {
282         case 0x4: case EOF:
283           /* Control-d (i.e., end of file) or a real EOF.  */
284           done = -1;
285           break;
286
287         case '\n':
288           done = 1;
289           break;
290
291         case 0x7f:
292           /* Backspace.  */
293           if (count > 0)
294             count --;
295           break;
296
297         default:
298           buffer[count ++] = c;
299           break;
300         }
301     }
302   buffer[count] = '\0';
303
304   tcsetattr (fileno(ttyfi), TCSANOW, &o_term);
305
306   if (done == -1)
307     {
308       secmem_free (buffer);
309       return NULL;
310     }
311
312   return buffer;
313 }
314
315
316 static int
317 password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
318 {
319   char *msg;
320   int done = 0;
321
322   msg = pinentry->description;
323   if (! msg)
324     msg = pinentry->title;
325   if (! msg)
326     msg = "Enter your passphrase.";
327
328   fprintf (ttyfo, "%s\n ", msg);
329
330   while (! done)
331     {
332       char *passphrase;
333
334       char *prompt = pinentry->prompt;
335       if (! prompt || !*prompt)
336         prompt = "PIN";
337
338       fprintf (ttyfo, "%s%s ",
339                prompt,
340                /* Make sure the prompt ends in a : or a question mark.  */
341                (prompt[strlen(prompt) - 1] == ':'
342                 || prompt[strlen(prompt) - 1] == '?') ? "" : ":");
343       fflush (ttyfo);
344
345       passphrase = read_password (ttyfi, ttyfo);
346       fputc ('\n', ttyfo);
347       if (! passphrase)
348         {
349           done = -1;
350           break;
351         }
352
353       if (! pinentry->repeat_passphrase)
354         done = 1;
355       else
356         {
357           char *passphrase2;
358
359           prompt = pinentry->repeat_passphrase;
360           fprintf (ttyfo, "%s%s ",
361                    prompt,
362                    /* Make sure the prompt ends in a : or a question mark.  */
363                    (prompt[strlen(prompt) - 1] == ':'
364                     || prompt[strlen(prompt) - 1] == '?') ? "" : ":");
365           fflush (ttyfo);
366
367           passphrase2 = read_password (ttyfi, ttyfo);
368           fputc ('\n', ttyfo);
369           if (! passphrase2)
370             {
371               done = -1;
372               break;
373             }
374
375           if (strcmp (passphrase, passphrase2) == 0)
376             done = 1;
377           else
378             fprintf (ttyfo, "*** %s%s%s ***\n",
379                      ALERT_START,
380                      pinentry->repeat_error_string
381                      ?: "Passphrases don't match.",
382                      NORMAL_RESTORE);
383
384           secmem_free (passphrase2);
385         }
386
387       if (done == 1)
388         pinentry_setbuffer_use (pinentry, passphrase, 0);
389       else
390         secmem_free (passphrase);
391     }
392
393   return done;
394 }
395
396
397 /* If a touch has been registered, touch that file.  */
398 static void
399 do_touch_file(pinentry_t pinentry)
400 {
401 #ifdef HAVE_UTIME_H
402   struct stat st;
403   time_t tim;
404
405   if (!pinentry->touch_file || !*pinentry->touch_file)
406     return;
407
408   if (stat(pinentry->touch_file, &st))
409     return; /* Oops.  */
410
411   /* Make sure that we actually update the mtime.  */
412   while ((tim = time(NULL)) == st.st_mtime)
413     sleep(1);
414
415   /* Update but ignore errors as we can't do anything in that case.
416      Printing error messages may even clubber the display further. */
417   utime (pinentry->touch_file, NULL);
418 #endif /*HAVE_UTIME_H*/
419 }
420
421 #ifndef HAVE_DOSISH_SYSTEM
422 static void
423 catchsig(int sig)
424 {
425   if (sig == SIGALRM)
426     timed_out = 1;
427 }
428 #endif
429
430 int
431 tty_cmd_handler(pinentry_t pinentry)
432 {
433   int rc = 0;
434   FILE *ttyfi = stdin;
435   FILE *ttyfo = stdout;
436
437 #ifndef HAVE_DOSISH_SYSTEM
438   timed_out = 0;
439
440   if (pinentry->timeout)
441     {
442       struct sigaction sa;
443
444       memset(&sa, 0, sizeof(sa));
445       sa.sa_handler = catchsig;
446       sigaction(SIGALRM, &sa, NULL);
447       alarm(pinentry->timeout);
448     }
449 #endif
450
451   if (pinentry->ttyname)
452     {
453       ttyfi = fopen (pinentry->ttyname, "r");
454       if (!ttyfi)
455         rc = -1;
456       else
457         {
458           ttyfo = fopen (pinentry->ttyname, "w");
459           if (!ttyfo)
460             {
461               int err = errno;
462               fclose (ttyfi);
463               errno = err;
464               rc = -1;
465             }
466         }
467     }
468
469   if (! rc)
470     {
471       if (pinentry->pin)
472         rc = password (pinentry, ttyfi, ttyfo);
473       else
474         rc = confirm (pinentry, ttyfi, ttyfo);
475     }
476
477   do_touch_file (pinentry);
478
479   if (pinentry->ttyname)
480     {
481       fclose (ttyfi);
482       fclose (ttyfo);
483     }
484
485   return rc;
486 }
487
488
489 pinentry_cmd_handler_t pinentry_cmd_handler = tty_cmd_handler;
490
491
492 int
493 main (int argc, char *argv[])
494 {
495   pinentry_init ("pinentry-tty");
496
497   /* Consumes all arguments.  */
498   pinentry_parse_opts(argc, argv);
499
500   if (pinentry_loop ())
501     return 1;
502
503   return 0;
504 }