tty: correct comments
[pinentry.git] / tty / pinentry-tty.c
1 /* pinentry-tty.c - A minimalist dumb terminal mechanism for PIN entry
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 <https://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 #include <gpg-error.h>
41
42 #include "pinentry.h"
43 #include "memory.h"
44
45 #ifndef HAVE_DOSISH_SYSTEM
46 static int timed_out;
47 #endif
48
49 static struct termios n_term;
50 static struct termios o_term;
51
52 static int
53 cbreak (int fd)
54 {
55   if ((tcgetattr(fd, &o_term)) == -1)
56     return -1;
57   n_term = o_term;
58   n_term.c_lflag = n_term.c_lflag & ~(ECHO|ICANON);
59   n_term.c_cc[VMIN] = 1;
60   n_term.c_cc[VTIME]= 0;
61   if ((tcsetattr(fd, TCSAFLUSH, &n_term)) == -1)
62     return -1;
63   return 1;
64 }
65
66 #define UNDERLINE_START "\033[4m"
67 /* Bold, red.  */
68 #define ALERT_START "\033[1;31m"
69 #define NORMAL_RESTORE "\033[0m"
70
71 static char
72 button (char *text, char *default_text, FILE *ttyfo)
73 {
74   char *highlight;
75
76   if (! text)
77     return 0;
78
79   /* Skip any leading white space.  */
80   while (*text == ' ')
81     text ++;
82
83   highlight = text;
84   while ((highlight = strchr (highlight, '_')))
85     {
86       highlight = highlight + 1;
87       if (*highlight == '_')
88         {
89           /* Escaped underscore.  Skip both characters.  */
90           highlight++;
91           continue;
92         }
93       if (!isalnum (*highlight))
94         /* Unusable accelerator.  */
95         continue;
96       break;
97     }
98
99   if (! highlight)
100     /* Not accelerator.  Take the first alpha-numeric character.  */
101     {
102       highlight = text;
103       while (*highlight && !isalnum (*highlight))
104         highlight ++;
105     }
106
107   if (! *highlight)
108     /* Hmm, no alpha-numeric characters.  */
109     {
110       if (! default_text)
111         return 0;
112       text = highlight = default_text;
113     }
114
115   fputs ("  ", ttyfo);
116   for (; *text; text ++)
117     {
118       /* Skip accelerator prefix.  */
119       if (*text == '_')
120         {
121           text ++;
122           if (! *text)
123             break;
124         }
125
126       if (text == highlight)
127         fputs (UNDERLINE_START, ttyfo);
128       fputc (*text, ttyfo);
129       if (text == highlight)
130         fputs (NORMAL_RESTORE, ttyfo);
131     }
132   fputc ('\n', ttyfo);
133
134   return tolower (*highlight);
135 }
136
137 static void
138 dump_error_text (FILE *ttyfo, const char *text)
139 {
140   int lines = 0;
141
142   if (! text || ! *text)
143     return;
144
145   for (;;)
146     {
147       const char *eol = strchr (text, '\n');
148       if (! eol)
149         eol = text + strlen (text);
150
151       lines ++;
152
153       fwrite ("\n *** ", 6, 1, ttyfo);
154       fputs (ALERT_START, ttyfo);
155       fwrite (text, (size_t) (eol - text), 1, ttyfo);
156       fputs (NORMAL_RESTORE, ttyfo);
157
158       if (! *eol)
159         break;
160
161       text = eol + 1;
162     }
163
164   if (lines > 1)
165     fputc ('\n', ttyfo);
166   else
167     fwrite (" ***\n", 5, 1, ttyfo);
168
169   fputc ('\n', ttyfo);
170 }
171
172 static int
173 confirm (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
174 {
175   char *msg;
176   char *msgbuffer = NULL;
177
178   char ok = 0;
179   char notok = 0;
180   char cancel = 0;
181
182   int ret;
183
184   dump_error_text (ttyfo, pinentry->error);
185
186   msg = pinentry->description;
187   if (! msg)
188     {
189       /* If there is no description, fallback to the title.  */
190       msg = msgbuffer = pinentry_get_title (pinentry);
191     }
192   if (! msg)
193     msg = "Confirm:";
194
195   if (msg)
196     {
197       fputs (msg, ttyfo);
198       fputc ('\n', ttyfo);
199     }
200   free (msgbuffer);
201
202   fflush (ttyfo);
203
204   if (pinentry->ok)
205     ok = button (pinentry->ok, "OK", ttyfo);
206   else if (pinentry->default_ok)
207     ok = button (pinentry->default_ok, "OK", ttyfo);
208   else
209     ok = button ("OK", NULL, ttyfo);
210
211   if (! pinentry->one_button)
212     {
213       if (pinentry->cancel)
214         cancel = button (pinentry->cancel, "Cancel", ttyfo);
215       else if (pinentry->default_cancel)
216         cancel = button (pinentry->default_cancel, "Cancel", ttyfo);
217
218       if (pinentry->notok)
219         notok = button (pinentry->notok, "No", ttyfo);
220     }
221
222   if (cbreak (fileno (ttyfi)) == -1)
223     {
224       int err = errno;
225       fprintf (stderr, "cbreak failure, exiting\n");
226       errno = err;
227       return -1;
228     }
229
230   while (1)
231     {
232       int input;
233
234       if (pinentry->one_button)
235         fprintf (ttyfo, "Press any key to continue.");
236       else
237         {
238           fputc ('[', ttyfo);
239           if (ok)
240             fputc (ok, ttyfo);
241           if (cancel)
242             fputc (cancel, ttyfo);
243           if (notok)
244             fputc (notok, ttyfo);
245           fputs("]? ", ttyfo);
246         }
247       fflush (ttyfo);
248
249       input = fgetc (ttyfi);
250       fprintf (ttyfo, "%c\n", input);
251       input = tolower (input);
252
253       if (input == EOF || input == 0x4)
254         /* End of file or control-d (= end of file).  */
255         {
256           pinentry->close_button = 1;
257
258           pinentry->canceled = 1;
259           ret = 0;
260           break;
261         }
262
263       if (pinentry->one_button)
264         {
265           ret = 1;
266           break;
267         }
268
269       if (cancel && input == cancel)
270         {
271           pinentry->canceled = 1;
272           ret = 0;
273           break;
274         }
275       else if (notok && input == notok)
276         {
277           ret = 0;
278           break;
279         }
280       else if (ok && input == ok)
281         {
282           ret = 1;
283           break;
284         }
285       else
286         {
287           fprintf (ttyfo, "Invalid selection.\n");
288         }
289     }
290
291 #ifndef HAVE_DOSISH_SYSTEM
292   if (timed_out)
293     pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
294 #endif
295
296   tcsetattr (fileno(ttyfi), TCSANOW, &o_term);
297
298   return ret;
299 }
300
301 static char *
302 read_password (FILE *ttyfi, FILE *ttyfo)
303 {
304   int done = 0;
305   int len = 128;
306   int count = 0;
307   char *buffer;
308
309   (void) ttyfo;
310
311   if (cbreak (fileno (ttyfi)) == -1)
312     {
313       int err = errno;
314       fprintf (stderr, "cbreak failure, exiting\n");
315       errno = err;
316       return NULL;
317     }
318
319   buffer = secmem_malloc (len);
320   if (! buffer)
321     return NULL;
322
323   while (!done)
324     {
325       int c;
326
327       if (count == len - 1)
328         /* Double the buffer's size.  Note: we check if count is len -
329            1 and not len so that we always have space for the NUL
330            character.  */
331         {
332           int new_len = 2 * len;
333           char *tmp = secmem_realloc (buffer, new_len);
334           if (! tmp)
335             {
336               secmem_free (tmp);
337               return NULL;
338             }
339           buffer = tmp;
340           len = new_len;
341         }
342
343       c = fgetc (ttyfi);
344       switch (c)
345         {
346         case 0x4: case EOF:
347           /* Control-d (i.e., end of file) or a real EOF.  */
348           done = -1;
349           break;
350
351         case '\n':
352           done = 1;
353           break;
354
355         case 0x7f:
356           /* Backspace.  */
357           if (count > 0)
358             count --;
359           break;
360
361         default:
362           buffer[count ++] = c;
363           break;
364         }
365     }
366   buffer[count] = '\0';
367
368   tcsetattr (fileno(ttyfi), TCSANOW, &o_term);
369
370   if (done == -1)
371     {
372       secmem_free (buffer);
373       return NULL;
374     }
375
376   return buffer;
377 }
378
379
380 static int
381 password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
382 {
383   char *msg;
384   char *msgbuffer = NULL;
385   int done = 0;
386
387   msg = pinentry->description;
388   if (! msg)
389     msg = msgbuffer = pinentry_get_title (pinentry);
390   if (! msg)
391     msg = "Enter your passphrase.";
392
393   dump_error_text (ttyfo, pinentry->error);
394
395   fprintf (ttyfo, "%s\n", msg);
396   free (msgbuffer);
397
398   while (! done)
399     {
400       char *passphrase;
401
402       char *prompt = pinentry->prompt;
403       if (! prompt || !*prompt)
404         prompt = "PIN";
405
406       fprintf (ttyfo, "%s%s ",
407                prompt,
408                /* Make sure the prompt ends in a : or a question mark.  */
409                (prompt[strlen(prompt) - 1] == ':'
410                 || prompt[strlen(prompt) - 1] == '?') ? "" : ":");
411       fflush (ttyfo);
412
413       passphrase = read_password (ttyfi, ttyfo);
414       fputc ('\n', ttyfo);
415       if (! passphrase)
416         {
417           done = -1;
418           break;
419         }
420
421       if (! pinentry->repeat_passphrase)
422         done = 1;
423       else
424         {
425           char *passphrase2;
426
427           prompt = pinentry->repeat_passphrase;
428           fprintf (ttyfo, "%s%s ",
429                    prompt,
430                    /* Make sure the prompt ends in a : or a question mark.  */
431                    (prompt[strlen(prompt) - 1] == ':'
432                     || prompt[strlen(prompt) - 1] == '?') ? "" : ":");
433           fflush (ttyfo);
434
435           passphrase2 = read_password (ttyfi, ttyfo);
436           fputc ('\n', ttyfo);
437           if (! passphrase2)
438             {
439               done = -1;
440               break;
441             }
442
443           if (strcmp (passphrase, passphrase2) == 0)
444             {
445               pinentry->repeat_okay = 1;
446               done = 1;
447             }
448           else
449             dump_error_text (ttyfo,
450                              pinentry->repeat_error_string
451                              ?: "Passphrases don't match.");
452
453           secmem_free (passphrase2);
454         }
455
456       if (done == 1)
457         pinentry_setbuffer_use (pinentry, passphrase, 0);
458       else
459         secmem_free (passphrase);
460     }
461
462 #ifndef HAVE_DOSISH_SYSTEM
463   if (timed_out)
464     pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
465 #endif
466
467   return done;
468 }
469
470
471 /* If a touch has been registered, touch that file.  */
472 static void
473 do_touch_file(pinentry_t pinentry)
474 {
475 #ifdef HAVE_UTIME_H
476   struct stat st;
477   time_t tim;
478
479   if (!pinentry->touch_file || !*pinentry->touch_file)
480     return;
481
482   if (stat(pinentry->touch_file, &st))
483     return; /* Oops.  */
484
485   /* Make sure that we actually update the mtime.  */
486   while ((tim = time(NULL)) == st.st_mtime)
487     sleep(1);
488
489   /* Update but ignore errors as we can't do anything in that case.
490      Printing error messages may even clubber the display further. */
491   utime (pinentry->touch_file, NULL);
492 #endif /*HAVE_UTIME_H*/
493 }
494
495 #ifndef HAVE_DOSISH_SYSTEM
496 static void
497 catchsig(int sig)
498 {
499   if (sig == SIGALRM)
500     timed_out = 1;
501 }
502 #endif
503
504 int
505 tty_cmd_handler(pinentry_t pinentry)
506 {
507   int rc = 0;
508   FILE *ttyfi = stdin;
509   FILE *ttyfo = stdout;
510
511 #ifndef HAVE_DOSISH_SYSTEM
512   timed_out = 0;
513
514   if (pinentry->timeout)
515     {
516       struct sigaction sa;
517
518       memset(&sa, 0, sizeof(sa));
519       sa.sa_handler = catchsig;
520       sigaction(SIGALRM, &sa, NULL);
521       alarm(pinentry->timeout);
522     }
523 #endif
524
525   if (pinentry->ttyname)
526     {
527       ttyfi = fopen (pinentry->ttyname, "r");
528       if (!ttyfi)
529         rc = -1;
530       else
531         {
532           ttyfo = fopen (pinentry->ttyname, "w");
533           if (!ttyfo)
534             {
535               int err = errno;
536               fclose (ttyfi);
537               errno = err;
538               rc = -1;
539             }
540         }
541     }
542
543   if (! rc)
544     {
545       if (pinentry->pin)
546         rc = password (pinentry, ttyfi, ttyfo);
547       else
548         rc = confirm (pinentry, ttyfi, ttyfo);
549     }
550
551   do_touch_file (pinentry);
552
553   if (pinentry->ttyname)
554     {
555       fclose (ttyfi);
556       fclose (ttyfo);
557     }
558
559   return rc;
560 }
561
562
563 pinentry_cmd_handler_t pinentry_cmd_handler = tty_cmd_handler;
564
565
566
567 int
568 main (int argc, char *argv[])
569 {
570   pinentry_init ("pinentry-tty");
571
572   /* Consumes all arguments.  */
573   pinentry_parse_opts(argc, argv);
574
575   if (pinentry_loop ())
576     return 1;
577
578   return 0;
579 }