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