/mandos/release

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