/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: teddy at recompile
  • Date: 2020-04-05 21:30:59 UTC
  • Revision ID: teddy@recompile.se-20200405213059-fb2a61ckqynrmatk
Fix file descriptor leak in mandos-client

When the local network has Mandos servers announcing themselves using
real, globally reachable, IPv6 addresses (i.e. not link-local
addresses), but there is no router on the local network providing IPv6
RA (Router Advertisement) packets, the client cannot reach the server
by normal means, since the client only has a link-local IPv6 address,
and has no usable route to reach the server's global IPv6 address.
(This is not a common situation, and usually only happens when the
router itself reboots and runs a Mandos client, since it cannot then
give RA packets to itself.)  The client code has a solution for
this, which consists of adding a temporary local route to reach the
address of the server during communication, and removing this
temporary route afterwards.

This solution with a temporary route works, but has a file descriptor
leak; it leaks one file descriptor for each addition and for each
removal of a route.  If one server requiring an added route is present
on the network, but no servers gives a password, making the client
retry after the default ten seconds, and we furthermore assume a
default 1024 open files limit, the client runs out of file descriptors
after about 90 minutes, after which time the client process will be
useless and fail to retrieve any passwords, necessitating manual
password entry via the keyboard.

Fix this by eliminating the file descriptor leak in the client.

* plugins.d/mandos-client.c (add_delete_local_route): Do
  close(devnull) also in parent process, also if fork() fails, and on
  any failure in child process.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Password-prompt - Read a password from the terminal and print it
4
4
 * 
5
 
 * Copyright © 2008-2010 Teddy Hogeborn
6
 
 * Copyright © 2008-2010 Björn Påhlsson
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
 
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
14
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
18
 * General Public License for more details.
17
19
 * 
18
20
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program.  If not, see
20
 
 * <http://www.gnu.org/licenses/>.
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
21
22
 * 
22
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
23
 * Contact the authors at <mandos@recompile.se>.
23
24
 */
24
25
 
25
 
#define _GNU_SOURCE             /* getline() */
 
26
#define _GNU_SOURCE             /* getline(), asprintf() */
26
27
 
27
 
#include <termios.h>            /* struct termios, tcsetattr(),
 
28
#include <termios.h>            /* struct termios, tcsetattr(),
28
29
                                   TCSAFLUSH, tcgetattr(), ECHO */
29
 
#include <unistd.h>             /* struct termios, tcsetattr(),
30
 
                                   STDIN_FILENO, TCSAFLUSH,
31
 
                                   tcgetattr(), ECHO */
 
30
#include <unistd.h>             /* access(), struct termios,
 
31
                                   tcsetattr(), STDIN_FILENO,
 
32
                                   TCSAFLUSH, tcgetattr(), ECHO,
 
33
                                   readlink() */
32
34
#include <signal.h>             /* sig_atomic_t, raise(), struct
33
35
                                   sigaction, sigemptyset(),
34
36
                                   sigaction(), sigaddset(), SIGINT,
35
37
                                   SIGQUIT, SIGHUP, SIGTERM,
36
38
                                   raise() */
37
39
#include <stddef.h>             /* NULL, size_t, ssize_t */
38
 
#include <sys/types.h>          /* ssize_t */
 
40
#include <sys/types.h>          /* ssize_t, struct dirent, pid_t,
 
41
                                   ssize_t, open() */
39
42
#include <stdlib.h>             /* EXIT_SUCCESS, EXIT_FAILURE,
40
 
                                   getenv() */
 
43
                                   getenv(), free() */
 
44
#include <dirent.h>             /* scandir(), alphasort() */
41
45
#include <stdio.h>              /* fprintf(), stderr, getline(),
42
 
                                   stdin, feof(), fputc()
43
 
                                */
 
46
                                   stdin, feof(), fputc(), vfprintf(),
 
47
                                   vasprintf() */
44
48
#include <errno.h>              /* errno, EBADF, ENOTTY, EINVAL,
45
49
                                   EFAULT, EFBIG, EIO, ENOSPC, EINTR
46
50
                                */
47
51
#include <error.h>              /* error() */
48
52
#include <iso646.h>             /* or, not */
49
53
#include <stdbool.h>            /* bool, false, true */
50
 
#include <string.h>             /* strlen, rindex */
 
54
#include <inttypes.h>           /* strtoumax() */
 
55
#include <sys/stat.h>           /* struct stat, lstat(), open() */
 
56
#include <string.h>             /* strlen, rindex, memcmp, strerror()
 
57
                                 */
51
58
#include <argp.h>               /* struct argp_option, struct
52
59
                                   argp_state, struct argp,
53
60
                                   argp_parse(), error_t,
55
62
                                   ARGP_ERR_UNKNOWN */
56
63
#include <sysexits.h>           /* EX_SOFTWARE, EX_OSERR,
57
64
                                   EX_UNAVAILABLE, EX_IOERR, EX_OK */
 
65
#include <fcntl.h>              /* open() */
 
66
#include <stdarg.h>             /* va_list, va_start(), ... */
58
67
 
59
68
volatile sig_atomic_t quit_now = 0;
60
69
int signal_received;
61
70
bool debug = false;
62
71
const char *argp_program_version = "password-prompt " VERSION;
63
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
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
}
64
99
 
65
100
static void termination_handler(int signum){
66
101
  if(quit_now){
70
105
  signal_received = signum;
71
106
}
72
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
 
73
235
int main(int argc, char **argv){
74
236
  ssize_t sret;
75
237
  int ret;
77
239
  struct termios t_new, t_old;
78
240
  char *buffer = NULL;
79
241
  char *prefix = NULL;
 
242
  char *prompt = NULL;
80
243
  int status = EXIT_SUCCESS;
81
244
  struct sigaction old_action,
82
245
    new_action = { .sa_handler = termination_handler,
86
249
      { .name = "prefix", .key = 'p',
87
250
        .arg = "PREFIX", .flags = 0,
88
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 },
89
255
      { .name = "debug", .key = 128,
90
256
        .doc = "Debug mode", .group = 3 },
91
257
      /*
100
266
      { .name = NULL }
101
267
    };
102
268
    
 
269
    __attribute__((nonnull(3)))
103
270
    error_t parse_opt (int key, char *arg, struct argp_state *state){
104
271
      errno = 0;
105
272
      switch (key){
106
 
      case 'p':
 
273
      case 'p':                 /* --prefix */
107
274
        prefix = arg;
108
275
        break;
109
 
      case 128:
 
276
      case 128:                 /* --debug */
110
277
        debug = true;
111
278
        break;
 
279
      case 129:                 /* --prompt */
 
280
        prompt = arg;
 
281
        break;
112
282
        /*
113
283
         * These reproduce what we would get without ARGP_NO_HELP
114
284
         */
116
286
        argp_state_help(state, state->out_stream,
117
287
                        (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
118
288
                        & ~(unsigned int)ARGP_HELP_EXIT_OK);
 
289
        __builtin_unreachable();
119
290
      case -3:                  /* --usage */
120
291
        argp_state_help(state, state->out_stream,
121
292
                        ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
 
293
        __builtin_unreachable();
122
294
      case 'V':                 /* --version */
123
295
        fprintf(state->out_stream, "%s\n", argp_program_version);
124
296
        exit(argp_err_exit_status);
141
313
    case ENOMEM:
142
314
    default:
143
315
      errno = ret;
144
 
      error(0, errno, "argp_parse");
 
316
      error_plus(0, errno, "argp_parse");
145
317
      return EX_OSERR;
146
318
    case EINVAL:
147
319
      return EX_USAGE;
151
323
  if(debug){
152
324
    fprintf(stderr, "Starting %s\n", argv[0]);
153
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
  
154
334
  if(debug){
155
335
    fprintf(stderr, "Storing current terminal attributes\n");
156
336
  }
157
337
  
158
338
  if(tcgetattr(STDIN_FILENO, &t_old) != 0){
159
339
    int e = errno;
160
 
    error(0, errno, "tcgetattr");
 
340
    error_plus(0, errno, "tcgetattr");
161
341
    switch(e){
162
342
    case EBADF:
163
343
    case ENOTTY:
170
350
  sigemptyset(&new_action.sa_mask);
171
351
  ret = sigaddset(&new_action.sa_mask, SIGINT);
172
352
  if(ret == -1){
173
 
    error(0, errno, "sigaddset");
 
353
    error_plus(0, errno, "sigaddset");
174
354
    return EX_OSERR;
175
355
  }
176
356
  ret = sigaddset(&new_action.sa_mask, SIGHUP);
177
357
  if(ret == -1){
178
 
    error(0, errno, "sigaddset");
 
358
    error_plus(0, errno, "sigaddset");
179
359
    return EX_OSERR;
180
360
  }
181
361
  ret = sigaddset(&new_action.sa_mask, SIGTERM);
182
362
  if(ret == -1){
183
 
    error(0, errno, "sigaddset");
 
363
    error_plus(0, errno, "sigaddset");
184
364
    return EX_OSERR;
185
365
  }
186
366
  /* Need to check if the handler is SIG_IGN before handling:
189
369
  */
190
370
  ret = sigaction(SIGINT, NULL, &old_action);
191
371
  if(ret == -1){
192
 
    error(0, errno, "sigaction");
 
372
    error_plus(0, errno, "sigaction");
193
373
    return EX_OSERR;
194
374
  }
195
375
  if(old_action.sa_handler != SIG_IGN){
196
376
    ret = sigaction(SIGINT, &new_action, NULL);
197
377
    if(ret == -1){
198
 
      error(0, errno, "sigaction");
 
378
      error_plus(0, errno, "sigaction");
199
379
      return EX_OSERR;
200
380
    }
201
381
  }
202
382
  ret = sigaction(SIGHUP, NULL, &old_action);
203
383
  if(ret == -1){
204
 
    error(0, errno, "sigaction");
 
384
    error_plus(0, errno, "sigaction");
205
385
    return EX_OSERR;
206
386
  }
207
387
  if(old_action.sa_handler != SIG_IGN){
208
388
    ret = sigaction(SIGHUP, &new_action, NULL);
209
389
    if(ret == -1){
210
 
      error(0, errno, "sigaction");
 
390
      error_plus(0, errno, "sigaction");
211
391
      return EX_OSERR;
212
392
    }
213
393
  }
214
394
  ret = sigaction(SIGTERM, NULL, &old_action);
215
395
  if(ret == -1){
216
 
    error(0, errno, "sigaction");
 
396
    error_plus(0, errno, "sigaction");
217
397
    return EX_OSERR;
218
398
  }
219
399
  if(old_action.sa_handler != SIG_IGN){
220
400
    ret = sigaction(SIGTERM, &new_action, NULL);
221
401
    if(ret == -1){
222
 
      error(0, errno, "sigaction");
 
402
      error_plus(0, errno, "sigaction");
223
403
      return EX_OSERR;
224
404
    }
225
405
  }
233
413
  t_new.c_lflag &= ~(tcflag_t)ECHO;
234
414
  if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){
235
415
    int e = errno;
236
 
    error(0, errno, "tcsetattr-echo");
 
416
    error_plus(0, errno, "tcsetattr-echo");
237
417
    switch(e){
238
418
    case EBADF:
239
419
    case ENOTTY:
259
439
    if(prefix){
260
440
      fprintf(stderr, "%s ", prefix);
261
441
    }
262
 
    {
 
442
    if(prompt != NULL){
 
443
      fprintf(stderr, "%s: ", prompt);
 
444
    } else {
263
445
      const char *cryptsource = getenv("CRYPTTAB_SOURCE");
264
446
      const char *crypttarget = getenv("CRYPTTAB_NAME");
265
447
      /* Before cryptsetup 1.1.0~rc2 */
303
485
        sret = write(STDOUT_FILENO, buffer + written, n - written);
304
486
        if(sret < 0){
305
487
          int e = errno;
306
 
          error(0, errno, "write");
 
488
          error_plus(0, errno, "write");
307
489
          switch(e){
308
490
          case EBADF:
309
491
          case EFAULT:
325
507
      sret = close(STDOUT_FILENO);
326
508
      if(sret == -1){
327
509
        int e = errno;
328
 
        error(0, errno, "close");
 
510
        error_plus(0, errno, "close");
329
511
        switch(e){
330
512
        case EBADF:
331
513
          status = EX_OSFILE;
340
522
    }
341
523
    if(sret < 0){
342
524
      int e = errno;
343
 
      if(errno != EINTR and not feof(stdin)){
344
 
        error(0, errno, "getline");
345
 
        switch(e){
346
 
        case EBADF:
347
 
          status = EX_UNAVAILABLE;
348
 
        case EIO:
349
 
        case EINVAL:
350
 
        default:
351
 
          status = EX_IOERR;
 
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
          }
352
538
          break;
 
539
        } else {
 
540
          clearerr(stdin);
353
541
        }
354
 
        break;
355
542
      }
356
543
    }
357
 
    /* if(sret == 0), then the only sensible thing to do is to retry to
358
 
       read from stdin */
 
544
    /* if(sret == 0), then the only sensible thing to do is to retry
 
545
       to read from stdin */
359
546
    fputc('\n', stderr);
360
547
    if(debug and not quit_now){
361
548
      /* If quit_now is nonzero, we were interrupted by a signal, and
370
557
    fprintf(stderr, "Restoring terminal attributes\n");
371
558
  }
372
559
  if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){
373
 
    error(0, errno, "tcsetattr+echo");
 
560
    error_plus(0, errno, "tcsetattr+echo");
374
561
  }
375
562
  
376
563
  if(quit_now){
378
565
    old_action.sa_handler = SIG_DFL;
379
566
    ret = sigaction(signal_received, &old_action, NULL);
380
567
    if(ret == -1){
381
 
      error(0, errno, "sigaction");
 
568
      error_plus(0, errno, "sigaction");
382
569
    }
383
570
    raise(signal_received);
384
571
  }