/mandos/release

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

« back to all changes in this revision

Viewing changes to plugins.d/splashy.c

  • Committer: Teddy Hogeborn
  • Date: 2021-02-03 23:10:42 UTC
  • mto: This revision was merged to the branch mainline in revision 406.
  • Revision ID: teddy@recompile.se-20210203231042-2z3egrvpo1zt7nej
mandos-ctl: Fix bad test for command.Remove and related minor issues

The test for command.Remove removes all clients from the spy server,
and then loops over all clients, looking for the corresponding Remove
command as recorded by the spy server.  But since since there aren't
any clients left after they were removed, no assertions are made, and
the test therefore does nothing.  Fix this.

In tests for command.Approve and command.Deny, add checks that clients
were not somehow removed by the command (in which case, likewise, no
assertions are made).

Add related checks to TestPropertySetterCmd.runTest; i.e. test that a
sequence is not empty before looping over it and making assertions.

* mandos-ctl (TestBaseCommands.test_Remove): Save a copy of the
  original "clients" dict, and loop over those instead.  Add assertion
  that all clients were indeed removed.  Also fix the code which looks
  for the Remove command, which now needs to actually work.
  (TestBaseCommands.test_Approve, TestBaseCommands.test_Deny): Add
  assertion that there are still clients before looping over them.
  (TestPropertySetterCmd.runTest): Add assertion that the list of
  values to get is not empty before looping over them.  Also add check
  that there are still clients before looping over clients.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*  -*- coding: utf-8 -*- */
 
2
/*
 
3
 * Splashy - Read a password from splashy and output it
 
4
 * 
 
5
 * Copyright © 2008-2018, 2021 Teddy Hogeborn
 
6
 * Copyright © 2008-2018, 2021 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             /* vasprintf(),
 
27
                                   program_invocation_short_name,
 
28
                                   asprintf(), TEMP_FAILURE_RETRY() */
 
29
#include <sys/types.h>          /* sig_atomic_t, pid_t, setuid(),
 
30
                                   geteuid(), setsid() */
 
31
#include <stdarg.h>             /* va_list, va_start(), vfprintf() */
 
32
#include <stdio.h>              /* vasprintf(), fprintf(), stderr,
 
33
                                   vfprintf(), asprintf() */
 
34
#include <errno.h>              /* program_invocation_short_name,
 
35
                                   errno, EACCES, ENOTDIR, ELOOP,
 
36
                                   ENOENT, ENAMETOOLONG, EMFILE,
 
37
                                   ENFILE, ENOMEM, ENOEXEC, EINVAL,
 
38
                                   E2BIG, EFAULT, EIO, ETXTBSY,
 
39
                                   EISDIR, ELIBBAD, EPERM, EINTR,
 
40
                                   ECHILD */
 
41
#include <string.h>             /* strerror(), memcmp() */
 
42
#include <error.h>              /* error() */
 
43
#include <stdlib.h>             /* free(), EXIT_FAILURE, getenv(),
 
44
                                   EXIT_SUCCESS, abort() */
 
45
#include <stddef.h>             /* NULL */
 
46
#include <dirent.h>             /* DIR, opendir(), struct dirent,
 
47
                                   readdir(), closedir() */
 
48
#include <sysexits.h>           /* EX_OSERR, EX_OSFILE,
 
49
                                   EX_UNAVAILABLE */
 
50
#include <inttypes.h>           /* intmax_t, strtoimax() */
 
51
#include <iso646.h>             /* or, not, and */
 
52
#include <unistd.h>             /* ssize_t, readlink(), fork(),
 
53
                                   execl(), _exit(),
 
54
                                   TEMP_FAILURE_RETRY(), sleep(),
 
55
                                   setuid(), geteuid(), setsid(),
 
56
                                   chdir(), dup2(), STDERR_FILENO,
 
57
                                   STDOUT_FILENO, pause() */
 
58
#include <sys/stat.h>           /* struct stat, lstat(), S_ISLNK() */
 
59
#include <signal.h>             /* struct sigaction, sigemptyset(),
 
60
                                   sigaddset(), SIGINT, SIGHUP,
 
61
                                   SIGTERM, SIG_IGN, kill(), SIGKILL,
 
62
                                   SIG_DFL, raise() */
 
63
#include <sys/wait.h>           /* waitpid(), WIFEXITED(),
 
64
                                   WEXITSTATUS() */
 
65
 
 
66
sig_atomic_t interrupted_by_signal = 0;
 
67
int signal_received;
 
68
 
 
69
/* Function to use when printing errors */
 
70
__attribute__((format (gnu_printf, 3, 4)))
 
71
void error_plus(int status, int errnum, const char *formatstring,
 
72
                ...){
 
73
  va_list ap;
 
74
  char *text;
 
75
  int ret;
 
76
  
 
77
  va_start(ap, formatstring);
 
78
  ret = vasprintf(&text, formatstring, ap);
 
79
  if(ret == -1){
 
80
    fprintf(stderr, "Mandos plugin %s: ",
 
81
            program_invocation_short_name);
 
82
    vfprintf(stderr, formatstring, ap);
 
83
    fprintf(stderr, ": ");
 
84
    fprintf(stderr, "%s\n", strerror(errnum));
 
85
    error(status, errno, "vasprintf while printing error");
 
86
    return;
 
87
  }
 
88
  fprintf(stderr, "Mandos plugin ");
 
89
  error(status, errnum, "%s", text);
 
90
  free(text);
 
91
}
 
92
 
 
93
 
 
94
static void termination_handler(int signum){
 
95
  if(interrupted_by_signal){
 
96
    return;
 
97
  }
 
98
  interrupted_by_signal = 1;
 
99
  signal_received = signum;
 
100
}
 
101
 
 
102
int main(__attribute__((unused))int argc,
 
103
         __attribute__((unused))char **argv){
 
104
  int ret = 0;
 
105
  char *prompt = NULL;
 
106
  DIR *proc_dir = NULL;
 
107
  pid_t splashy_pid = 0;
 
108
  pid_t splashy_command_pid = 0;
 
109
  int exitstatus = EXIT_FAILURE;
 
110
  
 
111
  /* Create prompt string */
 
112
  {
 
113
    const char *const cryptsource = getenv("cryptsource");
 
114
    const char *const crypttarget = getenv("crypttarget");
 
115
    const char *const prompt_start = "getpass "
 
116
      "Enter passphrase to unlock the disk";
 
117
    
 
118
    if(cryptsource == NULL){
 
119
      if(crypttarget == NULL){
 
120
        ret = asprintf(&prompt, "%s: ", prompt_start);
 
121
      } else {
 
122
        ret = asprintf(&prompt, "%s (%s): ", prompt_start,
 
123
                       crypttarget);
 
124
      }
 
125
    } else {
 
126
      if(crypttarget == NULL){
 
127
        ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource);
 
128
      } else {
 
129
        ret = asprintf(&prompt, "%s %s (%s): ", prompt_start,
 
130
                       cryptsource, crypttarget);
 
131
      }
 
132
    }
 
133
    if(ret == -1){
 
134
      prompt = NULL;
 
135
      exitstatus = EX_OSERR;
 
136
      goto failure;
 
137
    }
 
138
  }
 
139
  
 
140
  /* Find splashy process */
 
141
  {
 
142
    const char splashy_name[] = "/sbin/splashy";
 
143
    proc_dir = opendir("/proc");
 
144
    if(proc_dir == NULL){
 
145
      int e = errno;
 
146
      error_plus(0, errno, "opendir");
 
147
      switch(e){
 
148
      case EACCES:
 
149
      case ENOTDIR:
 
150
      case ELOOP:
 
151
      case ENOENT:
 
152
      default:
 
153
        exitstatus = EX_OSFILE;
 
154
        break;
 
155
      case ENAMETOOLONG:
 
156
      case EMFILE:
 
157
      case ENFILE:
 
158
      case ENOMEM:
 
159
        exitstatus = EX_OSERR;
 
160
        break;
 
161
      }
 
162
      goto failure;
 
163
    }
 
164
    for(struct dirent *proc_ent = readdir(proc_dir);
 
165
        proc_ent != NULL;
 
166
        proc_ent = readdir(proc_dir)){
 
167
      pid_t pid;
 
168
      {
 
169
        intmax_t tmpmax;
 
170
        char *tmp;
 
171
        errno = 0;
 
172
        tmpmax = strtoimax(proc_ent->d_name, &tmp, 10);
 
173
        if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0'
 
174
           or tmpmax != (pid_t)tmpmax){
 
175
          /* Not a process */
 
176
          continue;
 
177
        }
 
178
        pid = (pid_t)tmpmax;
 
179
      }
 
180
      /* Find the executable name by doing readlink() on the
 
181
         /proc/<pid>/exe link */
 
182
      char exe_target[sizeof(splashy_name)];
 
183
      ssize_t sret;
 
184
      {
 
185
        char *exe_link;
 
186
        ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
 
187
        if(ret == -1){
 
188
          error_plus(0, errno, "asprintf");
 
189
          exitstatus = EX_OSERR;
 
190
          goto failure;
 
191
        }
 
192
        
 
193
        /* Check that it refers to a symlink owned by root:root */
 
194
        struct stat exe_stat;
 
195
        ret = lstat(exe_link, &exe_stat);
 
196
        if(ret == -1){
 
197
          if(errno == ENOENT){
 
198
            free(exe_link);
 
199
            continue;
 
200
          }
 
201
          int e = errno;
 
202
          error_plus(0, errno, "lstat");
 
203
          free(exe_link);
 
204
          switch(e){
 
205
          case EACCES:
 
206
          case ENOTDIR:
 
207
          case ELOOP:
 
208
          default:
 
209
            exitstatus = EX_OSFILE;
 
210
            break;
 
211
          case ENAMETOOLONG:
 
212
            exitstatus = EX_OSERR;
 
213
            break;
 
214
          }
 
215
          goto failure;
 
216
        }
 
217
        if(not S_ISLNK(exe_stat.st_mode)
 
218
           or exe_stat.st_uid != 0
 
219
           or exe_stat.st_gid != 0){
 
220
          free(exe_link);
 
221
          continue;
 
222
        }
 
223
        
 
224
        sret = readlink(exe_link, exe_target, sizeof(exe_target));
 
225
        free(exe_link);
 
226
      }
 
227
      if((sret == ((ssize_t)sizeof(exe_target)-1))
 
228
         and (memcmp(splashy_name, exe_target,
 
229
                     sizeof(exe_target)-1) == 0)){
 
230
        splashy_pid = pid;
 
231
        break;
 
232
      }
 
233
    }
 
234
    closedir(proc_dir);
 
235
    proc_dir = NULL;
 
236
  }
 
237
  if(splashy_pid == 0){
 
238
    exitstatus = EX_UNAVAILABLE;
 
239
    goto failure;
 
240
  }
 
241
  
 
242
  /* Set up the signal handler */
 
243
  {
 
244
    struct sigaction old_action,
 
245
      new_action = { .sa_handler = termination_handler,
 
246
                     .sa_flags = 0 };
 
247
    sigemptyset(&new_action.sa_mask);
 
248
    ret = sigaddset(&new_action.sa_mask, SIGINT);
 
249
    if(ret == -1){
 
250
      error_plus(0, errno, "sigaddset");
 
251
      exitstatus = EX_OSERR;
 
252
      goto failure;
 
253
    }
 
254
    ret = sigaddset(&new_action.sa_mask, SIGHUP);
 
255
    if(ret == -1){
 
256
      error_plus(0, errno, "sigaddset");
 
257
      exitstatus = EX_OSERR;
 
258
      goto failure;
 
259
    }
 
260
    ret = sigaddset(&new_action.sa_mask, SIGTERM);
 
261
    if(ret == -1){
 
262
      error_plus(0, errno, "sigaddset");
 
263
      exitstatus = EX_OSERR;
 
264
      goto failure;
 
265
    }
 
266
    ret = sigaction(SIGINT, NULL, &old_action);
 
267
    if(ret == -1){
 
268
      error_plus(0, errno, "sigaction");
 
269
      exitstatus = EX_OSERR;
 
270
      goto failure;
 
271
    }
 
272
    if(old_action.sa_handler != SIG_IGN){
 
273
      ret = sigaction(SIGINT, &new_action, NULL);
 
274
      if(ret == -1){
 
275
        error_plus(0, errno, "sigaction");
 
276
        exitstatus = EX_OSERR;
 
277
        goto failure;
 
278
      }
 
279
    }
 
280
    ret = sigaction(SIGHUP, NULL, &old_action);
 
281
    if(ret == -1){
 
282
      error_plus(0, errno, "sigaction");
 
283
      exitstatus = EX_OSERR;
 
284
      goto failure;
 
285
    }
 
286
    if(old_action.sa_handler != SIG_IGN){
 
287
      ret = sigaction(SIGHUP, &new_action, NULL);
 
288
      if(ret == -1){
 
289
        error_plus(0, errno, "sigaction");
 
290
        exitstatus = EX_OSERR;
 
291
        goto failure;
 
292
      }
 
293
    }
 
294
    ret = sigaction(SIGTERM, NULL, &old_action);
 
295
    if(ret == -1){
 
296
      error_plus(0, errno, "sigaction");
 
297
      exitstatus = EX_OSERR;
 
298
      goto failure;
 
299
    }
 
300
    if(old_action.sa_handler != SIG_IGN){
 
301
      ret = sigaction(SIGTERM, &new_action, NULL);
 
302
      if(ret == -1){
 
303
        error_plus(0, errno, "sigaction");
 
304
        exitstatus = EX_OSERR;
 
305
        goto failure;
 
306
      }
 
307
    }
 
308
  }
 
309
  
 
310
  if(interrupted_by_signal){
 
311
    goto failure;
 
312
  }
 
313
  
 
314
  /* Fork off the splashy command to prompt for password */
 
315
  splashy_command_pid = fork();
 
316
  if(splashy_command_pid != 0 and interrupted_by_signal){
 
317
    goto failure;
 
318
  }
 
319
  if(splashy_command_pid == -1){
 
320
    error_plus(0, errno, "fork");
 
321
    exitstatus = EX_OSERR;
 
322
    goto failure;
 
323
  }
 
324
  /* Child */
 
325
  if(splashy_command_pid == 0){
 
326
    if(not interrupted_by_signal){
 
327
      const char splashy_command[] = "/sbin/splashy_update";
 
328
      execl(splashy_command, splashy_command, prompt, (char *)NULL);
 
329
      int e = errno;
 
330
      error_plus(0, errno, "execl");
 
331
      switch(e){
 
332
      case EACCES:
 
333
      case ENOENT:
 
334
      case ENOEXEC:
 
335
      case EINVAL:
 
336
        _exit(EX_UNAVAILABLE);
 
337
      case ENAMETOOLONG:
 
338
      case E2BIG:
 
339
      case ENOMEM:
 
340
      case EFAULT:
 
341
      case EIO:
 
342
      case EMFILE:
 
343
      case ENFILE:
 
344
      case ETXTBSY:
 
345
      default:
 
346
        _exit(EX_OSERR);
 
347
      case ENOTDIR:
 
348
      case ELOOP:
 
349
      case EISDIR:
 
350
#ifdef ELIBBAD
 
351
      case ELIBBAD:             /* Linux only */
 
352
#endif
 
353
      case EPERM:
 
354
        _exit(EX_OSFILE);
 
355
      }
 
356
    }
 
357
    free(prompt);
 
358
    _exit(EXIT_FAILURE);
 
359
  }
 
360
  
 
361
  /* Parent */
 
362
  free(prompt);
 
363
  prompt = NULL;
 
364
  
 
365
  if(interrupted_by_signal){
 
366
    goto failure;
 
367
  }
 
368
  
 
369
  /* Wait for command to complete */
 
370
  {
 
371
    int status;
 
372
    do {
 
373
      ret = waitpid(splashy_command_pid, &status, 0);
 
374
    } while(ret == -1 and errno == EINTR
 
375
            and not interrupted_by_signal);
 
376
    if(interrupted_by_signal){
 
377
      goto failure;
 
378
    }
 
379
    if(ret == -1){
 
380
      error_plus(0, errno, "waitpid");
 
381
      if(errno == ECHILD){
 
382
        splashy_command_pid = 0;
 
383
      }
 
384
    } else {
 
385
      /* The child process has exited */
 
386
      splashy_command_pid = 0;
 
387
      if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
 
388
        return EXIT_SUCCESS;
 
389
      }
 
390
    }
 
391
  }
 
392
  
 
393
 failure:
 
394
  
 
395
  free(prompt);
 
396
  
 
397
  if(proc_dir != NULL){
 
398
    TEMP_FAILURE_RETRY(closedir(proc_dir));
 
399
  }
 
400
  
 
401
  if(splashy_command_pid != 0){
 
402
    TEMP_FAILURE_RETRY(kill(splashy_command_pid, SIGTERM));
 
403
    
 
404
    TEMP_FAILURE_RETRY(kill(splashy_pid, SIGTERM));
 
405
    sleep(2);
 
406
    while(TEMP_FAILURE_RETRY(kill(splashy_pid, 0)) == 0){
 
407
      TEMP_FAILURE_RETRY(kill(splashy_pid, SIGKILL));
 
408
      sleep(1);
 
409
    }
 
410
    pid_t new_splashy_pid = (pid_t)TEMP_FAILURE_RETRY(fork());
 
411
    if(new_splashy_pid == 0){
 
412
      /* Child; will become new splashy process */
 
413
      
 
414
      /* Make the effective user ID (root) the only user ID instead of
 
415
         the real user ID (_mandos) */
 
416
      ret = setuid(geteuid());
 
417
      if(ret == -1){
 
418
        error_plus(0, errno, "setuid");
 
419
      }
 
420
      
 
421
      setsid();
 
422
      ret = chdir("/");
 
423
      if(ret == -1){
 
424
        error_plus(0, errno, "chdir");
 
425
      }
 
426
/*       if(fork() != 0){ */
 
427
/*      _exit(EXIT_SUCCESS); */
 
428
/*       } */
 
429
      ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */
 
430
      if(ret == -1){
 
431
        error_plus(0, errno, "dup2");
 
432
        _exit(EX_OSERR);
 
433
      }
 
434
      
 
435
      execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL);
 
436
      {
 
437
        int e = errno;
 
438
        error_plus(0, errno, "execl");
 
439
        switch(e){
 
440
        case EACCES:
 
441
        case ENOENT:
 
442
        case ENOEXEC:
 
443
        default:
 
444
          _exit(EX_UNAVAILABLE);
 
445
        case ENAMETOOLONG:
 
446
        case E2BIG:
 
447
        case ENOMEM:
 
448
          _exit(EX_OSERR);
 
449
        case ENOTDIR:
 
450
        case ELOOP:
 
451
          _exit(EX_OSFILE);
 
452
        }
 
453
      }
 
454
    }
 
455
  }
 
456
  
 
457
  if(interrupted_by_signal){
 
458
    struct sigaction signal_action;
 
459
    sigemptyset(&signal_action.sa_mask);
 
460
    signal_action.sa_handler = SIG_DFL;
 
461
    ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
 
462
                                            &signal_action, NULL));
 
463
    if(ret == -1){
 
464
      error_plus(0, errno, "sigaction");
 
465
    }
 
466
    do {
 
467
      ret = raise(signal_received);
 
468
    } while(ret != 0 and errno == EINTR);
 
469
    if(ret != 0){
 
470
      error_plus(0, errno, "raise");
 
471
      abort();
 
472
    }
 
473
    TEMP_FAILURE_RETRY(pause());
 
474
  }
 
475
  
 
476
  return exitstatus;
 
477
}