/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugins.d/password-prompt.c

  • Committer: Björn Påhlsson
  • Date: 2008-01-18 21:18:26 UTC
  • mto: This revision was merged to the branch mainline in revision 6.
  • Revision ID: belorn@legolas-20080118211826-5rbwo54l4bwim5x2
Client:
        [Working version in initrd for booting]
        Added #ifdef DEBUG statements through out the program
        Added support to keep bouth tcp and udp up at the same time
        Catching several more error return codes that was unchecked.
        Starts the Network interface during startup.
        Added support for entering password on console
        Added error handling, like looping until a password has been received.
        Added cleanup handling so console state is always restored
                
removed:
        Old server.cpp [see next version]
        Test certificates

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
2
 
/*
3
 
 * Password-prompt - Read a password from the terminal and print it
4
 
 * 
5
 
 * Copyright © 2008-2019 Teddy Hogeborn
6
 
 * Copyright © 2008-2019 Björn Påhlsson
7
 
 * 
8
 
 * This file is part of Mandos.
9
 
 * 
10
 
 * Mandos is free software: you can redistribute it and/or modify it
11
 
 * under the terms of the GNU General Public License as published by
12
 
 * the Free Software Foundation, either version 3 of the License, or
13
 
 * (at your option) any later version.
14
 
 * 
15
 
 * Mandos is distributed in the hope that it will be useful, but
16
 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
17
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 
 * General Public License for more details.
19
 
 * 
20
 
 * You should have received a copy of the GNU General Public License
21
 
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
22
 
 * 
23
 
 * Contact the authors at <mandos@recompile.se>.
24
 
 */
25
 
 
26
 
#define _GNU_SOURCE             /* getline(), asprintf() */
27
 
 
28
 
#include <termios.h>            /* struct termios, tcsetattr(),
29
 
                                   TCSAFLUSH, tcgetattr(), ECHO */
30
 
#include <unistd.h>             /* access(), struct termios,
31
 
                                   tcsetattr(), STDIN_FILENO,
32
 
                                   TCSAFLUSH, tcgetattr(), ECHO,
33
 
                                   readlink() */
34
 
#include <signal.h>             /* sig_atomic_t, raise(), struct
35
 
                                   sigaction, sigemptyset(),
36
 
                                   sigaction(), sigaddset(), SIGINT,
37
 
                                   SIGQUIT, SIGHUP, SIGTERM,
38
 
                                   raise() */
39
 
#include <stddef.h>             /* NULL, size_t, ssize_t */
40
 
#include <sys/types.h>          /* ssize_t, struct dirent, pid_t,
41
 
                                   ssize_t, open() */
42
 
#include <stdlib.h>             /* EXIT_SUCCESS, EXIT_FAILURE,
43
 
                                   getenv(), free() */
44
 
#include <dirent.h>             /* scandir(), alphasort() */
45
 
#include <stdio.h>              /* fprintf(), stderr, getline(),
46
 
                                   stdin, feof(), fputc(), vfprintf(),
47
 
                                   vasprintf() */
48
 
#include <errno.h>              /* errno, EBADF, ENOTTY, EINVAL,
49
 
                                   EFAULT, EFBIG, EIO, ENOSPC, EINTR
50
 
                                */
51
 
#include <error.h>              /* error() */
52
 
#include <iso646.h>             /* or, not */
53
 
#include <stdbool.h>            /* bool, false, true */
54
 
#include <inttypes.h>           /* strtoumax() */
55
 
#include <sys/stat.h>           /* struct stat, lstat(), open() */
56
 
#include <string.h>             /* strlen, rindex, memcmp, strerror()
57
 
                                 */
58
 
#include <argp.h>               /* struct argp_option, struct
59
 
                                   argp_state, struct argp,
60
 
                                   argp_parse(), error_t,
61
 
                                   ARGP_KEY_ARG, ARGP_KEY_END,
62
 
                                   ARGP_ERR_UNKNOWN */
63
 
#include <sysexits.h>           /* EX_SOFTWARE, EX_OSERR,
64
 
                                   EX_UNAVAILABLE, EX_IOERR, EX_OK */
65
 
#include <fcntl.h>              /* open() */
66
 
#include <stdarg.h>             /* va_list, va_start(), ... */
67
 
 
68
 
volatile sig_atomic_t quit_now = 0;
69
 
int signal_received;
70
 
bool debug = false;
71
 
const char *argp_program_version = "password-prompt " VERSION;
72
 
const char *argp_program_bug_address = "<mandos@recompile.se>";
73
 
 
74
 
/* Needed for conflict resolution */
75
 
const char plymouth_name[] = "plymouthd";
76
 
 
77
 
/* Function to use when printing errors */
78
 
__attribute__((format (gnu_printf, 3, 4)))
79
 
void error_plus(int status, int errnum, const char *formatstring,
80
 
                ...){
81
 
  va_list ap;
82
 
  char *text;
83
 
  int ret;
84
 
  
85
 
  va_start(ap, formatstring);
86
 
  ret = vasprintf(&text, formatstring, ap);
87
 
  if(ret == -1){
88
 
    fprintf(stderr, "Mandos plugin %s: ",
89
 
            program_invocation_short_name);
90
 
    vfprintf(stderr, formatstring, ap);
91
 
    fprintf(stderr, ": %s\n", strerror(errnum));
92
 
    error(status, errno, "vasprintf while printing error");
93
 
    return;
94
 
  }
95
 
  fprintf(stderr, "Mandos plugin ");
96
 
  error(status, errnum, "%s", text);
97
 
  free(text);
98
 
}
99
 
 
100
 
static void termination_handler(int signum){
101
 
  if(quit_now){
102
 
    return;
103
 
  }
104
 
  quit_now = 1;
105
 
  signal_received = signum;
106
 
}
107
 
 
108
 
bool conflict_detection(void){
109
 
 
110
 
  /* plymouth conflicts with password-prompt since both want to read
111
 
     from the terminal.  Password-prompt will exit if it detects
112
 
     plymouth since plymouth performs the same functionality.
113
 
   */
114
 
  if(access("/run/plymouth/pid", R_OK) == 0){
115
 
    return true;
116
 
  }
117
 
  
118
 
  __attribute__((nonnull))
119
 
  int is_plymouth(const struct dirent *proc_entry){
120
 
    int ret;
121
 
    int cl_fd;
122
 
    {
123
 
      uintmax_t proc_id;
124
 
      char *tmp;
125
 
      errno = 0;
126
 
      proc_id = strtoumax(proc_entry->d_name, &tmp, 10);
127
 
      
128
 
      if(errno != 0 or *tmp != '\0'
129
 
         or proc_id != (uintmax_t)((pid_t)proc_id)){
130
 
        return 0;
131
 
      }
132
 
    }
133
 
    
134
 
    char *cmdline_filename;
135
 
    ret = asprintf(&cmdline_filename, "/proc/%s/cmdline",
136
 
                   proc_entry->d_name);
137
 
    if(ret == -1){
138
 
      error_plus(0, errno, "asprintf");
139
 
      return 0;
140
 
    }
141
 
    
142
 
    /* Open /proc/<pid>/cmdline */
143
 
    cl_fd = open(cmdline_filename, O_RDONLY);
144
 
    free(cmdline_filename);
145
 
    if(cl_fd == -1){
146
 
      if(errno != ENOENT){
147
 
        error_plus(0, errno, "open");
148
 
      }
149
 
      return 0;
150
 
    }
151
 
    
152
 
    char *cmdline = NULL;
153
 
    {
154
 
      size_t cmdline_len = 0;
155
 
      size_t cmdline_allocated = 0;
156
 
      char *tmp;
157
 
      const size_t blocksize = 1024;
158
 
      ssize_t sret;
159
 
      do {
160
 
        /* Allocate more space? */
161
 
        if(cmdline_len + blocksize + 1 > cmdline_allocated){
162
 
          tmp = realloc(cmdline, cmdline_allocated + blocksize + 1);
163
 
          if(tmp == NULL){
164
 
            error_plus(0, errno, "realloc");
165
 
            free(cmdline);
166
 
            close(cl_fd);
167
 
            return 0;
168
 
          }
169
 
          cmdline = tmp;
170
 
          cmdline_allocated += blocksize;
171
 
        }
172
 
        
173
 
        /* Read data */
174
 
        sret = read(cl_fd, cmdline + cmdline_len,
175
 
                    cmdline_allocated - cmdline_len);
176
 
        if(sret == -1){
177
 
          error_plus(0, errno, "read");
178
 
          free(cmdline);
179
 
          close(cl_fd);
180
 
          return 0;
181
 
        }
182
 
        cmdline_len += (size_t)sret;
183
 
      } while(sret != 0);
184
 
      ret = close(cl_fd);
185
 
      if(ret == -1){
186
 
        error_plus(0, errno, "close");
187
 
        free(cmdline);
188
 
        return 0;
189
 
      }
190
 
      cmdline[cmdline_len] = '\0'; /* Make sure it is terminated */
191
 
    }
192
 
    /* we now have cmdline */
193
 
    
194
 
    /* get basename */
195
 
    char *cmdline_base = strrchr(cmdline, '/');
196
 
    if(cmdline_base != NULL){
197
 
      cmdline_base += 1;                /* skip the slash */
198
 
    } else {
199
 
      cmdline_base = cmdline;
200
 
    }
201
 
    
202
 
    if(strcmp(cmdline_base, plymouth_name) != 0){
203
 
      if(debug){
204
 
        fprintf(stderr, "\"%s\" is not \"%s\"\n", cmdline_base,
205
 
                plymouth_name);
206
 
      }
207
 
      free(cmdline);
208
 
      return 0;
209
 
    }
210
 
    if(debug){
211
 
      fprintf(stderr, "\"%s\" equals \"%s\"\n", cmdline_base,
212
 
              plymouth_name);
213
 
    }
214
 
    free(cmdline);
215
 
    return 1;
216
 
  }
217
 
  
218
 
  struct dirent **direntries = NULL;
219
 
  int ret;
220
 
  ret = scandir("/proc", &direntries, is_plymouth, alphasort);
221
 
  if(ret == -1){
222
 
    error_plus(1, errno, "scandir");
223
 
  }
224
 
  {
225
 
    int i = ret;
226
 
    while(i--){
227
 
      free(direntries[i]);
228
 
    }
229
 
  }
230
 
  free(direntries);
231
 
  return ret > 0;
232
 
}
233
 
 
234
 
 
235
 
int main(int argc, char **argv){
236
 
  ssize_t sret;
237
 
  int ret;
238
 
  size_t n;
239
 
  struct termios t_new, t_old;
240
 
  char *buffer = NULL;
241
 
  char *prefix = NULL;
242
 
  char *prompt = NULL;
243
 
  int status = EXIT_SUCCESS;
244
 
  struct sigaction old_action,
245
 
    new_action = { .sa_handler = termination_handler,
246
 
                   .sa_flags = 0 };
247
 
  {
248
 
    struct argp_option options[] = {
249
 
      { .name = "prefix", .key = 'p',
250
 
        .arg = "PREFIX", .flags = 0,
251
 
        .doc = "Prefix shown before the prompt", .group = 2 },
252
 
      { .name = "prompt", .key = 129,
253
 
        .arg = "PROMPT", .flags = 0,
254
 
        .doc = "The prompt to show", .group = 2 },
255
 
      { .name = "debug", .key = 128,
256
 
        .doc = "Debug mode", .group = 3 },
257
 
      /*
258
 
       * These reproduce what we would get without ARGP_NO_HELP
259
 
       */
260
 
      { .name = "help", .key = '?',
261
 
        .doc = "Give this help list", .group = -1 },
262
 
      { .name = "usage", .key = -3,
263
 
        .doc = "Give a short usage message", .group = -1 },
264
 
      { .name = "version", .key = 'V',
265
 
        .doc = "Print program version", .group = -1 },
266
 
      { .name = NULL }
267
 
    };
268
 
    
269
 
    __attribute__((nonnull(3)))
270
 
    error_t parse_opt (int key, char *arg, struct argp_state *state){
271
 
      errno = 0;
272
 
      switch (key){
273
 
      case 'p':                 /* --prefix */
274
 
        prefix = arg;
275
 
        break;
276
 
      case 128:                 /* --debug */
277
 
        debug = true;
278
 
        break;
279
 
      case 129:                 /* --prompt */
280
 
        prompt = arg;
281
 
        break;
282
 
        /*
283
 
         * These reproduce what we would get without ARGP_NO_HELP
284
 
         */
285
 
      case '?':                 /* --help */
286
 
        argp_state_help(state, state->out_stream,
287
 
                        (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
288
 
                        & ~(unsigned int)ARGP_HELP_EXIT_OK);
289
 
        __builtin_unreachable();
290
 
      case -3:                  /* --usage */
291
 
        argp_state_help(state, state->out_stream,
292
 
                        ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
293
 
        __builtin_unreachable();
294
 
      case 'V':                 /* --version */
295
 
        fprintf(state->out_stream, "%s\n", argp_program_version);
296
 
        exit(argp_err_exit_status);
297
 
        break;
298
 
      default:
299
 
        return ARGP_ERR_UNKNOWN;
300
 
      }
301
 
      return errno;
302
 
    }
303
 
    
304
 
    struct argp argp = { .options = options, .parser = parse_opt,
305
 
                         .args_doc = "",
306
 
                         .doc = "Mandos password-prompt -- Read and"
307
 
                         " output a password" };
308
 
    ret = argp_parse(&argp, argc, argv,
309
 
                     ARGP_IN_ORDER | ARGP_NO_HELP, NULL, NULL);
310
 
    switch(ret){
311
 
    case 0:
312
 
      break;
313
 
    case ENOMEM:
314
 
    default:
315
 
      errno = ret;
316
 
      error_plus(0, errno, "argp_parse");
317
 
      return EX_OSERR;
318
 
    case EINVAL:
319
 
      return EX_USAGE;
320
 
    }
321
 
  }
322
 
  
323
 
  if(debug){
324
 
    fprintf(stderr, "Starting %s\n", argv[0]);
325
 
  }
326
 
 
327
 
  if(conflict_detection()){
328
 
    if(debug){
329
 
      fprintf(stderr, "Stopping %s because of conflict\n", argv[0]);
330
 
    }
331
 
    return EXIT_FAILURE;
332
 
  }
333
 
  
334
 
  if(debug){
335
 
    fprintf(stderr, "Storing current terminal attributes\n");
336
 
  }
337
 
  
338
 
  if(tcgetattr(STDIN_FILENO, &t_old) != 0){
339
 
    int e = errno;
340
 
    error_plus(0, errno, "tcgetattr");
341
 
    switch(e){
342
 
    case EBADF:
343
 
    case ENOTTY:
344
 
      return EX_UNAVAILABLE;
345
 
    default:
346
 
      return EX_OSERR;
347
 
    }
348
 
  }
349
 
  
350
 
  sigemptyset(&new_action.sa_mask);
351
 
  ret = sigaddset(&new_action.sa_mask, SIGINT);
352
 
  if(ret == -1){
353
 
    error_plus(0, errno, "sigaddset");
354
 
    return EX_OSERR;
355
 
  }
356
 
  ret = sigaddset(&new_action.sa_mask, SIGHUP);
357
 
  if(ret == -1){
358
 
    error_plus(0, errno, "sigaddset");
359
 
    return EX_OSERR;
360
 
  }
361
 
  ret = sigaddset(&new_action.sa_mask, SIGTERM);
362
 
  if(ret == -1){
363
 
    error_plus(0, errno, "sigaddset");
364
 
    return EX_OSERR;
365
 
  }
366
 
  /* Need to check if the handler is SIG_IGN before handling:
367
 
     | [[info:libc:Initial Signal Actions]] |
368
 
     | [[info:libc:Basic Signal Handling]]  |
369
 
  */
370
 
  ret = sigaction(SIGINT, NULL, &old_action);
371
 
  if(ret == -1){
372
 
    error_plus(0, errno, "sigaction");
373
 
    return EX_OSERR;
374
 
  }
375
 
  if(old_action.sa_handler != SIG_IGN){
376
 
    ret = sigaction(SIGINT, &new_action, NULL);
377
 
    if(ret == -1){
378
 
      error_plus(0, errno, "sigaction");
379
 
      return EX_OSERR;
380
 
    }
381
 
  }
382
 
  ret = sigaction(SIGHUP, NULL, &old_action);
383
 
  if(ret == -1){
384
 
    error_plus(0, errno, "sigaction");
385
 
    return EX_OSERR;
386
 
  }
387
 
  if(old_action.sa_handler != SIG_IGN){
388
 
    ret = sigaction(SIGHUP, &new_action, NULL);
389
 
    if(ret == -1){
390
 
      error_plus(0, errno, "sigaction");
391
 
      return EX_OSERR;
392
 
    }
393
 
  }
394
 
  ret = sigaction(SIGTERM, NULL, &old_action);
395
 
  if(ret == -1){
396
 
    error_plus(0, errno, "sigaction");
397
 
    return EX_OSERR;
398
 
  }
399
 
  if(old_action.sa_handler != SIG_IGN){
400
 
    ret = sigaction(SIGTERM, &new_action, NULL);
401
 
    if(ret == -1){
402
 
      error_plus(0, errno, "sigaction");
403
 
      return EX_OSERR;
404
 
    }
405
 
  }
406
 
  
407
 
  
408
 
  if(debug){
409
 
    fprintf(stderr, "Removing echo flag from terminal attributes\n");
410
 
  }
411
 
  
412
 
  t_new = t_old;
413
 
  t_new.c_lflag &= ~(tcflag_t)ECHO;
414
 
  if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){
415
 
    int e = errno;
416
 
    error_plus(0, errno, "tcsetattr-echo");
417
 
    switch(e){
418
 
    case EBADF:
419
 
    case ENOTTY:
420
 
      return EX_UNAVAILABLE;
421
 
    case EINVAL:
422
 
    default:
423
 
      return EX_OSERR;
424
 
    }
425
 
  }
426
 
  
427
 
  if(debug){
428
 
    fprintf(stderr, "Waiting for input from stdin \n");
429
 
  }
430
 
  while(true){
431
 
    if(quit_now){
432
 
      if(debug){
433
 
        fprintf(stderr, "Interrupted by signal, exiting.\n");
434
 
      }
435
 
      status = EXIT_FAILURE;
436
 
      break;
437
 
    }
438
 
 
439
 
    if(prefix){
440
 
      fprintf(stderr, "%s ", prefix);
441
 
    }
442
 
    if(prompt != NULL){
443
 
      fprintf(stderr, "%s: ", prompt);
444
 
    } else {
445
 
      const char *cryptsource = getenv("CRYPTTAB_SOURCE");
446
 
      const char *crypttarget = getenv("CRYPTTAB_NAME");
447
 
      /* Before cryptsetup 1.1.0~rc2 */
448
 
      if(cryptsource == NULL){
449
 
        cryptsource = getenv("cryptsource");
450
 
      }
451
 
      if(crypttarget == NULL){
452
 
        crypttarget = getenv("crypttarget");
453
 
      }
454
 
      const char *const prompt1 = "Unlocking the disk";
455
 
      const char *const prompt2 = "Enter passphrase";
456
 
      if(cryptsource == NULL){
457
 
        if(crypttarget == NULL){
458
 
          fprintf(stderr, "%s to unlock the disk: ", prompt2);
459
 
        } else {
460
 
          fprintf(stderr, "%s (%s)\n%s: ", prompt1, crypttarget,
461
 
                  prompt2);
462
 
        }
463
 
      } else {
464
 
        if(crypttarget == NULL){
465
 
          fprintf(stderr, "%s %s\n%s: ", prompt1, cryptsource,
466
 
                  prompt2);
467
 
        } else {
468
 
          fprintf(stderr, "%s %s (%s)\n%s: ", prompt1, cryptsource,
469
 
                  crypttarget, prompt2);
470
 
        }
471
 
      }
472
 
    }
473
 
    sret = getline(&buffer, &n, stdin);
474
 
    if(sret > 0){
475
 
      status = EXIT_SUCCESS;
476
 
      /* Make n = data size instead of allocated buffer size */
477
 
      n = (size_t)sret;
478
 
      /* Strip final newline */
479
 
      if(n > 0 and buffer[n-1] == '\n'){
480
 
        buffer[n-1] = '\0';     /* not strictly necessary */
481
 
        n--;
482
 
      }
483
 
      size_t written = 0;
484
 
      while(written < n){
485
 
        sret = write(STDOUT_FILENO, buffer + written, n - written);
486
 
        if(sret < 0){
487
 
          int e = errno;
488
 
          error_plus(0, errno, "write");
489
 
          switch(e){
490
 
          case EBADF:
491
 
          case EFAULT:
492
 
          case EINVAL:
493
 
          case EFBIG:
494
 
          case EIO:
495
 
          case ENOSPC:
496
 
          default:
497
 
            status = EX_IOERR;
498
 
            break;
499
 
          case EINTR:
500
 
            status = EXIT_FAILURE;
501
 
            break;
502
 
          }
503
 
          break;
504
 
        }
505
 
        written += (size_t)sret;
506
 
      }
507
 
      sret = close(STDOUT_FILENO);
508
 
      if(sret == -1){
509
 
        int e = errno;
510
 
        error_plus(0, errno, "close");
511
 
        switch(e){
512
 
        case EBADF:
513
 
          status = EX_OSFILE;
514
 
          break;
515
 
        case EIO:
516
 
        default:
517
 
          status = EX_IOERR;
518
 
          break;
519
 
        }
520
 
      }
521
 
      break;
522
 
    }
523
 
    if(sret < 0){
524
 
      int e = errno;
525
 
      if(errno != EINTR){
526
 
        if(not feof(stdin)){
527
 
          error_plus(0, errno, "getline");
528
 
          switch(e){
529
 
          case EBADF:
530
 
            status = EX_UNAVAILABLE;
531
 
            break;
532
 
          case EIO:
533
 
          case EINVAL:
534
 
          default:
535
 
            status = EX_IOERR;
536
 
            break;
537
 
          }
538
 
          break;
539
 
        } else {
540
 
          clearerr(stdin);
541
 
        }
542
 
      }
543
 
    }
544
 
    /* if(sret == 0), then the only sensible thing to do is to retry
545
 
       to read from stdin */
546
 
    fputc('\n', stderr);
547
 
    if(debug and not quit_now){
548
 
      /* If quit_now is nonzero, we were interrupted by a signal, and
549
 
         will print that later, so no need to show this too. */
550
 
      fprintf(stderr, "getline() returned 0, retrying.\n");
551
 
    }
552
 
  }
553
 
  
554
 
  free(buffer);
555
 
  
556
 
  if(debug){
557
 
    fprintf(stderr, "Restoring terminal attributes\n");
558
 
  }
559
 
  if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){
560
 
    error_plus(0, errno, "tcsetattr+echo");
561
 
  }
562
 
  
563
 
  if(quit_now){
564
 
    sigemptyset(&old_action.sa_mask);
565
 
    old_action.sa_handler = SIG_DFL;
566
 
    ret = sigaction(signal_received, &old_action, NULL);
567
 
    if(ret == -1){
568
 
      error_plus(0, errno, "sigaction");
569
 
    }
570
 
    raise(signal_received);
571
 
  }
572
 
  
573
 
  if(debug){
574
 
    fprintf(stderr, "%s is exiting with status %d\n", argv[0],
575
 
            status);
576
 
  }
577
 
  if(status == EXIT_SUCCESS or status == EX_OK){
578
 
    fputc('\n', stderr);
579
 
  }
580
 
  
581
 
  return status;
582
 
}