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