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