/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release
237.7.675 by Teddy Hogeborn
Add dracut(8) support
1
/* -*- mode: c; coding: utf-8; after-save-hook: (lambda () (let* ((find-build-directory (lambda (try-directory &optional base-directory) (let ((base-directory (or base-directory try-directory))) (cond ((equal try-directory "/") base-directory) ((file-readable-p (concat (file-name-as-directory try-directory) "Makefile")) try-directory) ((funcall find-build-directory (directory-file-name (file-name-directory try-directory)) base-directory)))))) (build-directory (funcall find-build-directory (buffer-file-name))) (local-build-directory (if (fboundp 'file-local-name) (file-local-name build-directory) (or (file-remote-p build-directory 'localname) build-directory))) (command (file-relative-name (file-name-sans-extension (buffer-file-name)) build-directory))) (pcase (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (let ((qbdir (shell-quote-argument local-build-directory)) (qcmd (shell-quote-argument command))) (format "cd %s && CFLAGS=-Werror make --silent %s && %s --test --verbose" qbdir qcmd qcmd)) nil "*Test*")) (0 (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)))) (_ (with-current-buffer "*Test*" (compilation-mode) (cd-absolute build-directory)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); -*- */
2
/*
3
 * Mandos password agent - Simple password agent to run Mandos client
4
 *
5
 * Copyright © 2019 Teddy Hogeborn
6
 * Copyright © 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
27
#include <inttypes.h>		/* uintmax_t, PRIuMAX, PRIdMAX,
28
				   intmax_t, uint32_t, SCNx32,
29
				   SCNuMAX, SCNxMAX */
30
#include <stddef.h>		/* size_t */
31
#include <sys/types.h>		/* pid_t, uid_t, gid_t, getuid(),
32
				   getpid() */
33
#include <stdbool.h>		/* bool, true, false */
34
#include <signal.h>		/* struct sigaction, sigset_t,
35
				   sigemptyset(), sigaddset(),
36
				   SIGCHLD, pthread_sigmask(),
37
				   SIG_BLOCK, SIG_SETMASK, SA_RESTART,
38
				   SA_NOCLDSTOP, sigfillset(), kill(),
39
				   SIGTERM, sigdelset(), SIGKILL,
40
				   NSIG, sigismember(), SA_ONSTACK,
41
				   SIG_DFL, SIG_IGN, SIGINT, SIGQUIT,
42
				   SIGHUP, SIGSTOP, SIG_UNBLOCK */
43
#include <stdlib.h>		/* EXIT_SUCCESS, EXIT_FAILURE,
44
				   malloc(), free(), strtoumax(),
45
				   realloc(), setenv(), calloc(),
46
				   mkdtemp(), mkostemp() */
47
#include <iso646.h>		/* not, or, and, xor */
48
#include <error.h>		/* error() */
49
#include <sysexits.h>		/* EX_USAGE, EX_OSERR, EX_OSFILE */
50
#include <errno.h>		/* errno, error_t, EACCES,
237.7.692 by Teddy Hogeborn
dracut-module/password-agent.c: Update #include comments
51
				   ENAMETOOLONG, ENOENT, ENOTDIR,
237.7.753 by teddy at recompile
Use reallocarray() if available, or check for overflow
52
				   ENOMEM, EEXIST, ECHILD, EPERM,
237.7.692 by Teddy Hogeborn
dracut-module/password-agent.c: Update #include comments
53
				   EAGAIN, EINTR, ENOBUFS, EADDRINUSE,
237.7.675 by Teddy Hogeborn
Add dracut(8) support
54
				   ECONNREFUSED, ECONNRESET,
55
				   ETOOMANYREFS, EMSGSIZE, EBADF,
56
				   EINVAL */
57
#include <string.h>		/* strdup(), memcpy(),
58
				   explicit_bzero(), memset(),
59
				   strcmp(), strlen(), strncpy(),
60
				   memcmp(), basename() */
61
#include <argz.h>		/* argz_create(), argz_count(),
62
				   argz_extract(), argz_next(),
63
				   argz_add() */
64
#include <sys/epoll.h>		/* epoll_create1(), EPOLL_CLOEXEC,
65
				   epoll_ctl(), EPOLL_CTL_ADD,
66
				   struct epoll_event, EPOLLIN,
67
				   EPOLLRDHUP, EPOLLOUT,
68
				   epoll_pwait() */
69
#include <time.h>		/* struct timespec, clock_gettime(),
70
				   CLOCK_MONOTONIC */
71
#include <argp.h>		/* struct argp_option, OPTION_HIDDEN,
72
				   OPTION_ALIAS, struct argp_state,
73
				   ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
74
				   struct argp, argp_parse(),
75
				   ARGP_NO_EXIT */
237.7.753 by teddy at recompile
Use reallocarray() if available, or check for overflow
76
#include <stdint.h>		/* SIZE_MAX */
237.7.675 by Teddy Hogeborn
Add dracut(8) support
77
#include <unistd.h>		/* uid_t, gid_t, close(), pipe2(),
78
				   fork(), _exit(), dup2(),
79
				   STDOUT_FILENO, setresgid(),
80
				   setresuid(), execv(), ssize_t,
81
				   read(), dup3(), getuid(), dup(),
82
				   STDERR_FILENO, pause(), write(),
83
				   rmdir(), unlink(), getpid() */
84
#include <sys/mman.h>		/* munlock(), mlock() */
85
#include <fcntl.h>		/* O_CLOEXEC, O_NONBLOCK, fcntl(),
86
				   F_GETFD, F_GETFL, FD_CLOEXEC,
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
87
				   open(), O_WRONLY, O_NOCTTY,
237.7.692 by Teddy Hogeborn
dracut-module/password-agent.c: Update #include comments
88
				   O_RDONLY, O_NOFOLLOW */
237.7.675 by Teddy Hogeborn
Add dracut(8) support
89
#include <sys/wait.h>		/* waitpid(), WNOHANG, WIFEXITED(),
90
				   WEXITSTATUS() */
91
#include <limits.h>		/* PIPE_BUF, NAME_MAX, INT_MAX */
92
#include <sys/inotify.h>	/* inotify_init1(), IN_NONBLOCK,
93
				   IN_CLOEXEC, inotify_add_watch(),
94
				   IN_CLOSE_WRITE, IN_MOVED_TO,
237.7.692 by Teddy Hogeborn
dracut-module/password-agent.c: Update #include comments
95
				   IN_MOVED_FROM, IN_DELETE,
96
				   IN_EXCL_UNLINK, IN_ONLYDIR,
97
				   struct inotify_event */
237.7.675 by Teddy Hogeborn
Add dracut(8) support
98
#include <fnmatch.h>		/* fnmatch(), FNM_FILE_NAME */
237.7.753 by teddy at recompile
Use reallocarray() if available, or check for overflow
99
#include <stdio.h>		/* asprintf(), FILE, stderr, fopen(),
100
				   fclose(), getline(), sscanf(),
101
				   feof(), ferror(), rename(),
102
				   fdopen(), fprintf(), fscanf() */
237.7.675 by Teddy Hogeborn
Add dracut(8) support
103
#include <glib.h>    /* GKeyFile, g_key_file_free(), g_key_file_new(),
104
			GError, g_key_file_load_from_file(),
105
			G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT,
106
			g_key_file_get_string(), guint64,
107
			g_key_file_get_uint64(),
108
			G_KEY_FILE_ERROR_KEY_NOT_FOUND, gconstpointer,
109
			g_assert_true(), g_assert_nonnull(),
110
			g_assert_null(), g_assert_false(),
111
			g_assert_cmpint(), g_assert_cmpuint(),
112
			g_test_skip(), g_assert_cmpstr(),
113
			g_test_init(), g_test_add(), g_test_run(),
114
			GOptionContext, g_option_context_new(),
115
			g_option_context_set_help_enabled(), FALSE,
116
			g_option_context_set_ignore_unknown_options(),
117
			gboolean, GOptionEntry, G_OPTION_ARG_NONE,
118
			g_option_context_add_main_entries(),
119
			g_option_context_parse(),
120
			g_option_context_free(), g_error() */
121
#include <sys/un.h>		/* struct sockaddr_un, SUN_LEN */
122
#include <sys/socket.h>		/* AF_LOCAL, socket(), PF_LOCAL,
123
				   SOCK_DGRAM, SOCK_NONBLOCK,
124
				   SOCK_CLOEXEC, connect(),
125
				   struct sockaddr, socklen_t,
126
				   shutdown(), SHUT_RD, send(),
127
				   MSG_NOSIGNAL, bind(), recv(),
128
				   socketpair() */
129
#include <glob.h>		/* globfree(), glob_t, glob(),
130
				   GLOB_ERR, GLOB_NOSORT, GLOB_MARK,
131
				   GLOB_ABORTED, GLOB_NOMATCH,
132
				   GLOB_NOSPACE */
133
134
/* End of includes */
135

136
/* Start of declarations of private types and functions */
137
138
/* microseconds of CLOCK_MONOTONIC absolute time; 0 means unset */
139
typedef uintmax_t mono_microsecs;
140
141
/* "task_queue" - A queue of tasks to be run */
142
typedef struct {
143
  struct task_struct *tasks;	/* Tasks in this queue */
144
  size_t length;		/* Number of tasks */
145
  /* Memory allocated for "tasks", in bytes */
146
  size_t allocated;		
147
  /* Time when this queue should be run, at the latest */
148
  mono_microsecs next_run;
149
} __attribute__((designated_init)) task_queue;
150
237.7.729 by Teddy Hogeborn
Update comment text
151
/* "task_func" - A function type for task functions
237.7.675 by Teddy Hogeborn
Add dracut(8) support
152
153
   I.e. functions for the code which runs when a task is run, all have
154
   this type */
155
typedef void (task_func) (const struct task_struct,
156
			  task_queue *const)
157
  __attribute__((nonnull));
158
159
/* "buffer" - A data buffer for a growing array of bytes
160
161
   Used for the "password" variable */
162
typedef struct {
163
  char *data;
164
  size_t length;
165
  size_t allocated;
166
} __attribute__((designated_init)) buffer;
167
168
/* "string_set" - A set type which can contain strings
169
170
   Used by the "cancelled_filenames" variable */
171
typedef struct {
172
  char *argz;			/* Do not access these except in */
173
  size_t argz_len;		/* the string_set_* functions */
174
} __attribute__((designated_init)) string_set;
175
176
/* "task_context" - local variables for tasks
177
178
   This data structure distinguishes between different tasks which are
179
   using the same function.  This data structure is passed to every
180
   task function when each task is run.
181
182
   Note that not every task uses every struct member. */
183
typedef struct task_struct {
184
  task_func *const func;	 /* The function run by this task */
185
  char *const question_filename; /* The question file */
186
  const pid_t pid;		 /* Mandos client process ID */
187
  const int epoll_fd;		 /* The epoll set file descriptor */
188
  bool *const quit_now;		 /* Set to true on fatal errors */
189
  const int fd;			 /* General purpose file descriptor */
190
  bool *const mandos_client_exited; /* Set true when client exits */
191
  buffer *const password;	    /* As read from client process */
192
  bool *const password_is_read;	    /* "password" is done growing */
193
  char *filename;		    /* General purpose file name */
194
  /* A set of strings of all the file names of questions which have
195
     been cancelled for any reason; tasks pertaining to these question
196
     files should not be run */
197
  string_set *const cancelled_filenames;
198
  const mono_microsecs notafter; /* "NotAfter" from question file */
199
  /* Updated before each queue run; is compared with queue.next_run */
200
  const mono_microsecs *const current_time;
201
} __attribute__((designated_init)) task_context;
202
203
/* Declare all our functions here so we can define them in any order
204
   below.  Note: test functions are *not* declared here, they are
205
   declared in the test section. */
206
__attribute__((warn_unused_result))
207
static bool should_only_run_tests(int *, char **[]);
208
__attribute__((warn_unused_result, cold))
209
static bool run_tests(int, char *[]);
210
static void handle_sigchld(__attribute__((unused)) int sig){}
211
__attribute__((warn_unused_result, malloc))
212
task_queue *create_queue(void);
213
__attribute__((nonnull, warn_unused_result))
214
bool add_to_queue(task_queue *const, const task_context);
215
__attribute__((nonnull))
216
void cleanup_task(const task_context *const);
217
__attribute__((nonnull))
218
void cleanup_queue(task_queue *const *const);
219
__attribute__((pure, nonnull, warn_unused_result))
220
bool queue_has_question(const task_queue *const);
221
__attribute__((nonnull))
222
void cleanup_close(const int *const);
223
__attribute__((nonnull))
224
void cleanup_string(char *const *const);
225
__attribute__((nonnull))
226
void cleanup_buffer(buffer *const);
227
__attribute__((pure, nonnull, warn_unused_result))
228
bool string_set_contains(const string_set, const char *const);
229
__attribute__((nonnull, warn_unused_result))
230
bool string_set_add(string_set *const, const char *const);
231
__attribute__((nonnull))
232
void string_set_clear(string_set *);
233
void string_set_swap(string_set *const, string_set *const);
234
__attribute__((nonnull, warn_unused_result))
235
bool start_mandos_client(task_queue *const, const int, bool *const,
236
			 bool *const, buffer *const, bool *const,
237
			 const struct sigaction *const,
238
			 const sigset_t, const char *const,
239
			 const uid_t, const gid_t,
240
			 const char *const *const);
241
__attribute__((nonnull))
242
task_func wait_for_mandos_client_exit;
243
__attribute__((nonnull))
244
task_func read_mandos_client_output;
245
__attribute__((warn_unused_result))
246
bool add_inotify_dir_watch(task_queue *const, const int, bool *const,
247
			   buffer *const, const char *const,
248
			   string_set *, const mono_microsecs *const,
249
			   bool *const, bool *const);
250
__attribute__((nonnull))
251
task_func read_inotify_event;
252
__attribute__((nonnull))
253
task_func open_and_parse_question;
254
__attribute__((nonnull))
255
task_func cancel_old_question;
256
__attribute__((nonnull))
257
task_func connect_question_socket;
258
__attribute__((nonnull))
259
task_func send_password_to_socket;
260
__attribute__((warn_unused_result))
261
bool add_existing_questions(task_queue *const, const int,
262
			    buffer *const, string_set *,
263
			    const mono_microsecs *const,
264
			    bool *const, bool *const,
265
			    const char *const);
266
__attribute__((nonnull, warn_unused_result))
267
bool wait_for_event(const int, const mono_microsecs,
268
		    const mono_microsecs);
269
bool run_queue(task_queue **const, string_set *const, bool *const);
270
bool clear_all_fds_from_epoll_set(const int);
271
mono_microsecs get_current_time(void);
272
__attribute__((nonnull, warn_unused_result))
273
bool setup_signal_handler(struct sigaction *const);
274
__attribute__((nonnull))
275
bool restore_signal_handler(const struct sigaction *const);
276
__attribute__((nonnull, warn_unused_result))
277
bool block_sigchld(sigset_t *const);
278
__attribute__((nonnull))
279
bool restore_sigmask(const sigset_t *const);
280
__attribute__((nonnull))
281
bool parse_arguments(int, char *[], const bool, char **, char **,
282
		     uid_t *const , gid_t *const, char **, size_t *);
283
284
/* End of declarations of private types and functions */
285

286
/* Start of "main" section; this section LACKS TESTS!
287
288
   Code here should be as simple as possible. */
289
290
/* These are required to be global by Argp */
291
const char *argp_program_version = "password-agent " VERSION;
292
const char *argp_program_bug_address = "<mandos@recompile.se>";
293
294
int main(int argc, char *argv[]){
295
296
  /* If the --test option is passed, skip all normal operations and
297
     instead only run the run_tests() function, which also does all
298
     its own option parsing, so we don't have to do anything here. */
299
  if(should_only_run_tests(&argc, &argv)){
300
    if(run_tests(argc, argv)){
301
      return EXIT_SUCCESS;	/* All tests successful */
302
    }
303
    return EXIT_FAILURE;	/* Some test(s) failed */
304
  }
305
306
  __attribute__((cleanup(cleanup_string)))
307
    char *agent_directory = NULL;
308
309
  __attribute__((cleanup(cleanup_string)))
310
    char *helper_directory = NULL;
311
312
  uid_t user = 0;
313
  gid_t group = 0;
314
315
  __attribute__((cleanup(cleanup_string)))
316
    char *mandos_argz = NULL;
317
  size_t mandos_argz_length = 0;
318
319
  if(not parse_arguments(argc, argv, true, &agent_directory,
320
			 &helper_directory, &user, &group,
321
			 &mandos_argz, &mandos_argz_length)){
322
    /* This should never happen, since "true" is passed as the third
323
       argument to parse_arguments() above, which should make
324
       argp_parse() call exit() if any parsing error occurs. */
325
    error(EX_USAGE, errno, "Failed to parse arguments");
326
  }
327
328
  const char default_agent_directory[] = "/run/systemd/ask-password";
329
  const char default_helper_directory[]
330
    = "/lib/mandos/plugin-helpers";
331
  const char *const default_argv[]
332
    = {"/lib/mandos/plugins.d/mandos-client", NULL };
333
334
  /* Set variables to default values if unset */
335
  if(agent_directory == NULL){
336
    agent_directory = strdup(default_agent_directory);
337
    if(agent_directory == NULL){
338
      error(EX_OSERR, errno, "Failed strdup()");
339
    }
340
  }
341
  if(helper_directory == NULL){
342
    helper_directory = strdup(default_helper_directory);
343
    if(helper_directory == NULL){
344
      error(EX_OSERR, errno, "Failed strdup()");
345
    }
346
  }
347
  if(user == 0){
348
    user = 65534;		/* nobody */
349
  }
350
  if(group == 0){
351
    group = 65534;		/* nogroup */
352
  }
353
  /* If parse_opt did not create an argz vector, create one with
354
     default values */
355
  if(mandos_argz == NULL){
356
#ifdef __GNUC__
357
#pragma GCC diagnostic push
358
    /* argz_create() takes a non-const argv for some unknown reason -
359
       argz_create() isn't modifying the strings, just copying them.
360
       Therefore, this cast to non-const should be safe. */
361
#pragma GCC diagnostic ignored "-Wcast-qual"
362
#endif
363
    errno = argz_create((char *const *)default_argv, &mandos_argz,
364
			&mandos_argz_length);
365
#ifdef __GNUC__
366
#pragma GCC diagnostic pop
367
#endif
368
    if(errno != 0){
369
      error(EX_OSERR, errno, "Failed argz_create()");
370
    }
371
  }
372
  /* Use argz vector to create a normal argv, usable by execv() */
373
374
  char **mandos_argv = malloc((argz_count(mandos_argz,
375
					  mandos_argz_length)
376
			       + 1) * sizeof(char *));
377
  if(mandos_argv == NULL){
378
    error_t saved_errno = errno;
379
    free(mandos_argz);
380
    error(EX_OSERR, saved_errno, "Failed malloc()");
381
  }
382
  argz_extract(mandos_argz, mandos_argz_length, mandos_argv);
383
384
  sigset_t orig_sigmask;
385
  if(not block_sigchld(&orig_sigmask)){
386
    return EX_OSERR;
387
  }
388
389
  struct sigaction old_sigchld_action;
390
  if(not setup_signal_handler(&old_sigchld_action)){
391
    return EX_OSERR;
392
  }
393
394
  mono_microsecs current_time = 0;
395
396
  bool mandos_client_exited = false;
397
  bool quit_now = false;
398
  __attribute__((cleanup(cleanup_close)))
399
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
400
  if(epoll_fd < 0){
401
    error(EX_OSERR, errno, "Failed to create epoll set fd");
402
  }
403
  __attribute__((cleanup(cleanup_queue)))
404
    task_queue *queue = create_queue();
405
  if(queue == NULL){
406
    error(EX_OSERR, errno, "Failed to create task queue");
407
  }
408
409
  __attribute__((cleanup(cleanup_buffer)))
410
    buffer password = {};
411
  bool password_is_read = false;
412
413
  __attribute__((cleanup(string_set_clear)))
414
    string_set cancelled_filenames = {};
415
416
  /* Add tasks to queue */
417
  if(not start_mandos_client(queue, epoll_fd, &mandos_client_exited,
418
  			     &quit_now, &password, &password_is_read,
419
  			     &old_sigchld_action, orig_sigmask,
420
			     helper_directory, user, group,
421
			     (const char *const *)mandos_argv)){
422
    return EX_OSERR;		/* Error has already been printed */
423
  }
424
  /* These variables were only for start_mandos_client() and are not
425
     needed anymore */
426
  free(mandos_argv);
427
  free(mandos_argz);
428
  mandos_argz = NULL;
429
  if(not add_inotify_dir_watch(queue, epoll_fd, &quit_now, &password,
430
  			       agent_directory, &cancelled_filenames,
431
  			       &current_time, &mandos_client_exited,
432
  			       &password_is_read)){
433
    switch(errno){		/* Error has already been printed */
434
    case EACCES:
435
    case ENAMETOOLONG:
436
    case ENOENT:
237.7.690 by Teddy Hogeborn
dracut-module/password-agent.c: Require agent directory
437
    case ENOTDIR:
237.7.675 by Teddy Hogeborn
Add dracut(8) support
438
      return EX_OSFILE;
439
    default:
440
      return EX_OSERR;
441
    }
442
  }
443
  if(not add_existing_questions(queue, epoll_fd, &password,
444
				&cancelled_filenames, &current_time,
445
				&mandos_client_exited,
446
				&password_is_read, agent_directory)){
447
    return EXIT_FAILURE;	/* Error has already been printed */
448
  }
449
450
  /* Run queue */
451
  do {
452
    current_time = get_current_time();
453
    if(not wait_for_event(epoll_fd, queue->next_run, current_time)){
454
      const error_t saved_errno = errno;
455
      error(EXIT_FAILURE, saved_errno, "Failure while waiting for"
456
	    " events");
457
    }
458
459
    current_time = get_current_time();
460
    if(not run_queue(&queue, &cancelled_filenames, &quit_now)){
461
      const error_t saved_errno = errno;
462
      error(EXIT_FAILURE, saved_errno, "Failure while running queue");
463
    }
464
465
    /*  When no tasks about questions are left in the queue, break out
466
	of the loop (and implicitly exit the program) */
467
  } while(queue_has_question(queue));
468
469
  restore_signal_handler(&old_sigchld_action);
470
  restore_sigmask(&orig_sigmask);
471
472
  return EXIT_SUCCESS;
473
}
474
475
__attribute__((warn_unused_result))
476
mono_microsecs get_current_time(void){
477
  struct timespec currtime;
478
  if(clock_gettime(CLOCK_MONOTONIC, &currtime) != 0){
479
    error(0, errno, "Failed to get current time");
480
    return 0;
481
  }
482
  return ((mono_microsecs)currtime.tv_sec * 1000000) /* seconds */
483
    + ((mono_microsecs)currtime.tv_nsec / 1000);     /* nanoseconds */
484
}
485
486
/* End of "main" section */
487

488
/* Start of regular code section; ALL this code has tests */
489
490
__attribute__((nonnull))
491
bool parse_arguments(int argc, char *argv[], const bool exit_failure,
492
		     char **agent_directory, char **helper_directory,
493
		     uid_t *const user, gid_t *const group,
494
		     char **mandos_argz, size_t *mandos_argz_length){
495
496
  const struct argp_option options[] = {
497
    { .name="agent-directory",.key='d', .arg="DIRECTORY",
498
      .doc="Systemd password agent directory" },
499
    { .name="helper-directory",.key=128, .arg="DIRECTORY",
500
      .doc="Mandos Client password helper directory" },
501
    { .name="plugin-helper-dir", .key=129, /* From plugin-runner */
502
      .flags=OPTION_HIDDEN | OPTION_ALIAS },
503
    { .name="user", .key='u', .arg="USERID",
504
      .doc="User ID the Mandos Client will use as its unprivileged"
505
      " user" },
506
    { .name="userid", .key=130,	/* From plugin--runner */
507
      .flags=OPTION_HIDDEN | OPTION_ALIAS },
508
    { .name="group", .key='g', .arg="GROUPID",
509
      .doc="Group ID the Mandos Client will use as its unprivileged"
510
      " group" },
511
    { .name="groupid", .key=131, /* From plugin--runner */
512
      .flags=OPTION_HIDDEN | OPTION_ALIAS },
513
    { .name="test", .key=255, /* See should_only_run_tests() */
514
      .doc="Skip normal operation, and only run self-tests.  See"
515
      " --test --help.", .group=10, },
516
    { NULL },
517
  };
518
519
  __attribute__((nonnull(3)))
520
    error_t parse_opt(int key, char *arg, struct argp_state *state){
521
    errno = 0;
522
    switch(key){
523
    case 'd':			/* --agent-directory */
524
      *agent_directory = strdup(arg);
525
      break;
526
    case 128:			/* --helper-directory */
527
    case 129:			/* --plugin-helper-dir */
528
      *helper_directory = strdup(arg);
529
      break;
530
    case 'u':			/* --user */
531
    case 130:			/* --userid */
532
      {
533
	char *tmp;
534
	uintmax_t tmp_id = 0;
535
	errno = 0;
536
	tmp_id = (uid_t)strtoumax(arg, &tmp, 10);
537
	if(errno != 0 or tmp == arg or *tmp != '\0'
538
	   or tmp_id != (uid_t)tmp_id or (uid_t)tmp_id == 0){
539
	  return ARGP_ERR_UNKNOWN;
540
	}
541
	*user = (uid_t)tmp_id;
542
	errno = 0;
543
	break;
544
      }
545
    case 'g':			/* --group */
546
    case 131:			/* --groupid */
547
      {
548
	char *tmp;
549
	uintmax_t tmp_id = 0;
550
	errno = 0;
551
	tmp_id = (uid_t)strtoumax(arg, &tmp, 10);
552
	if(errno != 0 or tmp == arg or *tmp != '\0'
553
	   or tmp_id != (gid_t)tmp_id or (gid_t)tmp_id == 0){
554
	  return ARGP_ERR_UNKNOWN;
555
	}
556
	*group = (gid_t)tmp_id;
557
	errno = 0;
558
	break;
559
      }
560
    case ARGP_KEY_ARGS:
561
      /* Copy arguments into argz vector */
562
      return argz_create(state->argv + state->next, mandos_argz,
563
    			 mandos_argz_length);
564
    default:
565
      return ARGP_ERR_UNKNOWN;
566
    }
567
    return errno;
568
  }
569
570
  const struct argp argp = {
571
    .options=options,
572
    .parser=parse_opt,
573
    .args_doc="[MANDOS_CLIENT [OPTION...]]\n--test",
574
    .doc = "Mandos password agent -- runs Mandos client as a"
575
    " systemd password agent",
576
  };
577
578
  errno = argp_parse(&argp, argc, argv,
579
		     exit_failure ? 0 : ARGP_NO_EXIT, NULL, NULL);
580
581
  return errno == 0;
582
}
583
584
__attribute__((nonnull, warn_unused_result))
585
bool block_sigchld(sigset_t *const orig_sigmask){
586
  sigset_t sigchld_sigmask;
587
  if(sigemptyset(&sigchld_sigmask) < 0){
588
    error(0, errno, "Failed to empty signal set");
589
    return false;
590
  }
591
  if(sigaddset(&sigchld_sigmask, SIGCHLD) < 0){
592
    error(0, errno, "Failed to add SIGCHLD to signal set");
593
    return false;
594
  }
595
  if(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, orig_sigmask) != 0){
596
    error(0, errno, "Failed to block SIGCHLD signal");
597
    return false;
598
  }
599
  return true;
600
}
601
602
__attribute__((nonnull, warn_unused_result, const))
603
bool restore_sigmask(const sigset_t *const orig_sigmask){
604
  if(pthread_sigmask(SIG_SETMASK, orig_sigmask, NULL) != 0){
605
    error(0, errno, "Failed to restore blocked signals");
606
    return false;
607
  }
608
  return true;
609
}
610
611
__attribute__((nonnull, warn_unused_result))
612
bool setup_signal_handler(struct sigaction *const old_sigchld_action){
613
  struct sigaction sigchld_action = {
614
    .sa_handler=handle_sigchld,
615
    .sa_flags=SA_RESTART | SA_NOCLDSTOP,
616
  };
617
  /* Set all signals in "sa_mask" struct member; this makes all
618
     signals automatically blocked during signal handler */
619
  if(sigfillset(&sigchld_action.sa_mask) != 0){
620
    error(0, errno, "Failed to do sigfillset()");
621
    return false;
622
  }
623
  if(sigaction(SIGCHLD, &sigchld_action, old_sigchld_action) != 0){
624
    error(0, errno, "Failed to set SIGCHLD signal handler");
625
    return false;
626
  }
627
  return true;
628
}
629
630
__attribute__((nonnull, warn_unused_result))
631
bool restore_signal_handler(const struct sigaction *const
632
			    old_sigchld_action){
633
  if(sigaction(SIGCHLD, old_sigchld_action, NULL) != 0){
634
    error(0, errno, "Failed to restore signal handler");
635
    return false;
636
  }
637
  return true;
638
}
639
640
__attribute__((warn_unused_result, malloc))
641
task_queue *create_queue(void){
642
  task_queue *queue = malloc(sizeof(task_queue));
643
  if(queue){
644
    queue->tasks = NULL;
645
    queue->length = 0;
646
    queue->allocated = 0;
647
    queue->next_run = 0;
648
  }
649
  return queue;
650
}
651
652
__attribute__((nonnull, warn_unused_result))
653
bool add_to_queue(task_queue *const queue, const task_context task){
237.7.753 by teddy at recompile
Use reallocarray() if available, or check for overflow
654
  if((queue->length + 1) > (SIZE_MAX / sizeof(task_context))){
655
    /* overflow */
656
    error(0, ENOMEM, "Failed to allocate %" PRIuMAX
657
  	  " tasks for queue->tasks", (uintmax_t)(queue->length + 1));
658
    errno = ENOMEM;
659
    return false;
660
  }
237.7.675 by Teddy Hogeborn
Add dracut(8) support
661
  const size_t needed_size = sizeof(task_context)*(queue->length + 1);
662
  if(needed_size > (queue->allocated)){
663
    task_context *const new_tasks = realloc(queue->tasks,
664
					    needed_size);
665
    if(new_tasks == NULL){
666
      error(0, errno, "Failed to allocate %" PRIuMAX
667
	    " bytes for queue->tasks", (uintmax_t)needed_size);
668
      return false;
669
    }
670
    queue->tasks = new_tasks;
671
    queue->allocated = needed_size;
672
  }
673
  /* Using memcpy here is necessary because doing */
674
  /* queue->tasks[queue->length++] = task; */
675
  /* would violate const-ness of task members */
676
  memcpy(&(queue->tasks[queue->length++]), &task,
677
	 sizeof(task_context));
678
  return true;
679
}
680
681
__attribute__((nonnull))
682
void cleanup_task(const task_context *const task){
683
  const error_t saved_errno = errno;
684
  /* free and close all task data */
685
  free(task->question_filename);
686
  if(task->filename != task->question_filename){
687
    free(task->filename);
688
  }
689
  if(task->pid > 0){
690
    kill(task->pid, SIGTERM);
691
  }
692
  if(task->fd > 0){
693
    close(task->fd);
694
  }
695
  errno = saved_errno;
696
}
697
698
__attribute__((nonnull))
699
void free_queue(task_queue *const queue){
700
  free(queue->tasks);
701
  free(queue);
702
}
703
704
__attribute__((nonnull))
705
void cleanup_queue(task_queue *const *const queue){
706
  if(*queue == NULL){
707
    return;
708
  }
709
  for(size_t i = 0; i < (*queue)->length; i++){
710
    const task_context *const task = ((*queue)->tasks)+i;
711
    cleanup_task(task);
712
  }
713
  free_queue(*queue);
714
}
715
716
__attribute__((pure, nonnull, warn_unused_result))
717
bool queue_has_question(const task_queue *const queue){
718
  for(size_t i=0; i < queue->length; i++){
719
    if(queue->tasks[i].question_filename != NULL){
720
      return true;
721
    }
722
  }
723
  return false;
724
}
725
726
__attribute__((nonnull))
727
void cleanup_close(const int *const fd){
728
  const error_t saved_errno = errno;
729
  close(*fd);
730
  errno = saved_errno;
731
}
732
733
__attribute__((nonnull))
734
void cleanup_string(char *const *const ptr){
735
  free(*ptr);
736
}
737
738
__attribute__((nonnull))
739
void cleanup_buffer(buffer *buf){
740
  if(buf->allocated > 0){
741
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
742
    explicit_bzero(buf->data, buf->allocated);
743
#else
744
    memset(buf->data, '\0', buf->allocated);
745
#endif
746
  }
747
  if(buf->data != NULL){
748
    if(munlock(buf->data, buf->allocated) != 0){
749
      error(0, errno, "Failed to unlock memory of old buffer");
750
    }
751
    free(buf->data);
752
    buf->data = NULL;
753
  }
754
  buf->length = 0;
755
  buf->allocated = 0;
756
}
757
758
__attribute__((pure, nonnull, warn_unused_result))
759
bool string_set_contains(const string_set set, const char *const str){
760
  for(const char *s = set.argz; s != NULL and set.argz_len > 0;
761
      s = argz_next(set.argz, set.argz_len, s)){
762
    if(strcmp(s, str) == 0){
763
      return true;
764
    }
765
  }
766
  return false;
767
}
768
769
__attribute__((nonnull, warn_unused_result))
770
bool string_set_add(string_set *const set, const char *const str){
771
  if(string_set_contains(*set, str)){
772
    return true;
773
  }
774
  error_t error = argz_add(&set->argz, &set->argz_len, str);
775
  if(error == 0){
776
    return true;
777
  }
778
  errno = error;
779
  return false;
780
}
781
782
__attribute__((nonnull))
783
void string_set_clear(string_set *set){
784
  free(set->argz);
785
  set->argz = NULL;
786
  set->argz_len = 0;
787
}
788
789
__attribute__((nonnull))
790
void string_set_swap(string_set *const set1, string_set *const set2){
791
  /* Swap contents of two string sets */
792
  {
793
    char *const tmp_argz = set1->argz;
794
    set1->argz = set2->argz;
795
    set2->argz = tmp_argz;
796
  }
797
  {
798
    const size_t tmp_argz_len = set1->argz_len;
799
    set1->argz_len = set2->argz_len;
800
    set2->argz_len = tmp_argz_len;
801
  }
802
}
803
804
__attribute__((nonnull, warn_unused_result))
805
bool start_mandos_client(task_queue *const queue,
806
			 const int epoll_fd,
807
			 bool *const mandos_client_exited,
808
			 bool *const quit_now, buffer *const password,
809
			 bool *const password_is_read,
810
			 const struct sigaction *const
811
			 old_sigchld_action, const sigset_t sigmask,
812
			 const char *const helper_directory,
813
			 const uid_t user, const gid_t group,
814
			 const char *const *const argv){
815
  int pipefds[2];
816
  if(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK) != 0){
817
    error(0, errno, "Failed to pipe2(..., O_CLOEXEC | O_NONBLOCK)");
818
    return false;
819
  }
820
821
  const pid_t pid = fork();
822
  if(pid == 0){
823
    if(not restore_signal_handler(old_sigchld_action)){
824
      _exit(EXIT_FAILURE);
825
    }
826
    if(not restore_sigmask(&sigmask)){
827
      _exit(EXIT_FAILURE);
828
    }
829
    if(close(pipefds[0]) != 0){
830
      error(0, errno, "Failed to close() parent pipe fd");
831
      _exit(EXIT_FAILURE);
832
    }
833
    if(dup2(pipefds[1], STDOUT_FILENO) == -1){
834
      error(0, errno, "Failed to dup2() pipe fd to stdout");
835
      _exit(EXIT_FAILURE);
836
    }
837
    if(close(pipefds[1]) != 0){
838
      error(0, errno, "Failed to close() old child pipe fd");
839
      _exit(EXIT_FAILURE);
840
    }
841
    if(setenv("MANDOSPLUGINHELPERDIR", helper_directory, 1) != 0){
842
      error(0, errno, "Failed to setenv(\"MANDOSPLUGINHELPERDIR\","
843
	    " \"%s\", 1)", helper_directory);
844
      _exit(EXIT_FAILURE);
845
    }
846
    if(group != 0 and setresgid(group, 0, 0) == -1){
847
      error(0, errno, "Failed to setresgid(-1, %" PRIuMAX ", %"
848
	    PRIuMAX")", (uintmax_t)group, (uintmax_t)group);
849
      _exit(EXIT_FAILURE);
850
    }
851
    if(user != 0 and setresuid(user, 0, 0) == -1){
852
      error(0, errno, "Failed to setresuid(-1, %" PRIuMAX ", %"
853
	    PRIuMAX")", (uintmax_t)user, (uintmax_t)user);
854
      _exit(EXIT_FAILURE);
855
    }
856
#ifdef __GNUC__
857
#pragma GCC diagnostic push
858
    /* For historical reasons, the "argv" argument to execv() is not
859
       const, but it is safe to override this. */
860
#pragma GCC diagnostic ignored "-Wcast-qual"
861
#endif
862
    execv(argv[0], (char **)argv);
863
#ifdef __GNUC__
864
#pragma GCC diagnostic pop
865
#endif
866
    error(0, errno, "execv(\"%s\", ...) failed", argv[0]);
867
    _exit(EXIT_FAILURE);
868
  }
869
  close(pipefds[1]);
870
237.7.757 by teddy at recompile
Check for fork() returning -1
871
  if(pid == -1){
872
    error(0, errno, "Failed to fork()");
873
    close(pipefds[0]);
874
    return false;
875
  }
876
237.7.675 by Teddy Hogeborn
Add dracut(8) support
877
  if(not add_to_queue(queue, (task_context){
878
	.func=wait_for_mandos_client_exit,
879
	.pid=pid,
880
	.mandos_client_exited=mandos_client_exited,
881
	.quit_now=quit_now,
882
      })){
883
    error(0, errno, "Failed to add wait_for_mandos_client to queue");
884
    close(pipefds[0]);
885
    return false;
886
  }
887
888
  const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipefds[0],
889
			    &(struct epoll_event)
890
			    { .events=EPOLLIN | EPOLLRDHUP });
891
  if(ret != 0 and errno != EEXIST){
892
    error(0, errno, "Failed to add file descriptor to epoll set");
893
    close(pipefds[0]);
894
    return false;
895
  }
896
897
  return add_to_queue(queue, (task_context){
898
      .func=read_mandos_client_output,
899
      .epoll_fd=epoll_fd,
900
      .fd=pipefds[0],
901
      .quit_now=quit_now,
902
      .password=password,
903
      .password_is_read=password_is_read,
904
    });
905
}
906
907
__attribute__((nonnull))
908
void wait_for_mandos_client_exit(const task_context task,
909
				 task_queue *const queue){
910
  const pid_t pid = task.pid;
911
  bool *const mandos_client_exited = task.mandos_client_exited;
912
  bool *const quit_now = task.quit_now;
913
914
  int status;
915
  switch(waitpid(pid, &status, WNOHANG)){
916
  case 0:			/* Not exited yet */
917
    if(not add_to_queue(queue, task)){
918
      error(0, errno, "Failed to add myself to queue");
919
      *quit_now = true;
920
    }
921
    break;
922
  case -1:			/* Error */
923
    error(0, errno, "waitpid(%" PRIdMAX ") failed", (intmax_t)pid);
924
    if(errno != ECHILD){
925
      kill(pid, SIGTERM);
926
    }
927
    *quit_now = true;
928
    break;
929
  default:			/* Has exited */
930
    *mandos_client_exited = true;
931
    if((not WIFEXITED(status))
932
       or (WEXITSTATUS(status) != EXIT_SUCCESS)){
933
      error(0, 0, "Mandos client failed or was killed");
934
      *quit_now = true;
935
    }
936
  }
937
}
938
939
__attribute__((nonnull))
940
void read_mandos_client_output(const task_context task,
941
			       task_queue *const queue){
942
  buffer *const password = task.password;
943
  bool *const quit_now = task.quit_now;
944
  bool *const password_is_read = task.password_is_read;
945
  const int fd = task.fd;
946
  const int epoll_fd = task.epoll_fd;
947
948
  const size_t new_potential_size = (password->length + PIPE_BUF);
949
  if(password->allocated < new_potential_size){
950
    char *const new_buffer = calloc(new_potential_size, 1);
951
    if(new_buffer == NULL){
952
      error(0, errno, "Failed to allocate %" PRIuMAX
953
	    " bytes for password", (uintmax_t)new_potential_size);
954
      *quit_now = true;
955
      close(fd);
956
      return;
957
    }
958
    if(mlock(new_buffer, new_potential_size) != 0){
959
      /* Warn but do not treat as fatal error */
960
      if(errno != EPERM and errno != ENOMEM){
961
	error(0, errno, "Failed to lock memory for password");
962
      }
963
    }
964
    if(password->length > 0){
965
      memcpy(new_buffer, password->data, password->length);
966
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
967
      explicit_bzero(password->data, password->allocated);
968
#else
969
      memset(password->data, '\0', password->allocated);
970
#endif
971
    }
972
    if(password->data != NULL){
973
      if(munlock(password->data, password->allocated) != 0){
974
	error(0, errno, "Failed to unlock memory of old buffer");
975
      }
976
      free(password->data);
977
    }
978
    password->data = new_buffer;
979
    password->allocated = new_potential_size;
980
  }
981
982
  const ssize_t read_length = read(fd, password->data
983
				   + password->length, PIPE_BUF);
984
985
  if(read_length == 0){	/* EOF */
986
    *password_is_read = true;
987
    close(fd);
988
    return;
989
  }
990
  if(read_length < 0 and errno != EAGAIN){ /* Actual error */
991
    error(0, errno, "Failed to read password from Mandos client");
992
    *quit_now = true;
993
    close(fd);
994
    return;
995
  }
996
  if(read_length > 0){		/* Data has been read */
997
    password->length += (size_t)read_length;
998
  }
999
1000
  /* Either data was read, or EAGAIN was indicated, meaning no data
1001
     available yet */
1002
1003
  /* Re-add the fd to the epoll set */
1004
  const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1005
			    &(struct epoll_event)
1006
			    { .events=EPOLLIN | EPOLLRDHUP });
1007
  if(ret != 0 and errno != EEXIST){
1008
    error(0, errno, "Failed to re-add file descriptor to epoll set");
1009
    *quit_now = true;
1010
    close(fd);
1011
    return;
1012
  }
1013
1014
  /* Re-add myself to the queue */
1015
  if(not add_to_queue(queue, task)){
1016
    error(0, errno, "Failed to add myself to queue");
1017
    *quit_now = true;
1018
    close(fd);
1019
  }
1020
}
1021
1022
__attribute__((nonnull, warn_unused_result))
1023
bool add_inotify_dir_watch(task_queue *const queue,
1024
			   const int epoll_fd, bool *const quit_now,
1025
			   buffer *const password,
1026
			   const char *const dir,
1027
			   string_set *cancelled_filenames,
1028
			   const mono_microsecs *const current_time,
1029
			   bool *const mandos_client_exited,
1030
			   bool *const password_is_read){
1031
  const int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
1032
  if(fd == -1){
1033
    error(0, errno, "Failed to create inotify instance");
1034
    return false;
1035
  }
1036
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
1037
  if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO
237.7.690 by Teddy Hogeborn
dracut-module/password-agent.c: Require agent directory
1038
		       | IN_MOVED_FROM| IN_DELETE | IN_EXCL_UNLINK
1039
		       | IN_ONLYDIR)
237.7.675 by Teddy Hogeborn
Add dracut(8) support
1040
     == -1){
1041
    error(0, errno, "Failed to create inotify watch on %s", dir);
1042
    return false;
1043
  }
1044
1045
  /* Add the inotify fd to the epoll set */
1046
  const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1047
			    &(struct epoll_event)
1048
			    { .events=EPOLLIN | EPOLLRDHUP });
1049
  if(ret != 0 and errno != EEXIST){
1050
    error(0, errno, "Failed to add file descriptor to epoll set");
1051
    close(fd);
1052
    return false;
1053
  }
1054
1055
  const task_context read_inotify_event_task = {
1056
    .func=read_inotify_event,
1057
    .epoll_fd=epoll_fd,
1058
    .quit_now=quit_now,
1059
    .password=password,
1060
    .fd=fd,
1061
    .filename=strdup(dir),
1062
    .cancelled_filenames=cancelled_filenames,
1063
    .current_time=current_time,
1064
    .mandos_client_exited=mandos_client_exited,
1065
    .password_is_read=password_is_read,
1066
  };
1067
  if(read_inotify_event_task.filename == NULL){
1068
    error(0, errno, "Failed to strdup(\"%s\")", dir);
1069
    close(fd);
1070
    return false;
1071
  }
1072
1073
  return add_to_queue(queue, read_inotify_event_task);
1074
}
1075
1076
__attribute__((nonnull))
1077
void read_inotify_event(const task_context task,
1078
			task_queue *const queue){
1079
  const int fd = task.fd;
1080
  const int epoll_fd = task.epoll_fd;
1081
  char *const filename = task.filename;
1082
  bool *quit_now = task.quit_now;
1083
  buffer *const password = task.password;
1084
  string_set *const cancelled_filenames = task.cancelled_filenames;
1085
  const mono_microsecs *const current_time = task.current_time;
1086
  bool *const mandos_client_exited = task.mandos_client_exited;
1087
  bool *const password_is_read = task.password_is_read;
1088
1089
  /* "sufficient to read at least one event." - inotify(7) */
1090
  const size_t ievent_size = (sizeof(struct inotify_event)
1091
			      + NAME_MAX + 1);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
1092
  struct {
1093
    struct inotify_event event;
1094
    char name_buffer[NAME_MAX + 1];
1095
  } ievent_buffer;
1096
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
1097
1098
  const ssize_t read_length = read(fd, ievent, ievent_size);
1099
  if(read_length == 0){	/* EOF */
1100
    error(0, 0, "Got EOF from inotify fd for directory %s", filename);
1101
    *quit_now = true;
1102
    cleanup_task(&task);
1103
    return;
1104
  }
1105
  if(read_length < 0 and errno != EAGAIN){ /* Actual error */
1106
    error(0, errno, "Failed to read from inotify fd for directory %s",
1107
	  filename);
1108
    *quit_now = true;
1109
    cleanup_task(&task);
1110
    return;
1111
  }
1112
  if(read_length > 0		/* Data has been read */
1113
     and fnmatch("ask.*", ievent->name, FNM_FILE_NAME) == 0){
1114
    char *question_filename = NULL;
1115
    const ssize_t question_filename_length
1116
      = asprintf(&question_filename, "%s/%s", filename, ievent->name);
1117
    if(question_filename_length < 0){
1118
      error(0, errno, "Failed to create file name from directory name"
1119
	    " %s and file name %s", filename, ievent->name);
1120
    } else {
1121
      if(ievent->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)){
1122
	if(not add_to_queue(queue, (task_context){
1123
	      .func=open_and_parse_question,
1124
	      .epoll_fd=epoll_fd,
1125
	      .question_filename=question_filename,
1126
	      .filename=question_filename,
1127
	      .password=password,
1128
	      .cancelled_filenames=cancelled_filenames,
1129
	      .current_time=current_time,
1130
	      .mandos_client_exited=mandos_client_exited,
1131
	      .password_is_read=password_is_read,
1132
	    })){
1133
	  error(0, errno, "Failed to add open_and_parse_question task"
1134
		" for file name %s to queue", filename);
1135
	} else {
1136
	  /* Force the added task (open_and_parse_question) to run
1137
	     immediately */
1138
	  queue->next_run = 1;
1139
	}
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
1140
      } else if(ievent->mask & (IN_MOVED_FROM | IN_DELETE)){
237.7.675 by Teddy Hogeborn
Add dracut(8) support
1141
	if(not string_set_add(cancelled_filenames,
1142
			      question_filename)){
1143
	  error(0, errno, "Could not add question %s to"
1144
		" cancelled_questions", question_filename);
1145
	  *quit_now = true;
1146
	  free(question_filename);
1147
	  cleanup_task(&task);
1148
	  return;
1149
	}
1150
	free(question_filename);
1151
      }
1152
    }
1153
  }
1154
1155
  /* Either data was read, or EAGAIN was indicated, meaning no data
1156
     available yet */
1157
1158
  /* Re-add myself to the queue */
1159
  if(not add_to_queue(queue, task)){
1160
    error(0, errno, "Failed to re-add read_inotify_event(%s) to"
1161
	  " queue", filename);
1162
    *quit_now = true;
1163
    cleanup_task(&task);
1164
    return;
1165
  }
1166
1167
  /* Re-add the fd to the epoll set */
1168
  const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1169
			    &(struct epoll_event)
1170
			    { .events=EPOLLIN | EPOLLRDHUP });
1171
  if(ret != 0 and errno != EEXIST){
1172
    error(0, errno, "Failed to re-add inotify file descriptor %d for"
1173
	  " directory %s to epoll set", fd, filename);
1174
    /* Force the added task (read_inotify_event) to run again, at most
1175
       one second from now */
1176
    if((queue->next_run == 0)
1177
       or (queue->next_run > (*current_time + 1000000))){
1178
      queue->next_run = *current_time + 1000000;
1179
    }
1180
  }
1181
}
1182
1183
__attribute__((nonnull))
1184
void open_and_parse_question(const task_context task,
1185
			     task_queue *const queue){
1186
  __attribute__((cleanup(cleanup_string)))
1187
    char *question_filename = task.question_filename;
1188
  const int epoll_fd = task.epoll_fd;
1189
  buffer *const password = task.password;
1190
  string_set *const cancelled_filenames = task.cancelled_filenames;
1191
  const mono_microsecs *const current_time = task.current_time;
1192
  bool *const mandos_client_exited = task.mandos_client_exited;
1193
  bool *const password_is_read = task.password_is_read;
1194
1195
  /* We use the GLib "Key-value file parser" functions to parse the
1196
     question file.  See <https://www.freedesktop.org/wiki/Software
1197
     /systemd/PasswordAgents/> for specification of contents */
1198
  __attribute__((nonnull))
1199
    void cleanup_g_key_file(GKeyFile **key_file){
1200
    if(*key_file != NULL){
1201
      g_key_file_free(*key_file);
1202
    }
1203
  }
1204
1205
  __attribute__((cleanup(cleanup_g_key_file)))
1206
    GKeyFile *key_file = g_key_file_new();
1207
  if(key_file == NULL){
1208
    error(0, errno, "Failed g_key_file_new() for \"%s\"",
1209
	  question_filename);
1210
    return;
1211
  }
1212
  GError *glib_error = NULL;
1213
  if(g_key_file_load_from_file(key_file, question_filename,
1214
			       G_KEY_FILE_NONE, &glib_error) != TRUE){
1215
    /* If a file was removed, we should ignore it, so */
1216
    /* only show error message if file actually existed */
1217
    if(glib_error->code != G_FILE_ERROR_NOENT){
1218
      error(0, 0, "Failed to load question data from file \"%s\": %s",
1219
	    question_filename, glib_error->message);
1220
    }
1221
    return;
1222
  }
1223
1224
  __attribute__((cleanup(cleanup_string)))
1225
    char *socket_name = g_key_file_get_string(key_file, "Ask",
1226
					      "Socket",
1227
					      &glib_error);
1228
  if(socket_name == NULL){
1229
    error(0, 0, "Question file \"%s\" did not contain \"Socket\": %s",
1230
	  question_filename, glib_error->message);
1231
    return;
1232
  }
1233
1234
  if(strlen(socket_name) == 0){
1235
    error(0, 0, "Question file \"%s\" had empty \"Socket\" value",
1236
	  question_filename);
1237
    return;
1238
  }
1239
1240
  const guint64 pid = g_key_file_get_uint64(key_file, "Ask", "PID",
1241
					    &glib_error);
1242
  if(glib_error != NULL){
1243
    error(0, 0, "Question file \"%s\" contained bad \"PID\": %s",
1244
	  question_filename, glib_error->message);
1245
    return;
1246
  }
1247
1248
  if((pid != (guint64)((pid_t)pid))
1249
     or (kill((pid_t)pid, 0) != 0)){
1250
    error(0, 0, "PID %" PRIuMAX " in question file \"%s\" is bad or"
1251
	  " does not exist", (uintmax_t)pid, question_filename);
1252
    return;
1253
  }
1254
1255
  guint64 notafter = g_key_file_get_uint64(key_file, "Ask",
1256
					   "NotAfter", &glib_error);
1257
  if(glib_error != NULL){
1258
    if(glib_error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND){
1259
      error(0, 0, "Question file \"%s\" contained bad \"NotAfter\":"
1260
	    " %s", question_filename, glib_error->message);
1261
    }
1262
    notafter = 0;
1263
  }
1264
  if(notafter != 0){
1265
    if(queue->next_run == 0 or (queue->next_run > notafter)){
1266
      queue->next_run = notafter;
1267
    }
1268
    if(*current_time >= notafter){
1269
      return;
1270
    }
1271
  }
1272
1273
  const task_context connect_question_socket_task = {
1274
    .func=connect_question_socket,
1275
    .question_filename=strdup(question_filename),
1276
    .epoll_fd=epoll_fd,
1277
    .password=password,
1278
    .filename=strdup(socket_name),
1279
    .cancelled_filenames=task.cancelled_filenames,
1280
    .mandos_client_exited=mandos_client_exited,
1281
    .password_is_read=password_is_read,
1282
    .current_time=current_time,
1283
  };
1284
  if(connect_question_socket_task.question_filename == NULL
1285
     or connect_question_socket_task.filename == NULL
1286
     or not add_to_queue(queue, connect_question_socket_task)){
1287
    error(0, errno, "Failed to add connect_question_socket for socket"
1288
	  " %s (from \"%s\") to queue", socket_name,
1289
	  question_filename);
1290
    cleanup_task(&connect_question_socket_task);
1291
    return;
1292
  }
1293
  /* Force the added task (connect_question_socket) to run
1294
     immediately */
1295
  queue->next_run = 1;
1296
1297
  if(notafter > 0){
1298
    char *const dup_filename = strdup(question_filename);
1299
    const task_context cancel_old_question_task = {
1300
      .func=cancel_old_question,
1301
      .question_filename=dup_filename,
1302
      .notafter=notafter,
1303
      .filename=dup_filename,
1304
      .cancelled_filenames=cancelled_filenames,
1305
      .current_time=current_time,
1306
    };
1307
    if(cancel_old_question_task.question_filename == NULL
1308
       or not add_to_queue(queue, cancel_old_question_task)){
1309
      error(0, errno, "Failed to add cancel_old_question for file "
1310
	    "\"%s\" to queue", question_filename);
1311
      cleanup_task(&cancel_old_question_task);
1312
      return;
1313
    }
1314
  }
1315
}
1316
1317
__attribute__((nonnull))
1318
void cancel_old_question(const task_context task,
1319
			 task_queue *const queue){
1320
  char *const question_filename = task.question_filename;
1321
  string_set *const cancelled_filenames = task.cancelled_filenames;
1322
  const mono_microsecs notafter = task.notafter;
1323
  const mono_microsecs *const current_time = task.current_time;
1324
1325
  if(*current_time >= notafter){
1326
    if(not string_set_add(cancelled_filenames, question_filename)){
1327
      error(0, errno, "Failed to cancel question for file %s",
1328
	    question_filename);
1329
    }
1330
    cleanup_task(&task);
1331
    return;
1332
  }
1333
1334
  if(not add_to_queue(queue, task)){
1335
    error(0, errno, "Failed to add cancel_old_question for file "
1336
	  "%s to queue", question_filename);
1337
    cleanup_task(&task);
1338
    return;
1339
  }
1340
1341
  if((queue->next_run == 0) or (queue->next_run > notafter)){
1342
    queue->next_run = notafter;
1343
  }
1344
}
1345
1346
__attribute__((nonnull))
1347
void connect_question_socket(const task_context task,
1348
			     task_queue *const queue){
1349
  char *const question_filename = task.question_filename;
1350
  char *const filename = task.filename;
1351
  const int epoll_fd = task.epoll_fd;
1352
  buffer *const password = task.password;
1353
  string_set *const cancelled_filenames = task.cancelled_filenames;
1354
  bool *const mandos_client_exited = task.mandos_client_exited;
1355
  bool *const password_is_read = task.password_is_read;
1356
  const mono_microsecs *const current_time = task.current_time;
1357
1358
  struct sockaddr_un sock_name = { .sun_family=AF_LOCAL };
1359
1360
  if(sizeof(sock_name.sun_path) <= strlen(filename)){
1361
    error(0, 0, "Socket filename is larger than"
1362
	  " sizeof(sockaddr_un.sun_path); %" PRIuMAX ": \"%s\"",
1363
	  (uintmax_t)sizeof(sock_name.sun_path), filename);
1364
    if(not string_set_add(cancelled_filenames, question_filename)){
1365
      error(0, errno, "Failed to cancel question for file %s",
1366
	    question_filename);
1367
    }
1368
    cleanup_task(&task);
1369
    return;
1370
  }
1371
1372
  const int fd = socket(PF_LOCAL, SOCK_DGRAM
1373
			| SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
1374
  if(fd < 0){
1375
    error(0, errno,
1376
	  "Failed to create socket(PF_LOCAL, SOCK_DGRAM, 0)");
1377
    if(not add_to_queue(queue, task)){
1378
      error(0, errno, "Failed to add connect_question_socket for file"
1379
            " \"%s\" and socket \"%s\" to queue", question_filename,
1380
	    filename);
1381
      cleanup_task(&task);
1382
    } else {
1383
      /* Force the added task (connect_question_socket) to run
1384
	 immediately */
1385
      queue->next_run = 1;
1386
    }
1387
    return;
1388
  }
1389
1390
  strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path));
1391
  if(connect(fd, (struct sockaddr *)&sock_name,
1392
	     (socklen_t)SUN_LEN(&sock_name)) != 0){
1393
    error(0, errno, "Failed to connect socket to \"%s\"", filename);
1394
    if(not add_to_queue(queue, task)){
1395
      error(0, errno, "Failed to add connect_question_socket for file"
1396
            " \"%s\" and socket \"%s\" to queue", question_filename,
1397
	    filename);
1398
      cleanup_task(&task);
1399
    } else {
1400
      /* Force the added task (connect_question_socket) to run again,
1401
	 at most one second from now */
1402
      if((queue->next_run == 0)
1403
	 or (queue->next_run > (*current_time + 1000000))){
1404
	queue->next_run = *current_time + 1000000;
1405
      }
1406
    }
1407
    return;
1408
  }
1409
1410
  /* Not necessary, but we can try, and merely warn on failure */
1411
  if(shutdown(fd, SHUT_RD) != 0){
1412
    error(0, errno, "Failed to shutdown reading from socket \"%s\"",
1413
	  filename);
1414
  }
1415
1416
  /* Add the fd to the epoll set */
1417
  if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1418
	       &(struct epoll_event){ .events=EPOLLOUT })
1419
     != 0){
1420
    error(0, errno, "Failed to add inotify file descriptor %d for"
1421
	  " socket %s to epoll set", fd, filename);
1422
    if(not add_to_queue(queue, task)){
1423
      error(0, errno, "Failed to add connect_question_socket for file"
1424
            " \"%s\" and socket \"%s\" to queue", question_filename,
1425
	    filename);
1426
      cleanup_task(&task);
1427
    } else {
1428
      /* Force the added task (connect_question_socket) to run again,
1429
	 at most one second from now */
1430
      if((queue->next_run == 0)
1431
	 or (queue->next_run > (*current_time + 1000000))){
1432
	queue->next_run = *current_time + 1000000;
1433
      }
1434
    }
1435
    return;
1436
  }
1437
1438
  /* add task send_password_to_socket to queue */
1439
  const task_context send_password_to_socket_task = {
1440
    .func=send_password_to_socket,
1441
    .question_filename=question_filename,
1442
    .filename=filename,
1443
    .epoll_fd=epoll_fd,
1444
    .fd=fd,
1445
    .password=password,
1446
    .cancelled_filenames=cancelled_filenames,
1447
    .mandos_client_exited=mandos_client_exited,
1448
    .password_is_read=password_is_read,
1449
    .current_time=current_time,
1450
  };
1451
1452
  if(not add_to_queue(queue, send_password_to_socket_task)){
1453
    error(0, errno, "Failed to add send_password_to_socket for"
1454
	  " file \"%s\" and socket \"%s\" to queue",
1455
	  question_filename, filename);
1456
    cleanup_task(&send_password_to_socket_task);
1457
  }
1458
}
1459
1460
__attribute__((nonnull))
1461
void send_password_to_socket(const task_context task,
1462
			     task_queue *const queue){
1463
  char *const question_filename=task.question_filename;
1464
  char *const filename=task.filename;
1465
  const int epoll_fd=task.epoll_fd;
1466
  const int fd=task.fd;
1467
  buffer *const password=task.password;
1468
  string_set *const cancelled_filenames=task.cancelled_filenames;
1469
  bool *const mandos_client_exited = task.mandos_client_exited;
1470
  bool *const password_is_read = task.password_is_read;
1471
  const mono_microsecs *const current_time = task.current_time;
1472
1473
  if(*mandos_client_exited and *password_is_read){
1474
1475
    const size_t send_buffer_length = password->length + 2;
1476
    char *send_buffer = malloc(send_buffer_length);
1477
    if(send_buffer == NULL){
1478
      error(0, errno, "Failed to allocate send_buffer");
1479
    } else {
1480
      if(mlock(send_buffer, send_buffer_length) != 0){
1481
	/* Warn but do not treat as fatal error */
1482
	if(errno != EPERM and errno != ENOMEM){
1483
	  error(0, errno, "Failed to lock memory for password"
1484
		" buffer");
1485
	}
1486
      }
1487
      /* “[…] send a single datagram to the socket consisting of the
1488
	 password string either prefixed with "+" or with "-"
1489
	 depending on whether the password entry was successful or
1490
	 not. You may but don't have to include a final NUL byte in
1491
	 your message.
1492
1493
	 — <https://www.freedesktop.org/wiki/Software/systemd/
1494
	 PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
1495
      */
1496
      send_buffer[0] = '+';	/* Prefix with "+" */
1497
      /* Always add an extra NUL */
1498
      send_buffer[password->length + 1] = '\0';
1499
      if(password->length > 0){
1500
	memcpy(send_buffer + 1, password->data, password->length);
1501
      }
1502
      errno = 0;
1503
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1504
			   MSG_NOSIGNAL);
1505
      const error_t saved_errno = errno;
1506
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1507
      explicit_bzero(send_buffer, send_buffer_length);
1508
#else
1509
      memset(send_buffer, '\0', send_buffer_length);
1510
#endif
1511
      if(munlock(send_buffer, send_buffer_length) != 0){
1512
	error(0, errno, "Failed to unlock memory of send buffer");
1513
      }
1514
      free(send_buffer);
1515
      if(ssret < 0 or ssret < (ssize_t)send_buffer_length){
1516
	switch(saved_errno){
1517
	case EINTR:
1518
	case ENOBUFS:
1519
	case ENOMEM:
1520
	case EADDRINUSE:
1521
	case ECONNREFUSED:
1522
	case ECONNRESET:
1523
	case ENOENT:
1524
	case ETOOMANYREFS:
1525
	case EAGAIN:
1526
	  /* Retry, below */
1527
	  break;
1528
	case EMSGSIZE:
1529
	  error(0, 0, "Password of size %" PRIuMAX " is too big",
1530
		(uintmax_t)password->length);
1531
#if __GNUC__ < 7
1532
	  /* FALLTHROUGH */
1533
#else
1534
	  __attribute__((fallthrough));
1535
#endif
1536
	case 0:
1537
	  if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1538
	    error(0, 0, "Password only partially sent to socket");
1539
	  }
1540
#if __GNUC__ < 7
1541
	  /* FALLTHROUGH */
1542
#else
1543
	  __attribute__((fallthrough));
1544
#endif
1545
	default:
1546
	  error(0, saved_errno, "Failed to send() to socket %s",
1547
		filename);
1548
	  if(not string_set_add(cancelled_filenames,
1549
				question_filename)){
1550
	    error(0, errno, "Failed to cancel question for file %s",
1551
		  question_filename);
1552
	  }
1553
	  cleanup_task(&task);
1554
	  return;
1555
	}
1556
      } else {
1557
	/* Success */
1558
	cleanup_task(&task);
1559
	return;
1560
      }
1561
    }
1562
  }
1563
1564
  /* We failed or are not ready yet; retry later */
1565
1566
  if(not add_to_queue(queue, task)){
1567
    error(0, errno, "Failed to add send_password_to_socket for"
1568
	  " file %s and socket %s to queue", question_filename,
1569
	  filename);
1570
    cleanup_task(&task);
1571
  }
1572
1573
  /* Add the fd to the epoll set */
1574
  if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1575
	       &(struct epoll_event){ .events=EPOLLOUT })
1576
     != 0){
1577
    error(0, errno, "Failed to add socket file descriptor %d for"
1578
	  " socket %s to epoll set", fd, filename);
1579
    /* Force the added task (send_password_to_socket) to run again, at
1580
       most one second from now */
1581
    if((queue->next_run == 0)
1582
       or (queue->next_run > (*current_time + 1000000))){
1583
      queue->next_run = *current_time + 1000000;
1584
    }
1585
  }
1586
}
1587
1588
__attribute__((warn_unused_result))
1589
bool add_existing_questions(task_queue *const queue,
1590
			    const int epoll_fd,
1591
			    buffer *const password,
1592
			    string_set *cancelled_filenames,
1593
			    const mono_microsecs *const current_time,
1594
			    bool *const mandos_client_exited,
1595
			    bool *const password_is_read,
1596
			    const char *const dirname){
1597
  __attribute__((cleanup(cleanup_string)))
1598
    char *dir_pattern = NULL;
1599
  const int ret = asprintf(&dir_pattern, "%s/ask.*", dirname);
1600
  if(ret < 0 or dir_pattern == NULL){
1601
    error(0, errno, "Could not create glob pattern for directory %s",
1602
	  dirname);
1603
    return false;
1604
  }
1605
  __attribute__((cleanup(globfree)))
1606
    glob_t question_filenames = {};
1607
  switch(glob(dir_pattern, GLOB_ERR | GLOB_NOSORT | GLOB_MARK,
1608
	      NULL, &question_filenames)){
1609
  case GLOB_ABORTED:
1610
  default:
1611
    error(0, errno, "Failed to open directory %s", dirname);
1612
    return false;
1613
  case GLOB_NOMATCH:
1614
    error(0, errno, "There are no question files in %s", dirname);
1615
    return false;
1616
  case GLOB_NOSPACE:
1617
    error(0, errno, "Could not allocate memory for question file"
1618
	  " names in %s", dirname);
1619
#if __GNUC__ < 7
1620
    /* FALLTHROUGH */
1621
#else
1622
    __attribute__((fallthrough));
1623
#endif
1624
  case 0:
1625
    for(size_t i = 0; i < question_filenames.gl_pathc; i++){
1626
      char *const question_filename = strdup(question_filenames
1627
					     .gl_pathv[i]);
1628
      const task_context task = {
1629
	.func=open_and_parse_question,
1630
	.epoll_fd=epoll_fd,
1631
	.question_filename=question_filename,
1632
	.filename=question_filename,
1633
	.password=password,
1634
	.cancelled_filenames=cancelled_filenames,
1635
	.current_time=current_time,
1636
	.mandos_client_exited=mandos_client_exited,
1637
	.password_is_read=password_is_read,
1638
      };
1639
1640
      if(question_filename == NULL
1641
	 or not add_to_queue(queue, task)){
1642
	error(0, errno, "Failed to add open_and_parse_question for"
1643
	      " file %s to queue",
1644
	      question_filenames.gl_pathv[i]);
1645
	free(question_filename);
1646
      } else {
1647
	queue->next_run = 1;
1648
      }
1649
    }
1650
    return true;
1651
  }
1652
}
1653
1654
__attribute__((nonnull, warn_unused_result))
1655
bool wait_for_event(const int epoll_fd,
1656
		    const mono_microsecs queue_next_run,
1657
		    const mono_microsecs current_time){
1658
  __attribute__((const))
1659
    int milliseconds_to_wait(const mono_microsecs currtime,
1660
			     const mono_microsecs nextrun){
1661
    if(currtime >= nextrun){
1662
      return 0;
1663
    }
1664
    const uintmax_t wait_time_ms = (nextrun - currtime) / 1000;
1665
    if(wait_time_ms > (uintmax_t)INT_MAX){
1666
      return INT_MAX;
1667
    }
1668
    return (int)wait_time_ms;
1669
  }
1670
1671
  const int wait_time_ms = milliseconds_to_wait(current_time,
1672
						queue_next_run);
1673
1674
  /* Prepare unblocking of SIGCHLD during epoll_pwait */
1675
  sigset_t temporary_unblocked_sigmask;
1676
  /* Get current signal mask */
1677
  if(pthread_sigmask(-1, NULL, &temporary_unblocked_sigmask) != 0){
1678
    return false;
1679
  }
1680
  /* Remove SIGCHLD from the signal mask */
1681
  if(sigdelset(&temporary_unblocked_sigmask, SIGCHLD) != 0){
1682
    return false;
1683
  }
1684
  struct epoll_event events[8]; /* Ignored */
1685
  int ret = epoll_pwait(epoll_fd, events,
1686
			sizeof(events) / sizeof(struct epoll_event),
1687
			queue_next_run == 0 ? -1 : (int)wait_time_ms,
1688
			&temporary_unblocked_sigmask);
1689
  if(ret < 0 and errno != EINTR){
1690
    error(0, errno, "Failed epoll_pwait(epfd=%d, ..., timeout=%d,"
1691
	  " ...", epoll_fd,
1692
	  queue_next_run == 0 ? -1 : (int)wait_time_ms);
1693
    return false;
1694
  }
1695
  return clear_all_fds_from_epoll_set(epoll_fd);
1696
}
1697
1698
bool clear_all_fds_from_epoll_set(const int epoll_fd){
1699
  /* Create a new empty epoll set */
1700
  __attribute__((cleanup(cleanup_close)))
1701
    const int new_epoll_fd = epoll_create1(EPOLL_CLOEXEC);
1702
  if(new_epoll_fd < 0){
1703
    return false;
1704
  }
1705
  /* dup3() the new epoll set fd over the old one, replacing it */
1706
  if(dup3(new_epoll_fd, epoll_fd, O_CLOEXEC) < 0){
1707
    return false;
1708
  }
1709
  return true;
1710
}
1711
1712
__attribute__((nonnull, warn_unused_result))
1713
bool run_queue(task_queue **const queue,
1714
	       string_set *const cancelled_filenames,
1715
	       bool *const quit_now){
1716
1717
  task_queue *new_queue = create_queue();
1718
  if(new_queue == NULL){
1719
    return false;
1720
  }
1721
1722
  __attribute__((cleanup(string_set_clear)))
1723
    string_set old_cancelled_filenames = {};
1724
  string_set_swap(cancelled_filenames, &old_cancelled_filenames);
1725
1726
  /* Declare i outside the for loop, since we might need i after the
1727
     loop in case we aborted in the middle */
1728
  size_t i;
1729
  for(i=0; i < (*queue)->length and not *quit_now; i++){
1730
    task_context *const task = &((*queue)->tasks[i]);
1731
    const char *const question_filename = task->question_filename;
1732
    /* Skip any task referencing a cancelled question filename */
1733
    if(question_filename != NULL
1734
       and string_set_contains(old_cancelled_filenames,
1735
    			       question_filename)){
1736
      cleanup_task(task);
1737
      continue;
1738
    }
1739
    task->func(*task, new_queue);
1740
  }
1741
1742
  if(*quit_now){
1743
    /* we might be in the middle of the queue, so clean up any
1744
       remaining tasks in the current queue */
1745
    for(; i < (*queue)->length; i++){
1746
      cleanup_task(&((*queue)->tasks[i]));
1747
    }
1748
    free_queue(*queue);
1749
    *queue = new_queue;
1750
    new_queue = NULL;
1751
    return false;
1752
  }
1753
  free_queue(*queue);
1754
  *queue = new_queue;
1755
  new_queue = NULL;
1756
1757
  return true;
1758
}
1759
1760
/* End of regular code section */
1761

1762
/* Start of tests section; here are the tests for the above code */
1763
1764
/* This "fixture" data structure is used by the test setup and
1765
   teardown functions */
1766
typedef struct {
1767
  struct sigaction orig_sigaction;
1768
  sigset_t orig_sigmask;
1769
} test_fixture;
1770
1771
static void test_setup(test_fixture *fixture,
1772
		       __attribute__((unused))
1773
		       gconstpointer user_data){
1774
  g_assert_true(setup_signal_handler(&fixture->orig_sigaction));
1775
  g_assert_true(block_sigchld(&fixture->orig_sigmask));
1776
}
1777
1778
static void test_teardown(test_fixture *fixture,
1779
			  __attribute__((unused))
1780
			  gconstpointer user_data){
1781
  g_assert_true(restore_signal_handler(&fixture->orig_sigaction));
1782
  g_assert_true(restore_sigmask(&fixture->orig_sigmask));
1783
}
1784
1785
/* Utility function used by tests to search queue for matching task */
1786
__attribute__((pure, nonnull, warn_unused_result))
1787
static task_context *find_matching_task(const task_queue *const queue,
1788
					const task_context task){
1789
  /* The argument "task" structure is a pattern to match; 0 in any
1790
     member means any value matches, otherwise the value must match.
1791
     The filename strings are compared by strcmp(), not by pointer. */
1792
  for(size_t i = 0; i < queue->length; i++){
1793
    task_context *const current_task = queue->tasks+i;
1794
    /* Check all members of task_context, if set to a non-zero value.
1795
       If a member does not match, continue to next task in queue */
1796
1797
    /* task_func *const func */
1798
    if(task.func != NULL and current_task->func != task.func){
1799
      continue;
1800
    }
1801
    /* char *const question_filename; */
1802
    if(task.question_filename != NULL
1803
       and (current_task->question_filename == NULL
1804
	    or strcmp(current_task->question_filename,
1805
		      task.question_filename) != 0)){
1806
      continue;
1807
    }
1808
    /* const pid_t pid; */
1809
    if(task.pid != 0 and current_task->pid != task.pid){
1810
      continue;
1811
    }
1812
    /* const int epoll_fd; */
1813
    if(task.epoll_fd != 0
1814
       and current_task->epoll_fd != task.epoll_fd){
1815
      continue;
1816
    }
1817
    /* bool *const quit_now; */
1818
    if(task.quit_now != NULL
1819
       and current_task->quit_now != task.quit_now){
1820
      continue;
1821
    }
1822
    /* const int fd; */
1823
    if(task.fd != 0 and current_task->fd != task.fd){
1824
      continue;
1825
    }
1826
    /* bool *const mandos_client_exited; */
1827
    if(task.mandos_client_exited != NULL
1828
       and current_task->mandos_client_exited
1829
       != task.mandos_client_exited){
1830
      continue;
1831
    }
1832
    /* buffer *const password; */
1833
    if(task.password != NULL
1834
       and current_task->password != task.password){
1835
      continue;
1836
    }
1837
    /* bool *const password_is_read; */
1838
    if(task.password_is_read != NULL
1839
       and current_task->password_is_read != task.password_is_read){
1840
      continue;
1841
    }
1842
    /* char *filename; */
1843
    if(task.filename != NULL
1844
       and (current_task->filename == NULL
1845
	    or strcmp(current_task->filename, task.filename) != 0)){
1846
      continue;
1847
    }
1848
    /* string_set *const cancelled_filenames; */
1849
    if(task.cancelled_filenames != NULL
1850
       and current_task->cancelled_filenames
1851
       != task.cancelled_filenames){
1852
      continue;
1853
    }
1854
    /* const mono_microsecs notafter; */
1855
    if(task.notafter != 0
1856
       and current_task->notafter != task.notafter){
1857
      continue;
1858
    }
1859
    /* const mono_microsecs *const current_time; */
1860
    if(task.current_time != NULL
1861
       and current_task->current_time != task.current_time){
1862
      continue;
1863
    }
1864
    /* Current task matches all members; return it */
1865
    return current_task;
1866
  }
1867
  /* No task in queue matches passed pattern task */
1868
  return NULL;
1869
}
1870
1871
static void test_create_queue(__attribute__((unused))
1872
			      test_fixture *fixture,
1873
			      __attribute__((unused))
1874
			      gconstpointer user_data){
1875
  __attribute__((cleanup(cleanup_queue)))
1876
    task_queue *const queue = create_queue();
1877
  g_assert_nonnull(queue);
1878
  g_assert_null(queue->tasks);
1879
  g_assert_true(queue->length == 0);
1880
  g_assert_true(queue->next_run == 0);
1881
}
1882
1883
static task_func dummy_func;
1884
1885
static void test_add_to_queue(__attribute__((unused))
1886
			      test_fixture *fixture,
1887
			      __attribute__((unused))
1888
			      gconstpointer user_data){
1889
  __attribute__((cleanup(cleanup_queue)))
1890
    task_queue *queue = create_queue();
1891
  g_assert_nonnull(queue);
1892
1893
  g_assert_true(add_to_queue(queue,
1894
			     (task_context){ .func=dummy_func }));
1895
  g_assert_true(queue->length == 1);
1896
  g_assert_nonnull(queue->tasks);
1897
  g_assert_true(queue->tasks[0].func == dummy_func);
1898
}
1899
237.7.753 by teddy at recompile
Use reallocarray() if available, or check for overflow
1900
static void test_add_to_queue_overflow(__attribute__((unused))
1901
				       test_fixture *fixture,
1902
				       __attribute__((unused))
1903
				       gconstpointer user_data){
1904
  __attribute__((cleanup(cleanup_queue)))
1905
    task_queue *queue = create_queue();
1906
  g_assert_nonnull(queue);
1907
  g_assert_true(queue->length == 0);
1908
  queue->length = SIZE_MAX / sizeof(task_context); /* fake max size */
1909
1910
  FILE *real_stderr = stderr;
1911
  FILE *devnull = fopen("/dev/null", "we");
1912
  g_assert_nonnull(devnull);
1913
  stderr = devnull;
1914
  const bool ret = add_to_queue(queue,
1915
				(task_context){ .func=dummy_func });
1916
  g_assert_true(errno == ENOMEM);
1917
  g_assert_false(ret);
1918
  stderr = real_stderr;
1919
  g_assert_cmpint(fclose(devnull), ==, 0);
1920
  queue->length = 0;		/* Restore real size */
1921
}
1922
237.7.675 by Teddy Hogeborn
Add dracut(8) support
1923
static void dummy_func(__attribute__((unused))
1924
		       const task_context task,
1925
		       __attribute__((unused))
1926
		       task_queue *const queue){
1927
}
1928
1929
static void test_queue_has_question_empty(__attribute__((unused))
1930
					  test_fixture *fixture,
1931
					  __attribute__((unused))
1932
					  gconstpointer user_data){
1933
  __attribute__((cleanup(cleanup_queue)))
1934
    task_queue *queue = create_queue();
1935
  g_assert_nonnull(queue);
1936
  g_assert_false(queue_has_question(queue));
1937
}
1938
1939
static void test_queue_has_question_false(__attribute__((unused))
1940
					  test_fixture *fixture,
1941
					  __attribute__((unused))
1942
					  gconstpointer user_data){
1943
  __attribute__((cleanup(cleanup_queue)))
1944
    task_queue *queue = create_queue();
1945
  g_assert_nonnull(queue);
1946
  g_assert_true(add_to_queue(queue,
1947
			     (task_context){ .func=dummy_func }));
1948
  g_assert_false(queue_has_question(queue));
1949
}
1950
1951
static void test_queue_has_question_true(__attribute__((unused))
1952
					 test_fixture *fixture,
1953
					 __attribute__((unused))
1954
					 gconstpointer user_data){
1955
  __attribute__((cleanup(cleanup_queue)))
1956
    task_queue *queue = create_queue();
1957
  g_assert_nonnull(queue);
1958
  char *const question_filename
1959
    = strdup("/nonexistent/question_filename");
1960
  g_assert_nonnull(question_filename);
1961
  task_context task = {
1962
    .func=dummy_func,
1963
    .question_filename=question_filename,
1964
  };
1965
  g_assert_true(add_to_queue(queue, task));
1966
  g_assert_true(queue_has_question(queue));
1967
}
1968
1969
static void test_queue_has_question_false2(__attribute__((unused))
1970
					   test_fixture *fixture,
1971
					   __attribute__((unused))
1972
					   gconstpointer user_data){
1973
  __attribute__((cleanup(cleanup_queue)))
1974
    task_queue *queue = create_queue();
1975
  g_assert_nonnull(queue);
1976
  task_context task = { .func=dummy_func };
1977
  g_assert_true(add_to_queue(queue, task));
1978
  g_assert_true(add_to_queue(queue, task));
1979
  g_assert_cmpint((int)queue->length, ==, 2);
1980
  g_assert_false(queue_has_question(queue));
1981
}
1982
1983
static void test_queue_has_question_true2(__attribute__((unused))
1984
					  test_fixture *fixture,
1985
					  __attribute__((unused))
1986
					  gconstpointer user_data){
1987
  __attribute__((cleanup(cleanup_queue)))
1988
    task_queue *queue = create_queue();
1989
  g_assert_nonnull(queue);
1990
  task_context task1 = { .func=dummy_func };
1991
  g_assert_true(add_to_queue(queue, task1));
1992
  char *const question_filename
1993
    = strdup("/nonexistent/question_filename");
1994
  g_assert_nonnull(question_filename);
1995
  task_context task2 = {
1996
    .func=dummy_func,
1997
    .question_filename=question_filename,
1998
  };
1999
  g_assert_true(add_to_queue(queue, task2));
2000
  g_assert_cmpint((int)queue->length, ==, 2);
2001
  g_assert_true(queue_has_question(queue));
2002
}
2003
2004
static void test_cleanup_buffer(__attribute__((unused))
2005
				test_fixture *fixture,
2006
				__attribute__((unused))
2007
				gconstpointer user_data){
2008
  buffer buf = {};
2009
2010
  const size_t buffersize = 10;
2011
2012
  buf.data = malloc(buffersize);
2013
  g_assert_nonnull(buf.data);
2014
  if(mlock(buf.data, buffersize) != 0){
2015
    g_assert_true(errno == EPERM or errno == ENOMEM);
2016
  }
2017
2018
  cleanup_buffer(&buf);
2019
  g_assert_null(buf.data);
2020
}
2021
2022
static
2023
void test_string_set_new_set_contains_nothing(__attribute__((unused))
2024
					      test_fixture *fixture,
2025
					      __attribute__((unused))
2026
					      gconstpointer
2027
					      user_data){
2028
  __attribute__((cleanup(string_set_clear)))
2029
    string_set set = {};
2030
  g_assert_false(string_set_contains(set, "")); /* Empty string */
2031
  g_assert_false(string_set_contains(set, "test_string"));
2032
}
2033
2034
static void
2035
test_string_set_with_added_string_contains_it(__attribute__((unused))
2036
					      test_fixture *fixture,
2037
					      __attribute__((unused))
2038
					      gconstpointer
2039
					      user_data){
2040
  __attribute__((cleanup(string_set_clear)))
2041
    string_set set = {};
2042
  g_assert_true(string_set_add(&set, "test_string"));
2043
  g_assert_true(string_set_contains(set, "test_string"));
2044
}
2045
2046
static void
2047
test_string_set_cleared_does_not_contain_str(__attribute__((unused))
2048
					     test_fixture *fixture,
2049
					     __attribute__((unused))
2050
					     gconstpointer user_data){
2051
  __attribute__((cleanup(string_set_clear)))
2052
    string_set set = {};
2053
  g_assert_true(string_set_add(&set, "test_string"));
2054
  string_set_clear(&set);
2055
  g_assert_false(string_set_contains(set, "test_string"));
2056
}
2057
2058
static
2059
void test_string_set_swap_one_with_empty(__attribute__((unused))
2060
					 test_fixture *fixture,
2061
					 __attribute__((unused))
2062
					 gconstpointer user_data){
2063
  __attribute__((cleanup(string_set_clear)))
2064
    string_set set1 = {};
2065
  __attribute__((cleanup(string_set_clear)))
2066
    string_set set2 = {};
2067
  g_assert_true(string_set_add(&set1, "test_string1"));
2068
  string_set_swap(&set1, &set2);
2069
  g_assert_false(string_set_contains(set1, "test_string1"));
2070
  g_assert_true(string_set_contains(set2, "test_string1"));
2071
}
2072
2073
static
2074
void test_string_set_swap_empty_with_one(__attribute__((unused))
2075
					 test_fixture *fixture,
2076
					 __attribute__((unused))
2077
					 gconstpointer user_data){
2078
  __attribute__((cleanup(string_set_clear)))
2079
    string_set set1 = {};
2080
  __attribute__((cleanup(string_set_clear)))
2081
    string_set set2 = {};
2082
  g_assert_true(string_set_add(&set2, "test_string2"));
2083
  string_set_swap(&set1, &set2);
2084
  g_assert_true(string_set_contains(set1, "test_string2"));
2085
  g_assert_false(string_set_contains(set2, "test_string2"));
2086
}
2087
2088
static void test_string_set_swap_one_with_one(__attribute__((unused))
2089
					      test_fixture *fixture,
2090
					      __attribute__((unused))
2091
					      gconstpointer
2092
					      user_data){
2093
  __attribute__((cleanup(string_set_clear)))
2094
    string_set set1 = {};
2095
  __attribute__((cleanup(string_set_clear)))
2096
    string_set set2 = {};
2097
  g_assert_true(string_set_add(&set1, "test_string1"));
2098
  g_assert_true(string_set_add(&set2, "test_string2"));
2099
  string_set_swap(&set1, &set2);
2100
  g_assert_false(string_set_contains(set1, "test_string1"));
2101
  g_assert_true(string_set_contains(set1, "test_string2"));
2102
  g_assert_false(string_set_contains(set2, "test_string2"));
2103
  g_assert_true(string_set_contains(set2, "test_string1"));
2104
}
2105
2106
static bool fd_has_cloexec_and_nonblock(const int);
2107
2108
static bool epoll_set_contains(int, int, uint32_t);
2109
2110
static void test_start_mandos_client(test_fixture *fixture,
2111
				     __attribute__((unused))
2112
				     gconstpointer user_data){
2113
2114
  bool mandos_client_exited = false;
2115
  bool quit_now = false;
2116
  __attribute__((cleanup(cleanup_close)))
2117
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2118
  g_assert_cmpint(epoll_fd, >=, 0);
2119
  __attribute__((cleanup(cleanup_queue)))
2120
    task_queue *queue = create_queue();
2121
  g_assert_nonnull(queue);
2122
  buffer password = {};
2123
  bool password_is_read = false;
2124
  const char helper_directory[] = "/nonexistent";
2125
  const char *const argv[] = { "/bin/true", NULL };
2126
2127
  g_assert_true(start_mandos_client(queue, epoll_fd,
2128
				    &mandos_client_exited, &quit_now,
2129
				    &password, &password_is_read,
2130
				    &fixture->orig_sigaction,
2131
				    fixture->orig_sigmask,
2132
				    helper_directory, 0, 0, argv));
2133
2134
  g_assert_cmpuint((unsigned int)queue->length, >=, 2);
2135
2136
  const task_context *const added_wait_task
2137
    = find_matching_task(queue, (task_context){
2138
	.func=wait_for_mandos_client_exit,
2139
	.mandos_client_exited=&mandos_client_exited,
2140
	.quit_now=&quit_now,
2141
      });
2142
  g_assert_nonnull(added_wait_task);
2143
  g_assert_cmpint(added_wait_task->pid, >, 0);
2144
  g_assert_cmpint(kill(added_wait_task->pid, SIGKILL), ==, 0);
2145
  waitpid(added_wait_task->pid, NULL, 0);
2146
2147
  const task_context *const added_read_task
2148
    = find_matching_task(queue, (task_context){
2149
	.func=read_mandos_client_output,
2150
	.epoll_fd=epoll_fd,
2151
	.password=&password,
2152
	.password_is_read=&password_is_read,
2153
	.quit_now=&quit_now,
2154
      });
2155
  g_assert_nonnull(added_read_task);
2156
  g_assert_cmpint(added_read_task->fd, >, 2);
2157
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
2158
  g_assert_true(epoll_set_contains(epoll_fd, added_read_task->fd,
2159
				   EPOLLIN | EPOLLRDHUP));
2160
}
2161
2162
static bool fd_has_cloexec_and_nonblock(const int fd){
2163
  const int socket_fd_flags = fcntl(fd, F_GETFD, 0);
2164
  const int socket_file_flags = fcntl(fd, F_GETFL, 0);
2165
  return ((socket_fd_flags >= 0)
2166
	  and (socket_fd_flags & FD_CLOEXEC)
2167
	  and (socket_file_flags >= 0)
2168
	  and (socket_file_flags & O_NONBLOCK));
2169
}
2170
2171
__attribute__((const))
2172
bool is_privileged(void){
2173
  uid_t user = getuid() + 1;
2174
  if(user == 0){		/* Overflow check */
2175
    user++;
2176
  }
2177
  gid_t group = getuid() + 1;
2178
  if(group == 0){		/* Overflow check */
2179
    group++;
2180
  }
2181
  const pid_t pid = fork();
2182
  if(pid == 0){			/* Child */
2183
    if(setresgid((uid_t)-1, group, group) == -1){
2184
      if(errno != EPERM){
2185
	error(EXIT_FAILURE, errno, "Failed to setresgid(-1, %" PRIuMAX
2186
	      ", %" PRIuMAX")", (uintmax_t)group, (uintmax_t)group);
2187
      }
2188
      exit(EXIT_FAILURE);
2189
    }
2190
    if(setresuid((uid_t)-1, user, user) == -1){
2191
      if(errno != EPERM){
2192
	error(EXIT_FAILURE, errno, "Failed to setresuid(-1, %" PRIuMAX
2193
	      ", %" PRIuMAX")", (uintmax_t)user, (uintmax_t)user);
2194
      }
2195
      exit(EXIT_FAILURE);
2196
    }
2197
    exit(EXIT_SUCCESS);
2198
  }
237.7.757 by teddy at recompile
Check for fork() returning -1
2199
  if(pid == -1){
2200
    error(EXIT_FAILURE, errno, "Failed to fork()");
2201
  }
2202
237.7.675 by Teddy Hogeborn
Add dracut(8) support
2203
  int status;
2204
  waitpid(pid, &status, 0);
2205
  if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
2206
    return true;
2207
  }
2208
  return false;
2209
}
2210
2211
static bool epoll_set_contains(int epoll_fd, int fd, uint32_t events){
2212
  /* Only scan for events in this eventmask */
2213
  const uint32_t eventmask = EPOLLIN | EPOLLOUT | EPOLLRDHUP;
2214
  __attribute__((cleanup(cleanup_string)))
2215
    char *fdinfo_name = NULL;
2216
  int ret = asprintf(&fdinfo_name, "/proc/self/fdinfo/%d", epoll_fd);
2217
  g_assert_cmpint(ret, >, 0);
2218
  g_assert_nonnull(fdinfo_name);
2219
2220
  FILE *fdinfo = fopen(fdinfo_name, "r");
2221
  g_assert_nonnull(fdinfo);
2222
  uint32_t reported_events;
2223
  buffer line = {};
2224
  int found_fd = -1;
2225
2226
  do {
2227
    if(getline(&line.data, &line.allocated, fdinfo) < 0){
2228
      break;
2229
    }
2230
    /* See proc(5) for format of /proc/PID/fdinfo/FD for epoll fd's */
2231
    if(sscanf(line.data, "tfd: %d events: %" SCNx32 " ",
2232
	      &found_fd, &reported_events) == 2){
2233
      if(found_fd == fd){
2234
	break;
2235
      }
2236
    }
2237
  } while(not feof(fdinfo) and not ferror(fdinfo));
2238
  g_assert_cmpint(fclose(fdinfo), ==, 0);
2239
  free(line.data);
2240
  if(found_fd != fd){
2241
    return false;
2242
  }
2243
2244
  if(events == 0){
2245
    /* Don't check events if none are given */
2246
    return true;
2247
  }
2248
  return (reported_events & eventmask) == (events & eventmask);
2249
}
2250
2251
static void test_start_mandos_client_execv(test_fixture *fixture,
2252
					   __attribute__((unused))
2253
					   gconstpointer user_data){
2254
  bool mandos_client_exited = false;
2255
  bool quit_now = false;
2256
  __attribute__((cleanup(cleanup_close)))
2257
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2258
  g_assert_cmpint(epoll_fd, >=, 0);
2259
  __attribute__((cleanup(cleanup_queue)))
2260
    task_queue *queue = create_queue();
2261
  g_assert_nonnull(queue);
2262
  __attribute__((cleanup(cleanup_buffer)))
2263
    buffer password = {};
2264
  const char helper_directory[] = "/nonexistent";
2265
  /* Can't execv("/", ...), so this should fail */
2266
  const char *const argv[] = { "/", NULL };
2267
2268
  {
2269
    __attribute__((cleanup(cleanup_close)))
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
2270
      const int devnull_fd = open("/dev/null",
2271
				  O_WRONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
2272
    g_assert_cmpint(devnull_fd, >=, 0);
2273
    __attribute__((cleanup(cleanup_close)))
2274
      const int real_stderr_fd = dup(STDERR_FILENO);
2275
    g_assert_cmpint(real_stderr_fd, >=, 0);
2276
    dup2(devnull_fd, STDERR_FILENO);
2277
2278
    const bool success = start_mandos_client(queue, epoll_fd,
2279
					     &mandos_client_exited,
2280
					     &quit_now,
2281
					     &password,
2282
					     (bool[]){false},
2283
					     &fixture->orig_sigaction,
2284
					     fixture->orig_sigmask,
2285
					     helper_directory, 0, 0,
2286
					     argv);
2287
    dup2(real_stderr_fd, STDERR_FILENO);
2288
    g_assert_true(success);
2289
  }
2290
  g_assert_cmpuint((unsigned int)queue->length, ==, 2);
2291
2292
  struct timespec starttime, currtime;
2293
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2294
  do {
2295
    queue->next_run = 0;
2296
    string_set cancelled_filenames = {};
2297
2298
    {
2299
      __attribute__((cleanup(cleanup_close)))
2300
	const int devnull_fd = open("/dev/null",
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
2301
				    O_WRONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
2302
      g_assert_cmpint(devnull_fd, >=, 0);
2303
      __attribute__((cleanup(cleanup_close)))
2304
	const int real_stderr_fd = dup(STDERR_FILENO);
2305
      g_assert_cmpint(real_stderr_fd, >=, 0);
2306
      g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2307
      dup2(devnull_fd, STDERR_FILENO);
2308
      const bool success = run_queue(&queue, &cancelled_filenames,
2309
				     &quit_now);
2310
      dup2(real_stderr_fd, STDERR_FILENO);
2311
      if(not success){
2312
	break;
2313
      }
2314
    }
2315
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2316
  } while(((queue->length) > 0)
2317
	  and (not quit_now)
2318
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2319
2320
  g_assert_true(quit_now);
2321
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2322
  g_assert_true(mandos_client_exited);
2323
}
2324
2325
static void test_start_mandos_client_suid_euid(test_fixture *fixture,
2326
					       __attribute__((unused))
2327
					       gconstpointer
2328
					       user_data){
2329
  if(not is_privileged()){
2330
    g_test_skip("Not privileged");
2331
    return;
2332
  }
2333
2334
  bool mandos_client_exited = false;
2335
  bool quit_now = false;
2336
  __attribute__((cleanup(cleanup_close)))
2337
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2338
  g_assert_cmpint(epoll_fd, >=, 0);
2339
  __attribute__((cleanup(cleanup_queue)))
2340
    task_queue *queue = create_queue();
2341
  g_assert_nonnull(queue);
2342
  __attribute__((cleanup(cleanup_buffer)))
2343
    buffer password = {};
2344
  bool password_is_read = false;
2345
  const char helper_directory[] = "/nonexistent";
2346
  const char *const argv[] = { "/usr/bin/id", "--user", NULL };
2347
  uid_t user = 1000;
2348
  gid_t group = 1001;
2349
2350
  const bool success = start_mandos_client(queue, epoll_fd,
2351
					   &mandos_client_exited,
2352
					   &quit_now, &password,
2353
					   &password_is_read,
2354
					   &fixture->orig_sigaction,
2355
					   fixture->orig_sigmask,
2356
					   helper_directory, user,
2357
					   group, argv);
2358
  g_assert_true(success);
2359
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
2360
2361
  struct timespec starttime, currtime;
2362
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2363
  do {
2364
    queue->next_run = 0;
2365
    string_set cancelled_filenames = {};
2366
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2367
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2368
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2369
  } while(((queue->length) > 0)
2370
	  and (not quit_now)
2371
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2372
2373
  g_assert_false(quit_now);
2374
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2375
  g_assert_true(mandos_client_exited);
2376
2377
  g_assert_true(password_is_read);
2378
  g_assert_nonnull(password.data);
2379
2380
  uintmax_t id;
2381
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2382
		  ==, 1);
2383
  g_assert_true((uid_t)id == id);
2384
2385
  g_assert_cmpuint((unsigned int)id, ==, 0);
2386
}
2387
2388
static void test_start_mandos_client_suid_egid(test_fixture *fixture,
2389
					       __attribute__((unused))
2390
					       gconstpointer
2391
					       user_data){
2392
  if(not is_privileged()){
2393
    g_test_skip("Not privileged");
2394
    return;
2395
  }
2396
2397
  bool mandos_client_exited = false;
2398
  bool quit_now = false;
2399
  __attribute__((cleanup(cleanup_close)))
2400
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2401
  g_assert_cmpint(epoll_fd, >=, 0);
2402
  __attribute__((cleanup(cleanup_queue)))
2403
    task_queue *queue = create_queue();
2404
  g_assert_nonnull(queue);
2405
  __attribute__((cleanup(cleanup_buffer)))
2406
    buffer password = {};
2407
  bool password_is_read = false;
2408
  const char helper_directory[] = "/nonexistent";
2409
  const char *const argv[] = { "/usr/bin/id", "--group", NULL };
2410
  uid_t user = 1000;
2411
  gid_t group = 1001;
2412
2413
  const bool success = start_mandos_client(queue, epoll_fd,
2414
					   &mandos_client_exited,
2415
					   &quit_now, &password,
2416
					   &password_is_read,
2417
					   &fixture->orig_sigaction,
2418
					   fixture->orig_sigmask,
2419
					   helper_directory, user,
2420
					   group, argv);
2421
  g_assert_true(success);
2422
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
2423
2424
  struct timespec starttime, currtime;
2425
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2426
  do {
2427
    queue->next_run = 0;
2428
    string_set cancelled_filenames = {};
2429
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2430
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2431
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2432
  } while(((queue->length) > 0)
2433
	  and (not quit_now)
2434
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2435
2436
  g_assert_false(quit_now);
2437
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2438
  g_assert_true(mandos_client_exited);
2439
2440
  g_assert_true(password_is_read);
2441
  g_assert_nonnull(password.data);
2442
2443
  uintmax_t id;
2444
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2445
		  ==, 1);
2446
  g_assert_true((gid_t)id == id);
2447
2448
  g_assert_cmpuint((unsigned int)id, ==, 0);
2449
}
2450
2451
static void test_start_mandos_client_suid_ruid(test_fixture *fixture,
2452
					       __attribute__((unused))
2453
					       gconstpointer
2454
					       user_data){
2455
  if(not is_privileged()){
2456
    g_test_skip("Not privileged");
2457
    return;
2458
  }
2459
2460
  bool mandos_client_exited = false;
2461
  bool quit_now = false;
2462
  __attribute__((cleanup(cleanup_close)))
2463
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2464
  g_assert_cmpint(epoll_fd, >=, 0);
2465
  __attribute__((cleanup(cleanup_queue)))
2466
    task_queue *queue = create_queue();
2467
  g_assert_nonnull(queue);
2468
  __attribute__((cleanup(cleanup_buffer)))
2469
    buffer password = {};
2470
  bool password_is_read = false;
2471
  const char helper_directory[] = "/nonexistent";
2472
  const char *const argv[] = { "/usr/bin/id", "--user", "--real",
2473
    NULL };
2474
  uid_t user = 1000;
2475
  gid_t group = 1001;
2476
2477
  const bool success = start_mandos_client(queue, epoll_fd,
2478
					   &mandos_client_exited,
2479
					   &quit_now, &password,
2480
					   &password_is_read,
2481
					   &fixture->orig_sigaction,
2482
					   fixture->orig_sigmask,
2483
					   helper_directory, user,
2484
					   group, argv);
2485
  g_assert_true(success);
2486
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
2487
2488
  struct timespec starttime, currtime;
2489
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2490
  do {
2491
    queue->next_run = 0;
2492
    string_set cancelled_filenames = {};
2493
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2494
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2495
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2496
  } while(((queue->length) > 0)
2497
	  and (not quit_now)
2498
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2499
2500
  g_assert_false(quit_now);
2501
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2502
  g_assert_true(mandos_client_exited);
2503
2504
  g_assert_true(password_is_read);
2505
  g_assert_nonnull(password.data);
2506
2507
  uintmax_t id;
2508
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2509
		  ==, 1);
2510
  g_assert_true((uid_t)id == id);
2511
2512
  g_assert_cmpuint((unsigned int)id, ==, user);
2513
}
2514
2515
static void test_start_mandos_client_suid_rgid(test_fixture *fixture,
2516
					       __attribute__((unused))
2517
					       gconstpointer
2518
					       user_data){
2519
  if(not is_privileged()){
2520
    g_test_skip("Not privileged");
2521
    return;
2522
  }
2523
2524
  bool mandos_client_exited = false;
2525
  bool quit_now = false;
2526
  __attribute__((cleanup(cleanup_close)))
2527
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2528
  g_assert_cmpint(epoll_fd, >=, 0);
2529
  __attribute__((cleanup(cleanup_queue)))
2530
    task_queue *queue = create_queue();
2531
  g_assert_nonnull(queue);
2532
  __attribute__((cleanup(cleanup_buffer)))
2533
    buffer password = {};
2534
  bool password_is_read = false;
2535
  const char helper_directory[] = "/nonexistent";
2536
  const char *const argv[] = { "/usr/bin/id", "--group", "--real",
2537
    NULL };
2538
  uid_t user = 1000;
2539
  gid_t group = 1001;
2540
2541
  const bool success = start_mandos_client(queue, epoll_fd,
2542
					   &mandos_client_exited,
2543
					   &quit_now, &password,
2544
					   &password_is_read,
2545
					   &fixture->orig_sigaction,
2546
					   fixture->orig_sigmask,
2547
					   helper_directory, user,
2548
					   group, argv);
2549
  g_assert_true(success);
2550
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
2551
2552
  struct timespec starttime, currtime;
2553
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2554
  do {
2555
    queue->next_run = 0;
2556
    string_set cancelled_filenames = {};
2557
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2558
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2559
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2560
  } while(((queue->length) > 0)
2561
	  and (not quit_now)
2562
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2563
2564
  g_assert_false(quit_now);
2565
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2566
  g_assert_true(mandos_client_exited);
2567
2568
  g_assert_true(password_is_read);
2569
  g_assert_nonnull(password.data);
2570
2571
  uintmax_t id;
2572
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2573
		  ==, 1);
2574
  g_assert_true((gid_t)id == id);
2575
2576
  g_assert_cmpuint((unsigned int)id, ==, group);
2577
}
2578
2579
static void test_start_mandos_client_read(test_fixture *fixture,
2580
					  __attribute__((unused))
2581
					  gconstpointer user_data){
2582
  bool mandos_client_exited = false;
2583
  bool quit_now = false;
2584
  __attribute__((cleanup(cleanup_close)))
2585
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2586
  g_assert_cmpint(epoll_fd, >=, 0);
2587
  __attribute__((cleanup(cleanup_queue)))
2588
    task_queue *queue = create_queue();
2589
  g_assert_nonnull(queue);
2590
  __attribute__((cleanup(cleanup_buffer)))
2591
    buffer password = {};
2592
  bool password_is_read = false;
2593
  const char dummy_test_password[] = "dummy test password";
2594
  const char helper_directory[] = "/nonexistent";
2595
  const char *const argv[] = { "/bin/echo", "-n", dummy_test_password,
2596
    NULL };
2597
2598
  const bool success = start_mandos_client(queue, epoll_fd,
2599
					   &mandos_client_exited,
2600
					   &quit_now, &password,
2601
					   &password_is_read,
2602
					   &fixture->orig_sigaction,
2603
					   fixture->orig_sigmask,
2604
					   helper_directory, 0, 0,
2605
					   argv);
2606
  g_assert_true(success);
2607
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
2608
2609
  struct timespec starttime, currtime;
2610
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2611
  do {
2612
    queue->next_run = 0;
2613
    string_set cancelled_filenames = {};
2614
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2615
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2616
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2617
  } while(((queue->length) > 0)
2618
	  and (not quit_now)
2619
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2620
2621
  g_assert_false(quit_now);
2622
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2623
  g_assert_true(mandos_client_exited);
2624
2625
  g_assert_true(password_is_read);
2626
  g_assert_cmpint((int)password.length, ==,
2627
		  sizeof(dummy_test_password)-1);
2628
  g_assert_nonnull(password.data);
2629
  g_assert_cmpint(memcmp(dummy_test_password, password.data,
2630
			 sizeof(dummy_test_password)-1), ==, 0);
2631
}
2632
2633
static
2634
void test_start_mandos_client_helper_directory(test_fixture *fixture,
2635
					       __attribute__((unused))
2636
					       gconstpointer
2637
					       user_data){
2638
  bool mandos_client_exited = false;
2639
  bool quit_now = false;
2640
  __attribute__((cleanup(cleanup_close)))
2641
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2642
  g_assert_cmpint(epoll_fd, >=, 0);
2643
  __attribute__((cleanup(cleanup_queue)))
2644
    task_queue *queue = create_queue();
2645
  g_assert_nonnull(queue);
2646
  __attribute__((cleanup(cleanup_buffer)))
2647
    buffer password = {};
2648
  bool password_is_read = false;
2649
  const char helper_directory[] = "/nonexistent";
2650
  const char *const argv[] = { "/bin/sh", "-c",
2651
    "echo -n ${MANDOSPLUGINHELPERDIR}", NULL };
2652
2653
  const bool success = start_mandos_client(queue, epoll_fd,
2654
					   &mandos_client_exited,
2655
					   &quit_now, &password,
2656
					   &password_is_read,
2657
					   &fixture->orig_sigaction,
2658
					   fixture->orig_sigmask,
2659
					   helper_directory, 0, 0,
2660
					   argv);
2661
  g_assert_true(success);
2662
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
2663
2664
  struct timespec starttime, currtime;
2665
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2666
  do {
2667
    queue->next_run = 0;
2668
    string_set cancelled_filenames = {};
2669
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2670
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2671
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2672
  } while(((queue->length) > 0)
2673
	  and (not quit_now)
2674
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2675
2676
  g_assert_false(quit_now);
2677
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2678
  g_assert_true(mandos_client_exited);
2679
2680
  g_assert_true(password_is_read);
2681
  g_assert_cmpint((int)password.length, ==,
2682
		  sizeof(helper_directory)-1);
2683
  g_assert_nonnull(password.data);
2684
  g_assert_cmpint(memcmp(helper_directory, password.data,
2685
			 sizeof(helper_directory)-1), ==, 0);
2686
}
2687
2688
__attribute__((nonnull, warn_unused_result))
2689
static bool proc_status_sigblk_to_sigset(const char *const,
2690
					 sigset_t *const);
2691
2692
static void test_start_mandos_client_sigmask(test_fixture *fixture,
2693
					     __attribute__((unused))
2694
					     gconstpointer user_data){
2695
  bool mandos_client_exited = false;
2696
  bool quit_now = false;
2697
  __attribute__((cleanup(cleanup_close)))
2698
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2699
  g_assert_cmpint(epoll_fd, >=, 0);
2700
  __attribute__((cleanup(cleanup_queue)))
2701
    task_queue *queue = create_queue();
2702
  g_assert_nonnull(queue);
2703
  __attribute__((cleanup(cleanup_buffer)))
2704
    buffer password = {};
2705
  bool password_is_read = false;
2706
  const char helper_directory[] = "/nonexistent";
2707
  /* see proc(5) for format of /proc/self/status */
2708
  const char *const argv[] = { "/usr/bin/awk",
2709
    "$1==\"SigBlk:\"{ print $2 }", "/proc/self/status", NULL };
2710
2711
  g_assert_true(start_mandos_client(queue, epoll_fd,
2712
				    &mandos_client_exited, &quit_now,
2713
				    &password, &password_is_read,
2714
				    &fixture->orig_sigaction,
2715
				    fixture->orig_sigmask,
2716
				    helper_directory, 0, 0, argv));
2717
2718
  struct timespec starttime, currtime;
2719
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2720
  do {
2721
    queue->next_run = 0;
2722
    string_set cancelled_filenames = {};
2723
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2724
    g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2725
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2726
  } while((not (mandos_client_exited and password_is_read))
2727
	  and (not quit_now)
2728
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2729
  g_assert_true(mandos_client_exited);
2730
  g_assert_true(password_is_read);
2731
2732
  sigset_t parsed_sigmask;
2733
  g_assert_true(proc_status_sigblk_to_sigset(password.data,
2734
					     &parsed_sigmask));
2735
2736
  for(int signum = 1; signum < NSIG; signum++){
2737
    const bool has_signal = sigismember(&parsed_sigmask, signum);
2738
    if(sigismember(&fixture->orig_sigmask, signum)){
2739
      g_assert_true(has_signal);
2740
    } else {
2741
      g_assert_false(has_signal);
2742
    }
2743
  }
2744
}
2745
2746
__attribute__((nonnull, warn_unused_result))
2747
static bool proc_status_sigblk_to_sigset(const char *const sigblk,
2748
					 sigset_t *const sigmask){
2749
  /* parse /proc/PID/status SigBlk value and convert to a sigset_t */
2750
  uintmax_t scanned_sigmask;
2751
  if(sscanf(sigblk, "%" SCNxMAX " ", &scanned_sigmask) != 1){
2752
    return false;
2753
  }
2754
  if(sigemptyset(sigmask) != 0){
2755
    return false;
2756
  }
2757
  for(int signum = 1; signum < NSIG; signum++){
2758
    if(scanned_sigmask & ((uintmax_t)1 << (signum-1))){
2759
      if(sigaddset(sigmask, signum) != 0){
2760
	return false;
2761
      }
2762
    }
2763
  }
2764
  return true;
2765
}
2766
2767
static void run_task_with_stderr_to_dev_null(const task_context task,
2768
					     task_queue *const queue);
2769
2770
static
2771
void test_wait_for_mandos_client_exit_badpid(__attribute__((unused))
2772
					     test_fixture *fixture,
2773
					     __attribute__((unused))
2774
					     gconstpointer user_data){
2775
2776
  bool mandos_client_exited = false;
2777
  bool quit_now = false;
2778
2779
  __attribute__((cleanup(cleanup_queue)))
2780
    task_queue *queue = create_queue();
2781
  g_assert_nonnull(queue);
2782
  const task_context task = {
2783
    .func=wait_for_mandos_client_exit,
2784
    .pid=1,
2785
    .mandos_client_exited=&mandos_client_exited,
2786
    .quit_now=&quit_now,
2787
  };
2788
  run_task_with_stderr_to_dev_null(task, queue);
2789
2790
  g_assert_false(mandos_client_exited);
2791
  g_assert_true(quit_now);
2792
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2793
}
2794
2795
static void run_task_with_stderr_to_dev_null(const task_context task,
2796
					     task_queue *const queue){
2797
  FILE *real_stderr = stderr;
2798
  FILE *devnull = fopen("/dev/null", "we");
2799
  g_assert_nonnull(devnull);
2800
2801
  stderr = devnull;
2802
  task.func(task, queue);
2803
  stderr = real_stderr;
2804
2805
  g_assert_cmpint(fclose(devnull), ==, 0);
2806
}
2807
2808
static
2809
void test_wait_for_mandos_client_exit_noexit(test_fixture *fixture,
2810
					     __attribute__((unused))
2811
					     gconstpointer user_data){
2812
  bool mandos_client_exited = false;
2813
  bool quit_now = false;
2814
2815
  pid_t create_eternal_process(void){
2816
    const pid_t pid = fork();
2817
    if(pid == 0){		/* Child */
2818
      if(not restore_signal_handler(&fixture->orig_sigaction)){
2819
	_exit(EXIT_FAILURE);
2820
      }
2821
      if(not restore_sigmask(&fixture->orig_sigmask)){
2822
	_exit(EXIT_FAILURE);
2823
      }
2824
      while(true){
2825
	pause();
2826
      }
2827
    }
2828
    return pid;
2829
  }
2830
  pid_t pid = create_eternal_process();
2831
  g_assert_true(pid != -1);
2832
2833
  __attribute__((cleanup(cleanup_queue)))
2834
    task_queue *queue = create_queue();
2835
  g_assert_nonnull(queue);
2836
  const task_context task = {
2837
    .func=wait_for_mandos_client_exit,
2838
    .pid=pid,
2839
    .mandos_client_exited=&mandos_client_exited,
2840
    .quit_now=&quit_now,
2841
  };
2842
  task.func(task, queue);
2843
2844
  g_assert_false(mandos_client_exited);
2845
  g_assert_false(quit_now);
2846
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
2847
2848
  g_assert_nonnull(find_matching_task(queue, (task_context){
2849
	.func=wait_for_mandos_client_exit,
2850
	.pid=task.pid,
2851
	.mandos_client_exited=&mandos_client_exited,
2852
	.quit_now=&quit_now,
2853
      }));
2854
}
2855
2856
static
2857
void test_wait_for_mandos_client_exit_success(test_fixture *fixture,
2858
					      __attribute__((unused))
2859
					      gconstpointer
2860
					      user_data){
2861
  bool mandos_client_exited = false;
2862
  bool quit_now = false;
2863
2864
  pid_t create_successful_process(void){
2865
    const pid_t pid = fork();
2866
    if(pid == 0){		/* Child */
2867
      if(not restore_signal_handler(&fixture->orig_sigaction)){
2868
	_exit(EXIT_FAILURE);
2869
      }
2870
      if(not restore_sigmask(&fixture->orig_sigmask)){
2871
	_exit(EXIT_FAILURE);
2872
      }
2873
      exit(EXIT_SUCCESS);
2874
    }
2875
    return pid;
2876
  }
2877
  const pid_t pid = create_successful_process();
2878
  g_assert_true(pid != -1);
2879
2880
  __attribute__((cleanup(cleanup_queue)))
2881
    task_queue *queue = create_queue();
2882
  g_assert_nonnull(queue);
2883
  const task_context initial_task = {
2884
    .func=wait_for_mandos_client_exit,
2885
    .pid=pid,
2886
    .mandos_client_exited=&mandos_client_exited,
2887
    .quit_now=&quit_now,
2888
  };
2889
  g_assert_true(add_to_queue(queue, initial_task));
2890
2891
  struct timespec starttime, currtime;
2892
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2893
  __attribute__((cleanup(cleanup_close)))
2894
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2895
  do {
2896
    queue->next_run = 0;
2897
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2898
    g_assert_true(run_queue(&queue, (string_set[]){{}}, &quit_now));
2899
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2900
  } while((not mandos_client_exited)
2901
	  and (not quit_now)
2902
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2903
2904
  g_assert_true(mandos_client_exited);
2905
  g_assert_false(quit_now);
2906
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2907
}
2908
2909
static
2910
void test_wait_for_mandos_client_exit_failure(test_fixture *fixture,
2911
					      __attribute__((unused))
2912
					      gconstpointer
2913
					      user_data){
2914
  bool mandos_client_exited = false;
2915
  bool quit_now = false;
2916
2917
  pid_t create_failing_process(void){
2918
    const pid_t pid = fork();
2919
    if(pid == 0){		/* Child */
2920
      if(not restore_signal_handler(&fixture->orig_sigaction)){
2921
	_exit(EXIT_FAILURE);
2922
      }
2923
      if(not restore_sigmask(&fixture->orig_sigmask)){
2924
	_exit(EXIT_FAILURE);
2925
      }
2926
      exit(EXIT_FAILURE);
2927
    }
2928
    return pid;
2929
  }
2930
  const pid_t pid = create_failing_process();
2931
  g_assert_true(pid != -1);
2932
2933
  __attribute__((cleanup(string_set_clear)))
2934
    string_set cancelled_filenames = {};
2935
  __attribute__((cleanup(cleanup_close)))
2936
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2937
  g_assert_cmpint(epoll_fd, >=, 0);
2938
  __attribute__((cleanup(cleanup_queue)))
2939
    task_queue *queue = create_queue();
2940
  g_assert_nonnull(queue);
2941
  g_assert_true(add_to_queue(queue, (task_context){
2942
	.func=wait_for_mandos_client_exit,
2943
	.pid=pid,
2944
	.mandos_client_exited=&mandos_client_exited,
2945
	.quit_now=&quit_now,
2946
      }));
2947
2948
  g_assert_true(sigismember(&fixture->orig_sigmask, SIGCHLD) == 0);
2949
2950
  __attribute__((cleanup(cleanup_close)))
2951
    const int devnull_fd = open("/dev/null",
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
2952
				O_WRONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
2953
  g_assert_cmpint(devnull_fd, >=, 0);
2954
  __attribute__((cleanup(cleanup_close)))
2955
    const int real_stderr_fd = dup(STDERR_FILENO);
2956
  g_assert_cmpint(real_stderr_fd, >=, 0);
2957
2958
  struct timespec starttime, currtime;
2959
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2960
  do {
2961
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2962
    dup2(devnull_fd, STDERR_FILENO);
2963
    const bool success = run_queue(&queue, &cancelled_filenames,
2964
				   &quit_now);
2965
    dup2(real_stderr_fd, STDERR_FILENO);
2966
    if(not success){
2967
      break;
2968
    }
2969
2970
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2971
  } while((not mandos_client_exited)
2972
	  and (not quit_now)
2973
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
2974
2975
  g_assert_true(quit_now);
2976
  g_assert_true(mandos_client_exited);
2977
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2978
}
2979
2980
static
2981
void test_wait_for_mandos_client_exit_killed(test_fixture *fixture,
2982
					     __attribute__((unused))
2983
					     gconstpointer user_data){
2984
  bool mandos_client_exited = false;
2985
  bool quit_now = false;
2986
2987
  pid_t create_killed_process(void){
2988
    const pid_t pid = fork();
2989
    if(pid == 0){		/* Child */
2990
      if(not restore_signal_handler(&fixture->orig_sigaction)){
2991
	_exit(EXIT_FAILURE);
2992
      }
2993
      if(not restore_sigmask(&fixture->orig_sigmask)){
2994
	_exit(EXIT_FAILURE);
2995
      }
2996
      while(true){
2997
	pause();
2998
      }
2999
    }
3000
    kill(pid, SIGKILL);
3001
    return pid;
3002
  }
3003
  const pid_t pid = create_killed_process();
3004
  g_assert_true(pid != -1);
3005
3006
  __attribute__((cleanup(string_set_clear)))
3007
    string_set cancelled_filenames = {};
3008
  __attribute__((cleanup(cleanup_close)))
3009
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3010
  g_assert_cmpint(epoll_fd, >=, 0);
3011
  __attribute__((cleanup(cleanup_queue)))
3012
    task_queue *queue = create_queue();
3013
  g_assert_nonnull(queue);
3014
  g_assert_true(add_to_queue(queue, (task_context){
3015
	.func=wait_for_mandos_client_exit,
3016
	.pid=pid,
3017
	.mandos_client_exited=&mandos_client_exited,
3018
	.quit_now=&quit_now,
3019
      }));
3020
3021
  __attribute__((cleanup(cleanup_close)))
3022
    const int devnull_fd = open("/dev/null",
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
3023
				O_WRONLY | O_CLOEXEC, O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
3024
  g_assert_cmpint(devnull_fd, >=, 0);
3025
  __attribute__((cleanup(cleanup_close)))
3026
    const int real_stderr_fd = dup(STDERR_FILENO);
3027
  g_assert_cmpint(real_stderr_fd, >=, 0);
3028
3029
  struct timespec starttime, currtime;
3030
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
3031
  do {
3032
    g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
3033
    dup2(devnull_fd, STDERR_FILENO);
3034
    const bool success = run_queue(&queue, &cancelled_filenames,
3035
				   &quit_now);
3036
    dup2(real_stderr_fd, STDERR_FILENO);
3037
    if(not success){
3038
      break;
3039
    }
3040
3041
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
3042
  } while((not mandos_client_exited)
3043
	  and (not quit_now)
3044
	  and ((currtime.tv_sec - starttime.tv_sec) < 10));
3045
3046
  g_assert_true(mandos_client_exited);
3047
  g_assert_true(quit_now);
3048
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3049
}
3050
3051
static bool epoll_set_does_not_contain(int, int);
3052
3053
static
3054
void test_read_mandos_client_output_readerror(__attribute__((unused))
3055
					      test_fixture *fixture,
3056
					      __attribute__((unused))
3057
					      gconstpointer
3058
					      user_data){
3059
  __attribute__((cleanup(cleanup_close)))
3060
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3061
  g_assert_cmpint(epoll_fd, >=, 0);
3062
3063
  __attribute__((cleanup(cleanup_buffer)))
3064
    buffer password = {};
3065
3066
  /* Reading /proc/self/mem from offset 0 will always give EIO */
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
3067
  const int fd = open("/proc/self/mem",
3068
		      O_RDONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
3069
3070
  bool password_is_read = false;
3071
  bool quit_now = false;
3072
  __attribute__((cleanup(cleanup_queue)))
3073
    task_queue *queue = create_queue();
3074
  g_assert_nonnull(queue);
3075
3076
  task_context task = {
3077
    .func=read_mandos_client_output,
3078
    .epoll_fd=epoll_fd,
3079
    .fd=fd,
3080
    .password=&password,
3081
    .password_is_read=&password_is_read,
3082
    .quit_now=&quit_now,
3083
  };
3084
  run_task_with_stderr_to_dev_null(task, queue);
3085
  g_assert_false(password_is_read);
3086
  g_assert_cmpint((int)password.length, ==, 0);
3087
  g_assert_true(quit_now);
3088
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3089
3090
  g_assert_true(epoll_set_does_not_contain(epoll_fd, fd));
3091
3092
  g_assert_cmpint(close(fd), ==, -1);
3093
}
3094
3095
static bool epoll_set_does_not_contain(int epoll_fd, int fd){
3096
  return not epoll_set_contains(epoll_fd, fd, 0);
3097
}
3098
3099
static
3100
void test_read_mandos_client_output_nodata(__attribute__((unused))
3101
					   test_fixture *fixture,
3102
					   __attribute__((unused))
3103
					   gconstpointer user_data){
3104
  __attribute__((cleanup(cleanup_close)))
3105
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3106
  g_assert_cmpint(epoll_fd, >=, 0);
3107
3108
  int pipefds[2];
3109
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3110
3111
  __attribute__((cleanup(cleanup_buffer)))
3112
    buffer password = {};
3113
3114
  bool password_is_read = false;
3115
  bool quit_now = false;
3116
  __attribute__((cleanup(cleanup_queue)))
3117
    task_queue *queue = create_queue();
3118
  g_assert_nonnull(queue);
3119
3120
  task_context task = {
3121
    .func=read_mandos_client_output,
3122
    .epoll_fd=epoll_fd,
3123
    .fd=pipefds[0],
3124
    .password=&password,
3125
    .password_is_read=&password_is_read,
3126
    .quit_now=&quit_now,
3127
  };
3128
  task.func(task, queue);
3129
  g_assert_false(password_is_read);
3130
  g_assert_cmpint((int)password.length, ==, 0);
3131
  g_assert_false(quit_now);
3132
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3133
3134
  g_assert_nonnull(find_matching_task(queue, (task_context){
3135
	.func=read_mandos_client_output,
3136
	.epoll_fd=epoll_fd,
3137
	.fd=pipefds[0],
3138
	.password=&password,
3139
	.password_is_read=&password_is_read,
3140
	.quit_now=&quit_now,
3141
      }));
3142
3143
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3144
				   EPOLLIN | EPOLLRDHUP));
3145
3146
  g_assert_cmpint(close(pipefds[1]), ==, 0);
3147
}
3148
3149
static void test_read_mandos_client_output_eof(__attribute__((unused))
3150
					       test_fixture *fixture,
3151
					       __attribute__((unused))
3152
					       gconstpointer
3153
					       user_data){
3154
  __attribute__((cleanup(cleanup_close)))
3155
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3156
  g_assert_cmpint(epoll_fd, >=, 0);
3157
3158
  int pipefds[2];
3159
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3160
  g_assert_cmpint(close(pipefds[1]), ==, 0);
3161
3162
  __attribute__((cleanup(cleanup_buffer)))
3163
    buffer password = {};
3164
3165
  bool password_is_read = false;
3166
  bool quit_now = false;
3167
  __attribute__((cleanup(cleanup_queue)))
3168
    task_queue *queue = create_queue();
3169
  g_assert_nonnull(queue);
3170
3171
  task_context task = {
3172
    .func=read_mandos_client_output,
3173
    .epoll_fd=epoll_fd,
3174
    .fd=pipefds[0],
3175
    .password=&password,
3176
    .password_is_read=&password_is_read,
3177
    .quit_now=&quit_now,
3178
  };
3179
  task.func(task, queue);
3180
  g_assert_true(password_is_read);
3181
  g_assert_cmpint((int)password.length, ==, 0);
3182
  g_assert_false(quit_now);
3183
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3184
3185
  g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0]));
3186
3187
  g_assert_cmpint(close(pipefds[0]), ==, -1);
3188
}
3189
3190
static
3191
void test_read_mandos_client_output_once(__attribute__((unused))
3192
					 test_fixture *fixture,
3193
					 __attribute__((unused))
3194
					 gconstpointer user_data){
3195
  __attribute__((cleanup(cleanup_close)))
3196
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3197
  g_assert_cmpint(epoll_fd, >=, 0);
3198
3199
  int pipefds[2];
3200
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3201
3202
  const char dummy_test_password[] = "dummy test password";
3203
  /* Start with a pre-allocated buffer */
3204
  __attribute__((cleanup(cleanup_buffer)))
3205
    buffer password = {
3206
    .data=malloc(sizeof(dummy_test_password)),
3207
    .length=0,
3208
    .allocated=sizeof(dummy_test_password),
3209
  };
3210
  g_assert_nonnull(password.data);
3211
  if(mlock(password.data, password.allocated) != 0){
3212
    g_assert_true(errno == EPERM or errno == ENOMEM);
3213
  }
3214
3215
  bool password_is_read = false;
3216
  bool quit_now = false;
3217
  __attribute__((cleanup(cleanup_queue)))
3218
    task_queue *queue = create_queue();
3219
  g_assert_nonnull(queue);
3220
3221
  g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF);
3222
  g_assert_cmpint((int)write(pipefds[1], dummy_test_password,
3223
			     sizeof(dummy_test_password)),
3224
		  ==, (int)sizeof(dummy_test_password));
3225
3226
  task_context task = {
3227
    .func=read_mandos_client_output,
3228
    .epoll_fd=epoll_fd,
3229
    .fd=pipefds[0],
3230
    .password=&password,
3231
    .password_is_read=&password_is_read,
3232
    .quit_now=&quit_now,
3233
  };
3234
  task.func(task, queue);
3235
3236
  g_assert_false(password_is_read);
3237
  g_assert_cmpint((int)password.length, ==,
3238
		  (int)sizeof(dummy_test_password));
3239
  g_assert_nonnull(password.data);
3240
  g_assert_cmpint(memcmp(password.data, dummy_test_password,
3241
			 sizeof(dummy_test_password)), ==, 0);
3242
3243
  g_assert_false(quit_now);
3244
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3245
3246
  g_assert_nonnull(find_matching_task(queue, (task_context){
3247
	.func=read_mandos_client_output,
3248
	.epoll_fd=epoll_fd,
3249
	.fd=pipefds[0],
3250
	.password=&password,
3251
	.password_is_read=&password_is_read,
3252
	.quit_now=&quit_now,
3253
      }));
3254
3255
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3256
				   EPOLLIN | EPOLLRDHUP));
3257
3258
  g_assert_cmpint(close(pipefds[1]), ==, 0);
3259
}
3260
3261
static
3262
void test_read_mandos_client_output_malloc(__attribute__((unused))
3263
					   test_fixture *fixture,
3264
					   __attribute__((unused))
3265
					   gconstpointer user_data){
3266
  __attribute__((cleanup(cleanup_close)))
3267
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3268
  g_assert_cmpint(epoll_fd, >=, 0);
3269
3270
  int pipefds[2];
3271
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3272
3273
  const char dummy_test_password[] = "dummy test password";
3274
  /* Start with an empty buffer */
3275
  __attribute__((cleanup(cleanup_buffer)))
3276
    buffer password = {};
3277
3278
  bool password_is_read = false;
3279
  bool quit_now = false;
3280
  __attribute__((cleanup(cleanup_queue)))
3281
    task_queue *queue = create_queue();
3282
  g_assert_nonnull(queue);
3283
3284
  g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF);
3285
  g_assert_cmpint((int)write(pipefds[1], dummy_test_password,
3286
			     sizeof(dummy_test_password)),
3287
		  ==, (int)sizeof(dummy_test_password));
3288
3289
  task_context task = {
3290
    .func=read_mandos_client_output,
3291
    .epoll_fd=epoll_fd,
3292
    .fd=pipefds[0],
3293
    .password=&password,
3294
    .password_is_read=&password_is_read,
3295
    .quit_now=&quit_now,
3296
  };
3297
  task.func(task, queue);
3298
3299
  g_assert_false(password_is_read);
3300
  g_assert_cmpint((int)password.length, ==,
3301
		  (int)sizeof(dummy_test_password));
3302
  g_assert_nonnull(password.data);
3303
  g_assert_cmpint(memcmp(password.data, dummy_test_password,
3304
			 sizeof(dummy_test_password)), ==, 0);
3305
3306
  g_assert_false(quit_now);
3307
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3308
3309
  g_assert_nonnull(find_matching_task(queue, (task_context){
3310
	.func=read_mandos_client_output,
3311
	.epoll_fd=epoll_fd,
3312
	.fd=pipefds[0],
3313
	.password=&password,
3314
	.password_is_read=&password_is_read,
3315
	.quit_now=&quit_now,
3316
      }));
3317
3318
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3319
				   EPOLLIN | EPOLLRDHUP));
3320
3321
  g_assert_cmpint(close(pipefds[1]), ==, 0);
3322
}
3323
3324
static
3325
void test_read_mandos_client_output_append(__attribute__((unused))
3326
					   test_fixture *fixture,
3327
					   __attribute__((unused))
3328
					   gconstpointer user_data){
3329
  __attribute__((cleanup(cleanup_close)))
3330
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3331
  g_assert_cmpint(epoll_fd, >=, 0);
3332
3333
  int pipefds[2];
3334
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3335
3336
  const char dummy_test_password[] = "dummy test password";
3337
  __attribute__((cleanup(cleanup_buffer)))
3338
    buffer password = {
3339
    .data=malloc(PIPE_BUF),
3340
    .length=PIPE_BUF,
3341
    .allocated=PIPE_BUF,
3342
  };
3343
  g_assert_nonnull(password.data);
3344
  if(mlock(password.data, password.allocated) != 0){
3345
    g_assert_true(errno == EPERM or errno == ENOMEM);
3346
  }
3347
3348
  memset(password.data, 'x', PIPE_BUF);
3349
  char password_expected[PIPE_BUF];
3350
  memcpy(password_expected, password.data, PIPE_BUF);
3351
3352
  bool password_is_read = false;
3353
  bool quit_now = false;
3354
  __attribute__((cleanup(cleanup_queue)))
3355
    task_queue *queue = create_queue();
3356
  g_assert_nonnull(queue);
3357
3358
  g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF);
3359
  g_assert_cmpint((int)write(pipefds[1], dummy_test_password,
3360
			     sizeof(dummy_test_password)),
3361
		  ==, (int)sizeof(dummy_test_password));
3362
3363
  task_context task = {
3364
    .func=read_mandos_client_output,
3365
    .epoll_fd=epoll_fd,
3366
    .fd=pipefds[0],
3367
    .password=&password,
3368
    .password_is_read=&password_is_read,
3369
    .quit_now=&quit_now,
3370
  };
3371
  task.func(task, queue);
3372
3373
  g_assert_false(password_is_read);
3374
  g_assert_cmpint((int)password.length, ==,
3375
		  PIPE_BUF + sizeof(dummy_test_password));
3376
  g_assert_nonnull(password.data);
3377
  g_assert_cmpint(memcmp(password_expected, password.data, PIPE_BUF),
3378
		  ==, 0);
3379
  g_assert_cmpint(memcmp(password.data + PIPE_BUF,
3380
			 dummy_test_password,
3381
			 sizeof(dummy_test_password)), ==, 0);
3382
  g_assert_false(quit_now);
3383
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3384
3385
  g_assert_nonnull(find_matching_task(queue, (task_context){
3386
	.func=read_mandos_client_output,
3387
	.epoll_fd=epoll_fd,
3388
	.fd=pipefds[0],
3389
	.password=&password,
3390
	.password_is_read=&password_is_read,
3391
	.quit_now=&quit_now,
3392
      }));
3393
3394
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3395
				   EPOLLIN | EPOLLRDHUP));
3396
}
3397
3398
static char *make_temporary_directory(void);
3399
3400
static void test_add_inotify_dir_watch(__attribute__((unused))
3401
				       test_fixture *fixture,
3402
				       __attribute__((unused))
3403
				       gconstpointer user_data){
3404
  __attribute__((cleanup(cleanup_close)))
3405
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3406
  g_assert_cmpint(epoll_fd, >=, 0);
3407
  __attribute__((cleanup(cleanup_queue)))
3408
    task_queue *queue = create_queue();
3409
  g_assert_nonnull(queue);
3410
  __attribute__((cleanup(string_set_clear)))
3411
    string_set cancelled_filenames = {};
3412
  const mono_microsecs current_time = 0;
3413
3414
  bool quit_now = false;
3415
  buffer password = {};
3416
  bool mandos_client_exited = false;
3417
  bool password_is_read = false;
3418
3419
  __attribute__((cleanup(cleanup_string)))
3420
    char *tempdir = make_temporary_directory();
3421
  g_assert_nonnull(tempdir);
3422
3423
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3424
				      &password, tempdir,
3425
				      &cancelled_filenames,
3426
				      &current_time,
3427
				      &mandos_client_exited,
3428
				      &password_is_read));
3429
3430
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3431
3432
  const task_context *const added_read_task
3433
    = find_matching_task(queue, (task_context){
3434
	.func=read_inotify_event,
3435
	.epoll_fd=epoll_fd,
3436
	.quit_now=&quit_now,
3437
	.password=&password,
3438
	.filename=tempdir,
3439
	.cancelled_filenames=&cancelled_filenames,
3440
	.current_time=&current_time,
3441
	.mandos_client_exited=&mandos_client_exited,
3442
	.password_is_read=&password_is_read,
3443
      });
3444
  g_assert_nonnull(added_read_task);
3445
3446
  g_assert_cmpint(added_read_task->fd, >, 2);
3447
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3448
  g_assert_true(epoll_set_contains(added_read_task->epoll_fd,
3449
				   added_read_task->fd,
3450
				   EPOLLIN | EPOLLRDHUP));
3451
3452
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3453
}
3454
3455
static char *make_temporary_directory(void){
3456
  char *name = strdup("/tmp/mandosXXXXXX");
3457
  g_assert_nonnull(name);
3458
  char *result = mkdtemp(name);
3459
  if(result == NULL){
3460
    free(name);
3461
  }
3462
  return result;
3463
}
3464
3465
static void test_add_inotify_dir_watch_fail(__attribute__((unused))
3466
					    test_fixture *fixture,
3467
					    __attribute__((unused))
3468
					    gconstpointer user_data){
3469
  __attribute__((cleanup(cleanup_close)))
3470
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3471
  g_assert_cmpint(epoll_fd, >=, 0);
3472
  __attribute__((cleanup(cleanup_queue)))
3473
    task_queue *queue = create_queue();
3474
  g_assert_nonnull(queue);
3475
  __attribute__((cleanup(string_set_clear)))
3476
    string_set cancelled_filenames = {};
3477
  const mono_microsecs current_time = 0;
3478
3479
  bool quit_now = false;
3480
  buffer password = {};
3481
  bool mandos_client_exited = false;
3482
  bool password_is_read = false;
3483
3484
  const char nonexistent_dir[] = "/nonexistent";
3485
3486
  FILE *real_stderr = stderr;
3487
  FILE *devnull = fopen("/dev/null", "we");
3488
  g_assert_nonnull(devnull);
3489
  stderr = devnull;
3490
  g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3491
				       &password, nonexistent_dir,
3492
				       &cancelled_filenames,
3493
				       &current_time,
3494
				       &mandos_client_exited,
3495
				       &password_is_read));
3496
  stderr = real_stderr;
3497
  g_assert_cmpint(fclose(devnull), ==, 0);
3498
3499
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3500
}
3501
237.7.690 by Teddy Hogeborn
dracut-module/password-agent.c: Require agent directory
3502
static void test_add_inotify_dir_watch_nondir(__attribute__((unused))
3503
					      test_fixture *fixture,
3504
					    __attribute__((unused))
3505
					      gconstpointer
3506
					      user_data){
3507
  __attribute__((cleanup(cleanup_close)))
3508
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3509
  g_assert_cmpint(epoll_fd, >=, 0);
3510
  __attribute__((cleanup(cleanup_queue)))
3511
    task_queue *queue = create_queue();
3512
  g_assert_nonnull(queue);
3513
  __attribute__((cleanup(string_set_clear)))
3514
    string_set cancelled_filenames = {};
3515
  const mono_microsecs current_time = 0;
3516
3517
  bool quit_now = false;
3518
  buffer password = {};
3519
  bool mandos_client_exited = false;
3520
  bool password_is_read = false;
3521
3522
  const char not_a_directory[] = "/dev/tty";
3523
3524
  FILE *real_stderr = stderr;
3525
  FILE *devnull = fopen("/dev/null", "we");
3526
  g_assert_nonnull(devnull);
3527
  stderr = devnull;
3528
  g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3529
				       &password, not_a_directory,
3530
				       &cancelled_filenames,
3531
				       &current_time,
3532
				       &mandos_client_exited,
3533
				       &password_is_read));
3534
  stderr = real_stderr;
3535
  g_assert_cmpint(fclose(devnull), ==, 0);
3536
3537
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3538
}
3539
237.7.675 by Teddy Hogeborn
Add dracut(8) support
3540
static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused))
3541
					      test_fixture *fixture,
3542
					      __attribute__((unused))
3543
					      gconstpointer
3544
					      user_data){
3545
  __attribute__((cleanup(cleanup_close)))
3546
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3547
  g_assert_cmpint(epoll_fd, >=, 0);
3548
  __attribute__((cleanup(cleanup_queue)))
3549
    task_queue *queue = create_queue();
3550
  g_assert_nonnull(queue);
3551
  __attribute__((cleanup(string_set_clear)))
3552
    string_set cancelled_filenames = {};
3553
  const mono_microsecs current_time = 0;
3554
3555
  bool quit_now = false;
3556
  buffer password = {};
3557
  bool mandos_client_exited = false;
3558
  bool password_is_read = false;
3559
3560
  __attribute__((cleanup(cleanup_string)))
3561
    char *tempdir = make_temporary_directory();
3562
  g_assert_nonnull(tempdir);
3563
3564
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3565
				      &password, tempdir,
3566
				      &cancelled_filenames,
3567
				      &current_time,
3568
				      &mandos_client_exited,
3569
				      &password_is_read));
3570
3571
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3572
3573
  const task_context *const added_read_task
3574
    = find_matching_task(queue,
3575
			 (task_context){ .func=read_inotify_event });
3576
  g_assert_nonnull(added_read_task);
3577
3578
  g_assert_cmpint(added_read_task->fd, >, 2);
3579
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3580
3581
  /* "sufficient to read at least one event." - inotify(7) */
3582
  const size_t ievent_size = (sizeof(struct inotify_event)
3583
			      + NAME_MAX + 1);
3584
  struct inotify_event *ievent = malloc(ievent_size);
3585
  g_assert_nonnull(ievent);
3586
3587
  g_assert_cmpint(read(added_read_task->fd, ievent, ievent_size), ==,
3588
		  -1);
3589
  g_assert_cmpint(errno, ==, EAGAIN);
3590
3591
  free(ievent);
3592
3593
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3594
}
3595
3596
static char *make_temporary_file_in_directory(const char
3597
					      *const dir);
3598
3599
static
3600
void test_add_inotify_dir_watch_IN_CLOSE_WRITE(__attribute__((unused))
3601
					       test_fixture *fixture,
3602
					       __attribute__((unused))
3603
					       gconstpointer
3604
					       user_data){
3605
  __attribute__((cleanup(cleanup_close)))
3606
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3607
  g_assert_cmpint(epoll_fd, >=, 0);
3608
  __attribute__((cleanup(cleanup_queue)))
3609
    task_queue *queue = create_queue();
3610
  g_assert_nonnull(queue);
3611
  __attribute__((cleanup(string_set_clear)))
3612
    string_set cancelled_filenames = {};
3613
  const mono_microsecs current_time = 0;
3614
3615
  bool quit_now = false;
3616
  buffer password = {};
3617
  bool mandos_client_exited = false;
3618
  bool password_is_read = false;
3619
3620
  __attribute__((cleanup(cleanup_string)))
3621
    char *tempdir = make_temporary_directory();
3622
  g_assert_nonnull(tempdir);
3623
3624
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3625
				      &password, tempdir,
3626
				      &cancelled_filenames,
3627
				      &current_time,
3628
				      &mandos_client_exited,
3629
				      &password_is_read));
3630
3631
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3632
3633
  const task_context *const added_read_task
3634
    = find_matching_task(queue,
3635
			 (task_context){ .func=read_inotify_event });
3636
  g_assert_nonnull(added_read_task);
3637
3638
  g_assert_cmpint(added_read_task->fd, >, 2);
3639
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3640
3641
  __attribute__((cleanup(cleanup_string)))
3642
    char *filename = make_temporary_file_in_directory(tempdir);
3643
  g_assert_nonnull(filename);
3644
3645
  /* "sufficient to read at least one event." - inotify(7) */
3646
  const size_t ievent_size = (sizeof(struct inotify_event)
3647
			      + NAME_MAX + 1);
3648
  struct inotify_event *ievent = malloc(ievent_size);
3649
  g_assert_nonnull(ievent);
3650
3651
  ssize_t read_size = 0;
3652
  read_size = read(added_read_task->fd, ievent, ievent_size);
3653
3654
  g_assert_cmpint((int)read_size, >, 0);
3655
  g_assert_true(ievent->mask & IN_CLOSE_WRITE);
3656
  g_assert_cmpstr(ievent->name, ==, basename(filename));
3657
3658
  free(ievent);
3659
3660
  g_assert_cmpint(unlink(filename), ==, 0);
3661
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3662
}
3663
3664
static char *make_temporary_prefixed_file_in_directory(const char
3665
						       *const prefix,
3666
						       const char
3667
						       *const dir){
3668
  char *filename = NULL;
3669
  g_assert_cmpint(asprintf(&filename, "%s/%sXXXXXX", dir, prefix),
3670
		  >, 0);
3671
  g_assert_nonnull(filename);
3672
  const int fd = mkostemp(filename, O_CLOEXEC);
3673
  g_assert_cmpint(fd, >=, 0);
3674
  g_assert_cmpint(close(fd), ==, 0);
3675
  return filename;
3676
}
3677
3678
static char *make_temporary_file_in_directory(const char
3679
					      *const dir){
3680
  return make_temporary_prefixed_file_in_directory("temp", dir);
3681
}
3682
3683
static
3684
void test_add_inotify_dir_watch_IN_MOVED_TO(__attribute__((unused))
3685
					    test_fixture *fixture,
3686
					    __attribute__((unused))
3687
					    gconstpointer user_data){
3688
  __attribute__((cleanup(cleanup_close)))
3689
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3690
  g_assert_cmpint(epoll_fd, >=, 0);
3691
  __attribute__((cleanup(cleanup_queue)))
3692
    task_queue *queue = create_queue();
3693
  g_assert_nonnull(queue);
3694
  __attribute__((cleanup(string_set_clear)))
3695
    string_set cancelled_filenames = {};
3696
  const mono_microsecs current_time = 0;
3697
3698
  bool quit_now = false;
3699
  buffer password = {};
3700
  bool mandos_client_exited = false;
3701
  bool password_is_read = false;
3702
3703
  __attribute__((cleanup(cleanup_string)))
3704
    char *watchdir = make_temporary_directory();
3705
  g_assert_nonnull(watchdir);
3706
3707
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3708
				      &password, watchdir,
3709
				      &cancelled_filenames,
3710
				      &current_time,
3711
				      &mandos_client_exited,
3712
				      &password_is_read));
3713
3714
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3715
3716
  const task_context *const added_read_task
3717
    = find_matching_task(queue,
3718
			 (task_context){ .func=read_inotify_event });
3719
  g_assert_nonnull(added_read_task);
3720
3721
  g_assert_cmpint(added_read_task->fd, >, 2);
3722
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3723
3724
  char *sourcedir = make_temporary_directory();
3725
  g_assert_nonnull(sourcedir);
3726
3727
  __attribute__((cleanup(cleanup_string)))
3728
    char *filename = make_temporary_file_in_directory(sourcedir);
3729
  g_assert_nonnull(filename);
3730
3731
  __attribute__((cleanup(cleanup_string)))
3732
    char *targetfilename = NULL;
3733
  g_assert_cmpint(asprintf(&targetfilename, "%s/%s", watchdir,
3734
			   basename(filename)), >, 0);
3735
  g_assert_nonnull(targetfilename);
3736
3737
  g_assert_cmpint(rename(filename, targetfilename), ==, 0);
3738
  g_assert_cmpint(rmdir(sourcedir), ==, 0);
3739
  free(sourcedir);
3740
3741
  /* "sufficient to read at least one event." - inotify(7) */
3742
  const size_t ievent_size = (sizeof(struct inotify_event)
3743
			      + NAME_MAX + 1);
3744
  struct inotify_event *ievent = malloc(ievent_size);
3745
  g_assert_nonnull(ievent);
3746
3747
  ssize_t read_size = read(added_read_task->fd, ievent, ievent_size);
3748
3749
  g_assert_cmpint((int)read_size, >, 0);
3750
  g_assert_true(ievent->mask & IN_MOVED_TO);
3751
  g_assert_cmpstr(ievent->name, ==, basename(targetfilename));
3752
3753
  free(ievent);
3754
3755
  g_assert_cmpint(unlink(targetfilename), ==, 0);
3756
  g_assert_cmpint(rmdir(watchdir), ==, 0);
3757
}
3758
3759
static
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
3760
void test_add_inotify_dir_watch_IN_MOVED_FROM(__attribute__((unused))
3761
					      test_fixture *fixture,
3762
					      __attribute__((unused))
3763
					      gconstpointer
3764
					      user_data){
3765
  __attribute__((cleanup(cleanup_close)))
3766
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3767
  g_assert_cmpint(epoll_fd, >=, 0);
3768
  __attribute__((cleanup(cleanup_queue)))
3769
    task_queue *queue = create_queue();
3770
  g_assert_nonnull(queue);
3771
  __attribute__((cleanup(string_set_clear)))
3772
    string_set cancelled_filenames = {};
3773
  const mono_microsecs current_time = 0;
3774
3775
  bool quit_now = false;
3776
  buffer password = {};
3777
  bool mandos_client_exited = false;
3778
  bool password_is_read = false;
3779
3780
  __attribute__((cleanup(cleanup_string)))
3781
    char *tempdir = make_temporary_directory();
3782
  g_assert_nonnull(tempdir);
3783
3784
  __attribute__((cleanup(cleanup_string)))
3785
    char *tempfilename = make_temporary_file_in_directory(tempdir);
3786
  g_assert_nonnull(tempfilename);
3787
3788
  __attribute__((cleanup(cleanup_string)))
3789
    char *targetdir = make_temporary_directory();
3790
  g_assert_nonnull(targetdir);
3791
3792
  __attribute__((cleanup(cleanup_string)))
3793
    char *targetfilename = NULL;
3794
  g_assert_cmpint(asprintf(&targetfilename, "%s/%s", targetdir,
3795
			   basename(tempfilename)), >, 0);
3796
  g_assert_nonnull(targetfilename);
3797
3798
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3799
				      &password, tempdir,
3800
				      &cancelled_filenames,
3801
				      &current_time,
3802
				      &mandos_client_exited,
3803
				      &password_is_read));
3804
3805
  g_assert_cmpint(rename(tempfilename, targetfilename), ==, 0);
3806
3807
  const task_context *const added_read_task
3808
    = find_matching_task(queue,
3809
			 (task_context){ .func=read_inotify_event });
3810
  g_assert_nonnull(added_read_task);
3811
3812
  /* "sufficient to read at least one event." - inotify(7) */
3813
  const size_t ievent_size = (sizeof(struct inotify_event)
3814
			      + NAME_MAX + 1);
3815
  struct inotify_event *ievent = malloc(ievent_size);
3816
  g_assert_nonnull(ievent);
3817
3818
  ssize_t read_size = read(added_read_task->fd, ievent, ievent_size);
3819
3820
  g_assert_cmpint((int)read_size, >, 0);
3821
  g_assert_true(ievent->mask & IN_MOVED_FROM);
3822
  g_assert_cmpstr(ievent->name, ==, basename(tempfilename));
3823
3824
  free(ievent);
3825
3826
  g_assert_cmpint(unlink(targetfilename), ==, 0);
3827
  g_assert_cmpint(rmdir(targetdir), ==, 0);
3828
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3829
}
3830
3831
static
237.7.675 by Teddy Hogeborn
Add dracut(8) support
3832
void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused))
3833
					  test_fixture *fixture,
3834
					  __attribute__((unused))
3835
					  gconstpointer user_data){
3836
  __attribute__((cleanup(cleanup_close)))
3837
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3838
  g_assert_cmpint(epoll_fd, >=, 0);
3839
  __attribute__((cleanup(cleanup_queue)))
3840
    task_queue *queue = create_queue();
3841
  g_assert_nonnull(queue);
3842
  __attribute__((cleanup(string_set_clear)))
3843
    string_set cancelled_filenames = {};
3844
  const mono_microsecs current_time = 0;
3845
3846
  bool quit_now = false;
3847
  buffer password = {};
3848
  bool mandos_client_exited = false;
3849
  bool password_is_read = false;
3850
3851
  __attribute__((cleanup(cleanup_string)))
3852
    char *tempdir = make_temporary_directory();
3853
  g_assert_nonnull(tempdir);
3854
3855
  __attribute__((cleanup(cleanup_string)))
3856
    char *tempfile = make_temporary_file_in_directory(tempdir);
3857
  g_assert_nonnull(tempfile);
3858
3859
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3860
				      &password, tempdir,
3861
				      &cancelled_filenames,
3862
				      &current_time,
3863
				      &mandos_client_exited,
3864
				      &password_is_read));
3865
  g_assert_cmpint(unlink(tempfile), ==, 0);
3866
3867
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3868
3869
  const task_context *const added_read_task
3870
    = find_matching_task(queue,
3871
			 (task_context){ .func=read_inotify_event });
3872
  g_assert_nonnull(added_read_task);
3873
3874
  g_assert_cmpint(added_read_task->fd, >, 2);
3875
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3876
3877
  /* "sufficient to read at least one event." - inotify(7) */
3878
  const size_t ievent_size = (sizeof(struct inotify_event)
3879
			      + NAME_MAX + 1);
3880
  struct inotify_event *ievent = malloc(ievent_size);
3881
  g_assert_nonnull(ievent);
3882
3883
  ssize_t read_size = 0;
3884
  read_size = read(added_read_task->fd, ievent, ievent_size);
3885
3886
  g_assert_cmpint((int)read_size, >, 0);
3887
  g_assert_true(ievent->mask & IN_DELETE);
3888
  g_assert_cmpstr(ievent->name, ==, basename(tempfile));
3889
3890
  free(ievent);
3891
3892
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3893
}
3894
237.7.689 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Ignore deleted files
3895
static
3896
void test_add_inotify_dir_watch_IN_EXCL_UNLINK(__attribute__((unused))
3897
					       test_fixture *fixture,
3898
					       __attribute__((unused))
3899
					       gconstpointer
3900
					       user_data){
3901
  __attribute__((cleanup(cleanup_close)))
3902
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3903
  g_assert_cmpint(epoll_fd, >=, 0);
3904
  __attribute__((cleanup(cleanup_queue)))
3905
    task_queue *queue = create_queue();
3906
  g_assert_nonnull(queue);
3907
  __attribute__((cleanup(string_set_clear)))
3908
    string_set cancelled_filenames = {};
3909
  const mono_microsecs current_time = 0;
3910
3911
  bool quit_now = false;
3912
  buffer password = {};
3913
  bool mandos_client_exited = false;
3914
  bool password_is_read = false;
3915
3916
  __attribute__((cleanup(cleanup_string)))
3917
    char *tempdir = make_temporary_directory();
3918
  g_assert_nonnull(tempdir);
3919
3920
  __attribute__((cleanup(cleanup_string)))
3921
    char *tempfile = make_temporary_file_in_directory(tempdir);
3922
  g_assert_nonnull(tempfile);
3923
  int tempfile_fd = open(tempfile, O_WRONLY | O_CLOEXEC | O_NOCTTY
3924
			 | O_NOFOLLOW);
3925
  g_assert_cmpint(tempfile_fd, >, 2);
3926
3927
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3928
				      &password, tempdir,
3929
				      &cancelled_filenames,
3930
				      &current_time,
3931
				      &mandos_client_exited,
3932
				      &password_is_read));
3933
  g_assert_cmpint(unlink(tempfile), ==, 0);
3934
3935
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
3936
3937
  const task_context *const added_read_task
3938
    = find_matching_task(queue,
3939
			 (task_context){ .func=read_inotify_event });
3940
  g_assert_nonnull(added_read_task);
3941
3942
  g_assert_cmpint(added_read_task->fd, >, 2);
3943
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3944
3945
  /* "sufficient to read at least one event." - inotify(7) */
3946
  const size_t ievent_size = (sizeof(struct inotify_event)
3947
			      + NAME_MAX + 1);
3948
  struct inotify_event *ievent = malloc(ievent_size);
3949
  g_assert_nonnull(ievent);
3950
3951
  ssize_t read_size = 0;
3952
  read_size = read(added_read_task->fd, ievent, ievent_size);
3953
3954
  g_assert_cmpint((int)read_size, >, 0);
3955
  g_assert_true(ievent->mask & IN_DELETE);
3956
  g_assert_cmpstr(ievent->name, ==, basename(tempfile));
3957
3958
  g_assert_cmpint(close(tempfile_fd), ==, 0);
3959
3960
  /* IN_EXCL_UNLINK should make the closing of the previously unlinked
3961
     file not appear as an ievent, so we should not see it now. */
3962
  read_size = read(added_read_task->fd, ievent, ievent_size);
3963
  g_assert_cmpint((int)read_size, ==, -1);
3964
  g_assert_true(errno == EAGAIN);
3965
3966
  free(ievent);
3967
3968
  g_assert_cmpint(rmdir(tempdir), ==, 0);
3969
}
3970
237.7.675 by Teddy Hogeborn
Add dracut(8) support
3971
static void test_read_inotify_event_readerror(__attribute__((unused))
3972
					      test_fixture *fixture,
3973
					      __attribute__((unused))
3974
					      gconstpointer
3975
					      user_data){
3976
  __attribute__((cleanup(cleanup_close)))
3977
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3978
  g_assert_cmpint(epoll_fd, >=, 0);
3979
  const mono_microsecs current_time = 0;
3980
3981
  /* Reading /proc/self/mem from offset 0 will always result in EIO */
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
3982
  const int fd = open("/proc/self/mem",
3983
		      O_RDONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
3984
3985
  bool quit_now = false;
3986
  __attribute__((cleanup(cleanup_queue)))
3987
    task_queue *queue = create_queue();
3988
  g_assert_nonnull(queue);
3989
3990
  task_context task = {
3991
    .func=read_inotify_event,
3992
    .epoll_fd=epoll_fd,
3993
    .fd=fd,
3994
    .quit_now=&quit_now,
3995
    .filename=strdup("/nonexistent"),
3996
    .cancelled_filenames = &(string_set){},
3997
    .notafter=0,
3998
    .current_time=&current_time,
3999
  };
4000
  g_assert_nonnull(task.filename);
4001
  run_task_with_stderr_to_dev_null(task, queue);
4002
  g_assert_true(quit_now);
4003
  g_assert_true(queue->next_run == 0);
4004
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
4005
4006
  g_assert_true(epoll_set_does_not_contain(epoll_fd, fd));
4007
4008
  g_assert_cmpint(close(fd), ==, -1);
4009
}
4010
4011
static void test_read_inotify_event_bad_epoll(__attribute__((unused))
4012
					      test_fixture *fixture,
4013
					      __attribute__((unused))
4014
					      gconstpointer
4015
					      user_data){
4016
  const mono_microsecs current_time = 17;
4017
4018
  int pipefds[2];
4019
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4020
  const int epoll_fd = pipefds[0]; /* This will obviously fail */
4021
4022
  bool quit_now = false;
4023
  buffer password = {};
4024
  bool mandos_client_exited = false;
4025
  bool password_is_read = false;
4026
  __attribute__((cleanup(cleanup_queue)))
4027
    task_queue *queue = create_queue();
4028
  g_assert_nonnull(queue);
4029
4030
  task_context task = {
4031
    .func=read_inotify_event,
4032
    .epoll_fd=epoll_fd,
4033
    .fd=pipefds[0],
4034
    .quit_now=&quit_now,
4035
    .password=&password,
4036
    .filename=strdup("/nonexistent"),
4037
    .cancelled_filenames = &(string_set){},
4038
    .notafter=0,
4039
    .current_time=&current_time,
4040
    .mandos_client_exited=&mandos_client_exited,
4041
    .password_is_read=&password_is_read,
4042
  };
4043
  g_assert_nonnull(task.filename);
4044
  run_task_with_stderr_to_dev_null(task, queue);
4045
4046
  g_assert_nonnull(find_matching_task(queue, task));
4047
  g_assert_true(queue->next_run == 1000000 + current_time);
4048
4049
  g_assert_cmpint(close(pipefds[0]), ==, 0);
4050
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4051
}
4052
4053
static void test_read_inotify_event_nodata(__attribute__((unused))
4054
					   test_fixture *fixture,
4055
					   __attribute__((unused))
4056
					   gconstpointer user_data){
4057
  __attribute__((cleanup(cleanup_close)))
4058
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4059
  g_assert_cmpint(epoll_fd, >=, 0);
4060
  const mono_microsecs current_time = 0;
4061
4062
  int pipefds[2];
4063
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4064
4065
  bool quit_now = false;
4066
  buffer password = {};
4067
  bool mandos_client_exited = false;
4068
  bool password_is_read = false;
4069
  __attribute__((cleanup(cleanup_queue)))
4070
    task_queue *queue = create_queue();
4071
  g_assert_nonnull(queue);
4072
4073
  task_context task = {
4074
    .func=read_inotify_event,
4075
    .epoll_fd=epoll_fd,
4076
    .fd=pipefds[0],
4077
    .quit_now=&quit_now,
4078
    .password=&password,
4079
    .filename=strdup("/nonexistent"),
4080
    .cancelled_filenames = &(string_set){},
4081
    .notafter=0,
4082
    .current_time=&current_time,
4083
    .mandos_client_exited=&mandos_client_exited,
4084
    .password_is_read=&password_is_read,
4085
  };
4086
  g_assert_nonnull(task.filename);
4087
  task.func(task, queue);
4088
  g_assert_false(quit_now);
4089
  g_assert_true(queue->next_run == 0);
4090
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4091
4092
  g_assert_nonnull(find_matching_task(queue, (task_context){
4093
	.func=read_inotify_event,
4094
	.epoll_fd=epoll_fd,
4095
	.fd=pipefds[0],
4096
	.quit_now=&quit_now,
4097
	.password=&password,
4098
	.filename=task.filename,
4099
	.cancelled_filenames=task.cancelled_filenames,
4100
	.current_time=&current_time,
4101
	.mandos_client_exited=&mandos_client_exited,
4102
	.password_is_read=&password_is_read,
4103
      }));
4104
4105
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4106
				   EPOLLIN | EPOLLRDHUP));
4107
4108
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4109
}
4110
4111
static void test_read_inotify_event_eof(__attribute__((unused))
4112
					test_fixture *fixture,
4113
					__attribute__((unused))
4114
					gconstpointer user_data){
4115
  __attribute__((cleanup(cleanup_close)))
4116
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4117
  g_assert_cmpint(epoll_fd, >=, 0);
4118
  const mono_microsecs current_time = 0;
4119
4120
  int pipefds[2];
4121
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4122
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4123
4124
  bool quit_now = false;
4125
  buffer password = {};
4126
  __attribute__((cleanup(cleanup_queue)))
4127
    task_queue *queue = create_queue();
4128
  g_assert_nonnull(queue);
4129
4130
  task_context task = {
4131
    .func=read_inotify_event,
4132
    .epoll_fd=epoll_fd,
4133
    .fd=pipefds[0],
4134
    .quit_now=&quit_now,
4135
    .password=&password,
4136
    .filename=strdup("/nonexistent"),
4137
    .cancelled_filenames = &(string_set){},
4138
    .notafter=0,
4139
    .current_time=&current_time,
4140
  };
4141
  run_task_with_stderr_to_dev_null(task, queue);
4142
  g_assert_true(quit_now);
4143
  g_assert_true(queue->next_run == 0);
4144
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
4145
4146
  g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0]));
4147
4148
  g_assert_cmpint(close(pipefds[0]), ==, -1);
4149
}
4150
4151
static
4152
void test_read_inotify_event_IN_CLOSE_WRITE(__attribute__((unused))
4153
					    test_fixture *fixture,
4154
					    __attribute__((unused))
4155
					    gconstpointer user_data){
4156
  __attribute__((cleanup(cleanup_close)))
4157
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4158
  g_assert_cmpint(epoll_fd, >=, 0);
4159
  const mono_microsecs current_time = 0;
4160
4161
  int pipefds[2];
4162
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4163
4164
  /* "sufficient to read at least one event." - inotify(7) */
4165
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4166
				  + NAME_MAX + 1);
4167
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4168
  struct {
4169
    struct inotify_event event;
4170
    char name_buffer[NAME_MAX + 1];
4171
  } ievent_buffer;
4172
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4173
4174
  const char dummy_file_name[] = "ask.dummy_file_name";
4175
  ievent->mask = IN_CLOSE_WRITE;
4176
  ievent->len = sizeof(dummy_file_name);
4177
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4178
  const size_t ievent_size = (sizeof(struct inotify_event)
4179
			      + sizeof(dummy_file_name));
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4180
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4181
		  ==, ievent_size);
4182
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4183
4184
  bool quit_now = false;
4185
  buffer password = {};
4186
  bool mandos_client_exited = false;
4187
  bool password_is_read = false;
4188
  __attribute__((cleanup(cleanup_queue)))
4189
    task_queue *queue = create_queue();
4190
  g_assert_nonnull(queue);
4191
4192
  task_context task = {
4193
    .func=read_inotify_event,
4194
    .epoll_fd=epoll_fd,
4195
    .fd=pipefds[0],
4196
    .quit_now=&quit_now,
4197
    .password=&password,
4198
    .filename=strdup("/nonexistent"),
4199
    .cancelled_filenames = &(string_set){},
4200
    .notafter=0,
4201
    .current_time=&current_time,
4202
    .mandos_client_exited=&mandos_client_exited,
4203
    .password_is_read=&password_is_read,
4204
  };
4205
  task.func(task, queue);
4206
  g_assert_false(quit_now);
4207
  g_assert_true(queue->next_run != 0);
4208
  g_assert_cmpuint((unsigned int)queue->length, >=, 1);
4209
4210
  g_assert_nonnull(find_matching_task(queue, (task_context){
4211
	.func=read_inotify_event,
4212
	.epoll_fd=epoll_fd,
4213
	.fd=pipefds[0],
4214
	.quit_now=&quit_now,
4215
	.password=&password,
4216
	.filename=task.filename,
4217
	.cancelled_filenames=task.cancelled_filenames,
4218
	.current_time=&current_time,
4219
	.mandos_client_exited=&mandos_client_exited,
4220
	.password_is_read=&password_is_read,
4221
      }));
4222
4223
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4224
				   EPOLLIN | EPOLLRDHUP));
4225
4226
  g_assert_cmpuint((unsigned int)queue->length, >=, 2);
4227
4228
  __attribute__((cleanup(cleanup_string)))
4229
    char *filename = NULL;
4230
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4231
			   dummy_file_name), >, 0);
4232
  g_assert_nonnull(filename);
4233
  g_assert_nonnull(find_matching_task(queue, (task_context){
4234
	.func=open_and_parse_question,
4235
	.epoll_fd=epoll_fd,
4236
	.filename=filename,
4237
	.question_filename=filename,
4238
	.password=&password,
4239
	.cancelled_filenames=task.cancelled_filenames,
4240
	.current_time=&current_time,
4241
	.mandos_client_exited=&mandos_client_exited,
4242
	.password_is_read=&password_is_read,
4243
      }));
4244
}
4245
4246
static
4247
void test_read_inotify_event_IN_MOVED_TO(__attribute__((unused))
4248
					 test_fixture *fixture,
4249
					 __attribute__((unused))
4250
					 gconstpointer user_data){
4251
  __attribute__((cleanup(cleanup_close)))
4252
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4253
  g_assert_cmpint(epoll_fd, >=, 0);
4254
  const mono_microsecs current_time = 0;
4255
4256
  int pipefds[2];
4257
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4258
4259
  /* "sufficient to read at least one event." - inotify(7) */
4260
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4261
				  + NAME_MAX + 1);
4262
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4263
  struct {
4264
    struct inotify_event event;
4265
    char name_buffer[NAME_MAX + 1];
4266
  } ievent_buffer;
4267
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4268
4269
  const char dummy_file_name[] = "ask.dummy_file_name";
4270
  ievent->mask = IN_MOVED_TO;
4271
  ievent->len = sizeof(dummy_file_name);
4272
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4273
  const size_t ievent_size = (sizeof(struct inotify_event)
4274
			      + sizeof(dummy_file_name));
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4275
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4276
		  ==, ievent_size);
4277
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4278
4279
  bool quit_now = false;
4280
  buffer password = {};
4281
  bool mandos_client_exited = false;
4282
  bool password_is_read = false;
4283
  __attribute__((cleanup(cleanup_queue)))
4284
    task_queue *queue = create_queue();
4285
  g_assert_nonnull(queue);
4286
4287
  task_context task = {
4288
    .func=read_inotify_event,
4289
    .epoll_fd=epoll_fd,
4290
    .fd=pipefds[0],
4291
    .quit_now=&quit_now,
4292
    .password=&password,
4293
    .filename=strdup("/nonexistent"),
4294
    .cancelled_filenames = &(string_set){},
4295
    .notafter=0,
4296
    .current_time=&current_time,
4297
    .mandos_client_exited=&mandos_client_exited,
4298
    .password_is_read=&password_is_read,
4299
  };
4300
  task.func(task, queue);
4301
  g_assert_false(quit_now);
4302
  g_assert_true(queue->next_run != 0);
4303
  g_assert_cmpuint((unsigned int)queue->length, >=, 1);
4304
4305
  g_assert_nonnull(find_matching_task(queue, (task_context){
4306
	.func=read_inotify_event,
4307
	.epoll_fd=epoll_fd,
4308
	.fd=pipefds[0],
4309
	.quit_now=&quit_now,
4310
	.password=&password,
4311
	.filename=task.filename,
4312
	.cancelled_filenames=task.cancelled_filenames,
4313
	.current_time=&current_time,
4314
	.mandos_client_exited=&mandos_client_exited,
4315
	.password_is_read=&password_is_read,
4316
      }));
4317
4318
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4319
				   EPOLLIN | EPOLLRDHUP));
4320
4321
  g_assert_cmpuint((unsigned int)queue->length, >=, 2);
4322
4323
  __attribute__((cleanup(cleanup_string)))
4324
    char *filename = NULL;
4325
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4326
			   dummy_file_name), >, 0);
4327
  g_assert_nonnull(filename);
4328
  g_assert_nonnull(find_matching_task(queue, (task_context){
4329
	.func=open_and_parse_question,
4330
	.epoll_fd=epoll_fd,
4331
	.filename=filename,
4332
	.question_filename=filename,
4333
	.password=&password,
4334
	.cancelled_filenames=task.cancelled_filenames,
4335
	.current_time=&current_time,
4336
	.mandos_client_exited=&mandos_client_exited,
4337
	.password_is_read=&password_is_read,
4338
      }));
4339
}
4340
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
4341
static
4342
void test_read_inotify_event_IN_MOVED_FROM(__attribute__((unused))
4343
					   test_fixture *fixture,
4344
					   __attribute__((unused))
4345
					   gconstpointer user_data){
4346
  __attribute__((cleanup(cleanup_close)))
4347
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4348
  g_assert_cmpint(epoll_fd, >=, 0);
4349
  __attribute__((cleanup(string_set_clear)))
4350
    string_set cancelled_filenames = {};
4351
  const mono_microsecs current_time = 0;
4352
4353
  int pipefds[2];
4354
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4355
4356
  /* "sufficient to read at least one event." - inotify(7) */
4357
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4358
				  + NAME_MAX + 1);
4359
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4360
  struct {
4361
    struct inotify_event event;
4362
    char name_buffer[NAME_MAX + 1];
4363
  } ievent_buffer;
4364
  struct inotify_event *const ievent = &ievent_buffer.event;
4365
4366
  const char dummy_file_name[] = "ask.dummy_file_name";
4367
  ievent->mask = IN_MOVED_FROM;
4368
  ievent->len = sizeof(dummy_file_name);
4369
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4370
  const size_t ievent_size = (sizeof(struct inotify_event)
4371
			      + sizeof(dummy_file_name));
4372
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4373
		  ==, ievent_size);
4374
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4375
4376
  bool quit_now = false;
4377
  buffer password = {};
4378
  bool mandos_client_exited = false;
4379
  bool password_is_read = false;
4380
  __attribute__((cleanup(cleanup_queue)))
4381
    task_queue *queue = create_queue();
4382
  g_assert_nonnull(queue);
4383
4384
  task_context task = {
4385
    .func=read_inotify_event,
4386
    .epoll_fd=epoll_fd,
4387
    .fd=pipefds[0],
4388
    .quit_now=&quit_now,
4389
    .password=&password,
4390
    .filename=strdup("/nonexistent"),
4391
    .cancelled_filenames=&cancelled_filenames,
4392
    .current_time=&current_time,
4393
    .mandos_client_exited=&mandos_client_exited,
4394
    .password_is_read=&password_is_read,
4395
  };
4396
  task.func(task, queue);
4397
  g_assert_false(quit_now);
4398
  g_assert_true(queue->next_run == 0);
4399
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4400
4401
  g_assert_nonnull(find_matching_task(queue, (task_context){
4402
	.func=read_inotify_event,
4403
	.epoll_fd=epoll_fd,
4404
	.fd=pipefds[0],
4405
	.quit_now=&quit_now,
4406
	.password=&password,
4407
	.filename=task.filename,
4408
	.cancelled_filenames=&cancelled_filenames,
4409
	.current_time=&current_time,
4410
	.mandos_client_exited=&mandos_client_exited,
4411
	.password_is_read=&password_is_read,
4412
      }));
4413
4414
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4415
				   EPOLLIN | EPOLLRDHUP));
4416
4417
  __attribute__((cleanup(cleanup_string)))
4418
    char *filename = NULL;
4419
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4420
			   dummy_file_name), >, 0);
4421
  g_assert_nonnull(filename);
4422
  g_assert_true(string_set_contains(*task.cancelled_filenames,
4423
				    filename));
4424
}
4425
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4426
static void test_read_inotify_event_IN_DELETE(__attribute__((unused))
4427
					      test_fixture *fixture,
4428
					      __attribute__((unused))
4429
					      gconstpointer
4430
					      user_data){
4431
  __attribute__((cleanup(cleanup_close)))
4432
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4433
  g_assert_cmpint(epoll_fd, >=, 0);
4434
  __attribute__((cleanup(string_set_clear)))
4435
    string_set cancelled_filenames = {};
4436
  const mono_microsecs current_time = 0;
4437
4438
  int pipefds[2];
4439
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4440
4441
  /* "sufficient to read at least one event." - inotify(7) */
4442
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4443
				  + NAME_MAX + 1);
4444
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4445
  struct {
4446
    struct inotify_event event;
4447
    char name_buffer[NAME_MAX + 1];
4448
  } ievent_buffer;
4449
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4450
4451
  const char dummy_file_name[] = "ask.dummy_file_name";
4452
  ievent->mask = IN_DELETE;
4453
  ievent->len = sizeof(dummy_file_name);
4454
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4455
  const size_t ievent_size = (sizeof(struct inotify_event)
4456
			      + sizeof(dummy_file_name));
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4457
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4458
		  ==, ievent_size);
4459
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4460
4461
  bool quit_now = false;
4462
  buffer password = {};
4463
  bool mandos_client_exited = false;
4464
  bool password_is_read = false;
4465
  __attribute__((cleanup(cleanup_queue)))
4466
    task_queue *queue = create_queue();
4467
  g_assert_nonnull(queue);
4468
4469
  task_context task = {
4470
    .func=read_inotify_event,
4471
    .epoll_fd=epoll_fd,
4472
    .fd=pipefds[0],
4473
    .quit_now=&quit_now,
4474
    .password=&password,
4475
    .filename=strdup("/nonexistent"),
4476
    .cancelled_filenames=&cancelled_filenames,
4477
    .current_time=&current_time,
4478
    .mandos_client_exited=&mandos_client_exited,
4479
    .password_is_read=&password_is_read,
4480
  };
4481
  task.func(task, queue);
4482
  g_assert_false(quit_now);
4483
  g_assert_true(queue->next_run == 0);
4484
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4485
4486
  g_assert_nonnull(find_matching_task(queue, (task_context){
4487
	.func=read_inotify_event,
4488
	.epoll_fd=epoll_fd,
4489
	.fd=pipefds[0],
4490
	.quit_now=&quit_now,
4491
	.password=&password,
4492
	.filename=task.filename,
4493
	.cancelled_filenames=&cancelled_filenames,
4494
	.current_time=&current_time,
4495
	.mandos_client_exited=&mandos_client_exited,
4496
	.password_is_read=&password_is_read,
4497
      }));
4498
4499
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4500
				   EPOLLIN | EPOLLRDHUP));
4501
4502
  __attribute__((cleanup(cleanup_string)))
4503
    char *filename = NULL;
4504
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4505
			   dummy_file_name), >, 0);
4506
  g_assert_nonnull(filename);
4507
  g_assert_true(string_set_contains(*task.cancelled_filenames,
4508
				    filename));
4509
}
4510
4511
static void
4512
test_read_inotify_event_IN_CLOSE_WRITE_badname(__attribute__((unused))
4513
					       test_fixture *fixture,
4514
					       __attribute__((unused))
4515
					       gconstpointer
4516
					       user_data){
4517
  __attribute__((cleanup(cleanup_close)))
4518
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4519
  g_assert_cmpint(epoll_fd, >=, 0);
4520
  const mono_microsecs current_time = 0;
4521
4522
  int pipefds[2];
4523
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4524
4525
  /* "sufficient to read at least one event." - inotify(7) */
4526
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4527
				  + NAME_MAX + 1);
4528
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4529
  struct {
4530
    struct inotify_event event;
4531
    char name_buffer[NAME_MAX + 1];
4532
  } ievent_buffer;
4533
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4534
4535
  const char dummy_file_name[] = "ignored.dummy_file_name";
4536
  ievent->mask = IN_CLOSE_WRITE;
4537
  ievent->len = sizeof(dummy_file_name);
4538
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4539
  const size_t ievent_size = (sizeof(struct inotify_event)
4540
			      + sizeof(dummy_file_name));
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4541
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4542
		  ==, ievent_size);
4543
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4544
4545
  bool quit_now = false;
4546
  buffer password = {};
4547
  bool mandos_client_exited = false;
4548
  bool password_is_read = false;
4549
  __attribute__((cleanup(cleanup_queue)))
4550
    task_queue *queue = create_queue();
4551
  g_assert_nonnull(queue);
4552
4553
  task_context task = {
4554
    .func=read_inotify_event,
4555
    .epoll_fd=epoll_fd,
4556
    .fd=pipefds[0],
4557
    .quit_now=&quit_now,
4558
    .password=&password,
4559
    .filename=strdup("/nonexistent"),
4560
    .cancelled_filenames = &(string_set){},
4561
    .notafter=0,
4562
    .current_time=&current_time,
4563
    .mandos_client_exited=&mandos_client_exited,
4564
    .password_is_read=&password_is_read,
4565
  };
4566
  task.func(task, queue);
4567
  g_assert_false(quit_now);
4568
  g_assert_true(queue->next_run == 0);
4569
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4570
4571
  g_assert_nonnull(find_matching_task(queue, (task_context){
4572
	.func=read_inotify_event,
4573
	.epoll_fd=epoll_fd,
4574
	.fd=pipefds[0],
4575
	.quit_now=&quit_now,
4576
	.password=&password,
4577
	.filename=task.filename,
4578
	.cancelled_filenames=task.cancelled_filenames,
4579
	.current_time=&current_time,
4580
	.mandos_client_exited=&mandos_client_exited,
4581
	.password_is_read=&password_is_read,
4582
      }));
4583
4584
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4585
				   EPOLLIN | EPOLLRDHUP));
4586
}
4587
4588
static void
4589
test_read_inotify_event_IN_MOVED_TO_badname(__attribute__((unused))
4590
					    test_fixture *fixture,
4591
					    __attribute__((unused))
4592
					    gconstpointer user_data){
4593
  __attribute__((cleanup(cleanup_close)))
4594
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4595
  g_assert_cmpint(epoll_fd, >=, 0);
4596
  const mono_microsecs current_time = 0;
4597
4598
  int pipefds[2];
4599
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4600
4601
  /* "sufficient to read at least one event." - inotify(7) */
4602
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4603
				  + NAME_MAX + 1);
4604
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4605
  struct {
4606
    struct inotify_event event;
4607
    char name_buffer[NAME_MAX + 1];
4608
  } ievent_buffer;
4609
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4610
4611
  const char dummy_file_name[] = "ignored.dummy_file_name";
4612
  ievent->mask = IN_MOVED_TO;
4613
  ievent->len = sizeof(dummy_file_name);
4614
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4615
  const size_t ievent_size = (sizeof(struct inotify_event)
4616
			      + sizeof(dummy_file_name));
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4617
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4618
		  ==, ievent_size);
4619
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4620
4621
  bool quit_now = false;
4622
  buffer password = {};
4623
  bool mandos_client_exited = false;
4624
  bool password_is_read = false;
4625
  __attribute__((cleanup(cleanup_queue)))
4626
    task_queue *queue = create_queue();
4627
  g_assert_nonnull(queue);
4628
4629
  task_context task = {
4630
    .func=read_inotify_event,
4631
    .epoll_fd=epoll_fd,
4632
    .fd=pipefds[0],
4633
    .quit_now=&quit_now,
4634
    .password=&password,
4635
    .filename=strdup("/nonexistent"),
4636
    .cancelled_filenames = &(string_set){},
4637
    .notafter=0,
4638
    .current_time=&current_time,
4639
    .mandos_client_exited=&mandos_client_exited,
4640
    .password_is_read=&password_is_read,
4641
  };
4642
  task.func(task, queue);
4643
  g_assert_false(quit_now);
4644
  g_assert_true(queue->next_run == 0);
4645
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4646
4647
  g_assert_nonnull(find_matching_task(queue, (task_context){
4648
	.func=read_inotify_event,
4649
	.epoll_fd=epoll_fd,
4650
	.fd=pipefds[0],
4651
	.quit_now=&quit_now,
4652
	.password=&password,
4653
	.filename=task.filename,
4654
	.cancelled_filenames=task.cancelled_filenames,
4655
	.current_time=&current_time,
4656
	.mandos_client_exited=&mandos_client_exited,
4657
	.password_is_read=&password_is_read,
4658
      }));
4659
4660
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4661
				   EPOLLIN | EPOLLRDHUP));
4662
}
4663
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
4664
static void
4665
test_read_inotify_event_IN_MOVED_FROM_badname(__attribute__((unused))
4666
					      test_fixture *fixture,
4667
					      __attribute__((unused))
4668
					      gconstpointer
4669
					      user_data){
4670
  __attribute__((cleanup(cleanup_close)))
4671
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4672
  g_assert_cmpint(epoll_fd, >=, 0);
4673
  __attribute__((cleanup(string_set_clear)))
4674
    string_set cancelled_filenames = {};
4675
  const mono_microsecs current_time = 0;
4676
4677
  int pipefds[2];
4678
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4679
4680
  /* "sufficient to read at least one event." - inotify(7) */
4681
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4682
				  + NAME_MAX + 1);
4683
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
4684
  struct {
4685
    struct inotify_event event;
4686
    char name_buffer[NAME_MAX + 1];
4687
  } ievent_buffer;
4688
  struct inotify_event *const ievent = &ievent_buffer.event;
4689
4690
  const char dummy_file_name[] = "ignored.dummy_file_name";
4691
  ievent->mask = IN_MOVED_FROM;
4692
  ievent->len = sizeof(dummy_file_name);
4693
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4694
  const size_t ievent_size = (sizeof(struct inotify_event)
4695
			      + sizeof(dummy_file_name));
4696
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
4697
		  ==, ievent_size);
4698
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4699
4700
  bool quit_now = false;
4701
  buffer password = {};
4702
  bool mandos_client_exited = false;
4703
  bool password_is_read = false;
4704
  __attribute__((cleanup(cleanup_queue)))
4705
    task_queue *queue = create_queue();
4706
  g_assert_nonnull(queue);
4707
4708
  task_context task = {
4709
    .func=read_inotify_event,
4710
    .epoll_fd=epoll_fd,
4711
    .fd=pipefds[0],
4712
    .quit_now=&quit_now,
4713
    .password=&password,
4714
    .filename=strdup("/nonexistent"),
4715
    .cancelled_filenames=&cancelled_filenames,
4716
    .current_time=&current_time,
4717
    .mandos_client_exited=&mandos_client_exited,
4718
    .password_is_read=&password_is_read,
4719
  };
4720
  task.func(task, queue);
4721
  g_assert_false(quit_now);
4722
  g_assert_true(queue->next_run == 0);
4723
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4724
4725
  g_assert_nonnull(find_matching_task(queue, (task_context){
4726
	.func=read_inotify_event,
4727
	.epoll_fd=epoll_fd,
4728
	.fd=pipefds[0],
4729
	.quit_now=&quit_now,
4730
	.password=&password,
4731
	.filename=task.filename,
4732
	.cancelled_filenames=&cancelled_filenames,
4733
	.current_time=&current_time,
4734
	.mandos_client_exited=&mandos_client_exited,
4735
	.password_is_read=&password_is_read,
4736
      }));
4737
4738
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4739
				   EPOLLIN | EPOLLRDHUP));
4740
4741
  __attribute__((cleanup(cleanup_string)))
4742
    char *filename = NULL;
4743
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4744
			   dummy_file_name), >, 0);
4745
  g_assert_nonnull(filename);
4746
  g_assert_false(string_set_contains(cancelled_filenames, filename));
4747
}
4748
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4749
static
4750
void test_read_inotify_event_IN_DELETE_badname(__attribute__((unused))
4751
					       test_fixture *fixture,
4752
					       __attribute__((unused))
4753
					       gconstpointer
4754
					       user_data){
4755
  __attribute__((cleanup(cleanup_close)))
4756
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4757
  g_assert_cmpint(epoll_fd, >=, 0);
4758
  __attribute__((cleanup(string_set_clear)))
4759
    string_set cancelled_filenames = {};
4760
  const mono_microsecs current_time = 0;
4761
4762
  int pipefds[2];
4763
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
4764
4765
  /* "sufficient to read at least one event." - inotify(7) */
4766
  const size_t ievent_max_size = (sizeof(struct inotify_event)
4767
				  + NAME_MAX + 1);
4768
  g_assert_cmpint(ievent_max_size, <=, PIPE_BUF);
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4769
  struct {
4770
    struct inotify_event event;
4771
    char name_buffer[NAME_MAX + 1];
4772
  } ievent_buffer;
4773
  struct inotify_event *const ievent = &ievent_buffer.event;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4774
4775
  const char dummy_file_name[] = "ignored.dummy_file_name";
4776
  ievent->mask = IN_DELETE;
4777
  ievent->len = sizeof(dummy_file_name);
4778
  memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name));
4779
  const size_t ievent_size = (sizeof(struct inotify_event)
4780
			      + sizeof(dummy_file_name));
237.7.683 by Teddy Hogeborn
password-agent: Fix memory alignment issue
4781
  g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size),
237.7.675 by Teddy Hogeborn
Add dracut(8) support
4782
		  ==, ievent_size);
4783
  g_assert_cmpint(close(pipefds[1]), ==, 0);
4784
4785
  bool quit_now = false;
4786
  buffer password = {};
4787
  bool mandos_client_exited = false;
4788
  bool password_is_read = false;
4789
  __attribute__((cleanup(cleanup_queue)))
4790
    task_queue *queue = create_queue();
4791
  g_assert_nonnull(queue);
4792
4793
  task_context task = {
4794
    .func=read_inotify_event,
4795
    .epoll_fd=epoll_fd,
4796
    .fd=pipefds[0],
4797
    .quit_now=&quit_now,
4798
    .password=&password,
4799
    .filename=strdup("/nonexistent"),
4800
    .cancelled_filenames=&cancelled_filenames,
4801
    .current_time=&current_time,
4802
    .mandos_client_exited=&mandos_client_exited,
4803
    .password_is_read=&password_is_read,
4804
  };
4805
  task.func(task, queue);
4806
  g_assert_false(quit_now);
4807
  g_assert_true(queue->next_run == 0);
4808
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
4809
4810
  g_assert_nonnull(find_matching_task(queue, (task_context){
4811
	.func=read_inotify_event,
4812
	.epoll_fd=epoll_fd,
4813
	.fd=pipefds[0],
4814
	.quit_now=&quit_now,
4815
	.password=&password,
4816
	.filename=task.filename,
4817
	.cancelled_filenames=&cancelled_filenames,
4818
	.current_time=&current_time,
4819
	.mandos_client_exited=&mandos_client_exited,
4820
	.password_is_read=&password_is_read,
4821
      }));
4822
4823
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
4824
				   EPOLLIN | EPOLLRDHUP));
4825
4826
  __attribute__((cleanup(cleanup_string)))
4827
    char *filename = NULL;
4828
  g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename,
4829
			   dummy_file_name), >, 0);
4830
  g_assert_nonnull(filename);
4831
  g_assert_false(string_set_contains(cancelled_filenames, filename));
4832
}
4833
4834
static
4835
void test_open_and_parse_question_ENOENT(__attribute__((unused))
4836
					 test_fixture *fixture,
4837
					 __attribute__((unused))
4838
					 gconstpointer user_data){
4839
  __attribute__((cleanup(cleanup_close)))
4840
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4841
  g_assert_cmpint(epoll_fd, >=, 0);
4842
  __attribute__((cleanup(string_set_clear)))
4843
    string_set cancelled_filenames = {};
4844
  bool mandos_client_exited = false;
4845
  bool password_is_read = false;
4846
  __attribute__((cleanup(cleanup_queue)))
4847
    task_queue *queue = create_queue();
4848
  g_assert_nonnull(queue);
4849
4850
  char *const filename = strdup("/nonexistent");
4851
  g_assert_nonnull(filename);
4852
  task_context task = {
4853
    .func=open_and_parse_question,
4854
    .question_filename=filename,
4855
    .epoll_fd=epoll_fd,
4856
    .password=(buffer[]){{}},
4857
    .filename=filename,
4858
    .cancelled_filenames=&cancelled_filenames,
4859
    .current_time=(mono_microsecs[]){0},
4860
    .mandos_client_exited=&mandos_client_exited,
4861
    .password_is_read=&password_is_read,
4862
  };
4863
  task.func(task, queue);
4864
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
4865
}
4866
4867
static void test_open_and_parse_question_EIO(__attribute__((unused))
4868
					     test_fixture *fixture,
4869
					     __attribute__((unused))
4870
					     gconstpointer user_data){
4871
  __attribute__((cleanup(cleanup_close)))
4872
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4873
  g_assert_cmpint(epoll_fd, >=, 0);
4874
  __attribute__((cleanup(string_set_clear)))
4875
    string_set cancelled_filenames = {};
4876
  buffer password = {};
4877
  bool mandos_client_exited = false;
4878
  bool password_is_read = false;
4879
  __attribute__((cleanup(cleanup_queue)))
4880
    task_queue *queue = create_queue();
4881
  g_assert_nonnull(queue);
4882
  const mono_microsecs current_time = 0;
4883
4884
  char *filename = strdup("/proc/self/mem");
4885
  task_context task = {
4886
    .func=open_and_parse_question,
4887
    .question_filename=filename,
4888
    .epoll_fd=epoll_fd,
4889
    .password=&password,
4890
    .filename=filename,
4891
    .cancelled_filenames=&cancelled_filenames,
4892
    .current_time=&current_time,
4893
    .mandos_client_exited=&mandos_client_exited,
4894
    .password_is_read=&password_is_read,
4895
  };
4896
  run_task_with_stderr_to_dev_null(task, queue);
4897
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
4898
}
4899
4900
static void
4901
test_open_and_parse_question_parse_error(__attribute__((unused))
4902
					 test_fixture *fixture,
4903
					 __attribute__((unused))
4904
					 gconstpointer user_data){
4905
  __attribute__((cleanup(cleanup_close)))
4906
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4907
  g_assert_cmpint(epoll_fd, >=, 0);
4908
  __attribute__((cleanup(string_set_clear)))
4909
    string_set cancelled_filenames = {};
4910
  __attribute__((cleanup(cleanup_queue)))
4911
    task_queue *queue = create_queue();
4912
  g_assert_nonnull(queue);
4913
4914
  __attribute__((cleanup(cleanup_string)))
4915
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
4916
  g_assert_nonnull(tempfilename);
4917
  int tempfile = mkostemp(tempfilename, O_CLOEXEC);
4918
  g_assert_cmpint(tempfile, >, 0);
4919
  const char bad_data[] = "this is bad syntax\n";
4920
  g_assert_cmpint(write(tempfile, bad_data, sizeof(bad_data)),
4921
		  ==, sizeof(bad_data));
4922
  g_assert_cmpint(close(tempfile), ==, 0);
4923
4924
  char *const filename = strdup(tempfilename);
4925
  g_assert_nonnull(filename);
4926
  task_context task = {
4927
    .func=open_and_parse_question,
4928
    .question_filename=filename,
4929
    .epoll_fd=epoll_fd,
4930
    .password=(buffer[]){{}},
4931
    .filename=filename,
4932
    .cancelled_filenames=&cancelled_filenames,
4933
    .current_time=(mono_microsecs[]){0},
4934
    .mandos_client_exited=(bool[]){false},
4935
    .password_is_read=(bool[]){false},
4936
  };
4937
  run_task_with_stderr_to_dev_null(task, queue);
4938
4939
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
4940
4941
  g_assert_cmpint(unlink(tempfilename), ==, 0);
4942
}
4943
4944
static
4945
void test_open_and_parse_question_nosocket(__attribute__((unused))
4946
					   test_fixture *fixture,
4947
					   __attribute__((unused))
4948
					   gconstpointer user_data){
4949
  __attribute__((cleanup(cleanup_close)))
4950
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4951
  g_assert_cmpint(epoll_fd, >=, 0);
4952
  __attribute__((cleanup(string_set_clear)))
4953
    string_set cancelled_filenames = {};
4954
  __attribute__((cleanup(cleanup_queue)))
4955
    task_queue *queue = create_queue();
4956
  g_assert_nonnull(queue);
4957
4958
  __attribute__((cleanup(cleanup_string)))
4959
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
4960
  g_assert_nonnull(tempfilename);
4961
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
4962
  g_assert_cmpint(questionfile, >, 0);
4963
  FILE *qf = fdopen(questionfile, "w");
4964
  g_assert_cmpint(fprintf(qf, "[Ask]\nPID=1\n"), >, 0);
4965
  g_assert_cmpint(fclose(qf), ==, 0);
4966
4967
  char *const filename = strdup(tempfilename);
4968
  g_assert_nonnull(filename);
4969
  task_context task = {
4970
    .func=open_and_parse_question,
4971
    .question_filename=filename,
4972
    .epoll_fd=epoll_fd,
4973
    .password=(buffer[]){{}},
4974
    .filename=filename,
4975
    .cancelled_filenames=&cancelled_filenames,
4976
    .current_time=(mono_microsecs[]){0},
4977
    .mandos_client_exited=(bool[]){false},
4978
    .password_is_read=(bool[]){false},
4979
  };
4980
  run_task_with_stderr_to_dev_null(task, queue);
4981
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
4982
4983
  g_assert_cmpint(unlink(tempfilename), ==, 0);
4984
}
4985
4986
static
4987
void test_open_and_parse_question_badsocket(__attribute__((unused))
4988
					    test_fixture *fixture,
4989
					    __attribute__((unused))
4990
					    gconstpointer user_data){
4991
  __attribute__((cleanup(cleanup_close)))
4992
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
4993
  g_assert_cmpint(epoll_fd, >=, 0);
4994
  __attribute__((cleanup(string_set_clear)))
4995
    string_set cancelled_filenames = {};
4996
  __attribute__((cleanup(cleanup_queue)))
4997
    task_queue *queue = create_queue();
4998
  g_assert_nonnull(queue);
4999
5000
  __attribute__((cleanup(cleanup_string)))
5001
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5002
  g_assert_nonnull(tempfilename);
5003
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5004
  g_assert_cmpint(questionfile, >, 0);
5005
  FILE *qf = fdopen(questionfile, "w");
5006
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=\nPID=1\n"), >, 0);
5007
  g_assert_cmpint(fclose(qf), ==, 0);
5008
5009
  char *const filename = strdup(tempfilename);
5010
  g_assert_nonnull(filename);
5011
  task_context task = {
5012
    .func=open_and_parse_question,
5013
    .question_filename=filename,
5014
    .epoll_fd=epoll_fd,
5015
    .password=(buffer[]){{}},
5016
    .filename=filename,
5017
    .cancelled_filenames=&cancelled_filenames,
5018
    .current_time=(mono_microsecs[]){0},
5019
    .mandos_client_exited=(bool[]){false},
5020
    .password_is_read=(bool[]){false},
5021
  };
5022
  run_task_with_stderr_to_dev_null(task, queue);
5023
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5024
5025
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5026
}
5027
5028
static
5029
void test_open_and_parse_question_nopid(__attribute__((unused))
5030
					test_fixture *fixture,
5031
					__attribute__((unused))
5032
					gconstpointer user_data){
5033
  __attribute__((cleanup(cleanup_close)))
5034
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5035
  g_assert_cmpint(epoll_fd, >=, 0);
5036
  __attribute__((cleanup(string_set_clear)))
5037
    string_set cancelled_filenames = {};
5038
  __attribute__((cleanup(cleanup_queue)))
5039
    task_queue *queue = create_queue();
5040
  g_assert_nonnull(queue);
5041
5042
  __attribute__((cleanup(cleanup_string)))
5043
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5044
  g_assert_nonnull(tempfilename);
5045
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5046
  g_assert_cmpint(questionfile, >, 0);
5047
  FILE *qf = fdopen(questionfile, "w");
5048
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\n"), >, 0);
5049
  g_assert_cmpint(fclose(qf), ==, 0);
5050
5051
  char *const filename = strdup(tempfilename);
5052
  g_assert_nonnull(filename);
5053
  task_context task = {
5054
    .func=open_and_parse_question,
5055
    .question_filename=filename,
5056
    .epoll_fd=epoll_fd,
5057
    .password=(buffer[]){{}},
5058
    .filename=filename,
5059
    .cancelled_filenames=&cancelled_filenames,
5060
    .current_time=(mono_microsecs[]){0},
5061
    .mandos_client_exited=(bool[]){false},
5062
    .password_is_read=(bool[]){false},
5063
  };
5064
  run_task_with_stderr_to_dev_null(task, queue);
5065
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5066
5067
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5068
}
5069
5070
static
5071
void test_open_and_parse_question_badpid(__attribute__((unused))
5072
					 test_fixture *fixture,
5073
					 __attribute__((unused))
5074
					 gconstpointer user_data){
5075
  __attribute__((cleanup(cleanup_close)))
5076
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5077
  g_assert_cmpint(epoll_fd, >=, 0);
5078
  __attribute__((cleanup(string_set_clear)))
5079
    string_set cancelled_filenames = {};
5080
  __attribute__((cleanup(cleanup_queue)))
5081
    task_queue *queue = create_queue();
5082
  g_assert_nonnull(queue);
5083
5084
  __attribute__((cleanup(cleanup_string)))
5085
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5086
  g_assert_nonnull(tempfilename);
5087
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5088
  g_assert_cmpint(questionfile, >, 0);
5089
  FILE *qf = fdopen(questionfile, "w");
5090
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=\n"),
5091
		  >, 0);
5092
  g_assert_cmpint(fclose(qf), ==, 0);
5093
5094
  char *const filename = strdup(tempfilename);
5095
  g_assert_nonnull(filename);
5096
  task_context task = {
5097
    .func=open_and_parse_question,
5098
    .question_filename=filename,
5099
    .epoll_fd=epoll_fd,
5100
    .password=(buffer[]){{}},
5101
    .filename=filename,
5102
    .cancelled_filenames=&cancelled_filenames,
5103
    .current_time=(mono_microsecs[]){0},
5104
    .mandos_client_exited=(bool[]){false},
5105
    .password_is_read=(bool[]){false},
5106
  };
5107
  run_task_with_stderr_to_dev_null(task, queue);
5108
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5109
5110
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5111
}
5112
5113
static void
5114
test_open_and_parse_question_noexist_pid(__attribute__((unused))
5115
					 test_fixture *fixture,
5116
					 __attribute__((unused))
5117
					 gconstpointer user_data){
5118
  __attribute__((cleanup(cleanup_close)))
5119
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5120
  g_assert_cmpint(epoll_fd, >=, 0);
5121
  __attribute__((cleanup(string_set_clear)))
5122
    string_set cancelled_filenames = {};
5123
  buffer password = {};
5124
  bool mandos_client_exited = false;
5125
  bool password_is_read = false;
5126
  __attribute__((cleanup(cleanup_queue)))
5127
    task_queue *queue = create_queue();
5128
  g_assert_nonnull(queue);
5129
  const mono_microsecs current_time = 0;
5130
5131
  /* Find value of sysctl kernel.pid_max */
5132
  uintmax_t pid_max = 0;
5133
  FILE *sysctl_pid_max = fopen("/proc/sys/kernel/pid_max", "r");
5134
  g_assert_nonnull(sysctl_pid_max);
5135
  g_assert_cmpint(fscanf(sysctl_pid_max, "%" PRIuMAX, &pid_max),
5136
		  ==, 1);
5137
  g_assert_cmpint(fclose(sysctl_pid_max), ==, 0);
5138
5139
  pid_t nonexisting_pid = ((pid_t)pid_max)+1;
5140
  g_assert_true(nonexisting_pid > 0); /* Overflow check */
5141
5142
  __attribute__((cleanup(cleanup_string)))
5143
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5144
  g_assert_nonnull(tempfilename);
5145
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5146
  g_assert_cmpint(questionfile, >, 0);
5147
  FILE *qf = fdopen(questionfile, "w");
5148
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%"
5149
			  PRIuMAX"\n", (uintmax_t)nonexisting_pid),
5150
		  >, 0);
5151
  g_assert_cmpint(fclose(qf), ==, 0);
5152
5153
  char *const question_filename = strdup(tempfilename);
5154
  g_assert_nonnull(question_filename);
5155
  task_context task = {
5156
    .func=open_and_parse_question,
5157
    .question_filename=question_filename,
5158
    .epoll_fd=epoll_fd,
5159
    .password=&password,
5160
    .filename=question_filename,
5161
    .cancelled_filenames=&cancelled_filenames,
5162
    .current_time=&current_time,
5163
    .mandos_client_exited=&mandos_client_exited,
5164
    .password_is_read=&password_is_read,
5165
  };
5166
  run_task_with_stderr_to_dev_null(task, queue);
5167
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5168
5169
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5170
}
5171
5172
static void
5173
test_open_and_parse_question_no_notafter(__attribute__((unused))
5174
					 test_fixture *fixture,
5175
					 __attribute__((unused))
5176
					 gconstpointer user_data){
5177
  __attribute__((cleanup(cleanup_close)))
5178
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5179
  g_assert_cmpint(epoll_fd, >=, 0);
5180
  __attribute__((cleanup(string_set_clear)))
5181
    string_set cancelled_filenames = {};
5182
  buffer password = {};
5183
  bool mandos_client_exited = false;
5184
  bool password_is_read = false;
5185
  __attribute__((cleanup(cleanup_queue)))
5186
    task_queue *queue = create_queue();
5187
  g_assert_nonnull(queue);
5188
  const mono_microsecs current_time = 0;
5189
5190
  __attribute__((cleanup(cleanup_string)))
5191
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5192
  g_assert_nonnull(tempfilename);
5193
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5194
  g_assert_cmpint(questionfile, >, 0);
5195
  FILE *qf = fdopen(questionfile, "w");
5196
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%"
5197
			  PRIuMAX "\n", (uintmax_t)getpid()), >, 0);
5198
  g_assert_cmpint(fclose(qf), ==, 0);
5199
5200
  char *const filename = strdup(tempfilename);
5201
  g_assert_nonnull(filename);
5202
  task_context task = {
5203
    .func=open_and_parse_question,
5204
    .question_filename=filename,
5205
    .epoll_fd=epoll_fd,
5206
    .password=&password,
5207
    .filename=filename,
5208
    .cancelled_filenames=&cancelled_filenames,
5209
    .current_time=&current_time,
5210
    .mandos_client_exited=&mandos_client_exited,
5211
    .password_is_read=&password_is_read,
5212
  };
5213
  task.func(task, queue);
5214
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5215
5216
  __attribute__((cleanup(cleanup_string)))
5217
    char *socket_filename = strdup("/nonexistent");
5218
  g_assert_nonnull(socket_filename);
5219
  g_assert_nonnull(find_matching_task(queue, (task_context){
5220
	.func=connect_question_socket,
5221
	.question_filename=tempfilename,
5222
	.filename=socket_filename,
5223
	.epoll_fd=epoll_fd,
5224
	.password=&password,
5225
	.current_time=&current_time,
5226
	.mandos_client_exited=&mandos_client_exited,
5227
	.password_is_read=&password_is_read,
5228
      }));
5229
5230
  g_assert_true(queue->next_run != 0);
5231
5232
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5233
}
5234
5235
static void
5236
test_open_and_parse_question_bad_notafter(__attribute__((unused))
5237
					  test_fixture *fixture,
5238
					  __attribute__((unused))
5239
					  gconstpointer user_data){
5240
  __attribute__((cleanup(cleanup_close)))
5241
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5242
  g_assert_cmpint(epoll_fd, >=, 0);
5243
  __attribute__((cleanup(string_set_clear)))
5244
    string_set cancelled_filenames = {};
5245
  buffer password = {};
5246
  bool mandos_client_exited = false;
5247
  bool password_is_read = false;
5248
  __attribute__((cleanup(cleanup_queue)))
5249
    task_queue *queue = create_queue();
5250
  g_assert_nonnull(queue);
5251
  const mono_microsecs current_time = 0;
5252
5253
  __attribute__((cleanup(cleanup_string)))
5254
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5255
  g_assert_nonnull(tempfilename);
5256
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5257
  g_assert_cmpint(questionfile, >, 0);
5258
  FILE *qf = fdopen(questionfile, "w");
5259
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%"
5260
			  PRIuMAX "\nNotAfter=\n",
5261
			  (uintmax_t)getpid()), >, 0);
5262
  g_assert_cmpint(fclose(qf), ==, 0);
5263
5264
  char *const filename = strdup(tempfilename);
5265
  g_assert_nonnull(filename);
5266
  task_context task = {
5267
    .func=open_and_parse_question,
5268
    .question_filename=filename,
5269
    .epoll_fd=epoll_fd,
5270
    .password=&password,
5271
    .filename=filename,
5272
    .cancelled_filenames=&cancelled_filenames,
5273
    .current_time=&current_time,
5274
    .mandos_client_exited=&mandos_client_exited,
5275
    .password_is_read=&password_is_read,
5276
  };
5277
  run_task_with_stderr_to_dev_null(task, queue);
5278
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5279
5280
  __attribute__((cleanup(cleanup_string)))
5281
    char *socket_filename = strdup("/nonexistent");
5282
  g_assert_nonnull(find_matching_task(queue, (task_context){
5283
	.func=connect_question_socket,
5284
	.question_filename=tempfilename,
5285
	.filename=socket_filename,
5286
	.epoll_fd=epoll_fd,
5287
	.password=&password,
5288
	.current_time=&current_time,
5289
	.mandos_client_exited=&mandos_client_exited,
5290
	.password_is_read=&password_is_read,
5291
      }));
5292
  g_assert_true(queue->next_run != 0);
5293
5294
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5295
}
5296
5297
static
5298
void assert_open_and_parse_question_with_notafter(const mono_microsecs
5299
						  current_time,
5300
						  const mono_microsecs
5301
						  notafter,
5302
						  const mono_microsecs
5303
						  next_queue_run){
5304
  __attribute__((cleanup(cleanup_close)))
5305
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5306
  g_assert_cmpint(epoll_fd, >=, 0);
5307
  __attribute__((cleanup(string_set_clear)))
5308
    string_set cancelled_filenames = {};
5309
  buffer password = {};
5310
  bool mandos_client_exited = false;
5311
  bool password_is_read = false;
5312
  __attribute__((cleanup(cleanup_queue)))
5313
    task_queue *queue = create_queue();
5314
  g_assert_nonnull(queue);
5315
  queue->next_run = next_queue_run;
5316
5317
  __attribute__((cleanup(cleanup_string)))
5318
    char *tempfilename = strdup("/tmp/mandosXXXXXX");
5319
  g_assert_nonnull(tempfilename);
5320
  int questionfile = mkostemp(tempfilename, O_CLOEXEC);
5321
  g_assert_cmpint(questionfile, >, 0);
5322
  FILE *qf = fdopen(questionfile, "w");
5323
  g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%"
5324
			  PRIuMAX "\nNotAfter=%" PRIuMAX "\n",
5325
			  (uintmax_t)getpid(), notafter), >, 0);
5326
  g_assert_cmpint(fclose(qf), ==, 0);
5327
5328
  char *const filename = strdup(tempfilename);
5329
  g_assert_nonnull(filename);
5330
  task_context task = {
5331
    .func=open_and_parse_question,
5332
    .question_filename=filename,
5333
    .epoll_fd=epoll_fd,
5334
    .password=&password,
5335
    .filename=filename,
5336
    .cancelled_filenames=&cancelled_filenames,
5337
    .current_time=&current_time,
5338
    .mandos_client_exited=&mandos_client_exited,
5339
    .password_is_read=&password_is_read,
5340
  };
5341
  task.func(task, queue);
5342
5343
  if(queue->length >= 1){
5344
    __attribute__((cleanup(cleanup_string)))
5345
      char *socket_filename = strdup("/nonexistent");
5346
    g_assert_nonnull(find_matching_task(queue, (task_context){
5347
	  .func=connect_question_socket,
5348
	  .filename=socket_filename,
5349
	  .epoll_fd=epoll_fd,
5350
	  .password=&password,
5351
	  .current_time=&current_time,
5352
	  .cancelled_filenames=&cancelled_filenames,
5353
	  .mandos_client_exited=&mandos_client_exited,
5354
	  .password_is_read=&password_is_read,
5355
	}));
5356
    g_assert_true(queue->next_run != 0);
5357
  }
5358
5359
  if(notafter == 0){
5360
    g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5361
  } else if(current_time >= notafter) {
5362
    g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5363
  } else {
5364
    g_assert_nonnull(find_matching_task(queue, (task_context){
5365
	  .func=cancel_old_question,
5366
	  .question_filename=tempfilename,
5367
	  .filename=tempfilename,
5368
	  .notafter=notafter,
5369
	  .cancelled_filenames=&cancelled_filenames,
5370
	  .current_time=&current_time,
5371
	}));
5372
  }
5373
  g_assert_true(queue->next_run == 1);
5374
5375
  g_assert_cmpint(unlink(tempfilename), ==, 0);
5376
}
5377
5378
static void
5379
test_open_and_parse_question_notafter_0(__attribute__((unused))
5380
					test_fixture *fixture,
5381
					__attribute__((unused))
5382
					gconstpointer user_data){
5383
  /* current_time, notafter, next_queue_run */
5384
  assert_open_and_parse_question_with_notafter(0, 0, 0);
5385
}
5386
5387
static void
5388
test_open_and_parse_question_notafter_1(__attribute__((unused))
5389
					test_fixture *fixture,
5390
					__attribute__((unused))
5391
					gconstpointer user_data){
5392
  /* current_time, notafter, next_queue_run */
5393
  assert_open_and_parse_question_with_notafter(0, 1, 0);
5394
}
5395
5396
static void
5397
test_open_and_parse_question_notafter_1_1(__attribute__((unused))
5398
					  test_fixture *fixture,
5399
					  __attribute__((unused))
5400
					  gconstpointer user_data){
5401
  /* current_time, notafter, next_queue_run */
5402
  assert_open_and_parse_question_with_notafter(0, 1, 1);
5403
}
5404
5405
static void
5406
test_open_and_parse_question_notafter_1_2(__attribute__((unused))
5407
					  test_fixture *fixture,
5408
					  __attribute__((unused))
5409
					  gconstpointer user_data){
5410
  /* current_time, notafter, next_queue_run */
5411
  assert_open_and_parse_question_with_notafter(0, 1, 2);
5412
}
5413
5414
static void
5415
test_open_and_parse_question_equal_notafter(__attribute__((unused))
5416
					    test_fixture *fixture,
5417
					    __attribute__((unused))
5418
					    gconstpointer user_data){
5419
  /* current_time, notafter, next_queue_run */
5420
  assert_open_and_parse_question_with_notafter(1, 1, 0);
5421
}
5422
5423
static void
5424
test_open_and_parse_question_late_notafter(__attribute__((unused))
5425
					   test_fixture *fixture,
5426
					   __attribute__((unused))
5427
					   gconstpointer user_data){
5428
  /* current_time, notafter, next_queue_run */
5429
  assert_open_and_parse_question_with_notafter(2, 1, 0);
5430
}
5431
5432
static void assert_cancel_old_question_param(const mono_microsecs
5433
					     next_queue_run,
5434
					     const mono_microsecs
5435
					     notafter,
5436
					     const mono_microsecs
5437
					     current_time,
5438
					     const mono_microsecs
5439
					     next_set_to){
5440
  __attribute__((cleanup(string_set_clear)))
5441
    string_set cancelled_filenames = {};
5442
  __attribute__((cleanup(cleanup_queue)))
5443
    task_queue *queue = create_queue();
5444
  g_assert_nonnull(queue);
5445
  queue->next_run = next_queue_run;
5446
5447
  char *const question_filename = strdup("/nonexistent");
5448
  g_assert_nonnull(question_filename);
5449
  task_context task = {
5450
    .func=cancel_old_question,
5451
    .question_filename=question_filename,
5452
    .filename=question_filename,
5453
    .notafter=notafter,
5454
    .cancelled_filenames=&cancelled_filenames,
5455
    .current_time=&current_time,
5456
  };
5457
  task.func(task, queue);
5458
5459
  if(current_time >= notafter){
5460
    g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5461
    g_assert_true(string_set_contains(cancelled_filenames,
5462
				      "/nonexistent"));
5463
  } else {
5464
    g_assert_nonnull(find_matching_task(queue, (task_context){
5465
	  .func=cancel_old_question,
5466
	  .question_filename=question_filename,
5467
	  .filename=question_filename,
5468
	  .notafter=notafter,
5469
	  .cancelled_filenames=&cancelled_filenames,
5470
	  .current_time=&current_time,
5471
	}));
5472
5473
    g_assert_false(string_set_contains(cancelled_filenames,
5474
				       question_filename));
5475
  }
5476
  g_assert_cmpuint((unsigned int)queue->next_run, ==,
5477
		   (unsigned int)next_set_to);
5478
}
5479
5480
static void test_cancel_old_question_0_1_2(__attribute__((unused))
5481
					   test_fixture *fixture,
5482
					   __attribute__((unused))
5483
					   gconstpointer user_data){
5484
  /* next_queue_run unset,
5485
     cancellation should happen because time has come,
5486
     next_queue_run should be unchanged */
5487
  /* next_queue_run, notafter, current_time, next_set_to */
5488
  assert_cancel_old_question_param(0, 1, 2, 0);
5489
}
5490
5491
static void test_cancel_old_question_0_2_1(__attribute__((unused))
5492
					   test_fixture *fixture,
5493
					   __attribute__((unused))
5494
					   gconstpointer user_data){
5495
  /* If next_queue_run is 0, meaning unset, and notafter is 2,
5496
     and current_time is not yet notafter or greater,
5497
     update value of next_queue_run to value of notafter */
5498
  /* next_queue_run, notafter, current_time, next_set_to */
5499
  assert_cancel_old_question_param(0, 2, 1, 2);
5500
}
5501
5502
static void test_cancel_old_question_1_2_3(__attribute__((unused))
5503
					   test_fixture *fixture,
5504
					   __attribute__((unused))
5505
					   gconstpointer user_data){
5506
  /* next_queue_run 1,
5507
     cancellation should happen because time has come,
5508
     next_queue_run should be unchanged */
5509
  /* next_queue_run, notafter, current_time, next_set_to */
5510
  assert_cancel_old_question_param(1, 2, 3, 1);
5511
}
5512
5513
static void test_cancel_old_question_1_3_2(__attribute__((unused))
5514
					   test_fixture *fixture,
5515
					   __attribute__((unused))
5516
					   gconstpointer user_data){
5517
  /* If next_queue_run is set,
5518
     and current_time is not yet notafter or greater,
5519
     and notafter is larger than next_queue_run
5520
     next_queue_run should be unchanged */
5521
  /* next_queue_run, notafter, current_time, next_set_to */
5522
  assert_cancel_old_question_param(1, 3, 2, 1);
5523
}
5524
5525
static void test_cancel_old_question_2_1_3(__attribute__((unused))
5526
					   test_fixture *fixture,
5527
					   __attribute__((unused))
5528
					   gconstpointer user_data){
5529
  /* next_queue_run 2,
5530
     cancellation should happen because time has come,
5531
     next_queue_run should be unchanged */
5532
  /* next_queue_run, notafter, current_time, next_set_to */
5533
  assert_cancel_old_question_param(2, 1, 3, 2);
5534
}
5535
5536
static void test_cancel_old_question_2_3_1(__attribute__((unused))
5537
					   test_fixture *fixture,
5538
					   __attribute__((unused))
5539
					   gconstpointer user_data){
5540
  /* If next_queue_run is set,
5541
     and current_time is not yet notafter or greater,
5542
     and notafter is larger than next_queue_run
5543
     next_queue_run should be unchanged */
5544
  /* next_queue_run, notafter, current_time, next_set_to */
5545
  assert_cancel_old_question_param(2, 3, 1, 2);
5546
}
5547
5548
static void test_cancel_old_question_3_1_2(__attribute__((unused))
5549
					   test_fixture *fixture,
5550
					   __attribute__((unused))
5551
					   gconstpointer user_data){
5552
  /* next_queue_run 3,
5553
     cancellation should happen because time has come,
5554
     next_queue_run should be unchanged */
5555
  /* next_queue_run, notafter, current_time, next_set_to */
5556
  assert_cancel_old_question_param(3, 1, 2, 3);
5557
}
5558
5559
static void test_cancel_old_question_3_2_1(__attribute__((unused))
5560
					   test_fixture *fixture,
5561
					   __attribute__((unused))
5562
					   gconstpointer user_data){
5563
  /* If next_queue_run is set,
5564
     and current_time is not yet notafter or greater,
5565
     and notafter is smaller than next_queue_run
5566
     update value of next_queue_run to value of notafter */
5567
  /* next_queue_run, notafter, current_time, next_set_to */
5568
  assert_cancel_old_question_param(3, 2, 1, 2);
5569
}
5570
5571
static void
5572
test_connect_question_socket_name_too_long(__attribute__((unused))
5573
					   test_fixture *fixture,
5574
					   __attribute__((unused))
5575
					   gconstpointer user_data){
5576
  __attribute__((cleanup(cleanup_close)))
5577
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5578
  g_assert_cmpint(epoll_fd, >=, 0);
5579
  const char question_filename[] = "/nonexistent/question";
5580
  __attribute__((cleanup(string_set_clear)))
5581
    string_set cancelled_filenames = {};
5582
  __attribute__((cleanup(cleanup_queue)))
5583
    task_queue *queue = create_queue();
5584
  g_assert_nonnull(queue);
5585
  __attribute__((cleanup(cleanup_string)))
5586
    char *tempdir = make_temporary_directory();
5587
  g_assert_nonnull(tempdir);
5588
  struct sockaddr_un unix_socket = { .sun_family=AF_LOCAL };
5589
  char socket_name[sizeof(unix_socket.sun_path)];
5590
  memset(socket_name, 'x', sizeof(socket_name));
5591
  socket_name[sizeof(socket_name)-1] = '\0';
5592
  char *filename = NULL;
5593
  g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name),
5594
		  >, 0);
5595
  g_assert_nonnull(filename);
5596
5597
  task_context task = {
5598
    .func=connect_question_socket,
5599
    .question_filename=strdup(question_filename),
5600
    .epoll_fd=epoll_fd,
5601
    .password=(buffer[]){{}},
5602
    .filename=filename,
5603
    .cancelled_filenames=&cancelled_filenames,
5604
    .mandos_client_exited=(bool[]){false},
5605
    .password_is_read=(bool[]){false},
5606
    .current_time=(mono_microsecs[]){0},
5607
  };
5608
  g_assert_nonnull(task.question_filename);
5609
  run_task_with_stderr_to_dev_null(task, queue);
5610
5611
  g_assert_true(string_set_contains(cancelled_filenames,
5612
				    question_filename));
5613
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
5614
  g_assert_true(queue->next_run == 0);
5615
5616
  g_assert_cmpint(rmdir(tempdir), ==, 0);
5617
}
5618
5619
static
5620
void test_connect_question_socket_connect_fail(__attribute__((unused))
5621
					       test_fixture *fixture,
5622
					       __attribute__((unused))
5623
					       gconstpointer
5624
					       user_data){
5625
  __attribute__((cleanup(cleanup_close)))
5626
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5627
  g_assert_cmpint(epoll_fd, >=, 0);
5628
  const char question_filename[] = "/nonexistent/question";
5629
  __attribute__((cleanup(string_set_clear)))
5630
    string_set cancelled_filenames = {};
5631
  const mono_microsecs current_time = 3;
5632
  __attribute__((cleanup(cleanup_queue)))
5633
    task_queue *queue = create_queue();
5634
  g_assert_nonnull(queue);
5635
  __attribute__((cleanup(cleanup_string)))
5636
    char *tempdir = make_temporary_directory();
5637
  g_assert_nonnull(tempdir);
5638
  char socket_name[] = "nonexistent";
5639
  char *filename = NULL;
5640
  g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name),
5641
		  >, 0);
5642
  g_assert_nonnull(filename);
5643
5644
  task_context task = {
5645
    .func=connect_question_socket,
5646
    .question_filename=strdup(question_filename),
5647
    .epoll_fd=epoll_fd,
5648
    .password=(buffer[]){{}},
5649
    .filename=filename,
5650
    .cancelled_filenames=&cancelled_filenames,
5651
    .mandos_client_exited=(bool[]){false},
5652
    .password_is_read=(bool[]){false},
5653
    .current_time=&current_time,
5654
  };
5655
  g_assert_nonnull(task.question_filename);
5656
  run_task_with_stderr_to_dev_null(task, queue);
5657
5658
  g_assert_nonnull(find_matching_task(queue, task));
5659
5660
  g_assert_false(string_set_contains(cancelled_filenames,
5661
				     question_filename));
5662
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5663
  g_assert_true(queue->next_run == 1000000 + current_time);
5664
5665
  g_assert_cmpint(rmdir(tempdir), ==, 0);
5666
}
5667
5668
static
5669
void test_connect_question_socket_bad_epoll(__attribute__((unused))
5670
					    test_fixture *fixture,
5671
					    __attribute__((unused))
5672
					    gconstpointer user_data){
5673
  __attribute__((cleanup(cleanup_close)))
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
5674
    const int epoll_fd = open("/dev/null",
5675
			      O_WRONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
5676
  __attribute__((cleanup(cleanup_string)))
5677
    char *const question_filename = strdup("/nonexistent/question");
5678
  g_assert_nonnull(question_filename);
5679
  __attribute__((cleanup(string_set_clear)))
5680
    string_set cancelled_filenames = {};
5681
  const mono_microsecs current_time = 5;
5682
  __attribute__((cleanup(cleanup_queue)))
5683
    task_queue *queue = create_queue();
5684
  g_assert_nonnull(queue);
5685
  __attribute__((cleanup(cleanup_string)))
5686
    char *tempdir = make_temporary_directory();
5687
  g_assert_nonnull(tempdir);
5688
  __attribute__((cleanup(cleanup_close)))
5689
    const int sock_fd = socket(PF_LOCAL, SOCK_DGRAM
5690
			       | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
5691
  g_assert_cmpint(sock_fd, >=, 0);
5692
  struct sockaddr_un sock_name = { .sun_family=AF_LOCAL };
5693
  const char socket_name[] = "socket_name";
5694
  __attribute__((cleanup(cleanup_string)))
5695
    char *filename = NULL;
5696
  g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name),
5697
		  >, 0);
5698
  g_assert_nonnull(filename);
5699
  g_assert_cmpint((int)strlen(filename), <,
5700
		  (int)sizeof(sock_name.sun_path));
5701
  strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path));
5702
  sock_name.sun_path[sizeof(sock_name.sun_path)-1] = '\0';
5703
  g_assert_cmpint((int)bind(sock_fd, (struct sockaddr *)&sock_name,
5704
			    (socklen_t)SUN_LEN(&sock_name)), >=, 0);
5705
  task_context task = {
5706
    .func=connect_question_socket,
5707
    .question_filename=strdup(question_filename),
5708
    .epoll_fd=epoll_fd,
5709
    .password=(buffer[]){{}},
5710
    .filename=strdup(filename),
5711
    .cancelled_filenames=&cancelled_filenames,
5712
    .mandos_client_exited=(bool[]){false},
5713
    .password_is_read=(bool[]){false},
5714
    .current_time=&current_time,
5715
  };
5716
  g_assert_nonnull(task.question_filename);
5717
  run_task_with_stderr_to_dev_null(task, queue);
5718
5719
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5720
  const task_context *const added_task
5721
    = find_matching_task(queue, task);
5722
  g_assert_nonnull(added_task);
5723
  g_assert_true(queue->next_run == 1000000 + current_time);
5724
5725
  g_assert_cmpint(unlink(filename), ==, 0);
5726
  g_assert_cmpint(rmdir(tempdir), ==, 0);
5727
}
5728
5729
static
5730
void test_connect_question_socket_usable(__attribute__((unused))
5731
					 test_fixture *fixture,
5732
					 __attribute__((unused))
5733
					 gconstpointer user_data){
5734
  __attribute__((cleanup(cleanup_close)))
5735
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5736
  g_assert_cmpint(epoll_fd, >=, 0);
5737
  __attribute__((cleanup(cleanup_string)))
5738
    char *const question_filename = strdup("/nonexistent/question");
5739
  g_assert_nonnull(question_filename);
5740
  __attribute__((cleanup(string_set_clear)))
5741
    string_set cancelled_filenames = {};
5742
  buffer password = {};
5743
  bool mandos_client_exited = false;
5744
  bool password_is_read = false;
5745
  const mono_microsecs current_time = 0;
5746
  __attribute__((cleanup(cleanup_queue)))
5747
    task_queue *queue = create_queue();
5748
  g_assert_nonnull(queue);
5749
  __attribute__((cleanup(cleanup_string)))
5750
    char *tempdir = make_temporary_directory();
5751
  g_assert_nonnull(tempdir);
5752
  __attribute__((cleanup(cleanup_close)))
5753
    const int sock_fd = socket(PF_LOCAL, SOCK_DGRAM
5754
			       | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
5755
  g_assert_cmpint(sock_fd, >=, 0);
5756
  struct sockaddr_un sock_name = { .sun_family=AF_LOCAL };
5757
  const char socket_name[] = "socket_name";
5758
  __attribute__((cleanup(cleanup_string)))
5759
    char *filename = NULL;
5760
  g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name),
5761
		  >, 0);
5762
  g_assert_nonnull(filename);
5763
  g_assert_cmpint((int)strlen(filename), <,
5764
		  (int)sizeof(sock_name.sun_path));
5765
  strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path));
5766
  sock_name.sun_path[sizeof(sock_name.sun_path)-1] = '\0';
5767
  g_assert_cmpint((int)bind(sock_fd, (struct sockaddr *)&sock_name,
5768
			    (socklen_t)SUN_LEN(&sock_name)), >=, 0);
5769
  task_context task = {
5770
    .func=connect_question_socket,
5771
    .question_filename=strdup(question_filename),
5772
    .epoll_fd=epoll_fd,
5773
    .password=&password,
5774
    .filename=strdup(filename),
5775
    .cancelled_filenames=&cancelled_filenames,
5776
    .mandos_client_exited=&mandos_client_exited,
5777
    .password_is_read=&password_is_read,
5778
    .current_time=&current_time,
5779
  };
5780
  g_assert_nonnull(task.question_filename);
5781
  task.func(task, queue);
5782
5783
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5784
  const task_context *const added_task
5785
    = find_matching_task(queue, (task_context){
5786
	.func=send_password_to_socket,
5787
	.question_filename=question_filename,
5788
	.filename=filename,
5789
	.epoll_fd=epoll_fd,
5790
	.password=&password,
5791
	.cancelled_filenames=&cancelled_filenames,
5792
	.mandos_client_exited=&mandos_client_exited,
5793
	.password_is_read=&password_is_read,
5794
	.current_time=&current_time,
5795
      });
5796
  g_assert_nonnull(added_task);
5797
  g_assert_cmpint(added_task->fd, >, 0);
5798
5799
  g_assert_true(epoll_set_contains(epoll_fd, added_task->fd,
5800
				   EPOLLOUT));
5801
5802
  const int fd = added_task->fd;
5803
  g_assert_cmpint(fd, >, 0);
5804
  g_assert_true(fd_has_cloexec_and_nonblock(fd));
5805
5806
  /* write to fd */
5807
  char write_data[PIPE_BUF];
5808
  {
5809
    /* Construct test password buffer */
5810
    /* Start with + since that is what the real procotol uses */
5811
    write_data[0] = '+';
5812
    /* Set a special character at string end just to mark the end */
5813
    write_data[sizeof(write_data)-2] = 'y';
5814
    /* Set NUL at buffer end, as suggested by the protocol */
5815
    write_data[sizeof(write_data)-1] = '\0';
5816
    /* Fill rest of password with 'x' */
5817
    memset(write_data+1, 'x', sizeof(write_data)-3);
5818
    g_assert_cmpint((int)send(fd, write_data, sizeof(write_data),
5819
			      MSG_NOSIGNAL), ==, sizeof(write_data));
5820
  }
5821
5822
  /* read from sock_fd */
5823
  char read_data[sizeof(write_data)];
5824
  g_assert_cmpint((int)read(sock_fd, read_data, sizeof(read_data)),
5825
		  ==, sizeof(read_data));
5826
5827
  g_assert_true(memcmp(write_data, read_data, sizeof(write_data))
5828
		== 0);
5829
5830
  /* writing to sock_fd should fail */
5831
  g_assert_cmpint(send(sock_fd, write_data, sizeof(write_data),
5832
		       MSG_NOSIGNAL), <, 0);
5833
5834
  /* reading from fd should fail */
5835
  g_assert_cmpint((int)recv(fd, read_data, sizeof(read_data),
5836
			    MSG_NOSIGNAL), <, 0);
5837
5838
  g_assert_cmpint(unlink(filename), ==, 0);
5839
  g_assert_cmpint(rmdir(tempdir), ==, 0);
5840
}
5841
5842
static void
5843
test_send_password_to_socket_client_not_exited(__attribute__((unused))
5844
					       test_fixture *fixture,
5845
					       __attribute__((unused))
5846
					       gconstpointer
5847
					       user_data){
5848
  __attribute__((cleanup(cleanup_close)))
5849
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5850
  g_assert_cmpint(epoll_fd, >=, 0);
5851
  __attribute__((cleanup(cleanup_string)))
5852
    char *const question_filename = strdup("/nonexistent/question");
5853
  g_assert_nonnull(question_filename);
5854
  __attribute__((cleanup(cleanup_string)))
5855
    char *const filename = strdup("/nonexistent/socket");
5856
  g_assert_nonnull(filename);
5857
  __attribute__((cleanup(string_set_clear)))
5858
    string_set cancelled_filenames = {};
5859
  buffer password = {};
5860
  bool password_is_read = true;
5861
  __attribute__((cleanup(cleanup_queue)))
5862
    task_queue *queue = create_queue();
5863
  g_assert_nonnull(queue);
5864
  int socketfds[2];
5865
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5866
			     | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5867
			     socketfds), ==, 0);
5868
  __attribute__((cleanup(cleanup_close)))
5869
    const int read_socket = socketfds[0];
5870
  const int write_socket = socketfds[1];
5871
  task_context task = {
5872
    .func=send_password_to_socket,
5873
    .question_filename=strdup(question_filename),
5874
    .filename=strdup(filename),
5875
    .epoll_fd=epoll_fd,
5876
    .fd=write_socket,
5877
    .password=&password,
5878
    .cancelled_filenames=&cancelled_filenames,
5879
    .mandos_client_exited=(bool[]){false},
5880
    .password_is_read=&password_is_read,
5881
    .current_time=(mono_microsecs[]){0},
5882
  };
5883
  g_assert_nonnull(task.question_filename);
5884
5885
  task.func(task, queue);
5886
5887
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5888
5889
  const task_context *const added_task
5890
    = find_matching_task(queue, task);
5891
  g_assert_nonnull(added_task);
5892
  g_assert_cmpuint((unsigned int)password.length, ==, 0);
5893
  g_assert_true(password_is_read);
5894
5895
  g_assert_cmpint(added_task->fd, >, 0);
5896
  g_assert_true(epoll_set_contains(epoll_fd, added_task->fd,
5897
				   EPOLLOUT));
5898
}
5899
5900
static void
5901
test_send_password_to_socket_password_not_read(__attribute__((unused))
5902
					       test_fixture *fixture,
5903
					       __attribute__((unused))
5904
					       gconstpointer
5905
					       user_data){
5906
  __attribute__((cleanup(cleanup_close)))
5907
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5908
  g_assert_cmpint(epoll_fd, >=, 0);
5909
  __attribute__((cleanup(cleanup_string)))
5910
    char *const question_filename = strdup("/nonexistent/question");
5911
  g_assert_nonnull(question_filename);
5912
  __attribute__((cleanup(cleanup_string)))
5913
    char *const filename = strdup("/nonexistent/socket");
5914
  __attribute__((cleanup(string_set_clear)))
5915
    string_set cancelled_filenames = {};
5916
  buffer password = {};
5917
  __attribute__((cleanup(cleanup_queue)))
5918
    task_queue *queue = create_queue();
5919
  g_assert_nonnull(queue);
5920
  int socketfds[2];
5921
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5922
			     | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5923
			     socketfds), ==, 0);
5924
  __attribute__((cleanup(cleanup_close)))
5925
    const int read_socket = socketfds[0];
5926
  const int write_socket = socketfds[1];
5927
  task_context task = {
5928
    .func=send_password_to_socket,
5929
    .question_filename=strdup(question_filename),
5930
    .filename=strdup(filename),
5931
    .epoll_fd=epoll_fd,
5932
    .fd=write_socket,
5933
    .password=&password,
5934
    .cancelled_filenames=&cancelled_filenames,
5935
    .mandos_client_exited=(bool[]){false},
5936
    .password_is_read=(bool[]){false},
5937
    .current_time=(mono_microsecs[]){0},
5938
  };
5939
  g_assert_nonnull(task.question_filename);
5940
5941
  task.func(task, queue);
5942
5943
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
5944
5945
  const task_context *const added_task = find_matching_task(queue,
5946
							    task);
5947
  g_assert_nonnull(added_task);
5948
  g_assert_cmpuint((unsigned int)password.length, ==, 0);
5949
  g_assert_true(queue->next_run == 0);
5950
5951
  g_assert_cmpint(added_task->fd, >, 0);
5952
  g_assert_true(epoll_set_contains(epoll_fd, added_task->fd,
5953
				   EPOLLOUT));
5954
}
5955
5956
static
5957
void test_send_password_to_socket_EMSGSIZE(__attribute__((unused))
5958
					   test_fixture *fixture,
5959
					   __attribute__((unused))
5960
					   gconstpointer user_data){
5961
  __attribute__((cleanup(cleanup_close)))
5962
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
5963
  g_assert_cmpint(epoll_fd, >=, 0);
5964
  const char question_filename[] = "/nonexistent/question";
5965
  char *const filename = strdup("/nonexistent/socket");
5966
  __attribute__((cleanup(string_set_clear)))
5967
    string_set cancelled_filenames = {};
5968
  const size_t oversized = 1024*1024; /* Limit seems to be 212960 */
5969
  __attribute__((cleanup(cleanup_buffer)))
5970
    buffer password = {
5971
    .data=malloc(oversized),
5972
    .length=oversized,
5973
    .allocated=oversized,
5974
  };
5975
  g_assert_nonnull(password.data);
5976
  if(mlock(password.data, password.allocated) != 0){
5977
    g_assert_true(errno == EPERM or errno == ENOMEM);
5978
  }
5979
  /* Construct test password buffer */
5980
  /* Start with + since that is what the real procotol uses */
5981
  password.data[0] = '+';
5982
  /* Set a special character at string end just to mark the end */
5983
  password.data[oversized-3] = 'y';
5984
  /* Set NUL at buffer end, as suggested by the protocol */
5985
  password.data[oversized-2] = '\0';
5986
  /* Fill rest of password with 'x' */
5987
  memset(password.data+1, 'x', oversized-3);
5988
5989
  __attribute__((cleanup(cleanup_queue)))
5990
    task_queue *queue = create_queue();
5991
  g_assert_nonnull(queue);
5992
  int socketfds[2];
5993
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5994
			     | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5995
			     socketfds), ==, 0);
5996
  __attribute__((cleanup(cleanup_close)))
5997
    const int read_socket = socketfds[0];
5998
  __attribute__((cleanup(cleanup_close)))
5999
    const int write_socket = socketfds[1];
6000
  task_context task = {
6001
    .func=send_password_to_socket,
6002
    .question_filename=strdup(question_filename),
6003
    .filename=filename,
6004
    .epoll_fd=epoll_fd,
6005
    .fd=write_socket,
6006
    .password=&password,
6007
    .cancelled_filenames=&cancelled_filenames,
6008
    .mandos_client_exited=(bool[]){true},
6009
    .password_is_read=(bool[]){true},
6010
    .current_time=(mono_microsecs[]){0},
6011
  };
6012
  g_assert_nonnull(task.question_filename);
6013
6014
  run_task_with_stderr_to_dev_null(task, queue);
6015
6016
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
6017
  g_assert_true(string_set_contains(cancelled_filenames,
6018
				    question_filename));
6019
}
6020
6021
static void test_send_password_to_socket_retry(__attribute__((unused))
6022
					       test_fixture *fixture,
6023
					       __attribute__((unused))
6024
					       gconstpointer
6025
					       user_data){
6026
  __attribute__((cleanup(cleanup_close)))
6027
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6028
  g_assert_cmpint(epoll_fd, >=, 0);
6029
  __attribute__((cleanup(cleanup_string)))
6030
    char *const question_filename = strdup("/nonexistent/question");
6031
  g_assert_nonnull(question_filename);
6032
  __attribute__((cleanup(cleanup_string)))
6033
    char *const filename = strdup("/nonexistent/socket");
6034
  g_assert_nonnull(filename);
6035
  __attribute__((cleanup(string_set_clear)))
6036
    string_set cancelled_filenames = {};
6037
  __attribute__((cleanup(cleanup_buffer)))
6038
    buffer password = {};
6039
6040
  __attribute__((cleanup(cleanup_queue)))
6041
    task_queue *queue = create_queue();
6042
  g_assert_nonnull(queue);
6043
  int socketfds[2];
6044
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6045
			     | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6046
			     socketfds), ==, 0);
6047
  __attribute__((cleanup(cleanup_close)))
6048
    const int read_socket = socketfds[0];
6049
  const int write_socket = socketfds[1];
6050
  /* Close the server side socket to force ECONNRESET on client */
6051
  g_assert_cmpint(close(read_socket), ==, 0);
6052
  task_context task = {
6053
    .func=send_password_to_socket,
6054
    .question_filename=strdup(question_filename),
6055
    .filename=strdup(filename),
6056
    .epoll_fd=epoll_fd,
6057
    .fd=write_socket,
6058
    .password=&password,
6059
    .cancelled_filenames=&cancelled_filenames,
6060
    .mandos_client_exited=(bool[]){true},
6061
    .password_is_read=(bool[]){true},
6062
    .current_time=(mono_microsecs[]){0},
6063
  };
6064
  g_assert_nonnull(task.question_filename);
6065
6066
  task.func(task, queue);
6067
6068
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
6069
6070
  const task_context *const added_task = find_matching_task(queue,
6071
							    task);
6072
  g_assert_nonnull(added_task);
6073
  g_assert_cmpuint((unsigned int)password.length, ==, 0);
6074
6075
  g_assert_true(epoll_set_contains(epoll_fd, added_task->fd,
6076
				   EPOLLOUT));
6077
}
6078
6079
static
6080
void test_send_password_to_socket_bad_epoll(__attribute__((unused))
6081
					    test_fixture *fixture,
6082
					    __attribute__((unused))
6083
					    gconstpointer user_data){
6084
  __attribute__((cleanup(cleanup_close)))
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
6085
    const int epoll_fd = open("/dev/null",
6086
			      O_WRONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
6087
  __attribute__((cleanup(cleanup_string)))
6088
    char *const question_filename = strdup("/nonexistent/question");
6089
  g_assert_nonnull(question_filename);
6090
  __attribute__((cleanup(cleanup_string)))
6091
    char *const filename = strdup("/nonexistent/socket");
6092
  g_assert_nonnull(filename);
6093
  __attribute__((cleanup(string_set_clear)))
6094
    string_set cancelled_filenames = {};
6095
  __attribute__((cleanup(cleanup_buffer)))
6096
    buffer password = {};
6097
6098
  const mono_microsecs current_time = 11;
6099
  __attribute__((cleanup(cleanup_queue)))
6100
    task_queue *queue = create_queue();
6101
  g_assert_nonnull(queue);
6102
  int socketfds[2];
6103
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6104
			     | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6105
			     socketfds), ==, 0);
6106
  __attribute__((cleanup(cleanup_close)))
6107
    const int read_socket = socketfds[0];
6108
  const int write_socket = socketfds[1];
6109
  /* Close the server side socket to force ECONNRESET on client */
6110
  g_assert_cmpint(close(read_socket), ==, 0);
6111
  task_context task = {
6112
    .func=send_password_to_socket,
6113
    .question_filename=strdup(question_filename),
6114
    .filename=strdup(filename),
6115
    .epoll_fd=epoll_fd,
6116
    .fd=write_socket,
6117
    .password=&password,
6118
    .cancelled_filenames=&cancelled_filenames,
6119
    .mandos_client_exited=(bool[]){true},
6120
    .password_is_read=(bool[]){true},
6121
    .current_time=&current_time,
6122
  };
6123
  g_assert_nonnull(task.question_filename);
6124
6125
  run_task_with_stderr_to_dev_null(task, queue);
6126
6127
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
6128
6129
  const task_context *const added_task = find_matching_task(queue,
6130
							    task);
6131
  g_assert_nonnull(added_task);
6132
  g_assert_true(queue->next_run == current_time + 1000000);
6133
  g_assert_cmpuint((unsigned int)password.length, ==, 0);
6134
}
6135
6136
static void assert_send_password_to_socket_password(buffer password){
6137
  __attribute__((cleanup(cleanup_close)))
6138
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6139
  g_assert_cmpint(epoll_fd, >=, 0);
6140
  char *const question_filename = strdup("/nonexistent/question");
6141
  g_assert_nonnull(question_filename);
6142
  char *const filename = strdup("/nonexistent/socket");
6143
  g_assert_nonnull(filename);
6144
  __attribute__((cleanup(string_set_clear)))
6145
    string_set cancelled_filenames = {};
6146
6147
  __attribute__((cleanup(cleanup_queue)))
6148
    task_queue *queue = create_queue();
6149
  g_assert_nonnull(queue);
6150
  int socketfds[2];
6151
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6152
			     | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6153
			     socketfds), ==, 0);
6154
  __attribute__((cleanup(cleanup_close)))
6155
    const int read_socket = socketfds[0];
6156
  const int write_socket = socketfds[1];
6157
  task_context task = {
6158
    .func=send_password_to_socket,
6159
    .question_filename=question_filename,
6160
    .filename=filename,
6161
    .epoll_fd=epoll_fd,
6162
    .fd=write_socket,
6163
    .password=&password,
6164
    .cancelled_filenames=&cancelled_filenames,
6165
    .mandos_client_exited=(bool[]){true},
6166
    .password_is_read=(bool[]){true},
6167
    .current_time=(mono_microsecs[]){0},
6168
  };
6169
6170
  char *expected_written_data = malloc(password.length + 2);
6171
  g_assert_nonnull(expected_written_data);
6172
  expected_written_data[0] = '+';
6173
  expected_written_data[password.length + 1] = '\0';
6174
  if(password.length > 0){
6175
    g_assert_nonnull(password.data);
6176
    memcpy(expected_written_data + 1, password.data, password.length);
6177
  }
6178
6179
  task.func(task, queue);
6180
6181
  char buf[PIPE_BUF];
6182
  g_assert_cmpint((int)read(read_socket, buf, PIPE_BUF), ==,
6183
		  (int)(password.length + 2));
6184
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
6185
6186
  g_assert_true(memcmp(expected_written_data, buf,
6187
		       password.length + 2) == 0);
6188
6189
  g_assert_true(epoll_set_does_not_contain(epoll_fd, write_socket));
6190
6191
  free(expected_written_data);
6192
}
6193
6194
static void
6195
test_send_password_to_socket_null_password(__attribute__((unused))
6196
					   test_fixture *fixture,
6197
					   __attribute__((unused))
6198
					   gconstpointer user_data){
6199
  __attribute__((cleanup(cleanup_buffer)))
6200
    buffer password = {};
6201
  assert_send_password_to_socket_password(password);
6202
}
6203
6204
static void
6205
test_send_password_to_socket_empty_password(__attribute__((unused))
6206
					    test_fixture *fixture,
6207
					    __attribute__((unused))
6208
					    gconstpointer user_data){
6209
  __attribute__((cleanup(cleanup_buffer)))
6210
    buffer password = {
6211
    .data=malloc(1),	       /* because malloc(0) may return NULL */
6212
    .length=0,
6213
    .allocated=0,		/* deliberate lie */
6214
  };
6215
  g_assert_nonnull(password.data);
6216
  assert_send_password_to_socket_password(password);
6217
}
6218
6219
static void
6220
test_send_password_to_socket_empty_str_pass(__attribute__((unused))
6221
					    test_fixture *fixture,
6222
					    __attribute__((unused))
6223
					    gconstpointer user_data){
6224
  __attribute__((cleanup(cleanup_buffer)))
6225
    buffer password = {
6226
    .data=strdup(""),
6227
    .length=0,
6228
    .allocated=1,
6229
  };
6230
  if(mlock(password.data, password.allocated) != 0){
6231
    g_assert_true(errno == EPERM or errno == ENOMEM);
6232
  }
6233
  assert_send_password_to_socket_password(password);
6234
}
6235
6236
static void
6237
test_send_password_to_socket_text_password(__attribute__((unused))
6238
					   test_fixture *fixture,
6239
					   __attribute__((unused))
6240
					   gconstpointer user_data){
6241
  const char dummy_test_password[] = "dummy test password";
6242
  __attribute__((cleanup(cleanup_buffer)))
6243
    buffer password = {
6244
    .data = strdup(dummy_test_password),
6245
    .length = strlen(dummy_test_password),
6246
    .allocated = sizeof(dummy_test_password),
6247
  };
6248
  if(mlock(password.data, password.allocated) != 0){
6249
    g_assert_true(errno == EPERM or errno == ENOMEM);
6250
  }
6251
  assert_send_password_to_socket_password(password);
6252
}
6253
6254
static void
6255
test_send_password_to_socket_binary_password(__attribute__((unused))
6256
					     test_fixture *fixture,
6257
					     __attribute__((unused))
6258
					     gconstpointer user_data){
6259
  __attribute__((cleanup(cleanup_buffer)))
6260
    buffer password = {
6261
    .data=malloc(255),
6262
    .length=255,
6263
    .allocated=255,
6264
  };
6265
  g_assert_nonnull(password.data);
6266
  if(mlock(password.data, password.allocated) != 0){
6267
    g_assert_true(errno == EPERM or errno == ENOMEM);
6268
  }
6269
  char c = 1;			/* Start at 1, avoiding NUL */
6270
  for(int i=0; i < 255; i++){
6271
    password.data[i] = c++;
6272
  }
6273
  assert_send_password_to_socket_password(password);
6274
}
6275
6276
static void
6277
test_send_password_to_socket_nuls_in_password(__attribute__((unused))
6278
					      test_fixture *fixture,
6279
					      __attribute__((unused))
6280
					      gconstpointer
6281
					      user_data){
6282
  char test_password[] = {'\0', 'a', '\0', 'b', '\0', 'c', '\0'};
6283
  __attribute__((cleanup(cleanup_buffer)))
6284
    buffer password = {
6285
    .data=malloc(sizeof(test_password)),
6286
    .length=sizeof(test_password),
6287
    .allocated=sizeof(test_password),
6288
  };
6289
  g_assert_nonnull(password.data);
6290
  if(mlock(password.data, password.allocated) !=0){
6291
    g_assert_true(errno == EPERM or errno == ENOMEM);
6292
  }
6293
  memcpy(password.data, test_password, password.allocated);
6294
  assert_send_password_to_socket_password(password);
6295
}
6296
6297
static bool assert_add_existing_questions_to_devnull(task_queue
6298
						     *const,
6299
						     const int,
6300
						     buffer *const,
6301
						     string_set *,
6302
						     const
6303
						     mono_microsecs
6304
						     *const,
6305
						     bool *const,
6306
						     bool *const,
6307
						     const char
6308
						     *const);
6309
6310
static void test_add_existing_questions_ENOENT(__attribute__((unused))
6311
					       test_fixture *fixture,
6312
					       __attribute__((unused))
6313
					       gconstpointer
6314
					       user_data){
6315
  __attribute__((cleanup(cleanup_queue)))
6316
    task_queue *queue = create_queue();
6317
  g_assert_nonnull(queue);
6318
  __attribute__((cleanup(cleanup_close)))
6319
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6320
  g_assert_cmpint(epoll_fd, >=, 0);
6321
  __attribute__((cleanup(string_set_clear)))
6322
    string_set cancelled_filenames = {};
6323
6324
  g_assert_false(assert_add_existing_questions_to_devnull
6325
		 (queue,
6326
		  epoll_fd,
6327
		  (buffer[]){{}}, /* password */
6328
		  &cancelled_filenames,
6329
		  (mono_microsecs[]){0}, /* current_time */
6330
		  (bool[]){false},	 /* mandos_client_exited */
6331
		  (bool[]){false},	 /* password_is_read */
6332
		  "/nonexistent"));	 /* dirname */
6333
6334
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
6335
}
6336
6337
static
6338
bool assert_add_existing_questions_to_devnull(task_queue
6339
					      *const queue,
6340
					      const int
6341
					      epoll_fd,
6342
					      buffer *const
6343
					      password,
6344
					      string_set
6345
					      *cancelled_filenames,
6346
					      const mono_microsecs
6347
					      *const current_time,
6348
					      bool *const
6349
					      mandos_client_exited,
6350
					      bool *const
6351
					      password_is_read,
6352
					      const char *const
6353
					      dirname){
6354
  __attribute__((cleanup(cleanup_close)))
237.7.691 by Teddy Hogeborn
dracut-module/password-agent.c: Use O_NOCTTY
6355
    const int devnull_fd = open("/dev/null",
6356
				O_WRONLY | O_CLOEXEC | O_NOCTTY);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
6357
  g_assert_cmpint(devnull_fd, >=, 0);
6358
  __attribute__((cleanup(cleanup_close)))
6359
    const int real_stderr_fd = dup(STDERR_FILENO);
6360
  g_assert_cmpint(real_stderr_fd, >=, 0);
6361
  dup2(devnull_fd, STDERR_FILENO);
6362
  const bool ret = add_existing_questions(queue, epoll_fd, password,
6363
					  cancelled_filenames,
6364
					  current_time,
6365
					  mandos_client_exited,
6366
					  password_is_read, dirname);
6367
  dup2(real_stderr_fd, STDERR_FILENO);
6368
  return ret;
6369
}
6370
6371
static
6372
void test_add_existing_questions_no_questions(__attribute__((unused))
6373
					      test_fixture *fixture,
6374
					      __attribute__((unused))
6375
					      gconstpointer
6376
					      user_data){
6377
  __attribute__((cleanup(cleanup_queue)))
6378
    task_queue *queue = create_queue();
6379
  g_assert_nonnull(queue);
6380
  __attribute__((cleanup(cleanup_close)))
6381
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6382
  g_assert_cmpint(epoll_fd, >=, 0);
6383
  __attribute__((cleanup(string_set_clear)))
6384
    string_set cancelled_filenames = {};
6385
  __attribute__((cleanup(cleanup_string)))
6386
    char *tempdir = make_temporary_directory();
6387
  g_assert_nonnull(tempdir);
6388
6389
  g_assert_false(assert_add_existing_questions_to_devnull
6390
		 (queue,
6391
		  epoll_fd,
6392
		  (buffer[]){{}}, /* password */
6393
		  &cancelled_filenames,
6394
		  (mono_microsecs[]){0}, /* current_time */
6395
		  (bool[]){false},	 /* mandos_client_exited */
6396
		  (bool[]){false},	 /* password_is_read */
6397
		  tempdir));
6398
6399
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
6400
6401
  g_assert_cmpint(rmdir(tempdir), ==, 0);
6402
}
6403
6404
static char *make_question_file_in_directory(const char *const);
6405
6406
static
6407
void test_add_existing_questions_one_question(__attribute__((unused))
6408
					      test_fixture *fixture,
6409
					      __attribute__((unused))
6410
					      gconstpointer
6411
					      user_data){
6412
  __attribute__((cleanup(cleanup_queue)))
6413
    task_queue *queue = create_queue();
6414
  g_assert_nonnull(queue);
6415
  __attribute__((cleanup(cleanup_close)))
6416
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6417
  g_assert_cmpint(epoll_fd, >=, 0);
6418
  __attribute__((cleanup(cleanup_buffer)))
6419
    buffer password = {};
6420
  __attribute__((cleanup(string_set_clear)))
6421
    string_set cancelled_filenames = {};
6422
  const mono_microsecs current_time = 0;
6423
  bool mandos_client_exited = false;
6424
  bool password_is_read = false;
6425
  __attribute__((cleanup(cleanup_string)))
6426
    char *tempdir = make_temporary_directory();
6427
  g_assert_nonnull(tempdir);
6428
  __attribute__((cleanup(cleanup_string)))
6429
    char *question_filename
6430
    = make_question_file_in_directory(tempdir);
6431
  g_assert_nonnull(question_filename);
6432
6433
  g_assert_true(assert_add_existing_questions_to_devnull
6434
		(queue,
6435
		 epoll_fd,
6436
		 &password,
6437
		 &cancelled_filenames,
6438
		 &current_time,
6439
		 &mandos_client_exited,
6440
		 &password_is_read,
6441
		 tempdir));
6442
6443
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
6444
6445
  g_assert_nonnull(find_matching_task(queue, (task_context){
6446
	.func=open_and_parse_question,
6447
	.epoll_fd=epoll_fd,
6448
	.filename=question_filename,
6449
	.question_filename=question_filename,
6450
	.password=&password,
6451
	.cancelled_filenames=&cancelled_filenames,
6452
	.current_time=&current_time,
6453
	.mandos_client_exited=&mandos_client_exited,
6454
	.password_is_read=&password_is_read,
6455
      }));
6456
6457
  g_assert_true(queue->next_run == 1);
6458
6459
  g_assert_cmpint(unlink(question_filename), ==, 0);
6460
  g_assert_cmpint(rmdir(tempdir), ==, 0);
6461
}
6462
6463
static char *make_question_file_in_directory(const char
6464
					     *const dir){
6465
  return make_temporary_prefixed_file_in_directory("ask.", dir);
6466
}
6467
6468
static
6469
void test_add_existing_questions_two_questions(__attribute__((unused))
6470
					       test_fixture *fixture,
6471
					       __attribute__((unused))
6472
					       gconstpointer
6473
					       user_data){
6474
  __attribute__((cleanup(cleanup_queue)))
6475
    task_queue *queue = create_queue();
6476
  g_assert_nonnull(queue);
6477
  __attribute__((cleanup(cleanup_close)))
6478
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6479
  g_assert_cmpint(epoll_fd, >=, 0);
6480
  __attribute__((cleanup(cleanup_buffer)))
6481
    buffer password = {};
6482
  __attribute__((cleanup(string_set_clear)))
6483
    string_set cancelled_filenames = {};
6484
  const mono_microsecs current_time = 0;
6485
  bool mandos_client_exited = false;
6486
  bool password_is_read = false;
6487
  __attribute__((cleanup(cleanup_string)))
6488
    char *tempdir = make_temporary_directory();
6489
  g_assert_nonnull(tempdir);
6490
  __attribute__((cleanup(cleanup_string)))
6491
    char *question_filename1
6492
    = make_question_file_in_directory(tempdir);
6493
  g_assert_nonnull(question_filename1);
6494
  __attribute__((cleanup(cleanup_string)))
6495
    char *question_filename2
6496
    = make_question_file_in_directory(tempdir);
6497
  g_assert_nonnull(question_filename2);
6498
6499
  g_assert_true(assert_add_existing_questions_to_devnull
6500
		(queue,
6501
		 epoll_fd,
6502
		 &password,
6503
		 &cancelled_filenames,
6504
		 &current_time,
6505
		 &mandos_client_exited,
6506
		 &password_is_read,
6507
		 tempdir));
6508
6509
  g_assert_cmpuint((unsigned int)queue->length, ==, 2);
6510
6511
  g_assert_true(queue->next_run == 1);
6512
6513
  __attribute__((cleanup(string_set_clear)))
6514
    string_set seen_questions = {};
6515
6516
  bool queue_contains_question_opener(char *const question_filename){
6517
    return(find_matching_task(queue, (task_context){
6518
	  .func=open_and_parse_question,
6519
	  .epoll_fd=epoll_fd,
6520
	  .question_filename=question_filename,
6521
	  .password=&password,
6522
	  .cancelled_filenames=&cancelled_filenames,
6523
	  .current_time=&current_time,
6524
	  .mandos_client_exited=&mandos_client_exited,
6525
	  .password_is_read=&password_is_read,
6526
	}) != NULL);
6527
  }
6528
6529
  g_assert_true(queue_contains_question_opener(question_filename1));
6530
  g_assert_true(queue_contains_question_opener(question_filename2));
6531
6532
  g_assert_true(queue->next_run == 1);
6533
6534
  g_assert_cmpint(unlink(question_filename1), ==, 0);
6535
  g_assert_cmpint(unlink(question_filename2), ==, 0);
6536
  g_assert_cmpint(rmdir(tempdir), ==, 0);
6537
}
6538
6539
static void
6540
test_add_existing_questions_non_questions(__attribute__((unused))
6541
					  test_fixture *fixture,
6542
					  __attribute__((unused))
6543
					  gconstpointer user_data){
6544
  __attribute__((cleanup(cleanup_queue)))
6545
    task_queue *queue = create_queue();
6546
  g_assert_nonnull(queue);
6547
  __attribute__((cleanup(cleanup_close)))
6548
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6549
  g_assert_cmpint(epoll_fd, >=, 0);
6550
  __attribute__((cleanup(string_set_clear)))
6551
    string_set cancelled_filenames = {};
6552
  __attribute__((cleanup(cleanup_string)))
6553
    char *tempdir = make_temporary_directory();
6554
  g_assert_nonnull(tempdir);
6555
  __attribute__((cleanup(cleanup_string)))
6556
    char *question_filename1
6557
    = make_temporary_file_in_directory(tempdir);
6558
  g_assert_nonnull(question_filename1);
6559
  __attribute__((cleanup(cleanup_string)))
6560
    char *question_filename2
6561
    = make_temporary_file_in_directory(tempdir);
6562
  g_assert_nonnull(question_filename2);
6563
6564
  g_assert_false(assert_add_existing_questions_to_devnull
6565
		 (queue,
6566
		  epoll_fd,
6567
		  (buffer[]){{}}, /* password */
6568
		  &cancelled_filenames,
6569
		  (mono_microsecs[]){0}, /* current_time */
6570
		  (bool[]){false},	 /* mandos_client_exited */
6571
		  (bool[]){false},	 /* password_is_read */
6572
		  tempdir));
6573
6574
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
6575
6576
  g_assert_cmpint(unlink(question_filename1), ==, 0);
6577
  g_assert_cmpint(unlink(question_filename2), ==, 0);
6578
  g_assert_cmpint(rmdir(tempdir), ==, 0);
6579
}
6580
6581
static void
6582
test_add_existing_questions_both_types(__attribute__((unused))
6583
				       test_fixture *fixture,
6584
				       __attribute__((unused))
6585
				       gconstpointer user_data){
6586
  __attribute__((cleanup(cleanup_queue)))
6587
    task_queue *queue = create_queue();
6588
  g_assert_nonnull(queue);
6589
  __attribute__((cleanup(cleanup_close)))
6590
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6591
  g_assert_cmpint(epoll_fd, >=, 0);
6592
  __attribute__((cleanup(cleanup_buffer)))
6593
    buffer password = {};
6594
  __attribute__((cleanup(string_set_clear)))
6595
    string_set cancelled_filenames = {};
6596
  const mono_microsecs current_time = 0;
6597
  bool mandos_client_exited = false;
6598
  bool password_is_read = false;
6599
  __attribute__((cleanup(cleanup_string)))
6600
    char *tempdir = make_temporary_directory();
6601
  g_assert_nonnull(tempdir);
6602
  __attribute__((cleanup(cleanup_string)))
6603
    char *tempfilename1 = make_temporary_file_in_directory(tempdir);
6604
  g_assert_nonnull(tempfilename1);
6605
  __attribute__((cleanup(cleanup_string)))
6606
    char *tempfilename2 = make_temporary_file_in_directory(tempdir);
6607
  g_assert_nonnull(tempfilename2);
6608
  __attribute__((cleanup(cleanup_string)))
6609
    char *question_filename
6610
    = make_question_file_in_directory(tempdir);
6611
  g_assert_nonnull(question_filename);
6612
6613
  g_assert_true(assert_add_existing_questions_to_devnull
6614
		(queue,
6615
		 epoll_fd,
6616
		 &password,
6617
		 &cancelled_filenames,
6618
		 &current_time,
6619
		 &mandos_client_exited,
6620
		 &password_is_read,
6621
		 tempdir));
6622
6623
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
6624
6625
  g_assert_nonnull(find_matching_task(queue, (task_context){
6626
	.func=open_and_parse_question,
6627
	.epoll_fd=epoll_fd,
6628
	.filename=question_filename,
6629
	.question_filename=question_filename,
6630
	.password=&password,
6631
	.cancelled_filenames=&cancelled_filenames,
6632
	.current_time=&current_time,
6633
	.mandos_client_exited=&mandos_client_exited,
6634
	.password_is_read=&password_is_read,
6635
      }));
6636
6637
  g_assert_true(queue->next_run == 1);
6638
6639
  g_assert_cmpint(unlink(tempfilename1), ==, 0);
6640
  g_assert_cmpint(unlink(tempfilename2), ==, 0);
6641
  g_assert_cmpint(unlink(question_filename), ==, 0);
6642
  g_assert_cmpint(rmdir(tempdir), ==, 0);
6643
}
6644
6645
static void test_wait_for_event_timeout(__attribute__((unused))
6646
					test_fixture *fixture,
6647
					__attribute__((unused))
6648
					gconstpointer user_data){
6649
  __attribute__((cleanup(cleanup_close)))
6650
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6651
  g_assert_cmpint(epoll_fd, >=, 0);
6652
6653
  g_assert_true(wait_for_event(epoll_fd, 1, 0));
6654
}
6655
6656
static void test_wait_for_event_event(__attribute__((unused))
6657
				      test_fixture *fixture,
6658
				      __attribute__((unused))
6659
				      gconstpointer user_data){
6660
  __attribute__((cleanup(cleanup_close)))
6661
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6662
  g_assert_cmpint(epoll_fd, >=, 0);
6663
  int pipefds[2];
6664
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
6665
  __attribute__((cleanup(cleanup_close)))
6666
    const int read_pipe = pipefds[0];
6667
  __attribute__((cleanup(cleanup_close)))
6668
    const int write_pipe = pipefds[1];
6669
  g_assert_cmpint(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, read_pipe,
6670
			    &(struct epoll_event)
6671
			    { .events=EPOLLIN | EPOLLRDHUP }), ==, 0);
6672
  g_assert_cmpint((int)write(write_pipe, "x", 1), ==, 1);
6673
6674
  g_assert_true(wait_for_event(epoll_fd, 0, 0));
6675
}
6676
6677
static void test_wait_for_event_sigchld(test_fixture *fixture,
6678
					__attribute__((unused))
6679
					gconstpointer user_data){
6680
  const pid_t pid = fork();
6681
  if(pid == 0){		/* Child */
6682
    if(not restore_signal_handler(&fixture->orig_sigaction)){
6683
      _exit(EXIT_FAILURE);
6684
    }
6685
    if(not restore_sigmask(&fixture->orig_sigmask)){
6686
      _exit(EXIT_FAILURE);
6687
    }
6688
    exit(EXIT_SUCCESS);
6689
  }
6690
  g_assert_true(pid != -1);
6691
  __attribute__((cleanup(cleanup_close)))
6692
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6693
  g_assert_cmpint(epoll_fd, >=, 0);
6694
6695
  g_assert_true(wait_for_event(epoll_fd, 0, 0));
6696
6697
  int status;
6698
  g_assert_true(waitpid(pid, &status, 0) == pid);
6699
  g_assert_true(WIFEXITED(status));
6700
  g_assert_cmpint(WEXITSTATUS(status), ==, EXIT_SUCCESS);
6701
}
6702
6703
static void test_run_queue_zeroes_next_run(__attribute__((unused))
6704
					   test_fixture *fixture,
6705
					   __attribute__((unused))
6706
					   gconstpointer user_data){
6707
  __attribute__((cleanup(cleanup_queue)))
6708
    task_queue *queue = create_queue();
6709
  g_assert_nonnull(queue);
6710
  queue->next_run = 1;
6711
  __attribute__((cleanup(cleanup_close)))
6712
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
6713
  __attribute__((cleanup(string_set_clear)))
6714
    string_set cancelled_filenames = {};
6715
  bool quit_now = false;
6716
6717
  g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
6718
  g_assert_false(quit_now);
6719
  g_assert_cmpuint((unsigned int)queue->next_run, ==, 0);
6720
}
6721
6722
static
6723
void test_run_queue_clears_cancelled_filenames(__attribute__((unused))
6724
					       test_fixture *fixture,
6725
					       __attribute__((unused))
6726
					       gconstpointer
6727
					       user_data){
6728
  __attribute__((cleanup(cleanup_queue)))
6729
    task_queue *queue = create_queue();
6730
  g_assert_nonnull(queue);
6731
  __attribute__((cleanup(string_set_clear)))
6732
    string_set cancelled_filenames = {};
6733
  bool quit_now = false;
6734
  const char question_filename[] = "/nonexistent/question_filename";
6735
  g_assert_true(string_set_add(&cancelled_filenames,
6736
			       question_filename));
6737
6738
  g_assert_true(add_to_queue(queue,
6739
			     (task_context){ .func=dummy_func }));
6740
6741
  g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
6742
  g_assert_false(quit_now);
6743
  g_assert_cmpuint((unsigned int)(queue->length), ==, 0);
6744
  g_assert_false(string_set_contains(cancelled_filenames,
6745
				     question_filename));
6746
}
6747
6748
static
6749
void test_run_queue_skips_cancelled_filenames(__attribute__((unused))
6750
					      test_fixture *fixture,
6751
					      __attribute__((unused))
6752
					      gconstpointer
6753
					      user_data){
6754
  __attribute__((cleanup(cleanup_queue)))
6755
    task_queue *queue = create_queue();
6756
  g_assert_nonnull(queue);
6757
  __attribute__((cleanup(string_set_clear)))
6758
    string_set cancelled_filenames = {};
6759
  bool quit_now = false;
6760
  int pipefds[2];
6761
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
6762
  __attribute__((cleanup(cleanup_close)))
6763
    const int read_pipe = pipefds[0];
6764
  g_assert_cmpint(close(pipefds[1]), ==, 0);
6765
  const char question_filename[] = "/nonexistent/question_filename";
6766
  g_assert_true(string_set_add(&cancelled_filenames,
6767
			       question_filename));
6768
  __attribute__((nonnull))
6769
    void quit_func(const task_context task,
6770
		   __attribute__((unused)) task_queue *const q){
6771
    g_assert_nonnull(task.quit_now);
6772
    *task.quit_now = true;
6773
  }
6774
  task_context task = {
6775
    .func=quit_func,
6776
    .question_filename=strdup(question_filename),
6777
    .quit_now=&quit_now,
6778
    .fd=read_pipe,
6779
  };
6780
  g_assert_nonnull(task.question_filename);
6781
6782
  g_assert_true(add_to_queue(queue, task));
6783
6784
  g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
6785
  g_assert_false(quit_now);
6786
6787
  /* read_pipe should be closed already */
6788
  errno = 0;
6789
  bool read_pipe_closed = (close(read_pipe) == -1);
6790
  read_pipe_closed &= (errno == EBADF);
6791
  g_assert_true(read_pipe_closed);
6792
}
6793
6794
static void test_run_queue_one_task(__attribute__((unused))
6795
				    test_fixture *fixture,
6796
				    __attribute__((unused))
6797
				    gconstpointer user_data){
6798
  __attribute__((cleanup(cleanup_queue)))
6799
    task_queue *queue = create_queue();
6800
  g_assert_nonnull(queue);
6801
  __attribute__((cleanup(string_set_clear)))
6802
    string_set cancelled_filenames = {};
6803
  bool quit_now = false;
6804
6805
  __attribute__((nonnull))
6806
    void next_run_func(__attribute__((unused))
6807
		       const task_context task,
6808
		       task_queue *const q){
6809
    q->next_run = 1;
6810
  }
6811
6812
  task_context task = {
6813
    .func=next_run_func,
6814
  };
6815
  g_assert_true(add_to_queue(queue, task));
6816
6817
  g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
6818
  g_assert_cmpuint((unsigned int)(queue->next_run), ==, 1);
6819
  g_assert_cmpuint((unsigned int)(queue->length), ==, 0);
6820
}
6821
6822
static void test_run_queue_two_tasks(__attribute__((unused))
6823
				     test_fixture *fixture,
6824
				     __attribute__((unused))
6825
				     gconstpointer user_data){
6826
  __attribute__((cleanup(cleanup_queue)))
6827
    task_queue *queue = create_queue();
6828
  g_assert_nonnull(queue);
6829
  queue->next_run = 1;
6830
  __attribute__((cleanup(string_set_clear)))
6831
    string_set cancelled_filenames = {};
6832
  bool quit_now = false;
6833
  bool mandos_client_exited = false;
6834
6835
  __attribute__((nonnull))
6836
    void next_run_func(__attribute__((unused))
6837
		       const task_context task,
6838
		       task_queue *const q){
6839
    q->next_run = 1;
6840
  }
6841
6842
  __attribute__((nonnull))
6843
    void exited_func(const task_context task,
6844
		     __attribute__((unused)) task_queue *const q){
6845
    *task.mandos_client_exited = true;
6846
  }
6847
6848
  task_context task1 = {
6849
    .func=next_run_func,
6850
  };
6851
  g_assert_true(add_to_queue(queue, task1));
6852
6853
  task_context task2 = {
6854
    .func=exited_func,
6855
    .mandos_client_exited=&mandos_client_exited,
6856
  };
6857
  g_assert_true(add_to_queue(queue, task2));
6858
6859
  g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
6860
  g_assert_false(quit_now);
6861
  g_assert_cmpuint((unsigned int)(queue->next_run), ==, 1);
6862
  g_assert_true(mandos_client_exited);
6863
  g_assert_cmpuint((unsigned int)(queue->length), ==, 0);
6864
}
6865
6866
static void test_run_queue_two_tasks_quit(__attribute__((unused))
6867
					  test_fixture *fixture,
6868
					  __attribute__((unused))
6869
					  gconstpointer user_data){
6870
  __attribute__((cleanup(cleanup_queue)))
6871
    task_queue *queue = create_queue();
6872
  g_assert_nonnull(queue);
6873
  __attribute__((cleanup(string_set_clear)))
6874
    string_set cancelled_filenames = {};
6875
  bool quit_now = false;
6876
  bool mandos_client_exited = false;
6877
  bool password_is_read = false;
6878
6879
  __attribute__((nonnull))
6880
    void set_exited_func(const task_context task,
6881
			 __attribute__((unused)) task_queue *const q){
6882
    *task.mandos_client_exited = true;
6883
    *task.quit_now = true;
6884
  }
6885
  task_context task1 = {
6886
    .func=set_exited_func,
6887
    .quit_now=&quit_now,
6888
    .mandos_client_exited=&mandos_client_exited,
6889
  };
6890
  g_assert_true(add_to_queue(queue, task1));
6891
6892
  __attribute__((nonnull))
6893
    void set_read_func(const task_context task,
6894
		       __attribute__((unused)) task_queue *const q){
6895
    *task.quit_now = true;
6896
    *task.password_is_read = true;
6897
  }
6898
  task_context task2 = {
6899
    .func=set_read_func,
6900
    .quit_now=&quit_now,
6901
    .password_is_read=&password_is_read,
6902
  };
6903
  g_assert_true(add_to_queue(queue, task2));
6904
6905
  g_assert_false(run_queue(&queue, &cancelled_filenames, &quit_now));
6906
  g_assert_true(quit_now);
6907
  g_assert_true(mandos_client_exited xor password_is_read);
6908
  g_assert_cmpuint((unsigned int)(queue->length), ==, 0);
6909
}
6910
6911
static void test_run_queue_two_tasks_cleanup(__attribute__((unused))
6912
					     test_fixture *fixture,
6913
					     __attribute__((unused))
6914
					     gconstpointer user_data){
6915
  __attribute__((cleanup(cleanup_queue)))
6916
    task_queue *queue = create_queue();
6917
  g_assert_nonnull(queue);
6918
  __attribute__((cleanup(string_set_clear)))
6919
    string_set cancelled_filenames = {};
6920
  int pipefds[2];
6921
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
6922
  __attribute__((cleanup(cleanup_close)))
6923
    const int read_pipe = pipefds[0];
6924
  __attribute__((cleanup(cleanup_close)))
6925
    const int write_pipe = pipefds[1];
6926
  bool quit_now = false;
6927
6928
  __attribute__((nonnull))
6929
    void read_func(const task_context task,
6930
		   __attribute__((unused)) task_queue *const q){
6931
    *task.quit_now = true;
6932
  }
6933
  task_context task1 = {
6934
    .func=read_func,
6935
    .quit_now=&quit_now,
6936
    .fd=read_pipe,
6937
  };
6938
  g_assert_true(add_to_queue(queue, task1));
6939
6940
  __attribute__((nonnull))
6941
    void write_func(const task_context task,
6942
		    __attribute__((unused)) task_queue *const q){
6943
    *task.quit_now = true;
6944
  }
6945
  task_context task2 = {
6946
    .func=write_func,
6947
    .quit_now=&quit_now,
6948
    .fd=write_pipe,
6949
  };
6950
  g_assert_true(add_to_queue(queue, task2));
6951
6952
  g_assert_false(run_queue(&queue, &cancelled_filenames, &quit_now));
6953
  g_assert_true(quit_now);
6954
6955
  /* Either read_pipe or write_pipe should be closed already */
6956
  errno = 0;
6957
  bool close_read_pipe = (close(read_pipe) == -1);
6958
  close_read_pipe &= (errno == EBADF);
6959
  errno = 0;
6960
  bool close_write_pipe = (close(write_pipe) == -1);
6961
  close_write_pipe &= (errno == EBADF);
6962
  g_assert_true(close_read_pipe xor close_write_pipe);
6963
  g_assert_cmpuint((unsigned int)(queue->length), ==, 0);
6964
}
6965
6966
static void test_setup_signal_handler(__attribute__((unused))
6967
				      test_fixture *fixture,
6968
				      __attribute__((unused))
6969
				      gconstpointer user_data){
6970
  /* Save current SIGCHLD action, whatever it is */
6971
  struct sigaction expected_sigchld_action;
6972
  g_assert_cmpint(sigaction(SIGCHLD, NULL, &expected_sigchld_action),
6973
		  ==, 0);
6974
6975
  /* Act; i.e. run the setup_signal_handler() function */
6976
  struct sigaction actual_old_sigchld_action;
6977
  g_assert_true(setup_signal_handler(&actual_old_sigchld_action));
6978
6979
  /* Check that the function correctly set "actual_old_sigchld_action"
6980
     to the same values as the previously saved
6981
     "expected_sigchld_action" */
6982
  /* Check member sa_handler */
6983
  g_assert_true(actual_old_sigchld_action.sa_handler
6984
		== expected_sigchld_action.sa_handler);
6985
  /* Check member sa_mask */
6986
  for(int signum = 1; signum < NSIG; signum++){
6987
    const int expected_old_block_state
6988
      = sigismember(&expected_sigchld_action.sa_mask, signum);
6989
    g_assert_cmpint(expected_old_block_state, >=, 0);
6990
    const int actual_old_block_state
6991
      = sigismember(&actual_old_sigchld_action.sa_mask, signum);
6992
    g_assert_cmpint(actual_old_block_state, >=, 0);
6993
    g_assert_cmpint(actual_old_block_state,
6994
		    ==, expected_old_block_state);
6995
  }
6996
  /* Check member sa_flags */
6997
  g_assert_true((actual_old_sigchld_action.sa_flags
6998
		 & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))
6999
		== (expected_sigchld_action.sa_flags
7000
		    & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)));
7001
7002
  /* Retrieve the current signal handler for SIGCHLD as set by
7003
     setup_signal_handler() */
7004
  struct sigaction actual_new_sigchld_action;
7005
  g_assert_cmpint(sigaction(SIGCHLD, NULL,
7006
			    &actual_new_sigchld_action), ==, 0);
7007
  /* Check that the signal handler (member sa_handler) is correctly
7008
     set to the "handle_sigchld" function */
7009
  g_assert_true(actual_new_sigchld_action.sa_handler != SIG_DFL);
7010
  g_assert_true(actual_new_sigchld_action.sa_handler != SIG_IGN);
7011
  g_assert_true(actual_new_sigchld_action.sa_handler
7012
		== handle_sigchld);
7013
  /* Check (in member sa_mask) that at least a handful of signals are
7014
     actually blocked during the signal handler */
7015
  for(int signum = 1; signum < NSIG; signum++){
7016
    int actual_new_block_state;
7017
    switch(signum){
7018
    case SIGTERM:
7019
    case SIGINT:
7020
    case SIGQUIT:
7021
    case SIGHUP:
7022
      actual_new_block_state
7023
	= sigismember(&actual_new_sigchld_action.sa_mask, signum);
7024
      g_assert_cmpint(actual_new_block_state, ==, 1);
7025
      continue;
7026
    case SIGKILL:		/* non-blockable */
7027
    case SIGSTOP:		/* non-blockable */
7028
    case SIGCHLD:		/* always blocked */
7029
    default:
7030
      continue;
7031
    }
7032
  }
7033
  /* Check member sa_flags */
7034
  g_assert_true((actual_new_sigchld_action.sa_flags
7035
		 & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))
7036
		== (SA_NOCLDSTOP | SA_RESTART));
7037
7038
  /* Restore signal handler */
7039
  g_assert_cmpint(sigaction(SIGCHLD, &expected_sigchld_action, NULL),
7040
		  ==, 0);
7041
}
7042
7043
static void test_restore_signal_handler(__attribute__((unused))
7044
					test_fixture *fixture,
7045
					__attribute__((unused))
7046
					gconstpointer user_data){
7047
  /* Save current SIGCHLD action, whatever it is */
7048
  struct sigaction expected_sigchld_action;
7049
  g_assert_cmpint(sigaction(SIGCHLD, NULL, &expected_sigchld_action),
7050
		  ==, 0);
7051
  /* Since we haven't established a signal handler yet, there should
7052
     not be one established.  But another test may have relied on
7053
     restore_signal_handler() to restore the signal handler, and if
7054
     restore_signal_handler() is buggy (which we should be prepared
7055
     for in this test) the signal handler may not have been restored
7056
     properly; check for this: */
7057
  g_assert_true(expected_sigchld_action.sa_handler != handle_sigchld);
7058
7059
  /* Establish a signal handler */
7060
  struct sigaction sigchld_action = {
7061
    .sa_handler=handle_sigchld,
7062
    .sa_flags=SA_RESTART | SA_NOCLDSTOP,
7063
  };
7064
  g_assert_cmpint(sigfillset(&sigchld_action.sa_mask), ==, 0);
7065
  g_assert_cmpint(sigaction(SIGCHLD, &sigchld_action, NULL), ==, 0);
7066
7067
  /* Act; i.e. run the restore_signal_handler() function */
7068
  g_assert_true(restore_signal_handler(&expected_sigchld_action));
7069
7070
  /* Retrieve the restored signal handler data */
7071
  struct sigaction actual_restored_sigchld_action;
7072
  g_assert_cmpint(sigaction(SIGCHLD, NULL,
7073
			    &actual_restored_sigchld_action), ==, 0);
7074
7075
  /* Check that the function correctly restored the signal action, as
7076
     saved in "actual_restored_sigchld_action", to the same values as
7077
     the previously saved "expected_sigchld_action" */
7078
  /* Check member sa_handler */
7079
  g_assert_true(actual_restored_sigchld_action.sa_handler
7080
		== expected_sigchld_action.sa_handler);
7081
  /* Check member sa_mask */
7082
  for(int signum = 1; signum < NSIG; signum++){
7083
    const int expected_old_block_state
7084
      = sigismember(&expected_sigchld_action.sa_mask, signum);
7085
    g_assert_cmpint(expected_old_block_state, >=, 0);
7086
    const int actual_restored_block_state
7087
      = sigismember(&actual_restored_sigchld_action.sa_mask, signum);
7088
    g_assert_cmpint(actual_restored_block_state, >=, 0);
7089
    g_assert_cmpint(actual_restored_block_state,
7090
		    ==, expected_old_block_state);
7091
  }
7092
  /* Check member sa_flags */
7093
  g_assert_true((actual_restored_sigchld_action.sa_flags
7094
		 & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))
7095
		== (expected_sigchld_action.sa_flags
7096
		    & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)));
7097
}
7098
7099
static void test_block_sigchld(__attribute__((unused))
7100
			       test_fixture *fixture,
7101
			       __attribute__((unused))
7102
			       gconstpointer user_data){
7103
  /* Save original signal mask */
7104
  sigset_t expected_sigmask;
7105
  g_assert_cmpint(pthread_sigmask(-1, NULL, &expected_sigmask),
7106
		  ==, 0);
7107
7108
  /* Make sure SIGCHLD is unblocked for this test */
7109
  sigset_t sigchld_sigmask;
7110
  g_assert_cmpint(sigemptyset(&sigchld_sigmask), ==, 0);
7111
  g_assert_cmpint(sigaddset(&sigchld_sigmask, SIGCHLD), ==, 0);
7112
  g_assert_cmpint(pthread_sigmask(SIG_UNBLOCK, &sigchld_sigmask,
7113
				  NULL), ==, 0);
7114
7115
  /* Act; i.e. run the block_sigchld() function */
7116
  sigset_t actual_old_sigmask;
7117
  g_assert_true(block_sigchld(&actual_old_sigmask));
7118
7119
  /* Check the actual_old_sigmask; it should be the same as the
7120
     previously saved signal mask "expected_sigmask". */
7121
  for(int signum = 1; signum < NSIG; signum++){
7122
    const int expected_old_block_state
7123
      = sigismember(&expected_sigmask, signum);
7124
    g_assert_cmpint(expected_old_block_state, >=, 0);
7125
    const int actual_old_block_state
7126
      = sigismember(&actual_old_sigmask, signum);
7127
    g_assert_cmpint(actual_old_block_state, >=, 0);
7128
    g_assert_cmpint(actual_old_block_state,
7129
		    ==, expected_old_block_state);
7130
  }
7131
7132
  /* Retrieve the newly set signal mask */
7133
  sigset_t actual_sigmask;
7134
  g_assert_cmpint(pthread_sigmask(-1, NULL, &actual_sigmask), ==, 0);
7135
7136
  /* SIGCHLD should be blocked */
7137
  g_assert_cmpint(sigismember(&actual_sigmask, SIGCHLD), ==, 1);
7138
7139
  /* Restore signal mask */
7140
  g_assert_cmpint(pthread_sigmask(SIG_SETMASK, &expected_sigmask,
7141
				  NULL), ==, 0);
7142
}
7143
7144
static void test_restore_sigmask(__attribute__((unused))
7145
				 test_fixture *fixture,
7146
				 __attribute__((unused))
7147
				 gconstpointer user_data){
7148
  /* Save original signal mask */
7149
  sigset_t orig_sigmask;
7150
  g_assert_cmpint(pthread_sigmask(-1, NULL, &orig_sigmask), ==, 0);
7151
7152
  /* Make sure SIGCHLD is blocked for this test */
7153
  sigset_t sigchld_sigmask;
7154
  g_assert_cmpint(sigemptyset(&sigchld_sigmask), ==, 0);
7155
  g_assert_cmpint(sigaddset(&sigchld_sigmask, SIGCHLD), ==, 0);
7156
  g_assert_cmpint(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask,
7157
				  NULL), ==, 0);
7158
7159
  /* Act; i.e. run the restore_sigmask() function */
7160
  g_assert_true(restore_sigmask(&orig_sigmask));
7161
7162
  /* Retrieve the newly restored signal mask */
7163
  sigset_t restored_sigmask;
7164
  g_assert_cmpint(pthread_sigmask(-1, NULL, &restored_sigmask),
7165
		  ==, 0);
7166
7167
  /* Check the restored_sigmask; it should be the same as the
7168
     previously saved signal mask "orig_sigmask". */
7169
  for(int signum = 1; signum < NSIG; signum++){
7170
    const int orig_block_state = sigismember(&orig_sigmask, signum);
7171
    g_assert_cmpint(orig_block_state, >=, 0);
7172
    const int restored_block_state = sigismember(&restored_sigmask,
7173
						 signum);
7174
    g_assert_cmpint(restored_block_state, >=, 0);
7175
    g_assert_cmpint(restored_block_state, ==, orig_block_state);
7176
  }
7177
7178
  /* Restore signal mask */
7179
  g_assert_cmpint(pthread_sigmask(SIG_SETMASK, &orig_sigmask,
7180
				  NULL), ==, 0);
7181
}
7182
7183
static void test_parse_arguments_noargs(__attribute__((unused))
7184
					test_fixture *fixture,
7185
					__attribute__((unused))
7186
					gconstpointer user_data){
7187
  char *argv[] = {
7188
    strdup("prgname"),
7189
    NULL };
7190
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7191
7192
  char *agent_directory = NULL;
7193
  char *helper_directory = NULL;
7194
  uid_t user = 0;
7195
  gid_t group = 0;
7196
  char *mandos_argz = NULL;
7197
  size_t mandos_argz_length = 0;
7198
7199
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7200
				&helper_directory, &user, &group,
7201
				&mandos_argz, &mandos_argz_length));
7202
  g_assert_null(agent_directory);
7203
  g_assert_null(helper_directory);
7204
  g_assert_true(user == 0);
7205
  g_assert_true(group == 0);
7206
  g_assert_null(mandos_argz);
7207
  g_assert_true(mandos_argz_length == 0);
7208
7209
  for(char **arg = argv; *arg != NULL; arg++){
7210
    free(*arg);
7211
  }
7212
}
7213
7214
__attribute__((nonnull))
7215
static bool parse_arguments_devnull(int argc, char *argv[],
7216
				    const bool exit_failure,
7217
				    char **agent_directory,
7218
				    char **helper_directory,
7219
				    uid_t *const user,
7220
				    gid_t *const group,
7221
				    char **mandos_argz,
7222
				    size_t *mandos_argz_length){
7223
7224
  FILE *real_stderr = stderr;
7225
  FILE *devnull = fopen("/dev/null", "we");
7226
  g_assert_nonnull(devnull);
7227
  stderr = devnull;
7228
7229
  const bool ret = parse_arguments(argc, argv, exit_failure,
7230
				   agent_directory,
7231
				   helper_directory, user, group,
7232
				   mandos_argz, mandos_argz_length);
7233
  const error_t saved_errno = errno;
7234
7235
  stderr = real_stderr;
7236
  g_assert_cmpint(fclose(devnull), ==, 0);
7237
7238
  errno = saved_errno;
7239
7240
  return ret;
7241
}
7242
7243
static void test_parse_arguments_invalid(__attribute__((unused))
7244
					 test_fixture *fixture,
7245
					 __attribute__((unused))
7246
					 gconstpointer user_data){
7247
  char *argv[] = {
7248
    strdup("prgname"),
7249
    strdup("--invalid"),
7250
    NULL };
7251
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7252
7253
  char *agent_directory = NULL;
7254
  char *helper_directory = NULL;
7255
  uid_t user = 0;
7256
  gid_t group = 0;
7257
  char *mandos_argz = NULL;
7258
  size_t mandos_argz_length = 0;
7259
7260
  g_assert_false(parse_arguments_devnull(argc, argv, false,
7261
					 &agent_directory,
7262
					 &helper_directory, &user,
7263
					 &group, &mandos_argz,
7264
					 &mandos_argz_length));
7265
7266
  g_assert_true(errno == EINVAL);
7267
  g_assert_null(agent_directory);
7268
  g_assert_null(helper_directory);
7269
  g_assert_null(mandos_argz);
7270
  g_assert_true(mandos_argz_length == 0);
7271
7272
  for(char **arg = argv; *arg != NULL; arg++){
7273
    free(*arg);
7274
  }
7275
}
7276
7277
static void test_parse_arguments_long_dir(__attribute__((unused))
7278
					  test_fixture *fixture,
7279
					  __attribute__((unused))
7280
					  gconstpointer user_data){
7281
  char *argv[] = {
7282
    strdup("prgname"),
7283
    strdup("--agent-directory"),
7284
    strdup("/tmp"),
7285
    NULL };
7286
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7287
7288
  __attribute__((cleanup(cleanup_string)))
7289
    char *agent_directory = NULL;
7290
  char *helper_directory = NULL;
7291
  uid_t user = 0;
7292
  gid_t group = 0;
7293
  __attribute__((cleanup(cleanup_string)))
7294
    char *mandos_argz = NULL;
7295
  size_t mandos_argz_length = 0;
7296
7297
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7298
				&helper_directory, &user, &group,
7299
				&mandos_argz, &mandos_argz_length));
7300
7301
  g_assert_cmpstr(agent_directory, ==, "/tmp");
7302
  g_assert_null(helper_directory);
7303
  g_assert_true(user == 0);
7304
  g_assert_true(group == 0);
7305
  g_assert_null(mandos_argz);
7306
  g_assert_true(mandos_argz_length == 0);
7307
7308
  for(char **arg = argv; *arg != NULL; arg++){
7309
    free(*arg);
7310
  }
7311
}
7312
7313
static void test_parse_arguments_short_dir(__attribute__((unused))
7314
					   test_fixture *fixture,
7315
					   __attribute__((unused))
7316
					   gconstpointer user_data){
7317
  char *argv[] = {
7318
    strdup("prgname"),
7319
    strdup("-d"),
7320
    strdup("/tmp"),
7321
    NULL };
7322
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7323
7324
  __attribute__((cleanup(cleanup_string)))
7325
    char *agent_directory = NULL;
7326
  char *helper_directory = NULL;
7327
  uid_t user = 0;
7328
  gid_t group = 0;
7329
  __attribute__((cleanup(cleanup_string)))
7330
    char *mandos_argz = NULL;
7331
  size_t mandos_argz_length = 0;
7332
7333
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7334
				&helper_directory, &user, &group,
7335
				&mandos_argz, &mandos_argz_length));
7336
7337
  g_assert_cmpstr(agent_directory, ==, "/tmp");
7338
  g_assert_null(helper_directory);
7339
  g_assert_true(user == 0);
7340
  g_assert_true(group == 0);
7341
  g_assert_null(mandos_argz);
7342
  g_assert_true(mandos_argz_length == 0);
7343
7344
  for(char **arg = argv; *arg != NULL; arg++){
7345
    free(*arg);
7346
  }
7347
}
7348
7349
static
7350
void test_parse_arguments_helper_directory(__attribute__((unused))
7351
					   test_fixture *fixture,
7352
					   __attribute__((unused))
7353
					   gconstpointer user_data){
7354
  char *argv[] = {
7355
    strdup("prgname"),
7356
    strdup("--helper-directory"),
7357
    strdup("/tmp"),
7358
    NULL };
7359
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7360
7361
  char *agent_directory = NULL;
7362
  __attribute__((cleanup(cleanup_string)))
7363
    char *helper_directory = NULL;
7364
  uid_t user = 0;
7365
  gid_t group = 0;
7366
  __attribute__((cleanup(cleanup_string)))
7367
    char *mandos_argz = NULL;
7368
  size_t mandos_argz_length = 0;
7369
7370
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7371
				&helper_directory, &user, &group,
7372
				&mandos_argz, &mandos_argz_length));
7373
7374
  g_assert_cmpstr(helper_directory, ==, "/tmp");
7375
  g_assert_null(agent_directory);
7376
  g_assert_true(user == 0);
7377
  g_assert_true(group == 0);
7378
  g_assert_null(mandos_argz);
7379
  g_assert_true(mandos_argz_length == 0);
7380
7381
  for(char **arg = argv; *arg != NULL; arg++){
7382
    free(*arg);
7383
  }
7384
}
7385
7386
static
7387
void test_parse_arguments_plugin_helper_dir(__attribute__((unused))
7388
					    test_fixture *fixture,
7389
					    __attribute__((unused))
7390
					    gconstpointer user_data){
7391
  char *argv[] = {
7392
    strdup("prgname"),
7393
    strdup("--plugin-helper-dir"),
7394
    strdup("/tmp"),
7395
    NULL };
7396
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7397
7398
  char *agent_directory = NULL;
7399
  __attribute__((cleanup(cleanup_string)))
7400
    char *helper_directory = NULL;
7401
  uid_t user = 0;
7402
  gid_t group = 0;
7403
  __attribute__((cleanup(cleanup_string)))
7404
    char *mandos_argz = NULL;
7405
  size_t mandos_argz_length = 0;
7406
7407
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7408
				&helper_directory, &user, &group,
7409
				&mandos_argz, &mandos_argz_length));
7410
7411
  g_assert_cmpstr(helper_directory, ==, "/tmp");
7412
  g_assert_null(agent_directory);
7413
  g_assert_true(user == 0);
7414
  g_assert_true(group == 0);
7415
  g_assert_null(mandos_argz);
7416
  g_assert_true(mandos_argz_length == 0);
7417
7418
  for(char **arg = argv; *arg != NULL; arg++){
7419
    free(*arg);
7420
  }
7421
}
7422
7423
static void test_parse_arguments_user(__attribute__((unused))
7424
				      test_fixture *fixture,
7425
				      __attribute__((unused))
7426
				      gconstpointer user_data){
7427
  char *argv[] = {
7428
    strdup("prgname"),
7429
    strdup("--user"),
7430
    strdup("1000"),
7431
    NULL };
7432
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7433
7434
  char *agent_directory = NULL;
7435
  __attribute__((cleanup(cleanup_string)))
7436
    char *helper_directory = NULL;
7437
  uid_t user = 0;
7438
  gid_t group = 0;
7439
  __attribute__((cleanup(cleanup_string)))
7440
    char *mandos_argz = NULL;
7441
  size_t mandos_argz_length = 0;
7442
7443
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7444
				&helper_directory, &user, &group,
7445
				&mandos_argz, &mandos_argz_length));
7446
7447
  g_assert_null(helper_directory);
7448
  g_assert_null(agent_directory);
7449
  g_assert_cmpuint((unsigned int)user, ==, 1000);
7450
  g_assert_true(group == 0);
7451
  g_assert_null(mandos_argz);
7452
  g_assert_true(mandos_argz_length == 0);
7453
7454
  for(char **arg = argv; *arg != NULL; arg++){
7455
    free(*arg);
7456
  }
7457
}
7458
7459
static void test_parse_arguments_user_invalid(__attribute__((unused))
7460
					      test_fixture *fixture,
7461
					      __attribute__((unused))
7462
					      gconstpointer
7463
					      user_data){
7464
  char *argv[] = {
7465
    strdup("prgname"),
7466
    strdup("--user"),
7467
    strdup("invalid"),
7468
    NULL };
7469
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7470
7471
  char *agent_directory = NULL;
7472
  __attribute__((cleanup(cleanup_string)))
7473
    char *helper_directory = NULL;
7474
  uid_t user = 0;
7475
  gid_t group = 0;
7476
  __attribute__((cleanup(cleanup_string)))
7477
    char *mandos_argz = NULL;
7478
  size_t mandos_argz_length = 0;
7479
7480
  g_assert_false(parse_arguments_devnull(argc, argv, false,
7481
					 &agent_directory,
7482
					 &helper_directory, &user,
7483
					 &group, &mandos_argz,
7484
					 &mandos_argz_length));
7485
7486
  g_assert_null(helper_directory);
7487
  g_assert_null(agent_directory);
7488
  g_assert_cmpuint((unsigned int)user, ==, 0);
7489
  g_assert_true(group == 0);
7490
  g_assert_null(mandos_argz);
7491
  g_assert_true(mandos_argz_length == 0);
7492
7493
  for(char **arg = argv; *arg != NULL; arg++){
7494
    free(*arg);
7495
  }
7496
}
7497
7498
static
7499
void test_parse_arguments_user_zero_invalid(__attribute__((unused))
7500
					    test_fixture *fixture,
7501
					    __attribute__((unused))
7502
					    gconstpointer user_data){
7503
  char *argv[] = {
7504
    strdup("prgname"),
7505
    strdup("--user"),
7506
    strdup("0"),
7507
    NULL };
7508
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7509
7510
  char *agent_directory = NULL;
7511
  __attribute__((cleanup(cleanup_string)))
7512
    char *helper_directory = NULL;
7513
  uid_t user = 0;
7514
  gid_t group = 0;
7515
  __attribute__((cleanup(cleanup_string)))
7516
    char *mandos_argz = NULL;
7517
  size_t mandos_argz_length = 0;
7518
7519
  g_assert_false(parse_arguments_devnull(argc, argv, false,
7520
					 &agent_directory,
7521
					 &helper_directory, &user,
7522
					 &group, &mandos_argz,
7523
					 &mandos_argz_length));
7524
7525
  g_assert_null(helper_directory);
7526
  g_assert_null(agent_directory);
7527
  g_assert_cmpuint((unsigned int)user, ==, 0);
7528
  g_assert_true(group == 0);
7529
  g_assert_null(mandos_argz);
7530
  g_assert_true(mandos_argz_length == 0);
7531
7532
  for(char **arg = argv; *arg != NULL; arg++){
7533
    free(*arg);
7534
  }
7535
}
7536
7537
static void test_parse_arguments_group(__attribute__((unused))
7538
				       test_fixture *fixture,
7539
				       __attribute__((unused))
7540
				       gconstpointer user_data){
7541
  char *argv[] = {
7542
    strdup("prgname"),
7543
    strdup("--group"),
7544
    strdup("1000"),
7545
    NULL };
7546
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7547
7548
  char *agent_directory = NULL;
7549
  __attribute__((cleanup(cleanup_string)))
7550
    char *helper_directory = NULL;
7551
  uid_t user = 0;
7552
  gid_t group = 0;
7553
  __attribute__((cleanup(cleanup_string)))
7554
    char *mandos_argz = NULL;
7555
  size_t mandos_argz_length = 0;
7556
7557
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7558
				&helper_directory, &user, &group,
7559
				&mandos_argz, &mandos_argz_length));
7560
7561
  g_assert_null(helper_directory);
7562
  g_assert_null(agent_directory);
7563
  g_assert_true(user == 0);
7564
  g_assert_cmpuint((unsigned int)group, ==, 1000);
7565
  g_assert_null(mandos_argz);
7566
  g_assert_true(mandos_argz_length == 0);
7567
7568
  for(char **arg = argv; *arg != NULL; arg++){
7569
    free(*arg);
7570
  }
7571
}
7572
7573
static void test_parse_arguments_group_invalid(__attribute__((unused))
7574
					       test_fixture *fixture,
7575
					       __attribute__((unused))
7576
					       gconstpointer
7577
					       user_data){
7578
  char *argv[] = {
7579
    strdup("prgname"),
7580
    strdup("--group"),
7581
    strdup("invalid"),
7582
    NULL };
7583
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7584
7585
  char *agent_directory = NULL;
7586
  __attribute__((cleanup(cleanup_string)))
7587
    char *helper_directory = NULL;
7588
  uid_t user = 0;
7589
  gid_t group = 0;
7590
  __attribute__((cleanup(cleanup_string)))
7591
    char *mandos_argz = NULL;
7592
  size_t mandos_argz_length = 0;
7593
7594
  g_assert_false(parse_arguments_devnull(argc, argv, false,
7595
					 &agent_directory,
7596
					 &helper_directory, &user,
7597
					 &group, &mandos_argz,
7598
					 &mandos_argz_length));
7599
7600
  g_assert_null(helper_directory);
7601
  g_assert_null(agent_directory);
7602
  g_assert_true(user == 0);
7603
  g_assert_true(group == 0);
7604
  g_assert_null(mandos_argz);
7605
  g_assert_true(mandos_argz_length == 0);
7606
7607
  for(char **arg = argv; *arg != NULL; arg++){
7608
    free(*arg);
7609
  }
7610
}
7611
7612
static
7613
void test_parse_arguments_group_zero_invalid(__attribute__((unused))
7614
					     test_fixture *fixture,
7615
					     __attribute__((unused))
7616
					     gconstpointer user_data){
7617
  char *argv[] = {
7618
    strdup("prgname"),
7619
    strdup("--group"),
7620
    strdup("0"),
7621
    NULL };
7622
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7623
7624
  char *agent_directory = NULL;
7625
  __attribute__((cleanup(cleanup_string)))
7626
    char *helper_directory = NULL;
7627
  uid_t user = 0;
7628
  gid_t group = 0;
7629
  __attribute__((cleanup(cleanup_string)))
7630
    char *mandos_argz = NULL;
7631
  size_t mandos_argz_length = 0;
7632
7633
  g_assert_false(parse_arguments_devnull(argc, argv, false,
7634
					 &agent_directory,
7635
					 &helper_directory, &user,
7636
					 &group, &mandos_argz,
7637
					 &mandos_argz_length));
7638
7639
  g_assert_null(helper_directory);
7640
  g_assert_null(agent_directory);
7641
  g_assert_cmpuint((unsigned int)group, ==, 0);
7642
  g_assert_true(group == 0);
7643
  g_assert_null(mandos_argz);
7644
  g_assert_true(mandos_argz_length == 0);
7645
7646
  for(char **arg = argv; *arg != NULL; arg++){
7647
    free(*arg);
7648
  }
7649
}
7650
7651
static void test_parse_arguments_mandos_noargs(__attribute__((unused))
7652
					       test_fixture *fixture,
7653
					       __attribute__((unused))
7654
					       gconstpointer
7655
					       user_data){
7656
  char *argv[] = {
7657
    strdup("prgname"),
7658
    strdup("mandos-client"),
7659
    NULL };
7660
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7661
7662
  __attribute__((cleanup(cleanup_string)))
7663
    char *agent_directory = NULL;
7664
  __attribute__((cleanup(cleanup_string)))
7665
    char *helper_directory = NULL;
7666
  uid_t user = 0;
7667
  gid_t group = 0;
7668
  __attribute__((cleanup(cleanup_string)))
7669
    char *mandos_argz = NULL;
7670
  size_t mandos_argz_length = 0;
7671
7672
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7673
				&helper_directory, &user, &group,
7674
				&mandos_argz, &mandos_argz_length));
7675
7676
  g_assert_null(agent_directory);
7677
  g_assert_null(helper_directory);
7678
  g_assert_true(user == 0);
7679
  g_assert_true(group == 0);
7680
  g_assert_cmpstr(mandos_argz, ==, "mandos-client");
7681
  g_assert_cmpuint((unsigned int)argz_count(mandos_argz,
7682
					    mandos_argz_length),
7683
		   ==, 1);
7684
7685
  for(char **arg = argv; *arg != NULL; arg++){
7686
    free(*arg);
7687
  }
7688
}
7689
7690
static void test_parse_arguments_mandos_args(__attribute__((unused))
7691
					     test_fixture *fixture,
7692
					     __attribute__((unused))
7693
					     gconstpointer user_data){
7694
  char *argv[] = {
7695
    strdup("prgname"),
7696
    strdup("mandos-client"),
7697
    strdup("one"),
7698
    strdup("two"),
7699
    strdup("three"),
7700
    NULL };
7701
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7702
7703
  __attribute__((cleanup(cleanup_string)))
7704
    char *agent_directory = NULL;
7705
  __attribute__((cleanup(cleanup_string)))
7706
    char *helper_directory = NULL;
7707
  uid_t user = 0;
7708
  gid_t group = 0;
7709
  __attribute__((cleanup(cleanup_string)))
7710
    char *mandos_argz = NULL;
7711
  size_t mandos_argz_length = 0;
7712
7713
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7714
				&helper_directory, &user, &group,
7715
				&mandos_argz, &mandos_argz_length));
7716
7717
  g_assert_null(agent_directory);
7718
  g_assert_null(helper_directory);
7719
  g_assert_true(user == 0);
7720
  g_assert_true(group == 0);
7721
  char *marg = mandos_argz;
7722
  g_assert_cmpstr(marg, ==, "mandos-client");
7723
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7724
  g_assert_cmpstr(marg, ==, "one");
7725
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7726
  g_assert_cmpstr(marg, ==, "two");
7727
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7728
  g_assert_cmpstr(marg, ==, "three");
7729
  g_assert_cmpuint((unsigned int)argz_count(mandos_argz,
7730
					    mandos_argz_length),
7731
		   ==, 4);
7732
7733
  for(char **arg = argv; *arg != NULL; arg++){
7734
    free(*arg);
7735
  }
7736
}
7737
7738
static void test_parse_arguments_all_args(__attribute__((unused))
7739
					  test_fixture *fixture,
7740
					  __attribute__((unused))
7741
					  gconstpointer user_data){
7742
  char *argv[] = {
7743
    strdup("prgname"),
7744
    strdup("--agent-directory"),
7745
    strdup("/tmp"),
7746
    strdup("--helper-directory"),
7747
    strdup("/var/tmp"),
7748
    strdup("--user"),
7749
    strdup("1"),
7750
    strdup("--group"),
7751
    strdup("2"),
7752
    strdup("mandos-client"),
7753
    strdup("one"),
7754
    strdup("two"),
7755
    strdup("three"),
7756
    NULL };
7757
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7758
7759
  __attribute__((cleanup(cleanup_string)))
7760
    char *agent_directory = NULL;
7761
  __attribute__((cleanup(cleanup_string)))
7762
    char *helper_directory = NULL;
7763
  uid_t user = 0;
7764
  gid_t group = 0;
7765
  __attribute__((cleanup(cleanup_string)))
7766
    char *mandos_argz = NULL;
7767
  size_t mandos_argz_length = 0;
7768
7769
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7770
				&helper_directory, &user, &group,
7771
				&mandos_argz, &mandos_argz_length));
7772
7773
  g_assert_cmpstr(agent_directory, ==, "/tmp");
7774
  g_assert_cmpstr(helper_directory, ==, "/var/tmp");
7775
  g_assert_true(user == 1);
7776
  g_assert_true(group == 2);
7777
  char *marg = mandos_argz;
7778
  g_assert_cmpstr(marg, ==, "mandos-client");
7779
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7780
  g_assert_cmpstr(marg, ==, "one");
7781
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7782
  g_assert_cmpstr(marg, ==, "two");
7783
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7784
  g_assert_cmpstr(marg, ==, "three");
7785
  g_assert_cmpuint((unsigned int)argz_count(mandos_argz,
7786
					    mandos_argz_length),
7787
		   ==, 4);
7788
7789
  for(char **arg = argv; *arg != NULL; arg++){
7790
    free(*arg);
7791
  }
7792
}
7793
7794
static void test_parse_arguments_mixed(__attribute__((unused))
7795
				       test_fixture *fixture,
7796
				       __attribute__((unused))
7797
				       gconstpointer user_data){
7798
  char *argv[] = {
7799
    strdup("prgname"),
7800
    strdup("mandos-client"),
7801
    strdup("--user"),
7802
    strdup("1"),
7803
    strdup("one"),
7804
    strdup("--agent-directory"),
7805
    strdup("/tmp"),
7806
    strdup("two"),
7807
    strdup("three"),
7808
    strdup("--helper-directory=/var/tmp"),
7809
    NULL };
7810
  const int argc = (sizeof(argv) / sizeof(char *)) - 1;
7811
7812
  __attribute__((cleanup(cleanup_string)))
7813
    char *agent_directory = NULL;
7814
  __attribute__((cleanup(cleanup_string)))
7815
    char *helper_directory = NULL;
7816
  uid_t user = 0;
7817
  gid_t group = 0;
7818
  __attribute__((cleanup(cleanup_string)))
7819
    char *mandos_argz = NULL;
7820
  size_t mandos_argz_length = 0;
7821
7822
  g_assert_true(parse_arguments(argc, argv, false, &agent_directory,
7823
				&helper_directory, &user, &group,
7824
				&mandos_argz, &mandos_argz_length));
7825
7826
  g_assert_cmpstr(agent_directory, ==, "/tmp");
7827
  g_assert_cmpstr(helper_directory, ==, "/var/tmp");
7828
  g_assert_true(user == 1);
7829
  g_assert_true(group == 0);
7830
  char *marg = mandos_argz;
7831
  g_assert_cmpstr(marg, ==, "mandos-client");
7832
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7833
  g_assert_cmpstr(marg, ==, "one");
7834
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7835
  g_assert_cmpstr(marg, ==, "two");
7836
  marg = argz_next(mandos_argz, mandos_argz_length, marg);
7837
  g_assert_cmpstr(marg, ==, "three");
7838
  g_assert_cmpuint((unsigned int)argz_count(mandos_argz,
7839
					    mandos_argz_length),
7840
		   ==, 4);
7841
7842
  for(char **arg = argv; *arg != NULL; arg++){
7843
    free(*arg);
7844
  }
7845
}
7846
7847
/* End of tests section */
7848

7849
/* Test boilerplate section; New tests should be added to the test
7850
   suite definition here, in the "run_tests" function.
7851
7852
   Finally, this section also contains the should_only_run_tests()
7853
   function used by main() for deciding if tests should be run or to
7854
   start normally. */
7855
7856
__attribute__((cold))
7857
static bool run_tests(int argc, char *argv[]){
7858
  g_test_init(&argc, &argv, NULL);
7859
7860
  /* A macro to add a test with no setup or teardown functions */
7861
#define test_add(testpath, testfunc)			\
7862
  do {							\
7863
    g_test_add((testpath), test_fixture, NULL, NULL,	\
7864
	       (testfunc), NULL);			\
7865
  } while(false)
7866
7867
  /* Test the signal-related functions first, since some other tests
7868
     depend on these functions in their setups and teardowns */
7869
  test_add("/signal-handling/setup", test_setup_signal_handler);
7870
  test_add("/signal-handling/restore", test_restore_signal_handler);
7871
  test_add("/signal-handling/block", test_block_sigchld);
7872
  test_add("/signal-handling/restore-sigmask", test_restore_sigmask);
7873
7874
  /* Regular non-signal-related tests; these use no setups or
7875
     teardowns */
7876
  test_add("/parse_arguments/noargs", test_parse_arguments_noargs);
7877
  test_add("/parse_arguments/invalid", test_parse_arguments_invalid);
7878
  test_add("/parse_arguments/long-dir",
7879
	   test_parse_arguments_long_dir);
7880
  test_add("/parse_arguments/short-dir",
7881
	   test_parse_arguments_short_dir);
7882
  test_add("/parse_arguments/helper-directory",
7883
	   test_parse_arguments_helper_directory);
7884
  test_add("/parse_arguments/plugin-helper-dir",
7885
	   test_parse_arguments_plugin_helper_dir);
7886
  test_add("/parse_arguments/user", test_parse_arguments_user);
7887
  test_add("/parse_arguments/user-invalid",
7888
  	   test_parse_arguments_user_invalid);
7889
  test_add("/parse_arguments/user-zero-invalid",
7890
  	   test_parse_arguments_user_zero_invalid);
7891
  test_add("/parse_arguments/group", test_parse_arguments_group);
7892
  test_add("/parse_arguments/group-invalid",
7893
  	   test_parse_arguments_group_invalid);
7894
  test_add("/parse_arguments/group-zero-invalid",
7895
  	   test_parse_arguments_group_zero_invalid);
7896
  test_add("/parse_arguments/mandos-noargs",
7897
	   test_parse_arguments_mandos_noargs);
7898
  test_add("/parse_arguments/mandos-args",
7899
	   test_parse_arguments_mandos_args);
7900
  test_add("/parse_arguments/all-args",
7901
	   test_parse_arguments_all_args);
7902
  test_add("/parse_arguments/mixed", test_parse_arguments_mixed);
7903
  test_add("/queue/create", test_create_queue);
7904
  test_add("/queue/add", test_add_to_queue);
237.7.753 by teddy at recompile
Use reallocarray() if available, or check for overflow
7905
  test_add("/queue/add/overflow", test_add_to_queue_overflow);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
7906
  test_add("/queue/has_question/empty",
7907
	   test_queue_has_question_empty);
7908
  test_add("/queue/has_question/false",
7909
	   test_queue_has_question_false);
7910
  test_add("/queue/has_question/true", test_queue_has_question_true);
7911
  test_add("/queue/has_question/false2",
7912
	   test_queue_has_question_false2);
7913
  test_add("/queue/has_question/true2",
7914
	   test_queue_has_question_true2);
7915
  test_add("/buffer/cleanup", test_cleanup_buffer);
7916
  test_add("/string_set/net-set-contains-nothing",
7917
	   test_string_set_new_set_contains_nothing);
7918
  test_add("/string_set/with-added-string-contains-it",
7919
	   test_string_set_with_added_string_contains_it);
7920
  test_add("/string_set/cleared-does-not-contain-string",
7921
	   test_string_set_cleared_does_not_contain_str);
7922
  test_add("/string_set/swap/one-with-empty",
7923
	   test_string_set_swap_one_with_empty);
7924
  test_add("/string_set/swap/empty-with-one",
7925
	   test_string_set_swap_empty_with_one);
7926
  test_add("/string_set/swap/one-with-one",
7927
	   test_string_set_swap_one_with_one);
7928
7929
  /* A macro to add a test using the setup and teardown functions */
7930
#define test_add_st(path, func)					\
7931
  do {								\
7932
    g_test_add((path), test_fixture, NULL, test_setup, (func),	\
7933
	       test_teardown);					\
7934
  } while(false)
7935
7936
  /* Signal-related tests; these use setups and teardowns which
7937
     establish, during each test run, a signal handler for, and a
7938
     signal mask blocking, the SIGCHLD signal, just like main() */
7939
  test_add_st("/wait_for_event/timeout", test_wait_for_event_timeout);
7940
  test_add_st("/wait_for_event/event", test_wait_for_event_event);
7941
  test_add_st("/wait_for_event/sigchld", test_wait_for_event_sigchld);
7942
  test_add_st("/run_queue/zeroes-next-run",
7943
	      test_run_queue_zeroes_next_run);
7944
  test_add_st("/run_queue/clears-cancelled_filenames",
7945
	      test_run_queue_clears_cancelled_filenames);
7946
  test_add_st("/run_queue/skips-cancelled-filenames",
7947
  	      test_run_queue_skips_cancelled_filenames);
7948
  test_add_st("/run_queue/one-task", test_run_queue_one_task);
7949
  test_add_st("/run_queue/two-tasks", test_run_queue_two_tasks);
7950
  test_add_st("/run_queue/two-tasks/quit",
7951
	      test_run_queue_two_tasks_quit);
7952
  test_add_st("/run_queue/two-tasks-cleanup",
7953
	      test_run_queue_two_tasks_cleanup);
7954
  test_add_st("/task-creators/start_mandos_client",
7955
	      test_start_mandos_client);
7956
  test_add_st("/task-creators/start_mandos_client/execv",
7957
	      test_start_mandos_client_execv);
7958
  test_add_st("/task-creators/start_mandos_client/suid/euid",
7959
	      test_start_mandos_client_suid_euid);
7960
  test_add_st("/task-creators/start_mandos_client/suid/egid",
7961
  	      test_start_mandos_client_suid_egid);
7962
  test_add_st("/task-creators/start_mandos_client/suid/ruid",
7963
  	      test_start_mandos_client_suid_ruid);
7964
  test_add_st("/task-creators/start_mandos_client/suid/rgid",
7965
  	      test_start_mandos_client_suid_rgid);
7966
  test_add_st("/task-creators/start_mandos_client/read",
7967
	      test_start_mandos_client_read);
7968
  test_add_st("/task-creators/start_mandos_client/helper-directory",
7969
	      test_start_mandos_client_helper_directory);
7970
  test_add_st("/task-creators/start_mandos_client/sigmask",
7971
	      test_start_mandos_client_sigmask);
7972
  test_add_st("/task/wait_for_mandos_client_exit/badpid",
7973
	      test_wait_for_mandos_client_exit_badpid);
7974
  test_add_st("/task/wait_for_mandos_client_exit/noexit",
7975
	      test_wait_for_mandos_client_exit_noexit);
7976
  test_add_st("/task/wait_for_mandos_client_exit/success",
7977
	      test_wait_for_mandos_client_exit_success);
7978
  test_add_st("/task/wait_for_mandos_client_exit/failure",
7979
	      test_wait_for_mandos_client_exit_failure);
7980
  test_add_st("/task/wait_for_mandos_client_exit/killed",
7981
	      test_wait_for_mandos_client_exit_killed);
7982
  test_add_st("/task/read_mandos_client_output/readerror",
7983
	      test_read_mandos_client_output_readerror);
7984
  test_add_st("/task/read_mandos_client_output/nodata",
7985
	      test_read_mandos_client_output_nodata);
7986
  test_add_st("/task/read_mandos_client_output/eof",
7987
	      test_read_mandos_client_output_eof);
7988
  test_add_st("/task/read_mandos_client_output/once",
7989
	      test_read_mandos_client_output_once);
7990
  test_add_st("/task/read_mandos_client_output/malloc",
7991
	      test_read_mandos_client_output_malloc);
7992
  test_add_st("/task/read_mandos_client_output/append",
7993
	      test_read_mandos_client_output_append);
7994
  test_add_st("/task-creators/add_inotify_dir_watch",
7995
	      test_add_inotify_dir_watch);
7996
  test_add_st("/task-creators/add_inotify_dir_watch/fail",
7997
	      test_add_inotify_dir_watch_fail);
237.7.690 by Teddy Hogeborn
dracut-module/password-agent.c: Require agent directory
7998
  test_add_st("/task-creators/add_inotify_dir_watch/not-a-directory",
7999
	      test_add_inotify_dir_watch_nondir);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8000
  test_add_st("/task-creators/add_inotify_dir_watch/EAGAIN",
8001
	      test_add_inotify_dir_watch_EAGAIN);
8002
  test_add_st("/task-creators/add_inotify_dir_watch/IN_CLOSE_WRITE",
8003
	      test_add_inotify_dir_watch_IN_CLOSE_WRITE);
8004
  test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_TO",
8005
	      test_add_inotify_dir_watch_IN_MOVED_TO);
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
8006
  test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_FROM",
8007
	      test_add_inotify_dir_watch_IN_MOVED_FROM);
237.7.689 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Ignore deleted files
8008
  test_add_st("/task-creators/add_inotify_dir_watch/IN_EXCL_UNLINK",
8009
	      test_add_inotify_dir_watch_IN_EXCL_UNLINK);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8010
  test_add_st("/task-creators/add_inotify_dir_watch/IN_DELETE",
8011
	      test_add_inotify_dir_watch_IN_DELETE);
8012
  test_add_st("/task/read_inotify_event/readerror",
8013
	      test_read_inotify_event_readerror);
8014
  test_add_st("/task/read_inotify_event/bad-epoll",
8015
	      test_read_inotify_event_bad_epoll);
8016
  test_add_st("/task/read_inotify_event/nodata",
8017
	      test_read_inotify_event_nodata);
8018
  test_add_st("/task/read_inotify_event/eof",
8019
	      test_read_inotify_event_eof);
8020
  test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE",
8021
	      test_read_inotify_event_IN_CLOSE_WRITE);
8022
  test_add_st("/task/read_inotify_event/IN_MOVED_TO",
8023
	      test_read_inotify_event_IN_MOVED_TO);
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
8024
  test_add_st("/task/read_inotify_event/IN_MOVED_FROM",
8025
	      test_read_inotify_event_IN_MOVED_FROM);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8026
  test_add_st("/task/read_inotify_event/IN_DELETE",
8027
	      test_read_inotify_event_IN_DELETE);
8028
  test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE/badname",
8029
	      test_read_inotify_event_IN_CLOSE_WRITE_badname);
8030
  test_add_st("/task/read_inotify_event/IN_MOVED_TO/badname",
8031
	      test_read_inotify_event_IN_MOVED_TO_badname);
237.7.688 by Teddy Hogeborn
dracut-module/password-agent.c: Bug fix: Handle IN_MOVED_FROM
8032
  test_add_st("/task/read_inotify_event/IN_MOVED_FROM/badname",
8033
	      test_read_inotify_event_IN_MOVED_FROM_badname);
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8034
  test_add_st("/task/read_inotify_event/IN_DELETE/badname",
8035
	      test_read_inotify_event_IN_DELETE_badname);
8036
  test_add_st("/task/open_and_parse_question/ENOENT",
8037
	      test_open_and_parse_question_ENOENT);
8038
  test_add_st("/task/open_and_parse_question/EIO",
8039
	      test_open_and_parse_question_EIO);
8040
  test_add_st("/task/open_and_parse_question/parse-error",
8041
	      test_open_and_parse_question_parse_error);
8042
  test_add_st("/task/open_and_parse_question/nosocket",
8043
	      test_open_and_parse_question_nosocket);
8044
  test_add_st("/task/open_and_parse_question/badsocket",
8045
	      test_open_and_parse_question_badsocket);
8046
  test_add_st("/task/open_and_parse_question/nopid",
8047
	      test_open_and_parse_question_nopid);
8048
  test_add_st("/task/open_and_parse_question/badpid",
8049
	      test_open_and_parse_question_badpid);
8050
  test_add_st("/task/open_and_parse_question/noexist_pid",
8051
	      test_open_and_parse_question_noexist_pid);
8052
  test_add_st("/task/open_and_parse_question/no-notafter",
8053
	      test_open_and_parse_question_no_notafter);
8054
  test_add_st("/task/open_and_parse_question/bad-notafter",
8055
	      test_open_and_parse_question_bad_notafter);
8056
  test_add_st("/task/open_and_parse_question/notafter-0",
8057
	      test_open_and_parse_question_notafter_0);
8058
  test_add_st("/task/open_and_parse_question/notafter-1",
8059
	      test_open_and_parse_question_notafter_1);
8060
  test_add_st("/task/open_and_parse_question/notafter-1-1",
8061
	      test_open_and_parse_question_notafter_1_1);
8062
  test_add_st("/task/open_and_parse_question/notafter-1-2",
8063
	      test_open_and_parse_question_notafter_1_2);
8064
  test_add_st("/task/open_and_parse_question/equal-notafter",
8065
	      test_open_and_parse_question_equal_notafter);
8066
  test_add_st("/task/open_and_parse_question/late-notafter",
8067
	      test_open_and_parse_question_late_notafter);
8068
  test_add_st("/task/cancel_old_question/0-1-2",
8069
	      test_cancel_old_question_0_1_2);
8070
  test_add_st("/task/cancel_old_question/0-2-1",
8071
	      test_cancel_old_question_0_2_1);
8072
  test_add_st("/task/cancel_old_question/1-2-3",
8073
	      test_cancel_old_question_1_2_3);
8074
  test_add_st("/task/cancel_old_question/1-3-2",
8075
	      test_cancel_old_question_1_3_2);
8076
  test_add_st("/task/cancel_old_question/2-1-3",
8077
	      test_cancel_old_question_2_1_3);
8078
  test_add_st("/task/cancel_old_question/2-3-1",
8079
	      test_cancel_old_question_2_3_1);
8080
  test_add_st("/task/cancel_old_question/3-1-2",
8081
	      test_cancel_old_question_3_1_2);
8082
  test_add_st("/task/cancel_old_question/3-2-1",
8083
	      test_cancel_old_question_3_2_1);
8084
  test_add_st("/task/connect_question_socket/name-too-long",
8085
	      test_connect_question_socket_name_too_long);
8086
  test_add_st("/task/connect_question_socket/connect-fail",
8087
	      test_connect_question_socket_connect_fail);
8088
  test_add_st("/task/connect_question_socket/bad-epoll",
8089
	      test_connect_question_socket_bad_epoll);
8090
  test_add_st("/task/connect_question_socket/usable",
8091
	      test_connect_question_socket_usable);
8092
  test_add_st("/task/send_password_to_socket/client-not-exited",
8093
	      test_send_password_to_socket_client_not_exited);
8094
  test_add_st("/task/send_password_to_socket/password-not-read",
8095
	      test_send_password_to_socket_password_not_read);
8096
  test_add_st("/task/send_password_to_socket/EMSGSIZE",
8097
	      test_send_password_to_socket_EMSGSIZE);
8098
  test_add_st("/task/send_password_to_socket/retry",
8099
	      test_send_password_to_socket_retry);
8100
  test_add_st("/task/send_password_to_socket/bad-epoll",
8101
	      test_send_password_to_socket_bad_epoll);
8102
  test_add_st("/task/send_password_to_socket/null-password",
8103
	      test_send_password_to_socket_null_password);
8104
  test_add_st("/task/send_password_to_socket/empty-password",
8105
	      test_send_password_to_socket_empty_password);
8106
  test_add_st("/task/send_password_to_socket/empty-str-password",
8107
	      test_send_password_to_socket_empty_str_pass);
8108
  test_add_st("/task/send_password_to_socket/text-password",
8109
	      test_send_password_to_socket_text_password);
8110
  test_add_st("/task/send_password_to_socket/binary-password",
8111
	      test_send_password_to_socket_binary_password);
8112
  test_add_st("/task/send_password_to_socket/nuls-in-password",
8113
	      test_send_password_to_socket_nuls_in_password);
8114
  test_add_st("/task-creators/add_existing_questions/ENOENT",
8115
	      test_add_existing_questions_ENOENT);
8116
  test_add_st("/task-creators/add_existing_questions/no-questions",
8117
	      test_add_existing_questions_no_questions);
8118
  test_add_st("/task-creators/add_existing_questions/one-question",
8119
	      test_add_existing_questions_one_question);
8120
  test_add_st("/task-creators/add_existing_questions/two-questions",
8121
	      test_add_existing_questions_two_questions);
8122
  test_add_st("/task-creators/add_existing_questions/non-questions",
8123
	      test_add_existing_questions_non_questions);
8124
  test_add_st("/task-creators/add_existing_questions/both-types",
8125
	      test_add_existing_questions_both_types);
8126
8127
  return g_test_run() == 0;
8128
}
8129
8130
static bool should_only_run_tests(int *argc_p, char **argv_p[]){
8131
  GOptionContext *context = g_option_context_new("");
8132
8133
  g_option_context_set_help_enabled(context, FALSE);
8134
  g_option_context_set_ignore_unknown_options(context, TRUE);
8135
237.7.747 by Teddy Hogeborn
Minor code cleanup by renaming a local variable
8136
  gboolean should_run_tests = FALSE;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8137
  GOptionEntry entries[] = {
8138
    { "test", 0, 0, G_OPTION_ARG_NONE,
237.7.747 by Teddy Hogeborn
Minor code cleanup by renaming a local variable
8139
      &should_run_tests, "Run tests", NULL },
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8140
    { NULL }
8141
  };
8142
  g_option_context_add_main_entries(context, entries, NULL);
8143
8144
  GError *error = NULL;
8145
8146
  if(g_option_context_parse(context, argc_p, argv_p, &error) != TRUE){
8147
    g_option_context_free(context);
8148
    g_error("Failed to parse options: %s", error->message);
8149
  }
8150
8151
  g_option_context_free(context);
237.7.747 by Teddy Hogeborn
Minor code cleanup by renaming a local variable
8152
  return should_run_tests != FALSE;
237.7.675 by Teddy Hogeborn
Add dracut(8) support
8153
}