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