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