1
/* -*- mode: c; coding: utf-8; after-save-hook: (lambda () (let* ((find-build-directory (lambda (try-directory &optional base-directory) (let ((base-directory (or base-directory try-directory))) (cond ((equal try-directory "/") base-directory) ((file-readable-p (concat (file-name-as-directory try-directory) "Makefile")) try-directory) ((funcall find-build-directory (directory-file-name (file-name-directory try-directory)) base-directory)))))) (build-directory (funcall find-build-directory (buffer-file-name))) (local-build-directory (if (fboundp 'file-local-name) (file-local-name build-directory) (or (file-remote-p build-directory 'localname) build-directory))) (command (file-relative-name (file-name-sans-extension (buffer-file-name)) build-directory))) (pcase (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (let ((qbdir (shell-quote-argument local-build-directory)) (qcmd (shell-quote-argument command))) (format "cd %s && CFLAGS=-Werror make --silent %s && %s --test --verbose" qbdir qcmd qcmd)) nil "*Test*")) (0 (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)))) (_ (with-current-buffer "*Test*" (compilation-mode) (cd-absolute build-directory)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); -*- */
3
* Mandos password agent - Simple password agent to run Mandos client
5
* Copyright © 2019 Teddy Hogeborn
6
* Copyright © 2019 Björn Påhlsson
8
* This file is part of Mandos.
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.
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.
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/>.
23
* Contact the authors at <mandos@recompile.se>.
27
#include <inttypes.h> /* uintmax_t, PRIuMAX, PRIdMAX,
28
intmax_t, uint32_t, SCNx32,
30
#include <stddef.h> /* size_t */
31
#include <sys/types.h> /* pid_t, uid_t, gid_t, getuid(),
33
#include <stdbool.h> /* bool, true, false */
34
#include <signal.h> /* struct sigaction, sigset_t,
35
sigemptyset(), sigaddset(),
36
SIGCHLD, pthread_sigmask(),
37
SIG_BLOCK, SIG_SETMASK, SA_RESTART,
38
SA_NOCLDSTOP, sigfillset(), kill(),
39
SIGTERM, sigdelset(), SIGKILL,
40
NSIG, sigismember(), SA_ONSTACK,
41
SIG_DFL, SIG_IGN, SIGINT, SIGQUIT,
42
SIGHUP, SIGSTOP, SIG_UNBLOCK */
43
#include <stdlib.h> /* EXIT_SUCCESS, EXIT_FAILURE,
44
malloc(), free(), strtoumax(),
45
realloc(), setenv(), calloc(),
46
mkdtemp(), mkostemp() */
47
#include <iso646.h> /* not, or, and, xor */
48
#include <error.h> /* error() */
49
#include <sysexits.h> /* EX_USAGE, EX_OSERR, EX_OSFILE */
50
#include <errno.h> /* errno, error_t, EACCES,
51
ENAMETOOLONG, ENOENT, EEXIST,
52
ECHILD, EPERM, ENOMEM, EAGAIN,
53
EINTR, ENOBUFS, EADDRINUSE,
54
ECONNREFUSED, ECONNRESET,
55
ETOOMANYREFS, EMSGSIZE, EBADF,
57
#include <string.h> /* strdup(), memcpy(),
58
explicit_bzero(), memset(),
59
strcmp(), strlen(), strncpy(),
60
memcmp(), basename() */
61
#include <argz.h> /* argz_create(), argz_count(),
62
argz_extract(), argz_next(),
64
#include <sys/epoll.h> /* epoll_create1(), EPOLL_CLOEXEC,
65
epoll_ctl(), EPOLL_CTL_ADD,
66
struct epoll_event, EPOLLIN,
69
#include <time.h> /* struct timespec, clock_gettime(),
71
#include <argp.h> /* struct argp_option, OPTION_HIDDEN,
72
OPTION_ALIAS, struct argp_state,
73
ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
74
struct argp, argp_parse(),
76
#include <unistd.h> /* uid_t, gid_t, close(), pipe2(),
77
fork(), _exit(), dup2(),
78
STDOUT_FILENO, setresgid(),
79
setresuid(), execv(), ssize_t,
80
read(), dup3(), getuid(), dup(),
81
STDERR_FILENO, pause(), write(),
82
rmdir(), unlink(), getpid() */
83
#include <sys/mman.h> /* munlock(), mlock() */
84
#include <fcntl.h> /* O_CLOEXEC, O_NONBLOCK, fcntl(),
85
F_GETFD, F_GETFL, FD_CLOEXEC,
86
open(), O_WRONLY, O_RDONLY */
87
#include <sys/wait.h> /* waitpid(), WNOHANG, WIFEXITED(),
89
#include <limits.h> /* PIPE_BUF, NAME_MAX, INT_MAX */
90
#include <sys/inotify.h> /* inotify_init1(), IN_NONBLOCK,
91
IN_CLOEXEC, inotify_add_watch(),
92
IN_CLOSE_WRITE, IN_MOVED_TO,
93
IN_DELETE, struct inotify_event */
94
#include <fnmatch.h> /* fnmatch(), FNM_FILE_NAME */
95
#include <stdio.h> /* asprintf(), FILE, fopen(),
96
getline(), sscanf(), feof(),
97
ferror(), fclose(), stderr,
98
rename(), fdopen(), fprintf(),
100
#include <glib.h> /* GKeyFile, g_key_file_free(), g_key_file_new(),
101
GError, g_key_file_load_from_file(),
102
G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT,
103
g_key_file_get_string(), guint64,
104
g_key_file_get_uint64(),
105
G_KEY_FILE_ERROR_KEY_NOT_FOUND, gconstpointer,
106
g_assert_true(), g_assert_nonnull(),
107
g_assert_null(), g_assert_false(),
108
g_assert_cmpint(), g_assert_cmpuint(),
109
g_test_skip(), g_assert_cmpstr(),
110
g_test_init(), g_test_add(), g_test_run(),
111
GOptionContext, g_option_context_new(),
112
g_option_context_set_help_enabled(), FALSE,
113
g_option_context_set_ignore_unknown_options(),
114
gboolean, GOptionEntry, G_OPTION_ARG_NONE,
115
g_option_context_add_main_entries(),
116
g_option_context_parse(),
117
g_option_context_free(), g_error() */
118
#include <sys/un.h> /* struct sockaddr_un, SUN_LEN */
119
#include <sys/socket.h> /* AF_LOCAL, socket(), PF_LOCAL,
120
SOCK_DGRAM, SOCK_NONBLOCK,
121
SOCK_CLOEXEC, connect(),
122
struct sockaddr, socklen_t,
123
shutdown(), SHUT_RD, send(),
124
MSG_NOSIGNAL, bind(), recv(),
126
#include <glob.h> /* globfree(), glob_t, glob(),
127
GLOB_ERR, GLOB_NOSORT, GLOB_MARK,
128
GLOB_ABORTED, GLOB_NOMATCH,
131
/* End of includes */
133
/* Start of declarations of private types and functions */
135
/* microseconds of CLOCK_MONOTONIC absolute time; 0 means unset */
136
typedef uintmax_t mono_microsecs;
138
/* "task_queue" - A queue of tasks to be run */
140
struct task_struct *tasks; /* Tasks in this queue */
141
size_t length; /* Number of tasks */
142
/* Memory allocated for "tasks", in bytes */
144
/* Time when this queue should be run, at the latest */
145
mono_microsecs next_run;
146
} __attribute__((designated_init)) task_queue;
148
/* "func_type" - A function type for task functions
150
I.e. functions for the code which runs when a task is run, all have
152
typedef void (task_func) (const struct task_struct,
154
__attribute__((nonnull));
156
/* "buffer" - A data buffer for a growing array of bytes
158
Used for the "password" variable */
163
} __attribute__((designated_init)) buffer;
165
/* "string_set" - A set type which can contain strings
167
Used by the "cancelled_filenames" variable */
169
char *argz; /* Do not access these except in */
170
size_t argz_len; /* the string_set_* functions */
171
} __attribute__((designated_init)) string_set;
173
/* "task_context" - local variables for tasks
175
This data structure distinguishes between different tasks which are
176
using the same function. This data structure is passed to every
177
task function when each task is run.
179
Note that not every task uses every struct member. */
180
typedef struct task_struct {
181
task_func *const func; /* The function run by this task */
182
char *const question_filename; /* The question file */
183
const pid_t pid; /* Mandos client process ID */
184
const int epoll_fd; /* The epoll set file descriptor */
185
bool *const quit_now; /* Set to true on fatal errors */
186
const int fd; /* General purpose file descriptor */
187
bool *const mandos_client_exited; /* Set true when client exits */
188
buffer *const password; /* As read from client process */
189
bool *const password_is_read; /* "password" is done growing */
190
char *filename; /* General purpose file name */
191
/* A set of strings of all the file names of questions which have
192
been cancelled for any reason; tasks pertaining to these question
193
files should not be run */
194
string_set *const cancelled_filenames;
195
const mono_microsecs notafter; /* "NotAfter" from question file */
196
/* Updated before each queue run; is compared with queue.next_run */
197
const mono_microsecs *const current_time;
198
} __attribute__((designated_init)) task_context;
200
/* Declare all our functions here so we can define them in any order
201
below. Note: test functions are *not* declared here, they are
202
declared in the test section. */
203
__attribute__((warn_unused_result))
204
static bool should_only_run_tests(int *, char **[]);
205
__attribute__((warn_unused_result, cold))
206
static bool run_tests(int, char *[]);
207
static void handle_sigchld(__attribute__((unused)) int sig){}
208
__attribute__((warn_unused_result, malloc))
209
task_queue *create_queue(void);
210
__attribute__((nonnull, warn_unused_result))
211
bool add_to_queue(task_queue *const, const task_context);
212
__attribute__((nonnull))
213
void cleanup_task(const task_context *const);
214
__attribute__((nonnull))
215
void cleanup_queue(task_queue *const *const);
216
__attribute__((pure, nonnull, warn_unused_result))
217
bool queue_has_question(const task_queue *const);
218
__attribute__((nonnull))
219
void cleanup_close(const int *const);
220
__attribute__((nonnull))
221
void cleanup_string(char *const *const);
222
__attribute__((nonnull))
223
void cleanup_buffer(buffer *const);
224
__attribute__((pure, nonnull, warn_unused_result))
225
bool string_set_contains(const string_set, const char *const);
226
__attribute__((nonnull, warn_unused_result))
227
bool string_set_add(string_set *const, const char *const);
228
__attribute__((nonnull))
229
void string_set_clear(string_set *);
230
void string_set_swap(string_set *const, string_set *const);
231
__attribute__((nonnull, warn_unused_result))
232
bool start_mandos_client(task_queue *const, const int, bool *const,
233
bool *const, buffer *const, bool *const,
234
const struct sigaction *const,
235
const sigset_t, const char *const,
236
const uid_t, const gid_t,
237
const char *const *const);
238
__attribute__((nonnull))
239
task_func wait_for_mandos_client_exit;
240
__attribute__((nonnull))
241
task_func read_mandos_client_output;
242
__attribute__((warn_unused_result))
243
bool add_inotify_dir_watch(task_queue *const, const int, bool *const,
244
buffer *const, const char *const,
245
string_set *, const mono_microsecs *const,
246
bool *const, bool *const);
247
__attribute__((nonnull))
248
task_func read_inotify_event;
249
__attribute__((nonnull))
250
task_func open_and_parse_question;
251
__attribute__((nonnull))
252
task_func cancel_old_question;
253
__attribute__((nonnull))
254
task_func connect_question_socket;
255
__attribute__((nonnull))
256
task_func send_password_to_socket;
257
__attribute__((warn_unused_result))
258
bool add_existing_questions(task_queue *const, const int,
259
buffer *const, string_set *,
260
const mono_microsecs *const,
261
bool *const, bool *const,
263
__attribute__((nonnull, warn_unused_result))
264
bool wait_for_event(const int, const mono_microsecs,
265
const mono_microsecs);
266
bool run_queue(task_queue **const, string_set *const, bool *const);
267
bool clear_all_fds_from_epoll_set(const int);
268
mono_microsecs get_current_time(void);
269
__attribute__((nonnull, warn_unused_result))
270
bool setup_signal_handler(struct sigaction *const);
271
__attribute__((nonnull))
272
bool restore_signal_handler(const struct sigaction *const);
273
__attribute__((nonnull, warn_unused_result))
274
bool block_sigchld(sigset_t *const);
275
__attribute__((nonnull))
276
bool restore_sigmask(const sigset_t *const);
277
__attribute__((nonnull))
278
bool parse_arguments(int, char *[], const bool, char **, char **,
279
uid_t *const , gid_t *const, char **, size_t *);
281
/* End of declarations of private types and functions */
283
/* Start of "main" section; this section LACKS TESTS!
285
Code here should be as simple as possible. */
287
/* These are required to be global by Argp */
288
const char *argp_program_version = "password-agent " VERSION;
289
const char *argp_program_bug_address = "<mandos@recompile.se>";
291
int main(int argc, char *argv[]){
293
/* If the --test option is passed, skip all normal operations and
294
instead only run the run_tests() function, which also does all
295
its own option parsing, so we don't have to do anything here. */
296
if(should_only_run_tests(&argc, &argv)){
297
if(run_tests(argc, argv)){
298
return EXIT_SUCCESS; /* All tests successful */
300
return EXIT_FAILURE; /* Some test(s) failed */
303
__attribute__((cleanup(cleanup_string)))
304
char *agent_directory = NULL;
306
__attribute__((cleanup(cleanup_string)))
307
char *helper_directory = NULL;
312
__attribute__((cleanup(cleanup_string)))
313
char *mandos_argz = NULL;
314
size_t mandos_argz_length = 0;
316
if(not parse_arguments(argc, argv, true, &agent_directory,
317
&helper_directory, &user, &group,
318
&mandos_argz, &mandos_argz_length)){
319
/* This should never happen, since "true" is passed as the third
320
argument to parse_arguments() above, which should make
321
argp_parse() call exit() if any parsing error occurs. */
322
error(EX_USAGE, errno, "Failed to parse arguments");
325
const char default_agent_directory[] = "/run/systemd/ask-password";
326
const char default_helper_directory[]
327
= "/lib/mandos/plugin-helpers";
328
const char *const default_argv[]
329
= {"/lib/mandos/plugins.d/mandos-client", NULL };
331
/* Set variables to default values if unset */
332
if(agent_directory == NULL){
333
agent_directory = strdup(default_agent_directory);
334
if(agent_directory == NULL){
335
error(EX_OSERR, errno, "Failed strdup()");
338
if(helper_directory == NULL){
339
helper_directory = strdup(default_helper_directory);
340
if(helper_directory == NULL){
341
error(EX_OSERR, errno, "Failed strdup()");
345
user = 65534; /* nobody */
348
group = 65534; /* nogroup */
350
/* If parse_opt did not create an argz vector, create one with
352
if(mandos_argz == NULL){
354
#pragma GCC diagnostic push
355
/* argz_create() takes a non-const argv for some unknown reason -
356
argz_create() isn't modifying the strings, just copying them.
357
Therefore, this cast to non-const should be safe. */
358
#pragma GCC diagnostic ignored "-Wcast-qual"
360
errno = argz_create((char *const *)default_argv, &mandos_argz,
361
&mandos_argz_length);
363
#pragma GCC diagnostic pop
366
error(EX_OSERR, errno, "Failed argz_create()");
369
/* Use argz vector to create a normal argv, usable by execv() */
371
char **mandos_argv = malloc((argz_count(mandos_argz,
373
+ 1) * sizeof(char *));
374
if(mandos_argv == NULL){
375
error_t saved_errno = errno;
377
error(EX_OSERR, saved_errno, "Failed malloc()");
379
argz_extract(mandos_argz, mandos_argz_length, mandos_argv);
381
sigset_t orig_sigmask;
382
if(not block_sigchld(&orig_sigmask)){
386
struct sigaction old_sigchld_action;
387
if(not setup_signal_handler(&old_sigchld_action)){
391
mono_microsecs current_time = 0;
393
bool mandos_client_exited = false;
394
bool quit_now = false;
395
__attribute__((cleanup(cleanup_close)))
396
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
398
error(EX_OSERR, errno, "Failed to create epoll set fd");
400
__attribute__((cleanup(cleanup_queue)))
401
task_queue *queue = create_queue();
403
error(EX_OSERR, errno, "Failed to create task queue");
406
__attribute__((cleanup(cleanup_buffer)))
407
buffer password = {};
408
bool password_is_read = false;
410
__attribute__((cleanup(string_set_clear)))
411
string_set cancelled_filenames = {};
413
/* Add tasks to queue */
414
if(not start_mandos_client(queue, epoll_fd, &mandos_client_exited,
415
&quit_now, &password, &password_is_read,
416
&old_sigchld_action, orig_sigmask,
417
helper_directory, user, group,
418
(const char *const *)mandos_argv)){
419
return EX_OSERR; /* Error has already been printed */
421
/* These variables were only for start_mandos_client() and are not
426
if(not add_inotify_dir_watch(queue, epoll_fd, &quit_now, &password,
427
agent_directory, &cancelled_filenames,
428
¤t_time, &mandos_client_exited,
430
switch(errno){ /* Error has already been printed */
439
if(not add_existing_questions(queue, epoll_fd, &password,
440
&cancelled_filenames, ¤t_time,
441
&mandos_client_exited,
442
&password_is_read, agent_directory)){
443
return EXIT_FAILURE; /* Error has already been printed */
448
current_time = get_current_time();
449
if(not wait_for_event(epoll_fd, queue->next_run, current_time)){
450
const error_t saved_errno = errno;
451
error(EXIT_FAILURE, saved_errno, "Failure while waiting for"
455
current_time = get_current_time();
456
if(not run_queue(&queue, &cancelled_filenames, &quit_now)){
457
const error_t saved_errno = errno;
458
error(EXIT_FAILURE, saved_errno, "Failure while running queue");
461
/* When no tasks about questions are left in the queue, break out
462
of the loop (and implicitly exit the program) */
463
} while(queue_has_question(queue));
465
restore_signal_handler(&old_sigchld_action);
466
restore_sigmask(&orig_sigmask);
471
__attribute__((warn_unused_result))
472
mono_microsecs get_current_time(void){
473
struct timespec currtime;
474
if(clock_gettime(CLOCK_MONOTONIC, &currtime) != 0){
475
error(0, errno, "Failed to get current time");
478
return ((mono_microsecs)currtime.tv_sec * 1000000) /* seconds */
479
+ ((mono_microsecs)currtime.tv_nsec / 1000); /* nanoseconds */
482
/* End of "main" section */
484
/* Start of regular code section; ALL this code has tests */
486
__attribute__((nonnull))
487
bool parse_arguments(int argc, char *argv[], const bool exit_failure,
488
char **agent_directory, char **helper_directory,
489
uid_t *const user, gid_t *const group,
490
char **mandos_argz, size_t *mandos_argz_length){
492
const struct argp_option options[] = {
493
{ .name="agent-directory",.key='d', .arg="DIRECTORY",
494
.doc="Systemd password agent directory" },
495
{ .name="helper-directory",.key=128, .arg="DIRECTORY",
496
.doc="Mandos Client password helper directory" },
497
{ .name="plugin-helper-dir", .key=129, /* From plugin-runner */
498
.flags=OPTION_HIDDEN | OPTION_ALIAS },
499
{ .name="user", .key='u', .arg="USERID",
500
.doc="User ID the Mandos Client will use as its unprivileged"
502
{ .name="userid", .key=130, /* From plugin--runner */
503
.flags=OPTION_HIDDEN | OPTION_ALIAS },
504
{ .name="group", .key='g', .arg="GROUPID",
505
.doc="Group ID the Mandos Client will use as its unprivileged"
507
{ .name="groupid", .key=131, /* From plugin--runner */
508
.flags=OPTION_HIDDEN | OPTION_ALIAS },
509
{ .name="test", .key=255, /* See should_only_run_tests() */
510
.doc="Skip normal operation, and only run self-tests. See"
511
" --test --help.", .group=10, },
515
__attribute__((nonnull(3)))
516
error_t parse_opt(int key, char *arg, struct argp_state *state){
519
case 'd': /* --agent-directory */
520
*agent_directory = strdup(arg);
522
case 128: /* --helper-directory */
523
case 129: /* --plugin-helper-dir */
524
*helper_directory = strdup(arg);
526
case 'u': /* --user */
527
case 130: /* --userid */
530
uintmax_t tmp_id = 0;
532
tmp_id = (uid_t)strtoumax(arg, &tmp, 10);
533
if(errno != 0 or tmp == arg or *tmp != '\0'
534
or tmp_id != (uid_t)tmp_id or (uid_t)tmp_id == 0){
535
return ARGP_ERR_UNKNOWN;
537
*user = (uid_t)tmp_id;
541
case 'g': /* --group */
542
case 131: /* --groupid */
545
uintmax_t tmp_id = 0;
547
tmp_id = (uid_t)strtoumax(arg, &tmp, 10);
548
if(errno != 0 or tmp == arg or *tmp != '\0'
549
or tmp_id != (gid_t)tmp_id or (gid_t)tmp_id == 0){
550
return ARGP_ERR_UNKNOWN;
552
*group = (gid_t)tmp_id;
557
/* Copy arguments into argz vector */
558
return argz_create(state->argv + state->next, mandos_argz,
561
return ARGP_ERR_UNKNOWN;
566
const struct argp argp = {
569
.args_doc="[MANDOS_CLIENT [OPTION...]]\n--test",
570
.doc = "Mandos password agent -- runs Mandos client as a"
571
" systemd password agent",
574
errno = argp_parse(&argp, argc, argv,
575
exit_failure ? 0 : ARGP_NO_EXIT, NULL, NULL);
580
__attribute__((nonnull, warn_unused_result))
581
bool block_sigchld(sigset_t *const orig_sigmask){
582
sigset_t sigchld_sigmask;
583
if(sigemptyset(&sigchld_sigmask) < 0){
584
error(0, errno, "Failed to empty signal set");
587
if(sigaddset(&sigchld_sigmask, SIGCHLD) < 0){
588
error(0, errno, "Failed to add SIGCHLD to signal set");
591
if(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, orig_sigmask) != 0){
592
error(0, errno, "Failed to block SIGCHLD signal");
598
__attribute__((nonnull, warn_unused_result, const))
599
bool restore_sigmask(const sigset_t *const orig_sigmask){
600
if(pthread_sigmask(SIG_SETMASK, orig_sigmask, NULL) != 0){
601
error(0, errno, "Failed to restore blocked signals");
607
__attribute__((nonnull, warn_unused_result))
608
bool setup_signal_handler(struct sigaction *const old_sigchld_action){
609
struct sigaction sigchld_action = {
610
.sa_handler=handle_sigchld,
611
.sa_flags=SA_RESTART | SA_NOCLDSTOP,
613
/* Set all signals in "sa_mask" struct member; this makes all
614
signals automatically blocked during signal handler */
615
if(sigfillset(&sigchld_action.sa_mask) != 0){
616
error(0, errno, "Failed to do sigfillset()");
619
if(sigaction(SIGCHLD, &sigchld_action, old_sigchld_action) != 0){
620
error(0, errno, "Failed to set SIGCHLD signal handler");
626
__attribute__((nonnull, warn_unused_result))
627
bool restore_signal_handler(const struct sigaction *const
629
if(sigaction(SIGCHLD, old_sigchld_action, NULL) != 0){
630
error(0, errno, "Failed to restore signal handler");
636
__attribute__((warn_unused_result, malloc))
637
task_queue *create_queue(void){
638
task_queue *queue = malloc(sizeof(task_queue));
642
queue->allocated = 0;
648
__attribute__((nonnull, warn_unused_result))
649
bool add_to_queue(task_queue *const queue, const task_context task){
650
const size_t needed_size = sizeof(task_context)*(queue->length + 1);
651
if(needed_size > (queue->allocated)){
652
task_context *const new_tasks = realloc(queue->tasks,
654
if(new_tasks == NULL){
655
error(0, errno, "Failed to allocate %" PRIuMAX
656
" bytes for queue->tasks", (uintmax_t)needed_size);
659
queue->tasks = new_tasks;
660
queue->allocated = needed_size;
662
/* Using memcpy here is necessary because doing */
663
/* queue->tasks[queue->length++] = task; */
664
/* would violate const-ness of task members */
665
memcpy(&(queue->tasks[queue->length++]), &task,
666
sizeof(task_context));
670
__attribute__((nonnull))
671
void cleanup_task(const task_context *const task){
672
const error_t saved_errno = errno;
673
/* free and close all task data */
674
free(task->question_filename);
675
if(task->filename != task->question_filename){
676
free(task->filename);
679
kill(task->pid, SIGTERM);
687
__attribute__((nonnull))
688
void free_queue(task_queue *const queue){
693
__attribute__((nonnull))
694
void cleanup_queue(task_queue *const *const queue){
698
for(size_t i = 0; i < (*queue)->length; i++){
699
const task_context *const task = ((*queue)->tasks)+i;
705
__attribute__((pure, nonnull, warn_unused_result))
706
bool queue_has_question(const task_queue *const queue){
707
for(size_t i=0; i < queue->length; i++){
708
if(queue->tasks[i].question_filename != NULL){
715
__attribute__((nonnull))
716
void cleanup_close(const int *const fd){
717
const error_t saved_errno = errno;
722
__attribute__((nonnull))
723
void cleanup_string(char *const *const ptr){
727
__attribute__((nonnull))
728
void cleanup_buffer(buffer *buf){
729
if(buf->allocated > 0){
730
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
731
explicit_bzero(buf->data, buf->allocated);
733
memset(buf->data, '\0', buf->allocated);
736
if(buf->data != NULL){
737
if(munlock(buf->data, buf->allocated) != 0){
738
error(0, errno, "Failed to unlock memory of old buffer");
747
__attribute__((pure, nonnull, warn_unused_result))
748
bool string_set_contains(const string_set set, const char *const str){
749
for(const char *s = set.argz; s != NULL and set.argz_len > 0;
750
s = argz_next(set.argz, set.argz_len, s)){
751
if(strcmp(s, str) == 0){
758
__attribute__((nonnull, warn_unused_result))
759
bool string_set_add(string_set *const set, const char *const str){
760
if(string_set_contains(*set, str)){
763
error_t error = argz_add(&set->argz, &set->argz_len, str);
771
__attribute__((nonnull))
772
void string_set_clear(string_set *set){
778
__attribute__((nonnull))
779
void string_set_swap(string_set *const set1, string_set *const set2){
780
/* Swap contents of two string sets */
782
char *const tmp_argz = set1->argz;
783
set1->argz = set2->argz;
784
set2->argz = tmp_argz;
787
const size_t tmp_argz_len = set1->argz_len;
788
set1->argz_len = set2->argz_len;
789
set2->argz_len = tmp_argz_len;
793
__attribute__((nonnull, warn_unused_result))
794
bool start_mandos_client(task_queue *const queue,
796
bool *const mandos_client_exited,
797
bool *const quit_now, buffer *const password,
798
bool *const password_is_read,
799
const struct sigaction *const
800
old_sigchld_action, const sigset_t sigmask,
801
const char *const helper_directory,
802
const uid_t user, const gid_t group,
803
const char *const *const argv){
805
if(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK) != 0){
806
error(0, errno, "Failed to pipe2(..., O_CLOEXEC | O_NONBLOCK)");
810
const pid_t pid = fork();
812
if(not restore_signal_handler(old_sigchld_action)){
815
if(not restore_sigmask(&sigmask)){
818
if(close(pipefds[0]) != 0){
819
error(0, errno, "Failed to close() parent pipe fd");
822
if(dup2(pipefds[1], STDOUT_FILENO) == -1){
823
error(0, errno, "Failed to dup2() pipe fd to stdout");
826
if(close(pipefds[1]) != 0){
827
error(0, errno, "Failed to close() old child pipe fd");
830
if(setenv("MANDOSPLUGINHELPERDIR", helper_directory, 1) != 0){
831
error(0, errno, "Failed to setenv(\"MANDOSPLUGINHELPERDIR\","
832
" \"%s\", 1)", helper_directory);
835
if(group != 0 and setresgid(group, 0, 0) == -1){
836
error(0, errno, "Failed to setresgid(-1, %" PRIuMAX ", %"
837
PRIuMAX")", (uintmax_t)group, (uintmax_t)group);
840
if(user != 0 and setresuid(user, 0, 0) == -1){
841
error(0, errno, "Failed to setresuid(-1, %" PRIuMAX ", %"
842
PRIuMAX")", (uintmax_t)user, (uintmax_t)user);
846
#pragma GCC diagnostic push
847
/* For historical reasons, the "argv" argument to execv() is not
848
const, but it is safe to override this. */
849
#pragma GCC diagnostic ignored "-Wcast-qual"
851
execv(argv[0], (char **)argv);
853
#pragma GCC diagnostic pop
855
error(0, errno, "execv(\"%s\", ...) failed", argv[0]);
860
if(not add_to_queue(queue, (task_context){
861
.func=wait_for_mandos_client_exit,
863
.mandos_client_exited=mandos_client_exited,
866
error(0, errno, "Failed to add wait_for_mandos_client to queue");
871
const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipefds[0],
872
&(struct epoll_event)
873
{ .events=EPOLLIN | EPOLLRDHUP });
874
if(ret != 0 and errno != EEXIST){
875
error(0, errno, "Failed to add file descriptor to epoll set");
880
return add_to_queue(queue, (task_context){
881
.func=read_mandos_client_output,
886
.password_is_read=password_is_read,
890
__attribute__((nonnull))
891
void wait_for_mandos_client_exit(const task_context task,
892
task_queue *const queue){
893
const pid_t pid = task.pid;
894
bool *const mandos_client_exited = task.mandos_client_exited;
895
bool *const quit_now = task.quit_now;
898
switch(waitpid(pid, &status, WNOHANG)){
899
case 0: /* Not exited yet */
900
if(not add_to_queue(queue, task)){
901
error(0, errno, "Failed to add myself to queue");
906
error(0, errno, "waitpid(%" PRIdMAX ") failed", (intmax_t)pid);
912
default: /* Has exited */
913
*mandos_client_exited = true;
914
if((not WIFEXITED(status))
915
or (WEXITSTATUS(status) != EXIT_SUCCESS)){
916
error(0, 0, "Mandos client failed or was killed");
922
__attribute__((nonnull))
923
void read_mandos_client_output(const task_context task,
924
task_queue *const queue){
925
buffer *const password = task.password;
926
bool *const quit_now = task.quit_now;
927
bool *const password_is_read = task.password_is_read;
928
const int fd = task.fd;
929
const int epoll_fd = task.epoll_fd;
931
const size_t new_potential_size = (password->length + PIPE_BUF);
932
if(password->allocated < new_potential_size){
933
char *const new_buffer = calloc(new_potential_size, 1);
934
if(new_buffer == NULL){
935
error(0, errno, "Failed to allocate %" PRIuMAX
936
" bytes for password", (uintmax_t)new_potential_size);
941
if(mlock(new_buffer, new_potential_size) != 0){
942
/* Warn but do not treat as fatal error */
943
if(errno != EPERM and errno != ENOMEM){
944
error(0, errno, "Failed to lock memory for password");
947
if(password->length > 0){
948
memcpy(new_buffer, password->data, password->length);
949
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
950
explicit_bzero(password->data, password->allocated);
952
memset(password->data, '\0', password->allocated);
955
if(password->data != NULL){
956
if(munlock(password->data, password->allocated) != 0){
957
error(0, errno, "Failed to unlock memory of old buffer");
959
free(password->data);
961
password->data = new_buffer;
962
password->allocated = new_potential_size;
965
const ssize_t read_length = read(fd, password->data
966
+ password->length, PIPE_BUF);
968
if(read_length == 0){ /* EOF */
969
*password_is_read = true;
973
if(read_length < 0 and errno != EAGAIN){ /* Actual error */
974
error(0, errno, "Failed to read password from Mandos client");
979
if(read_length > 0){ /* Data has been read */
980
password->length += (size_t)read_length;
983
/* Either data was read, or EAGAIN was indicated, meaning no data
986
/* Re-add the fd to the epoll set */
987
const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
988
&(struct epoll_event)
989
{ .events=EPOLLIN | EPOLLRDHUP });
990
if(ret != 0 and errno != EEXIST){
991
error(0, errno, "Failed to re-add file descriptor to epoll set");
997
/* Re-add myself to the queue */
998
if(not add_to_queue(queue, task)){
999
error(0, errno, "Failed to add myself to queue");
1005
__attribute__((nonnull, warn_unused_result))
1006
bool add_inotify_dir_watch(task_queue *const queue,
1007
const int epoll_fd, bool *const quit_now,
1008
buffer *const password,
1009
const char *const dir,
1010
string_set *cancelled_filenames,
1011
const mono_microsecs *const current_time,
1012
bool *const mandos_client_exited,
1013
bool *const password_is_read){
1014
const int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
1016
error(0, errno, "Failed to create inotify instance");
1020
if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE
1021
| IN_MOVED_TO | IN_DELETE)
1023
error(0, errno, "Failed to create inotify watch on %s", dir);
1027
/* Add the inotify fd to the epoll set */
1028
const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1029
&(struct epoll_event)
1030
{ .events=EPOLLIN | EPOLLRDHUP });
1031
if(ret != 0 and errno != EEXIST){
1032
error(0, errno, "Failed to add file descriptor to epoll set");
1037
const task_context read_inotify_event_task = {
1038
.func=read_inotify_event,
1043
.filename=strdup(dir),
1044
.cancelled_filenames=cancelled_filenames,
1045
.current_time=current_time,
1046
.mandos_client_exited=mandos_client_exited,
1047
.password_is_read=password_is_read,
1049
if(read_inotify_event_task.filename == NULL){
1050
error(0, errno, "Failed to strdup(\"%s\")", dir);
1055
return add_to_queue(queue, read_inotify_event_task);
1058
__attribute__((nonnull))
1059
void read_inotify_event(const task_context task,
1060
task_queue *const queue){
1061
const int fd = task.fd;
1062
const int epoll_fd = task.epoll_fd;
1063
char *const filename = task.filename;
1064
bool *quit_now = task.quit_now;
1065
buffer *const password = task.password;
1066
string_set *const cancelled_filenames = task.cancelled_filenames;
1067
const mono_microsecs *const current_time = task.current_time;
1068
bool *const mandos_client_exited = task.mandos_client_exited;
1069
bool *const password_is_read = task.password_is_read;
1071
/* "sufficient to read at least one event." - inotify(7) */
1072
const size_t ievent_size = (sizeof(struct inotify_event)
1074
char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
1075
struct inotify_event *ievent = ((struct inotify_event *)
1078
const ssize_t read_length = read(fd, ievent, ievent_size);
1079
if(read_length == 0){ /* EOF */
1080
error(0, 0, "Got EOF from inotify fd for directory %s", filename);
1082
cleanup_task(&task);
1085
if(read_length < 0 and errno != EAGAIN){ /* Actual error */
1086
error(0, errno, "Failed to read from inotify fd for directory %s",
1089
cleanup_task(&task);
1092
if(read_length > 0 /* Data has been read */
1093
and fnmatch("ask.*", ievent->name, FNM_FILE_NAME) == 0){
1094
char *question_filename = NULL;
1095
const ssize_t question_filename_length
1096
= asprintf(&question_filename, "%s/%s", filename, ievent->name);
1097
if(question_filename_length < 0){
1098
error(0, errno, "Failed to create file name from directory name"
1099
" %s and file name %s", filename, ievent->name);
1101
if(ievent->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)){
1102
if(not add_to_queue(queue, (task_context){
1103
.func=open_and_parse_question,
1105
.question_filename=question_filename,
1106
.filename=question_filename,
1108
.cancelled_filenames=cancelled_filenames,
1109
.current_time=current_time,
1110
.mandos_client_exited=mandos_client_exited,
1111
.password_is_read=password_is_read,
1113
error(0, errno, "Failed to add open_and_parse_question task"
1114
" for file name %s to queue", filename);
1116
/* Force the added task (open_and_parse_question) to run
1118
queue->next_run = 1;
1120
} else if(ievent->mask & IN_DELETE){
1121
if(not string_set_add(cancelled_filenames,
1122
question_filename)){
1123
error(0, errno, "Could not add question %s to"
1124
" cancelled_questions", question_filename);
1126
free(question_filename);
1127
cleanup_task(&task);
1130
free(question_filename);
1135
/* Either data was read, or EAGAIN was indicated, meaning no data
1138
/* Re-add myself to the queue */
1139
if(not add_to_queue(queue, task)){
1140
error(0, errno, "Failed to re-add read_inotify_event(%s) to"
1141
" queue", filename);
1143
cleanup_task(&task);
1147
/* Re-add the fd to the epoll set */
1148
const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1149
&(struct epoll_event)
1150
{ .events=EPOLLIN | EPOLLRDHUP });
1151
if(ret != 0 and errno != EEXIST){
1152
error(0, errno, "Failed to re-add inotify file descriptor %d for"
1153
" directory %s to epoll set", fd, filename);
1154
/* Force the added task (read_inotify_event) to run again, at most
1155
one second from now */
1156
if((queue->next_run == 0)
1157
or (queue->next_run > (*current_time + 1000000))){
1158
queue->next_run = *current_time + 1000000;
1163
__attribute__((nonnull))
1164
void open_and_parse_question(const task_context task,
1165
task_queue *const queue){
1166
__attribute__((cleanup(cleanup_string)))
1167
char *question_filename = task.question_filename;
1168
const int epoll_fd = task.epoll_fd;
1169
buffer *const password = task.password;
1170
string_set *const cancelled_filenames = task.cancelled_filenames;
1171
const mono_microsecs *const current_time = task.current_time;
1172
bool *const mandos_client_exited = task.mandos_client_exited;
1173
bool *const password_is_read = task.password_is_read;
1175
/* We use the GLib "Key-value file parser" functions to parse the
1176
question file. See <https://www.freedesktop.org/wiki/Software
1177
/systemd/PasswordAgents/> for specification of contents */
1178
__attribute__((nonnull))
1179
void cleanup_g_key_file(GKeyFile **key_file){
1180
if(*key_file != NULL){
1181
g_key_file_free(*key_file);
1185
__attribute__((cleanup(cleanup_g_key_file)))
1186
GKeyFile *key_file = g_key_file_new();
1187
if(key_file == NULL){
1188
error(0, errno, "Failed g_key_file_new() for \"%s\"",
1192
GError *glib_error = NULL;
1193
if(g_key_file_load_from_file(key_file, question_filename,
1194
G_KEY_FILE_NONE, &glib_error) != TRUE){
1195
/* If a file was removed, we should ignore it, so */
1196
/* only show error message if file actually existed */
1197
if(glib_error->code != G_FILE_ERROR_NOENT){
1198
error(0, 0, "Failed to load question data from file \"%s\": %s",
1199
question_filename, glib_error->message);
1204
__attribute__((cleanup(cleanup_string)))
1205
char *socket_name = g_key_file_get_string(key_file, "Ask",
1208
if(socket_name == NULL){
1209
error(0, 0, "Question file \"%s\" did not contain \"Socket\": %s",
1210
question_filename, glib_error->message);
1214
if(strlen(socket_name) == 0){
1215
error(0, 0, "Question file \"%s\" had empty \"Socket\" value",
1220
const guint64 pid = g_key_file_get_uint64(key_file, "Ask", "PID",
1222
if(glib_error != NULL){
1223
error(0, 0, "Question file \"%s\" contained bad \"PID\": %s",
1224
question_filename, glib_error->message);
1228
if((pid != (guint64)((pid_t)pid))
1229
or (kill((pid_t)pid, 0) != 0)){
1230
error(0, 0, "PID %" PRIuMAX " in question file \"%s\" is bad or"
1231
" does not exist", (uintmax_t)pid, question_filename);
1235
guint64 notafter = g_key_file_get_uint64(key_file, "Ask",
1236
"NotAfter", &glib_error);
1237
if(glib_error != NULL){
1238
if(glib_error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND){
1239
error(0, 0, "Question file \"%s\" contained bad \"NotAfter\":"
1240
" %s", question_filename, glib_error->message);
1245
if(queue->next_run == 0 or (queue->next_run > notafter)){
1246
queue->next_run = notafter;
1248
if(*current_time >= notafter){
1253
const task_context connect_question_socket_task = {
1254
.func=connect_question_socket,
1255
.question_filename=strdup(question_filename),
1258
.filename=strdup(socket_name),
1259
.cancelled_filenames=task.cancelled_filenames,
1260
.mandos_client_exited=mandos_client_exited,
1261
.password_is_read=password_is_read,
1262
.current_time=current_time,
1264
if(connect_question_socket_task.question_filename == NULL
1265
or connect_question_socket_task.filename == NULL
1266
or not add_to_queue(queue, connect_question_socket_task)){
1267
error(0, errno, "Failed to add connect_question_socket for socket"
1268
" %s (from \"%s\") to queue", socket_name,
1270
cleanup_task(&connect_question_socket_task);
1273
/* Force the added task (connect_question_socket) to run
1275
queue->next_run = 1;
1278
char *const dup_filename = strdup(question_filename);
1279
const task_context cancel_old_question_task = {
1280
.func=cancel_old_question,
1281
.question_filename=dup_filename,
1283
.filename=dup_filename,
1284
.cancelled_filenames=cancelled_filenames,
1285
.current_time=current_time,
1287
if(cancel_old_question_task.question_filename == NULL
1288
or not add_to_queue(queue, cancel_old_question_task)){
1289
error(0, errno, "Failed to add cancel_old_question for file "
1290
"\"%s\" to queue", question_filename);
1291
cleanup_task(&cancel_old_question_task);
1297
__attribute__((nonnull))
1298
void cancel_old_question(const task_context task,
1299
task_queue *const queue){
1300
char *const question_filename = task.question_filename;
1301
string_set *const cancelled_filenames = task.cancelled_filenames;
1302
const mono_microsecs notafter = task.notafter;
1303
const mono_microsecs *const current_time = task.current_time;
1305
if(*current_time >= notafter){
1306
if(not string_set_add(cancelled_filenames, question_filename)){
1307
error(0, errno, "Failed to cancel question for file %s",
1310
cleanup_task(&task);
1314
if(not add_to_queue(queue, task)){
1315
error(0, errno, "Failed to add cancel_old_question for file "
1316
"%s to queue", question_filename);
1317
cleanup_task(&task);
1321
if((queue->next_run == 0) or (queue->next_run > notafter)){
1322
queue->next_run = notafter;
1326
__attribute__((nonnull))
1327
void connect_question_socket(const task_context task,
1328
task_queue *const queue){
1329
char *const question_filename = task.question_filename;
1330
char *const filename = task.filename;
1331
const int epoll_fd = task.epoll_fd;
1332
buffer *const password = task.password;
1333
string_set *const cancelled_filenames = task.cancelled_filenames;
1334
bool *const mandos_client_exited = task.mandos_client_exited;
1335
bool *const password_is_read = task.password_is_read;
1336
const mono_microsecs *const current_time = task.current_time;
1338
struct sockaddr_un sock_name = { .sun_family=AF_LOCAL };
1340
if(sizeof(sock_name.sun_path) <= strlen(filename)){
1341
error(0, 0, "Socket filename is larger than"
1342
" sizeof(sockaddr_un.sun_path); %" PRIuMAX ": \"%s\"",
1343
(uintmax_t)sizeof(sock_name.sun_path), filename);
1344
if(not string_set_add(cancelled_filenames, question_filename)){
1345
error(0, errno, "Failed to cancel question for file %s",
1348
cleanup_task(&task);
1352
const int fd = socket(PF_LOCAL, SOCK_DGRAM
1353
| SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
1356
"Failed to create socket(PF_LOCAL, SOCK_DGRAM, 0)");
1357
if(not add_to_queue(queue, task)){
1358
error(0, errno, "Failed to add connect_question_socket for file"
1359
" \"%s\" and socket \"%s\" to queue", question_filename,
1361
cleanup_task(&task);
1363
/* Force the added task (connect_question_socket) to run
1365
queue->next_run = 1;
1370
strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path));
1371
if(connect(fd, (struct sockaddr *)&sock_name,
1372
(socklen_t)SUN_LEN(&sock_name)) != 0){
1373
error(0, errno, "Failed to connect socket to \"%s\"", filename);
1374
if(not add_to_queue(queue, task)){
1375
error(0, errno, "Failed to add connect_question_socket for file"
1376
" \"%s\" and socket \"%s\" to queue", question_filename,
1378
cleanup_task(&task);
1380
/* Force the added task (connect_question_socket) to run again,
1381
at most one second from now */
1382
if((queue->next_run == 0)
1383
or (queue->next_run > (*current_time + 1000000))){
1384
queue->next_run = *current_time + 1000000;
1390
/* Not necessary, but we can try, and merely warn on failure */
1391
if(shutdown(fd, SHUT_RD) != 0){
1392
error(0, errno, "Failed to shutdown reading from socket \"%s\"",
1396
/* Add the fd to the epoll set */
1397
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1398
&(struct epoll_event){ .events=EPOLLOUT })
1400
error(0, errno, "Failed to add inotify file descriptor %d for"
1401
" socket %s to epoll set", fd, filename);
1402
if(not add_to_queue(queue, task)){
1403
error(0, errno, "Failed to add connect_question_socket for file"
1404
" \"%s\" and socket \"%s\" to queue", question_filename,
1406
cleanup_task(&task);
1408
/* Force the added task (connect_question_socket) to run again,
1409
at most one second from now */
1410
if((queue->next_run == 0)
1411
or (queue->next_run > (*current_time + 1000000))){
1412
queue->next_run = *current_time + 1000000;
1418
/* add task send_password_to_socket to queue */
1419
const task_context send_password_to_socket_task = {
1420
.func=send_password_to_socket,
1421
.question_filename=question_filename,
1426
.cancelled_filenames=cancelled_filenames,
1427
.mandos_client_exited=mandos_client_exited,
1428
.password_is_read=password_is_read,
1429
.current_time=current_time,
1432
if(not add_to_queue(queue, send_password_to_socket_task)){
1433
error(0, errno, "Failed to add send_password_to_socket for"
1434
" file \"%s\" and socket \"%s\" to queue",
1435
question_filename, filename);
1436
cleanup_task(&send_password_to_socket_task);
1440
__attribute__((nonnull))
1441
void send_password_to_socket(const task_context task,
1442
task_queue *const queue){
1443
char *const question_filename=task.question_filename;
1444
char *const filename=task.filename;
1445
const int epoll_fd=task.epoll_fd;
1446
const int fd=task.fd;
1447
buffer *const password=task.password;
1448
string_set *const cancelled_filenames=task.cancelled_filenames;
1449
bool *const mandos_client_exited = task.mandos_client_exited;
1450
bool *const password_is_read = task.password_is_read;
1451
const mono_microsecs *const current_time = task.current_time;
1453
if(*mandos_client_exited and *password_is_read){
1455
const size_t send_buffer_length = password->length + 2;
1456
char *send_buffer = malloc(send_buffer_length);
1457
if(send_buffer == NULL){
1458
error(0, errno, "Failed to allocate send_buffer");
1460
if(mlock(send_buffer, send_buffer_length) != 0){
1461
/* Warn but do not treat as fatal error */
1462
if(errno != EPERM and errno != ENOMEM){
1463
error(0, errno, "Failed to lock memory for password"
1467
/* “[…] send a single datagram to the socket consisting of the
1468
password string either prefixed with "+" or with "-"
1469
depending on whether the password entry was successful or
1470
not. You may but don't have to include a final NUL byte in
1473
— <https://www.freedesktop.org/wiki/Software/systemd/
1474
PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
1476
send_buffer[0] = '+'; /* Prefix with "+" */
1477
/* Always add an extra NUL */
1478
send_buffer[password->length + 1] = '\0';
1479
if(password->length > 0){
1480
memcpy(send_buffer + 1, password->data, password->length);
1483
ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1485
const error_t saved_errno = errno;
1486
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1487
explicit_bzero(send_buffer, send_buffer_length);
1489
memset(send_buffer, '\0', send_buffer_length);
1491
if(munlock(send_buffer, send_buffer_length) != 0){
1492
error(0, errno, "Failed to unlock memory of send buffer");
1495
if(ssret < 0 or ssret < (ssize_t)send_buffer_length){
1496
switch(saved_errno){
1509
error(0, 0, "Password of size %" PRIuMAX " is too big",
1510
(uintmax_t)password->length);
1514
__attribute__((fallthrough));
1517
if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1518
error(0, 0, "Password only partially sent to socket");
1523
__attribute__((fallthrough));
1526
error(0, saved_errno, "Failed to send() to socket %s",
1528
if(not string_set_add(cancelled_filenames,
1529
question_filename)){
1530
error(0, errno, "Failed to cancel question for file %s",
1533
cleanup_task(&task);
1538
cleanup_task(&task);
1544
/* We failed or are not ready yet; retry later */
1546
if(not add_to_queue(queue, task)){
1547
error(0, errno, "Failed to add send_password_to_socket for"
1548
" file %s and socket %s to queue", question_filename,
1550
cleanup_task(&task);
1553
/* Add the fd to the epoll set */
1554
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
1555
&(struct epoll_event){ .events=EPOLLOUT })
1557
error(0, errno, "Failed to add socket file descriptor %d for"
1558
" socket %s to epoll set", fd, filename);
1559
/* Force the added task (send_password_to_socket) to run again, at
1560
most one second from now */
1561
if((queue->next_run == 0)
1562
or (queue->next_run > (*current_time + 1000000))){
1563
queue->next_run = *current_time + 1000000;
1568
__attribute__((warn_unused_result))
1569
bool add_existing_questions(task_queue *const queue,
1571
buffer *const password,
1572
string_set *cancelled_filenames,
1573
const mono_microsecs *const current_time,
1574
bool *const mandos_client_exited,
1575
bool *const password_is_read,
1576
const char *const dirname){
1577
__attribute__((cleanup(cleanup_string)))
1578
char *dir_pattern = NULL;
1579
const int ret = asprintf(&dir_pattern, "%s/ask.*", dirname);
1580
if(ret < 0 or dir_pattern == NULL){
1581
error(0, errno, "Could not create glob pattern for directory %s",
1585
__attribute__((cleanup(globfree)))
1586
glob_t question_filenames = {};
1587
switch(glob(dir_pattern, GLOB_ERR | GLOB_NOSORT | GLOB_MARK,
1588
NULL, &question_filenames)){
1591
error(0, errno, "Failed to open directory %s", dirname);
1594
error(0, errno, "There are no question files in %s", dirname);
1597
error(0, errno, "Could not allocate memory for question file"
1598
" names in %s", dirname);
1602
__attribute__((fallthrough));
1605
for(size_t i = 0; i < question_filenames.gl_pathc; i++){
1606
char *const question_filename = strdup(question_filenames
1608
const task_context task = {
1609
.func=open_and_parse_question,
1611
.question_filename=question_filename,
1612
.filename=question_filename,
1614
.cancelled_filenames=cancelled_filenames,
1615
.current_time=current_time,
1616
.mandos_client_exited=mandos_client_exited,
1617
.password_is_read=password_is_read,
1620
if(question_filename == NULL
1621
or not add_to_queue(queue, task)){
1622
error(0, errno, "Failed to add open_and_parse_question for"
1623
" file %s to queue",
1624
question_filenames.gl_pathv[i]);
1625
free(question_filename);
1627
queue->next_run = 1;
1634
__attribute__((nonnull, warn_unused_result))
1635
bool wait_for_event(const int epoll_fd,
1636
const mono_microsecs queue_next_run,
1637
const mono_microsecs current_time){
1638
__attribute__((const))
1639
int milliseconds_to_wait(const mono_microsecs currtime,
1640
const mono_microsecs nextrun){
1641
if(currtime >= nextrun){
1644
const uintmax_t wait_time_ms = (nextrun - currtime) / 1000;
1645
if(wait_time_ms > (uintmax_t)INT_MAX){
1648
return (int)wait_time_ms;
1651
const int wait_time_ms = milliseconds_to_wait(current_time,
1654
/* Prepare unblocking of SIGCHLD during epoll_pwait */
1655
sigset_t temporary_unblocked_sigmask;
1656
/* Get current signal mask */
1657
if(pthread_sigmask(-1, NULL, &temporary_unblocked_sigmask) != 0){
1660
/* Remove SIGCHLD from the signal mask */
1661
if(sigdelset(&temporary_unblocked_sigmask, SIGCHLD) != 0){
1664
struct epoll_event events[8]; /* Ignored */
1665
int ret = epoll_pwait(epoll_fd, events,
1666
sizeof(events) / sizeof(struct epoll_event),
1667
queue_next_run == 0 ? -1 : (int)wait_time_ms,
1668
&temporary_unblocked_sigmask);
1669
if(ret < 0 and errno != EINTR){
1670
error(0, errno, "Failed epoll_pwait(epfd=%d, ..., timeout=%d,"
1672
queue_next_run == 0 ? -1 : (int)wait_time_ms);
1675
return clear_all_fds_from_epoll_set(epoll_fd);
1678
bool clear_all_fds_from_epoll_set(const int epoll_fd){
1679
/* Create a new empty epoll set */
1680
__attribute__((cleanup(cleanup_close)))
1681
const int new_epoll_fd = epoll_create1(EPOLL_CLOEXEC);
1682
if(new_epoll_fd < 0){
1685
/* dup3() the new epoll set fd over the old one, replacing it */
1686
if(dup3(new_epoll_fd, epoll_fd, O_CLOEXEC) < 0){
1692
__attribute__((nonnull, warn_unused_result))
1693
bool run_queue(task_queue **const queue,
1694
string_set *const cancelled_filenames,
1695
bool *const quit_now){
1697
task_queue *new_queue = create_queue();
1698
if(new_queue == NULL){
1702
__attribute__((cleanup(string_set_clear)))
1703
string_set old_cancelled_filenames = {};
1704
string_set_swap(cancelled_filenames, &old_cancelled_filenames);
1706
/* Declare i outside the for loop, since we might need i after the
1707
loop in case we aborted in the middle */
1709
for(i=0; i < (*queue)->length and not *quit_now; i++){
1710
task_context *const task = &((*queue)->tasks[i]);
1711
const char *const question_filename = task->question_filename;
1712
/* Skip any task referencing a cancelled question filename */
1713
if(question_filename != NULL
1714
and string_set_contains(old_cancelled_filenames,
1715
question_filename)){
1719
task->func(*task, new_queue);
1723
/* we might be in the middle of the queue, so clean up any
1724
remaining tasks in the current queue */
1725
for(; i < (*queue)->length; i++){
1726
cleanup_task(&((*queue)->tasks[i]));
1740
/* End of regular code section */
1742
/* Start of tests section; here are the tests for the above code */
1744
/* This "fixture" data structure is used by the test setup and
1745
teardown functions */
1747
struct sigaction orig_sigaction;
1748
sigset_t orig_sigmask;
1751
static void test_setup(test_fixture *fixture,
1752
__attribute__((unused))
1753
gconstpointer user_data){
1754
g_assert_true(setup_signal_handler(&fixture->orig_sigaction));
1755
g_assert_true(block_sigchld(&fixture->orig_sigmask));
1758
static void test_teardown(test_fixture *fixture,
1759
__attribute__((unused))
1760
gconstpointer user_data){
1761
g_assert_true(restore_signal_handler(&fixture->orig_sigaction));
1762
g_assert_true(restore_sigmask(&fixture->orig_sigmask));
1765
/* Utility function used by tests to search queue for matching task */
1766
__attribute__((pure, nonnull, warn_unused_result))
1767
static task_context *find_matching_task(const task_queue *const queue,
1768
const task_context task){
1769
/* The argument "task" structure is a pattern to match; 0 in any
1770
member means any value matches, otherwise the value must match.
1771
The filename strings are compared by strcmp(), not by pointer. */
1772
for(size_t i = 0; i < queue->length; i++){
1773
task_context *const current_task = queue->tasks+i;
1774
/* Check all members of task_context, if set to a non-zero value.
1775
If a member does not match, continue to next task in queue */
1777
/* task_func *const func */
1778
if(task.func != NULL and current_task->func != task.func){
1781
/* char *const question_filename; */
1782
if(task.question_filename != NULL
1783
and (current_task->question_filename == NULL
1784
or strcmp(current_task->question_filename,
1785
task.question_filename) != 0)){
1788
/* const pid_t pid; */
1789
if(task.pid != 0 and current_task->pid != task.pid){
1792
/* const int epoll_fd; */
1793
if(task.epoll_fd != 0
1794
and current_task->epoll_fd != task.epoll_fd){
1797
/* bool *const quit_now; */
1798
if(task.quit_now != NULL
1799
and current_task->quit_now != task.quit_now){
1803
if(task.fd != 0 and current_task->fd != task.fd){
1806
/* bool *const mandos_client_exited; */
1807
if(task.mandos_client_exited != NULL
1808
and current_task->mandos_client_exited
1809
!= task.mandos_client_exited){
1812
/* buffer *const password; */
1813
if(task.password != NULL
1814
and current_task->password != task.password){
1817
/* bool *const password_is_read; */
1818
if(task.password_is_read != NULL
1819
and current_task->password_is_read != task.password_is_read){
1822
/* char *filename; */
1823
if(task.filename != NULL
1824
and (current_task->filename == NULL
1825
or strcmp(current_task->filename, task.filename) != 0)){
1828
/* string_set *const cancelled_filenames; */
1829
if(task.cancelled_filenames != NULL
1830
and current_task->cancelled_filenames
1831
!= task.cancelled_filenames){
1834
/* const mono_microsecs notafter; */
1835
if(task.notafter != 0
1836
and current_task->notafter != task.notafter){
1839
/* const mono_microsecs *const current_time; */
1840
if(task.current_time != NULL
1841
and current_task->current_time != task.current_time){
1844
/* Current task matches all members; return it */
1845
return current_task;
1847
/* No task in queue matches passed pattern task */
1851
static void test_create_queue(__attribute__((unused))
1852
test_fixture *fixture,
1853
__attribute__((unused))
1854
gconstpointer user_data){
1855
__attribute__((cleanup(cleanup_queue)))
1856
task_queue *const queue = create_queue();
1857
g_assert_nonnull(queue);
1858
g_assert_null(queue->tasks);
1859
g_assert_true(queue->length == 0);
1860
g_assert_true(queue->next_run == 0);
1863
static task_func dummy_func;
1865
static void test_add_to_queue(__attribute__((unused))
1866
test_fixture *fixture,
1867
__attribute__((unused))
1868
gconstpointer user_data){
1869
__attribute__((cleanup(cleanup_queue)))
1870
task_queue *queue = create_queue();
1871
g_assert_nonnull(queue);
1873
g_assert_true(add_to_queue(queue,
1874
(task_context){ .func=dummy_func }));
1875
g_assert_true(queue->length == 1);
1876
g_assert_nonnull(queue->tasks);
1877
g_assert_true(queue->tasks[0].func == dummy_func);
1880
static void dummy_func(__attribute__((unused))
1881
const task_context task,
1882
__attribute__((unused))
1883
task_queue *const queue){
1886
static void test_queue_has_question_empty(__attribute__((unused))
1887
test_fixture *fixture,
1888
__attribute__((unused))
1889
gconstpointer user_data){
1890
__attribute__((cleanup(cleanup_queue)))
1891
task_queue *queue = create_queue();
1892
g_assert_nonnull(queue);
1893
g_assert_false(queue_has_question(queue));
1896
static void test_queue_has_question_false(__attribute__((unused))
1897
test_fixture *fixture,
1898
__attribute__((unused))
1899
gconstpointer user_data){
1900
__attribute__((cleanup(cleanup_queue)))
1901
task_queue *queue = create_queue();
1902
g_assert_nonnull(queue);
1903
g_assert_true(add_to_queue(queue,
1904
(task_context){ .func=dummy_func }));
1905
g_assert_false(queue_has_question(queue));
1908
static void test_queue_has_question_true(__attribute__((unused))
1909
test_fixture *fixture,
1910
__attribute__((unused))
1911
gconstpointer user_data){
1912
__attribute__((cleanup(cleanup_queue)))
1913
task_queue *queue = create_queue();
1914
g_assert_nonnull(queue);
1915
char *const question_filename
1916
= strdup("/nonexistent/question_filename");
1917
g_assert_nonnull(question_filename);
1918
task_context task = {
1920
.question_filename=question_filename,
1922
g_assert_true(add_to_queue(queue, task));
1923
g_assert_true(queue_has_question(queue));
1926
static void test_queue_has_question_false2(__attribute__((unused))
1927
test_fixture *fixture,
1928
__attribute__((unused))
1929
gconstpointer user_data){
1930
__attribute__((cleanup(cleanup_queue)))
1931
task_queue *queue = create_queue();
1932
g_assert_nonnull(queue);
1933
task_context task = { .func=dummy_func };
1934
g_assert_true(add_to_queue(queue, task));
1935
g_assert_true(add_to_queue(queue, task));
1936
g_assert_cmpint((int)queue->length, ==, 2);
1937
g_assert_false(queue_has_question(queue));
1940
static void test_queue_has_question_true2(__attribute__((unused))
1941
test_fixture *fixture,
1942
__attribute__((unused))
1943
gconstpointer user_data){
1944
__attribute__((cleanup(cleanup_queue)))
1945
task_queue *queue = create_queue();
1946
g_assert_nonnull(queue);
1947
task_context task1 = { .func=dummy_func };
1948
g_assert_true(add_to_queue(queue, task1));
1949
char *const question_filename
1950
= strdup("/nonexistent/question_filename");
1951
g_assert_nonnull(question_filename);
1952
task_context task2 = {
1954
.question_filename=question_filename,
1956
g_assert_true(add_to_queue(queue, task2));
1957
g_assert_cmpint((int)queue->length, ==, 2);
1958
g_assert_true(queue_has_question(queue));
1961
static void test_cleanup_buffer(__attribute__((unused))
1962
test_fixture *fixture,
1963
__attribute__((unused))
1964
gconstpointer user_data){
1967
const size_t buffersize = 10;
1969
buf.data = malloc(buffersize);
1970
g_assert_nonnull(buf.data);
1971
if(mlock(buf.data, buffersize) != 0){
1972
g_assert_true(errno == EPERM or errno == ENOMEM);
1975
cleanup_buffer(&buf);
1976
g_assert_null(buf.data);
1980
void test_string_set_new_set_contains_nothing(__attribute__((unused))
1981
test_fixture *fixture,
1982
__attribute__((unused))
1985
__attribute__((cleanup(string_set_clear)))
1986
string_set set = {};
1987
g_assert_false(string_set_contains(set, "")); /* Empty string */
1988
g_assert_false(string_set_contains(set, "test_string"));
1992
test_string_set_with_added_string_contains_it(__attribute__((unused))
1993
test_fixture *fixture,
1994
__attribute__((unused))
1997
__attribute__((cleanup(string_set_clear)))
1998
string_set set = {};
1999
g_assert_true(string_set_add(&set, "test_string"));
2000
g_assert_true(string_set_contains(set, "test_string"));
2004
test_string_set_cleared_does_not_contain_str(__attribute__((unused))
2005
test_fixture *fixture,
2006
__attribute__((unused))
2007
gconstpointer user_data){
2008
__attribute__((cleanup(string_set_clear)))
2009
string_set set = {};
2010
g_assert_true(string_set_add(&set, "test_string"));
2011
string_set_clear(&set);
2012
g_assert_false(string_set_contains(set, "test_string"));
2016
void test_string_set_swap_one_with_empty(__attribute__((unused))
2017
test_fixture *fixture,
2018
__attribute__((unused))
2019
gconstpointer user_data){
2020
__attribute__((cleanup(string_set_clear)))
2021
string_set set1 = {};
2022
__attribute__((cleanup(string_set_clear)))
2023
string_set set2 = {};
2024
g_assert_true(string_set_add(&set1, "test_string1"));
2025
string_set_swap(&set1, &set2);
2026
g_assert_false(string_set_contains(set1, "test_string1"));
2027
g_assert_true(string_set_contains(set2, "test_string1"));
2031
void test_string_set_swap_empty_with_one(__attribute__((unused))
2032
test_fixture *fixture,
2033
__attribute__((unused))
2034
gconstpointer user_data){
2035
__attribute__((cleanup(string_set_clear)))
2036
string_set set1 = {};
2037
__attribute__((cleanup(string_set_clear)))
2038
string_set set2 = {};
2039
g_assert_true(string_set_add(&set2, "test_string2"));
2040
string_set_swap(&set1, &set2);
2041
g_assert_true(string_set_contains(set1, "test_string2"));
2042
g_assert_false(string_set_contains(set2, "test_string2"));
2045
static void test_string_set_swap_one_with_one(__attribute__((unused))
2046
test_fixture *fixture,
2047
__attribute__((unused))
2050
__attribute__((cleanup(string_set_clear)))
2051
string_set set1 = {};
2052
__attribute__((cleanup(string_set_clear)))
2053
string_set set2 = {};
2054
g_assert_true(string_set_add(&set1, "test_string1"));
2055
g_assert_true(string_set_add(&set2, "test_string2"));
2056
string_set_swap(&set1, &set2);
2057
g_assert_false(string_set_contains(set1, "test_string1"));
2058
g_assert_true(string_set_contains(set1, "test_string2"));
2059
g_assert_false(string_set_contains(set2, "test_string2"));
2060
g_assert_true(string_set_contains(set2, "test_string1"));
2063
static bool fd_has_cloexec_and_nonblock(const int);
2065
static bool epoll_set_contains(int, int, uint32_t);
2067
static void test_start_mandos_client(test_fixture *fixture,
2068
__attribute__((unused))
2069
gconstpointer user_data){
2071
bool mandos_client_exited = false;
2072
bool quit_now = false;
2073
__attribute__((cleanup(cleanup_close)))
2074
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2075
g_assert_cmpint(epoll_fd, >=, 0);
2076
__attribute__((cleanup(cleanup_queue)))
2077
task_queue *queue = create_queue();
2078
g_assert_nonnull(queue);
2079
buffer password = {};
2080
bool password_is_read = false;
2081
const char helper_directory[] = "/nonexistent";
2082
const char *const argv[] = { "/bin/true", NULL };
2084
g_assert_true(start_mandos_client(queue, epoll_fd,
2085
&mandos_client_exited, &quit_now,
2086
&password, &password_is_read,
2087
&fixture->orig_sigaction,
2088
fixture->orig_sigmask,
2089
helper_directory, 0, 0, argv));
2091
g_assert_cmpuint((unsigned int)queue->length, >=, 2);
2093
const task_context *const added_wait_task
2094
= find_matching_task(queue, (task_context){
2095
.func=wait_for_mandos_client_exit,
2096
.mandos_client_exited=&mandos_client_exited,
2097
.quit_now=&quit_now,
2099
g_assert_nonnull(added_wait_task);
2100
g_assert_cmpint(added_wait_task->pid, >, 0);
2101
g_assert_cmpint(kill(added_wait_task->pid, SIGKILL), ==, 0);
2102
waitpid(added_wait_task->pid, NULL, 0);
2104
const task_context *const added_read_task
2105
= find_matching_task(queue, (task_context){
2106
.func=read_mandos_client_output,
2108
.password=&password,
2109
.password_is_read=&password_is_read,
2110
.quit_now=&quit_now,
2112
g_assert_nonnull(added_read_task);
2113
g_assert_cmpint(added_read_task->fd, >, 2);
2114
g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
2115
g_assert_true(epoll_set_contains(epoll_fd, added_read_task->fd,
2116
EPOLLIN | EPOLLRDHUP));
2119
static bool fd_has_cloexec_and_nonblock(const int fd){
2120
const int socket_fd_flags = fcntl(fd, F_GETFD, 0);
2121
const int socket_file_flags = fcntl(fd, F_GETFL, 0);
2122
return ((socket_fd_flags >= 0)
2123
and (socket_fd_flags & FD_CLOEXEC)
2124
and (socket_file_flags >= 0)
2125
and (socket_file_flags & O_NONBLOCK));
2128
__attribute__((const))
2129
bool is_privileged(void){
2130
uid_t user = getuid() + 1;
2131
if(user == 0){ /* Overflow check */
2134
gid_t group = getuid() + 1;
2135
if(group == 0){ /* Overflow check */
2138
const pid_t pid = fork();
2139
if(pid == 0){ /* Child */
2140
if(setresgid((uid_t)-1, group, group) == -1){
2142
error(EXIT_FAILURE, errno, "Failed to setresgid(-1, %" PRIuMAX
2143
", %" PRIuMAX")", (uintmax_t)group, (uintmax_t)group);
2147
if(setresuid((uid_t)-1, user, user) == -1){
2149
error(EXIT_FAILURE, errno, "Failed to setresuid(-1, %" PRIuMAX
2150
", %" PRIuMAX")", (uintmax_t)user, (uintmax_t)user);
2157
waitpid(pid, &status, 0);
2158
if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
2164
static bool epoll_set_contains(int epoll_fd, int fd, uint32_t events){
2165
/* Only scan for events in this eventmask */
2166
const uint32_t eventmask = EPOLLIN | EPOLLOUT | EPOLLRDHUP;
2167
__attribute__((cleanup(cleanup_string)))
2168
char *fdinfo_name = NULL;
2169
int ret = asprintf(&fdinfo_name, "/proc/self/fdinfo/%d", epoll_fd);
2170
g_assert_cmpint(ret, >, 0);
2171
g_assert_nonnull(fdinfo_name);
2173
FILE *fdinfo = fopen(fdinfo_name, "r");
2174
g_assert_nonnull(fdinfo);
2175
uint32_t reported_events;
2180
if(getline(&line.data, &line.allocated, fdinfo) < 0){
2183
/* See proc(5) for format of /proc/PID/fdinfo/FD for epoll fd's */
2184
if(sscanf(line.data, "tfd: %d events: %" SCNx32 " ",
2185
&found_fd, &reported_events) == 2){
2190
} while(not feof(fdinfo) and not ferror(fdinfo));
2191
g_assert_cmpint(fclose(fdinfo), ==, 0);
2198
/* Don't check events if none are given */
2201
return (reported_events & eventmask) == (events & eventmask);
2204
static void test_start_mandos_client_execv(test_fixture *fixture,
2205
__attribute__((unused))
2206
gconstpointer user_data){
2207
bool mandos_client_exited = false;
2208
bool quit_now = false;
2209
__attribute__((cleanup(cleanup_close)))
2210
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2211
g_assert_cmpint(epoll_fd, >=, 0);
2212
__attribute__((cleanup(cleanup_queue)))
2213
task_queue *queue = create_queue();
2214
g_assert_nonnull(queue);
2215
__attribute__((cleanup(cleanup_buffer)))
2216
buffer password = {};
2217
const char helper_directory[] = "/nonexistent";
2218
/* Can't execv("/", ...), so this should fail */
2219
const char *const argv[] = { "/", NULL };
2222
__attribute__((cleanup(cleanup_close)))
2223
const int devnull_fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
2224
g_assert_cmpint(devnull_fd, >=, 0);
2225
__attribute__((cleanup(cleanup_close)))
2226
const int real_stderr_fd = dup(STDERR_FILENO);
2227
g_assert_cmpint(real_stderr_fd, >=, 0);
2228
dup2(devnull_fd, STDERR_FILENO);
2230
const bool success = start_mandos_client(queue, epoll_fd,
2231
&mandos_client_exited,
2235
&fixture->orig_sigaction,
2236
fixture->orig_sigmask,
2237
helper_directory, 0, 0,
2239
dup2(real_stderr_fd, STDERR_FILENO);
2240
g_assert_true(success);
2242
g_assert_cmpuint((unsigned int)queue->length, ==, 2);
2244
struct timespec starttime, currtime;
2245
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2247
queue->next_run = 0;
2248
string_set cancelled_filenames = {};
2251
__attribute__((cleanup(cleanup_close)))
2252
const int devnull_fd = open("/dev/null",
2253
O_WRONLY | O_CLOEXEC);
2254
g_assert_cmpint(devnull_fd, >=, 0);
2255
__attribute__((cleanup(cleanup_close)))
2256
const int real_stderr_fd = dup(STDERR_FILENO);
2257
g_assert_cmpint(real_stderr_fd, >=, 0);
2258
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2259
dup2(devnull_fd, STDERR_FILENO);
2260
const bool success = run_queue(&queue, &cancelled_filenames,
2262
dup2(real_stderr_fd, STDERR_FILENO);
2267
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2268
} while(((queue->length) > 0)
2270
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2272
g_assert_true(quit_now);
2273
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2274
g_assert_true(mandos_client_exited);
2277
static void test_start_mandos_client_suid_euid(test_fixture *fixture,
2278
__attribute__((unused))
2281
if(not is_privileged()){
2282
g_test_skip("Not privileged");
2286
bool mandos_client_exited = false;
2287
bool quit_now = false;
2288
__attribute__((cleanup(cleanup_close)))
2289
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2290
g_assert_cmpint(epoll_fd, >=, 0);
2291
__attribute__((cleanup(cleanup_queue)))
2292
task_queue *queue = create_queue();
2293
g_assert_nonnull(queue);
2294
__attribute__((cleanup(cleanup_buffer)))
2295
buffer password = {};
2296
bool password_is_read = false;
2297
const char helper_directory[] = "/nonexistent";
2298
const char *const argv[] = { "/usr/bin/id", "--user", NULL };
2302
const bool success = start_mandos_client(queue, epoll_fd,
2303
&mandos_client_exited,
2304
&quit_now, &password,
2306
&fixture->orig_sigaction,
2307
fixture->orig_sigmask,
2308
helper_directory, user,
2310
g_assert_true(success);
2311
g_assert_cmpuint((unsigned int)queue->length, >, 0);
2313
struct timespec starttime, currtime;
2314
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2316
queue->next_run = 0;
2317
string_set cancelled_filenames = {};
2318
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2319
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2320
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2321
} while(((queue->length) > 0)
2323
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2325
g_assert_false(quit_now);
2326
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2327
g_assert_true(mandos_client_exited);
2329
g_assert_true(password_is_read);
2330
g_assert_nonnull(password.data);
2333
g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2335
g_assert_true((uid_t)id == id);
2337
g_assert_cmpuint((unsigned int)id, ==, 0);
2340
static void test_start_mandos_client_suid_egid(test_fixture *fixture,
2341
__attribute__((unused))
2344
if(not is_privileged()){
2345
g_test_skip("Not privileged");
2349
bool mandos_client_exited = false;
2350
bool quit_now = false;
2351
__attribute__((cleanup(cleanup_close)))
2352
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2353
g_assert_cmpint(epoll_fd, >=, 0);
2354
__attribute__((cleanup(cleanup_queue)))
2355
task_queue *queue = create_queue();
2356
g_assert_nonnull(queue);
2357
__attribute__((cleanup(cleanup_buffer)))
2358
buffer password = {};
2359
bool password_is_read = false;
2360
const char helper_directory[] = "/nonexistent";
2361
const char *const argv[] = { "/usr/bin/id", "--group", NULL };
2365
const bool success = start_mandos_client(queue, epoll_fd,
2366
&mandos_client_exited,
2367
&quit_now, &password,
2369
&fixture->orig_sigaction,
2370
fixture->orig_sigmask,
2371
helper_directory, user,
2373
g_assert_true(success);
2374
g_assert_cmpuint((unsigned int)queue->length, >, 0);
2376
struct timespec starttime, currtime;
2377
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2379
queue->next_run = 0;
2380
string_set cancelled_filenames = {};
2381
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2382
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2383
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2384
} while(((queue->length) > 0)
2386
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2388
g_assert_false(quit_now);
2389
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2390
g_assert_true(mandos_client_exited);
2392
g_assert_true(password_is_read);
2393
g_assert_nonnull(password.data);
2396
g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2398
g_assert_true((gid_t)id == id);
2400
g_assert_cmpuint((unsigned int)id, ==, 0);
2403
static void test_start_mandos_client_suid_ruid(test_fixture *fixture,
2404
__attribute__((unused))
2407
if(not is_privileged()){
2408
g_test_skip("Not privileged");
2412
bool mandos_client_exited = false;
2413
bool quit_now = false;
2414
__attribute__((cleanup(cleanup_close)))
2415
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2416
g_assert_cmpint(epoll_fd, >=, 0);
2417
__attribute__((cleanup(cleanup_queue)))
2418
task_queue *queue = create_queue();
2419
g_assert_nonnull(queue);
2420
__attribute__((cleanup(cleanup_buffer)))
2421
buffer password = {};
2422
bool password_is_read = false;
2423
const char helper_directory[] = "/nonexistent";
2424
const char *const argv[] = { "/usr/bin/id", "--user", "--real",
2429
const bool success = start_mandos_client(queue, epoll_fd,
2430
&mandos_client_exited,
2431
&quit_now, &password,
2433
&fixture->orig_sigaction,
2434
fixture->orig_sigmask,
2435
helper_directory, user,
2437
g_assert_true(success);
2438
g_assert_cmpuint((unsigned int)queue->length, >, 0);
2440
struct timespec starttime, currtime;
2441
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2443
queue->next_run = 0;
2444
string_set cancelled_filenames = {};
2445
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2446
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2447
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2448
} while(((queue->length) > 0)
2450
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2452
g_assert_false(quit_now);
2453
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2454
g_assert_true(mandos_client_exited);
2456
g_assert_true(password_is_read);
2457
g_assert_nonnull(password.data);
2460
g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2462
g_assert_true((uid_t)id == id);
2464
g_assert_cmpuint((unsigned int)id, ==, user);
2467
static void test_start_mandos_client_suid_rgid(test_fixture *fixture,
2468
__attribute__((unused))
2471
if(not is_privileged()){
2472
g_test_skip("Not privileged");
2476
bool mandos_client_exited = false;
2477
bool quit_now = false;
2478
__attribute__((cleanup(cleanup_close)))
2479
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2480
g_assert_cmpint(epoll_fd, >=, 0);
2481
__attribute__((cleanup(cleanup_queue)))
2482
task_queue *queue = create_queue();
2483
g_assert_nonnull(queue);
2484
__attribute__((cleanup(cleanup_buffer)))
2485
buffer password = {};
2486
bool password_is_read = false;
2487
const char helper_directory[] = "/nonexistent";
2488
const char *const argv[] = { "/usr/bin/id", "--group", "--real",
2493
const bool success = start_mandos_client(queue, epoll_fd,
2494
&mandos_client_exited,
2495
&quit_now, &password,
2497
&fixture->orig_sigaction,
2498
fixture->orig_sigmask,
2499
helper_directory, user,
2501
g_assert_true(success);
2502
g_assert_cmpuint((unsigned int)queue->length, >, 0);
2504
struct timespec starttime, currtime;
2505
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2507
queue->next_run = 0;
2508
string_set cancelled_filenames = {};
2509
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2510
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2511
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2512
} while(((queue->length) > 0)
2514
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2516
g_assert_false(quit_now);
2517
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2518
g_assert_true(mandos_client_exited);
2520
g_assert_true(password_is_read);
2521
g_assert_nonnull(password.data);
2524
g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
2526
g_assert_true((gid_t)id == id);
2528
g_assert_cmpuint((unsigned int)id, ==, group);
2531
static void test_start_mandos_client_read(test_fixture *fixture,
2532
__attribute__((unused))
2533
gconstpointer user_data){
2534
bool mandos_client_exited = false;
2535
bool quit_now = false;
2536
__attribute__((cleanup(cleanup_close)))
2537
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2538
g_assert_cmpint(epoll_fd, >=, 0);
2539
__attribute__((cleanup(cleanup_queue)))
2540
task_queue *queue = create_queue();
2541
g_assert_nonnull(queue);
2542
__attribute__((cleanup(cleanup_buffer)))
2543
buffer password = {};
2544
bool password_is_read = false;
2545
const char dummy_test_password[] = "dummy test password";
2546
const char helper_directory[] = "/nonexistent";
2547
const char *const argv[] = { "/bin/echo", "-n", dummy_test_password,
2550
const bool success = start_mandos_client(queue, epoll_fd,
2551
&mandos_client_exited,
2552
&quit_now, &password,
2554
&fixture->orig_sigaction,
2555
fixture->orig_sigmask,
2556
helper_directory, 0, 0,
2558
g_assert_true(success);
2559
g_assert_cmpuint((unsigned int)queue->length, >, 0);
2561
struct timespec starttime, currtime;
2562
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2564
queue->next_run = 0;
2565
string_set cancelled_filenames = {};
2566
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2567
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2568
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2569
} while(((queue->length) > 0)
2571
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2573
g_assert_false(quit_now);
2574
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2575
g_assert_true(mandos_client_exited);
2577
g_assert_true(password_is_read);
2578
g_assert_cmpint((int)password.length, ==,
2579
sizeof(dummy_test_password)-1);
2580
g_assert_nonnull(password.data);
2581
g_assert_cmpint(memcmp(dummy_test_password, password.data,
2582
sizeof(dummy_test_password)-1), ==, 0);
2586
void test_start_mandos_client_helper_directory(test_fixture *fixture,
2587
__attribute__((unused))
2590
bool mandos_client_exited = false;
2591
bool quit_now = false;
2592
__attribute__((cleanup(cleanup_close)))
2593
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2594
g_assert_cmpint(epoll_fd, >=, 0);
2595
__attribute__((cleanup(cleanup_queue)))
2596
task_queue *queue = create_queue();
2597
g_assert_nonnull(queue);
2598
__attribute__((cleanup(cleanup_buffer)))
2599
buffer password = {};
2600
bool password_is_read = false;
2601
const char helper_directory[] = "/nonexistent";
2602
const char *const argv[] = { "/bin/sh", "-c",
2603
"echo -n ${MANDOSPLUGINHELPERDIR}", NULL };
2605
const bool success = start_mandos_client(queue, epoll_fd,
2606
&mandos_client_exited,
2607
&quit_now, &password,
2609
&fixture->orig_sigaction,
2610
fixture->orig_sigmask,
2611
helper_directory, 0, 0,
2613
g_assert_true(success);
2614
g_assert_cmpuint((unsigned int)queue->length, >, 0);
2616
struct timespec starttime, currtime;
2617
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2619
queue->next_run = 0;
2620
string_set cancelled_filenames = {};
2621
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2622
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2623
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2624
} while(((queue->length) > 0)
2626
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2628
g_assert_false(quit_now);
2629
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2630
g_assert_true(mandos_client_exited);
2632
g_assert_true(password_is_read);
2633
g_assert_cmpint((int)password.length, ==,
2634
sizeof(helper_directory)-1);
2635
g_assert_nonnull(password.data);
2636
g_assert_cmpint(memcmp(helper_directory, password.data,
2637
sizeof(helper_directory)-1), ==, 0);
2640
__attribute__((nonnull, warn_unused_result))
2641
static bool proc_status_sigblk_to_sigset(const char *const,
2644
static void test_start_mandos_client_sigmask(test_fixture *fixture,
2645
__attribute__((unused))
2646
gconstpointer user_data){
2647
bool mandos_client_exited = false;
2648
bool quit_now = false;
2649
__attribute__((cleanup(cleanup_close)))
2650
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2651
g_assert_cmpint(epoll_fd, >=, 0);
2652
__attribute__((cleanup(cleanup_queue)))
2653
task_queue *queue = create_queue();
2654
g_assert_nonnull(queue);
2655
__attribute__((cleanup(cleanup_buffer)))
2656
buffer password = {};
2657
bool password_is_read = false;
2658
const char helper_directory[] = "/nonexistent";
2659
/* see proc(5) for format of /proc/self/status */
2660
const char *const argv[] = { "/usr/bin/awk",
2661
"$1==\"SigBlk:\"{ print $2 }", "/proc/self/status", NULL };
2663
g_assert_true(start_mandos_client(queue, epoll_fd,
2664
&mandos_client_exited, &quit_now,
2665
&password, &password_is_read,
2666
&fixture->orig_sigaction,
2667
fixture->orig_sigmask,
2668
helper_directory, 0, 0, argv));
2670
struct timespec starttime, currtime;
2671
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2673
queue->next_run = 0;
2674
string_set cancelled_filenames = {};
2675
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2676
g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now));
2677
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2678
} while((not (mandos_client_exited and password_is_read))
2680
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2681
g_assert_true(mandos_client_exited);
2682
g_assert_true(password_is_read);
2684
sigset_t parsed_sigmask;
2685
g_assert_true(proc_status_sigblk_to_sigset(password.data,
2688
for(int signum = 1; signum < NSIG; signum++){
2689
const bool has_signal = sigismember(&parsed_sigmask, signum);
2690
if(sigismember(&fixture->orig_sigmask, signum)){
2691
g_assert_true(has_signal);
2693
g_assert_false(has_signal);
2698
__attribute__((nonnull, warn_unused_result))
2699
static bool proc_status_sigblk_to_sigset(const char *const sigblk,
2700
sigset_t *const sigmask){
2701
/* parse /proc/PID/status SigBlk value and convert to a sigset_t */
2702
uintmax_t scanned_sigmask;
2703
if(sscanf(sigblk, "%" SCNxMAX " ", &scanned_sigmask) != 1){
2706
if(sigemptyset(sigmask) != 0){
2709
for(int signum = 1; signum < NSIG; signum++){
2710
if(scanned_sigmask & ((uintmax_t)1 << (signum-1))){
2711
if(sigaddset(sigmask, signum) != 0){
2719
static void run_task_with_stderr_to_dev_null(const task_context task,
2720
task_queue *const queue);
2723
void test_wait_for_mandos_client_exit_badpid(__attribute__((unused))
2724
test_fixture *fixture,
2725
__attribute__((unused))
2726
gconstpointer user_data){
2728
bool mandos_client_exited = false;
2729
bool quit_now = false;
2731
__attribute__((cleanup(cleanup_queue)))
2732
task_queue *queue = create_queue();
2733
g_assert_nonnull(queue);
2734
const task_context task = {
2735
.func=wait_for_mandos_client_exit,
2737
.mandos_client_exited=&mandos_client_exited,
2738
.quit_now=&quit_now,
2740
run_task_with_stderr_to_dev_null(task, queue);
2742
g_assert_false(mandos_client_exited);
2743
g_assert_true(quit_now);
2744
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2747
static void run_task_with_stderr_to_dev_null(const task_context task,
2748
task_queue *const queue){
2749
FILE *real_stderr = stderr;
2750
FILE *devnull = fopen("/dev/null", "we");
2751
g_assert_nonnull(devnull);
2754
task.func(task, queue);
2755
stderr = real_stderr;
2757
g_assert_cmpint(fclose(devnull), ==, 0);
2761
void test_wait_for_mandos_client_exit_noexit(test_fixture *fixture,
2762
__attribute__((unused))
2763
gconstpointer user_data){
2764
bool mandos_client_exited = false;
2765
bool quit_now = false;
2767
pid_t create_eternal_process(void){
2768
const pid_t pid = fork();
2769
if(pid == 0){ /* Child */
2770
if(not restore_signal_handler(&fixture->orig_sigaction)){
2771
_exit(EXIT_FAILURE);
2773
if(not restore_sigmask(&fixture->orig_sigmask)){
2774
_exit(EXIT_FAILURE);
2782
pid_t pid = create_eternal_process();
2783
g_assert_true(pid != -1);
2785
__attribute__((cleanup(cleanup_queue)))
2786
task_queue *queue = create_queue();
2787
g_assert_nonnull(queue);
2788
const task_context task = {
2789
.func=wait_for_mandos_client_exit,
2791
.mandos_client_exited=&mandos_client_exited,
2792
.quit_now=&quit_now,
2794
task.func(task, queue);
2796
g_assert_false(mandos_client_exited);
2797
g_assert_false(quit_now);
2798
g_assert_cmpuint((unsigned int)queue->length, ==, 1);
2800
g_assert_nonnull(find_matching_task(queue, (task_context){
2801
.func=wait_for_mandos_client_exit,
2803
.mandos_client_exited=&mandos_client_exited,
2804
.quit_now=&quit_now,
2809
void test_wait_for_mandos_client_exit_success(test_fixture *fixture,
2810
__attribute__((unused))
2813
bool mandos_client_exited = false;
2814
bool quit_now = false;
2816
pid_t create_successful_process(void){
2817
const pid_t pid = fork();
2818
if(pid == 0){ /* Child */
2819
if(not restore_signal_handler(&fixture->orig_sigaction)){
2820
_exit(EXIT_FAILURE);
2822
if(not restore_sigmask(&fixture->orig_sigmask)){
2823
_exit(EXIT_FAILURE);
2829
const pid_t pid = create_successful_process();
2830
g_assert_true(pid != -1);
2832
__attribute__((cleanup(cleanup_queue)))
2833
task_queue *queue = create_queue();
2834
g_assert_nonnull(queue);
2835
const task_context initial_task = {
2836
.func=wait_for_mandos_client_exit,
2838
.mandos_client_exited=&mandos_client_exited,
2839
.quit_now=&quit_now,
2841
g_assert_true(add_to_queue(queue, initial_task));
2843
struct timespec starttime, currtime;
2844
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2845
__attribute__((cleanup(cleanup_close)))
2846
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2848
queue->next_run = 0;
2849
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2850
g_assert_true(run_queue(&queue, (string_set[]){{}}, &quit_now));
2851
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2852
} while((not mandos_client_exited)
2854
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2856
g_assert_true(mandos_client_exited);
2857
g_assert_false(quit_now);
2858
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2862
void test_wait_for_mandos_client_exit_failure(test_fixture *fixture,
2863
__attribute__((unused))
2866
bool mandos_client_exited = false;
2867
bool quit_now = false;
2869
pid_t create_failing_process(void){
2870
const pid_t pid = fork();
2871
if(pid == 0){ /* Child */
2872
if(not restore_signal_handler(&fixture->orig_sigaction)){
2873
_exit(EXIT_FAILURE);
2875
if(not restore_sigmask(&fixture->orig_sigmask)){
2876
_exit(EXIT_FAILURE);
2882
const pid_t pid = create_failing_process();
2883
g_assert_true(pid != -1);
2885
__attribute__((cleanup(string_set_clear)))
2886
string_set cancelled_filenames = {};
2887
__attribute__((cleanup(cleanup_close)))
2888
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2889
g_assert_cmpint(epoll_fd, >=, 0);
2890
__attribute__((cleanup(cleanup_queue)))
2891
task_queue *queue = create_queue();
2892
g_assert_nonnull(queue);
2893
g_assert_true(add_to_queue(queue, (task_context){
2894
.func=wait_for_mandos_client_exit,
2896
.mandos_client_exited=&mandos_client_exited,
2897
.quit_now=&quit_now,
2900
g_assert_true(sigismember(&fixture->orig_sigmask, SIGCHLD) == 0);
2902
__attribute__((cleanup(cleanup_close)))
2903
const int devnull_fd = open("/dev/null",
2904
O_WRONLY | O_CLOEXEC);
2905
g_assert_cmpint(devnull_fd, >=, 0);
2906
__attribute__((cleanup(cleanup_close)))
2907
const int real_stderr_fd = dup(STDERR_FILENO);
2908
g_assert_cmpint(real_stderr_fd, >=, 0);
2910
struct timespec starttime, currtime;
2911
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2913
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2914
dup2(devnull_fd, STDERR_FILENO);
2915
const bool success = run_queue(&queue, &cancelled_filenames,
2917
dup2(real_stderr_fd, STDERR_FILENO);
2922
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2923
} while((not mandos_client_exited)
2925
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2927
g_assert_true(quit_now);
2928
g_assert_true(mandos_client_exited);
2929
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
2933
void test_wait_for_mandos_client_exit_killed(test_fixture *fixture,
2934
__attribute__((unused))
2935
gconstpointer user_data){
2936
bool mandos_client_exited = false;
2937
bool quit_now = false;
2939
pid_t create_killed_process(void){
2940
const pid_t pid = fork();
2941
if(pid == 0){ /* Child */
2942
if(not restore_signal_handler(&fixture->orig_sigaction)){
2943
_exit(EXIT_FAILURE);
2945
if(not restore_sigmask(&fixture->orig_sigmask)){
2946
_exit(EXIT_FAILURE);
2955
const pid_t pid = create_killed_process();
2956
g_assert_true(pid != -1);
2958
__attribute__((cleanup(string_set_clear)))
2959
string_set cancelled_filenames = {};
2960
__attribute__((cleanup(cleanup_close)))
2961
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
2962
g_assert_cmpint(epoll_fd, >=, 0);
2963
__attribute__((cleanup(cleanup_queue)))
2964
task_queue *queue = create_queue();
2965
g_assert_nonnull(queue);
2966
g_assert_true(add_to_queue(queue, (task_context){
2967
.func=wait_for_mandos_client_exit,
2969
.mandos_client_exited=&mandos_client_exited,
2970
.quit_now=&quit_now,
2973
__attribute__((cleanup(cleanup_close)))
2974
const int devnull_fd = open("/dev/null",
2975
O_WRONLY | O_CLOEXEC);
2976
g_assert_cmpint(devnull_fd, >=, 0);
2977
__attribute__((cleanup(cleanup_close)))
2978
const int real_stderr_fd = dup(STDERR_FILENO);
2979
g_assert_cmpint(real_stderr_fd, >=, 0);
2981
struct timespec starttime, currtime;
2982
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
2984
g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0));
2985
dup2(devnull_fd, STDERR_FILENO);
2986
const bool success = run_queue(&queue, &cancelled_filenames,
2988
dup2(real_stderr_fd, STDERR_FILENO);
2993
g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
2994
} while((not mandos_client_exited)
2996
and ((currtime.tv_sec - starttime.tv_sec) < 10));
2998
g_assert_true(mandos_client_exited);
2999
g_assert_true(quit_now);
3000
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3003
static bool epoll_set_does_not_contain(int, int);
3006
void test_read_mandos_client_output_readerror(__attribute__((unused))
3007
test_fixture *fixture,
3008
__attribute__((unused))
3011
__attribute__((cleanup(cleanup_close)))
3012
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3013
g_assert_cmpint(epoll_fd, >=, 0);
3015
__attribute__((cleanup(cleanup_buffer)))
3016
buffer password = {};
3018
/* Reading /proc/self/mem from offset 0 will always give EIO */
3019
const int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
3021
bool password_is_read = false;
3022
bool quit_now = false;
3023
__attribute__((cleanup(cleanup_queue)))
3024
task_queue *queue = create_queue();
3025
g_assert_nonnull(queue);
3027
task_context task = {
3028
.func=read_mandos_client_output,
3031
.password=&password,
3032
.password_is_read=&password_is_read,
3033
.quit_now=&quit_now,
3035
run_task_with_stderr_to_dev_null(task, queue);
3036
g_assert_false(password_is_read);
3037
g_assert_cmpint((int)password.length, ==, 0);
3038
g_assert_true(quit_now);
3039
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3041
g_assert_true(epoll_set_does_not_contain(epoll_fd, fd));
3043
g_assert_cmpint(close(fd), ==, -1);
3046
static bool epoll_set_does_not_contain(int epoll_fd, int fd){
3047
return not epoll_set_contains(epoll_fd, fd, 0);
3051
void test_read_mandos_client_output_nodata(__attribute__((unused))
3052
test_fixture *fixture,
3053
__attribute__((unused))
3054
gconstpointer user_data){
3055
__attribute__((cleanup(cleanup_close)))
3056
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3057
g_assert_cmpint(epoll_fd, >=, 0);
3060
g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3062
__attribute__((cleanup(cleanup_buffer)))
3063
buffer password = {};
3065
bool password_is_read = false;
3066
bool quit_now = false;
3067
__attribute__((cleanup(cleanup_queue)))
3068
task_queue *queue = create_queue();
3069
g_assert_nonnull(queue);
3071
task_context task = {
3072
.func=read_mandos_client_output,
3075
.password=&password,
3076
.password_is_read=&password_is_read,
3077
.quit_now=&quit_now,
3079
task.func(task, queue);
3080
g_assert_false(password_is_read);
3081
g_assert_cmpint((int)password.length, ==, 0);
3082
g_assert_false(quit_now);
3083
g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3085
g_assert_nonnull(find_matching_task(queue, (task_context){
3086
.func=read_mandos_client_output,
3089
.password=&password,
3090
.password_is_read=&password_is_read,
3091
.quit_now=&quit_now,
3094
g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3095
EPOLLIN | EPOLLRDHUP));
3097
g_assert_cmpint(close(pipefds[1]), ==, 0);
3100
static void test_read_mandos_client_output_eof(__attribute__((unused))
3101
test_fixture *fixture,
3102
__attribute__((unused))
3105
__attribute__((cleanup(cleanup_close)))
3106
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3107
g_assert_cmpint(epoll_fd, >=, 0);
3110
g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3111
g_assert_cmpint(close(pipefds[1]), ==, 0);
3113
__attribute__((cleanup(cleanup_buffer)))
3114
buffer password = {};
3116
bool password_is_read = false;
3117
bool quit_now = false;
3118
__attribute__((cleanup(cleanup_queue)))
3119
task_queue *queue = create_queue();
3120
g_assert_nonnull(queue);
3122
task_context task = {
3123
.func=read_mandos_client_output,
3126
.password=&password,
3127
.password_is_read=&password_is_read,
3128
.quit_now=&quit_now,
3130
task.func(task, queue);
3131
g_assert_true(password_is_read);
3132
g_assert_cmpint((int)password.length, ==, 0);
3133
g_assert_false(quit_now);
3134
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3136
g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0]));
3138
g_assert_cmpint(close(pipefds[0]), ==, -1);
3142
void test_read_mandos_client_output_once(__attribute__((unused))
3143
test_fixture *fixture,
3144
__attribute__((unused))
3145
gconstpointer user_data){
3146
__attribute__((cleanup(cleanup_close)))
3147
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3148
g_assert_cmpint(epoll_fd, >=, 0);
3151
g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3153
const char dummy_test_password[] = "dummy test password";
3154
/* Start with a pre-allocated buffer */
3155
__attribute__((cleanup(cleanup_buffer)))
3157
.data=malloc(sizeof(dummy_test_password)),
3159
.allocated=sizeof(dummy_test_password),
3161
g_assert_nonnull(password.data);
3162
if(mlock(password.data, password.allocated) != 0){
3163
g_assert_true(errno == EPERM or errno == ENOMEM);
3166
bool password_is_read = false;
3167
bool quit_now = false;
3168
__attribute__((cleanup(cleanup_queue)))
3169
task_queue *queue = create_queue();
3170
g_assert_nonnull(queue);
3172
g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF);
3173
g_assert_cmpint((int)write(pipefds[1], dummy_test_password,
3174
sizeof(dummy_test_password)),
3175
==, (int)sizeof(dummy_test_password));
3177
task_context task = {
3178
.func=read_mandos_client_output,
3181
.password=&password,
3182
.password_is_read=&password_is_read,
3183
.quit_now=&quit_now,
3185
task.func(task, queue);
3187
g_assert_false(password_is_read);
3188
g_assert_cmpint((int)password.length, ==,
3189
(int)sizeof(dummy_test_password));
3190
g_assert_nonnull(password.data);
3191
g_assert_cmpint(memcmp(password.data, dummy_test_password,
3192
sizeof(dummy_test_password)), ==, 0);
3194
g_assert_false(quit_now);
3195
g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3197
g_assert_nonnull(find_matching_task(queue, (task_context){
3198
.func=read_mandos_client_output,
3201
.password=&password,
3202
.password_is_read=&password_is_read,
3203
.quit_now=&quit_now,
3206
g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3207
EPOLLIN | EPOLLRDHUP));
3209
g_assert_cmpint(close(pipefds[1]), ==, 0);
3213
void test_read_mandos_client_output_malloc(__attribute__((unused))
3214
test_fixture *fixture,
3215
__attribute__((unused))
3216
gconstpointer user_data){
3217
__attribute__((cleanup(cleanup_close)))
3218
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3219
g_assert_cmpint(epoll_fd, >=, 0);
3222
g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3224
const char dummy_test_password[] = "dummy test password";
3225
/* Start with an empty buffer */
3226
__attribute__((cleanup(cleanup_buffer)))
3227
buffer password = {};
3229
bool password_is_read = false;
3230
bool quit_now = false;
3231
__attribute__((cleanup(cleanup_queue)))
3232
task_queue *queue = create_queue();
3233
g_assert_nonnull(queue);
3235
g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF);
3236
g_assert_cmpint((int)write(pipefds[1], dummy_test_password,
3237
sizeof(dummy_test_password)),
3238
==, (int)sizeof(dummy_test_password));
3240
task_context task = {
3241
.func=read_mandos_client_output,
3244
.password=&password,
3245
.password_is_read=&password_is_read,
3246
.quit_now=&quit_now,
3248
task.func(task, queue);
3250
g_assert_false(password_is_read);
3251
g_assert_cmpint((int)password.length, ==,
3252
(int)sizeof(dummy_test_password));
3253
g_assert_nonnull(password.data);
3254
g_assert_cmpint(memcmp(password.data, dummy_test_password,
3255
sizeof(dummy_test_password)), ==, 0);
3257
g_assert_false(quit_now);
3258
g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3260
g_assert_nonnull(find_matching_task(queue, (task_context){
3261
.func=read_mandos_client_output,
3264
.password=&password,
3265
.password_is_read=&password_is_read,
3266
.quit_now=&quit_now,
3269
g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3270
EPOLLIN | EPOLLRDHUP));
3272
g_assert_cmpint(close(pipefds[1]), ==, 0);
3276
void test_read_mandos_client_output_append(__attribute__((unused))
3277
test_fixture *fixture,
3278
__attribute__((unused))
3279
gconstpointer user_data){
3280
__attribute__((cleanup(cleanup_close)))
3281
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3282
g_assert_cmpint(epoll_fd, >=, 0);
3285
g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
3287
const char dummy_test_password[] = "dummy test password";
3288
__attribute__((cleanup(cleanup_buffer)))
3290
.data=malloc(PIPE_BUF),
3292
.allocated=PIPE_BUF,
3294
g_assert_nonnull(password.data);
3295
if(mlock(password.data, password.allocated) != 0){
3296
g_assert_true(errno == EPERM or errno == ENOMEM);
3299
memset(password.data, 'x', PIPE_BUF);
3300
char password_expected[PIPE_BUF];
3301
memcpy(password_expected, password.data, PIPE_BUF);
3303
bool password_is_read = false;
3304
bool quit_now = false;
3305
__attribute__((cleanup(cleanup_queue)))
3306
task_queue *queue = create_queue();
3307
g_assert_nonnull(queue);
3309
g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF);
3310
g_assert_cmpint((int)write(pipefds[1], dummy_test_password,
3311
sizeof(dummy_test_password)),
3312
==, (int)sizeof(dummy_test_password));
3314
task_context task = {
3315
.func=read_mandos_client_output,
3318
.password=&password,
3319
.password_is_read=&password_is_read,
3320
.quit_now=&quit_now,
3322
task.func(task, queue);
3324
g_assert_false(password_is_read);
3325
g_assert_cmpint((int)password.length, ==,
3326
PIPE_BUF + sizeof(dummy_test_password));
3327
g_assert_nonnull(password.data);
3328
g_assert_cmpint(memcmp(password_expected, password.data, PIPE_BUF),
3330
g_assert_cmpint(memcmp(password.data + PIPE_BUF,
3331
dummy_test_password,
3332
sizeof(dummy_test_password)), ==, 0);
3333
g_assert_false(quit_now);
3334
g_assert_cmpuint((unsigned int)queue->length, ==, 1);
3336
g_assert_nonnull(find_matching_task(queue, (task_context){
3337
.func=read_mandos_client_output,
3340
.password=&password,
3341
.password_is_read=&password_is_read,
3342
.quit_now=&quit_now,
3345
g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
3346
EPOLLIN | EPOLLRDHUP));
3349
static char *make_temporary_directory(void);
3351
static void test_add_inotify_dir_watch(__attribute__((unused))
3352
test_fixture *fixture,
3353
__attribute__((unused))
3354
gconstpointer user_data){
3355
__attribute__((cleanup(cleanup_close)))
3356
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3357
g_assert_cmpint(epoll_fd, >=, 0);
3358
__attribute__((cleanup(cleanup_queue)))
3359
task_queue *queue = create_queue();
3360
g_assert_nonnull(queue);
3361
__attribute__((cleanup(string_set_clear)))
3362
string_set cancelled_filenames = {};
3363
const mono_microsecs current_time = 0;
3365
bool quit_now = false;
3366
buffer password = {};
3367
bool mandos_client_exited = false;
3368
bool password_is_read = false;
3370
__attribute__((cleanup(cleanup_string)))
3371
char *tempdir = make_temporary_directory();
3372
g_assert_nonnull(tempdir);
3374
g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3376
&cancelled_filenames,
3378
&mandos_client_exited,
3379
&password_is_read));
3381
g_assert_cmpuint((unsigned int)queue->length, >, 0);
3383
const task_context *const added_read_task
3384
= find_matching_task(queue, (task_context){
3385
.func=read_inotify_event,
3387
.quit_now=&quit_now,
3388
.password=&password,
3390
.cancelled_filenames=&cancelled_filenames,
3391
.current_time=¤t_time,
3392
.mandos_client_exited=&mandos_client_exited,
3393
.password_is_read=&password_is_read,
3395
g_assert_nonnull(added_read_task);
3397
g_assert_cmpint(added_read_task->fd, >, 2);
3398
g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3399
g_assert_true(epoll_set_contains(added_read_task->epoll_fd,
3400
added_read_task->fd,
3401
EPOLLIN | EPOLLRDHUP));
3403
g_assert_cmpint(rmdir(tempdir), ==, 0);
3406
static char *make_temporary_directory(void){
3407
char *name = strdup("/tmp/mandosXXXXXX");
3408
g_assert_nonnull(name);
3409
char *result = mkdtemp(name);
3416
static void test_add_inotify_dir_watch_fail(__attribute__((unused))
3417
test_fixture *fixture,
3418
__attribute__((unused))
3419
gconstpointer user_data){
3420
__attribute__((cleanup(cleanup_close)))
3421
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3422
g_assert_cmpint(epoll_fd, >=, 0);
3423
__attribute__((cleanup(cleanup_queue)))
3424
task_queue *queue = create_queue();
3425
g_assert_nonnull(queue);
3426
__attribute__((cleanup(string_set_clear)))
3427
string_set cancelled_filenames = {};
3428
const mono_microsecs current_time = 0;
3430
bool quit_now = false;
3431
buffer password = {};
3432
bool mandos_client_exited = false;
3433
bool password_is_read = false;
3435
const char nonexistent_dir[] = "/nonexistent";
3437
FILE *real_stderr = stderr;
3438
FILE *devnull = fopen("/dev/null", "we");
3439
g_assert_nonnull(devnull);
3441
g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3442
&password, nonexistent_dir,
3443
&cancelled_filenames,
3445
&mandos_client_exited,
3446
&password_is_read));
3447
stderr = real_stderr;
3448
g_assert_cmpint(fclose(devnull), ==, 0);
3450
g_assert_cmpuint((unsigned int)queue->length, ==, 0);
3453
static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused))
3454
test_fixture *fixture,
3455
__attribute__((unused))
3458
__attribute__((cleanup(cleanup_close)))
3459
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3460
g_assert_cmpint(epoll_fd, >=, 0);
3461
__attribute__((cleanup(cleanup_queue)))
3462
task_queue *queue = create_queue();
3463
g_assert_nonnull(queue);
3464
__attribute__((cleanup(string_set_clear)))
3465
string_set cancelled_filenames = {};
3466
const mono_microsecs current_time = 0;
3468
bool quit_now = false;
3469
buffer password = {};
3470
bool mandos_client_exited = false;
3471
bool password_is_read = false;
3473
__attribute__((cleanup(cleanup_string)))
3474
char *tempdir = make_temporary_directory();
3475
g_assert_nonnull(tempdir);
3477
g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3479
&cancelled_filenames,
3481
&mandos_client_exited,
3482
&password_is_read));
3484
g_assert_cmpuint((unsigned int)queue->length, >, 0);
3486
const task_context *const added_read_task
3487
= find_matching_task(queue,
3488
(task_context){ .func=read_inotify_event });
3489
g_assert_nonnull(added_read_task);
3491
g_assert_cmpint(added_read_task->fd, >, 2);
3492
g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3494
/* "sufficient to read at least one event." - inotify(7) */
3495
const size_t ievent_size = (sizeof(struct inotify_event)
3497
struct inotify_event *ievent = malloc(ievent_size);
3498
g_assert_nonnull(ievent);
3500
g_assert_cmpint(read(added_read_task->fd, ievent, ievent_size), ==,
3502
g_assert_cmpint(errno, ==, EAGAIN);
3506
g_assert_cmpint(rmdir(tempdir), ==, 0);
3509
static char *make_temporary_file_in_directory(const char
3513
void test_add_inotify_dir_watch_IN_CLOSE_WRITE(__attribute__((unused))
3514
test_fixture *fixture,
3515
__attribute__((unused))
3518
__attribute__((cleanup(cleanup_close)))
3519
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3520
g_assert_cmpint(epoll_fd, >=, 0);
3521
__attribute__((cleanup(cleanup_queue)))
3522
task_queue *queue = create_queue();
3523
g_assert_nonnull(queue);
3524
__attribute__((cleanup(string_set_clear)))
3525
string_set cancelled_filenames = {};
3526
const mono_microsecs current_time = 0;
3528
bool quit_now = false;
3529
buffer password = {};
3530
bool mandos_client_exited = false;
3531
bool password_is_read = false;
3533
__attribute__((cleanup(cleanup_string)))
3534
char *tempdir = make_temporary_directory();
3535
g_assert_nonnull(tempdir);
3537
g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3539
&cancelled_filenames,
3541
&mandos_client_exited,
3542
&password_is_read));
3544
g_assert_cmpuint((unsigned int)queue->length, >, 0);
3546
const task_context *const added_read_task
3547
= find_matching_task(queue,
3548
(task_context){ .func=read_inotify_event });
3549
g_assert_nonnull(added_read_task);
3551
g_assert_cmpint(added_read_task->fd, >, 2);
3552
g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3554
__attribute__((cleanup(cleanup_string)))
3555
char *filename = make_temporary_file_in_directory(tempdir);
3556
g_assert_nonnull(filename);
3558
/* "sufficient to read at least one event." - inotify(7) */
3559
const size_t ievent_size = (sizeof(struct inotify_event)
3561
struct inotify_event *ievent = malloc(ievent_size);
3562
g_assert_nonnull(ievent);
3564
ssize_t read_size = 0;
3565
read_size = read(added_read_task->fd, ievent, ievent_size);
3567
g_assert_cmpint((int)read_size, >, 0);
3568
g_assert_true(ievent->mask & IN_CLOSE_WRITE);
3569
g_assert_cmpstr(ievent->name, ==, basename(filename));
3573
g_assert_cmpint(unlink(filename), ==, 0);
3574
g_assert_cmpint(rmdir(tempdir), ==, 0);
3577
static char *make_temporary_prefixed_file_in_directory(const char
3581
char *filename = NULL;
3582
g_assert_cmpint(asprintf(&filename, "%s/%sXXXXXX", dir, prefix),
3584
g_assert_nonnull(filename);
3585
const int fd = mkostemp(filename, O_CLOEXEC);
3586
g_assert_cmpint(fd, >=, 0);
3587
g_assert_cmpint(close(fd), ==, 0);
3591
static char *make_temporary_file_in_directory(const char
3593
return make_temporary_prefixed_file_in_directory("temp", dir);
3597
void test_add_inotify_dir_watch_IN_MOVED_TO(__attribute__((unused))
3598
test_fixture *fixture,
3599
__attribute__((unused))
3600
gconstpointer user_data){
3601
__attribute__((cleanup(cleanup_close)))
3602
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3603
g_assert_cmpint(epoll_fd, >=, 0);
3604
__attribute__((cleanup(cleanup_queue)))
3605
task_queue *queue = create_queue();
3606
g_assert_nonnull(queue);
3607
__attribute__((cleanup(string_set_clear)))
3608
string_set cancelled_filenames = {};
3609
const mono_microsecs current_time = 0;
3611
bool quit_now = false;
3612
buffer password = {};
3613
bool mandos_client_exited = false;
3614
bool password_is_read = false;
3616
__attribute__((cleanup(cleanup_string)))
3617
char *watchdir = make_temporary_directory();
3618
g_assert_nonnull(watchdir);
3620
g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3621
&password, watchdir,
3622
&cancelled_filenames,
3624
&mandos_client_exited,
3625
&password_is_read));
3627
g_assert_cmpuint((unsigned int)queue->length, >, 0);
3629
const task_context *const added_read_task
3630
= find_matching_task(queue,
3631
(task_context){ .func=read_inotify_event });
3632
g_assert_nonnull(added_read_task);
3634
g_assert_cmpint(added_read_task->fd, >, 2);
3635
g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3637
char *sourcedir = make_temporary_directory();
3638
g_assert_nonnull(sourcedir);
3640
__attribute__((cleanup(cleanup_string)))
3641
char *filename = make_temporary_file_in_directory(sourcedir);
3642
g_assert_nonnull(filename);
3644
__attribute__((cleanup(cleanup_string)))
3645
char *targetfilename = NULL;
3646
g_assert_cmpint(asprintf(&targetfilename, "%s/%s", watchdir,
3647
basename(filename)), >, 0);
3648
g_assert_nonnull(targetfilename);
3650
g_assert_cmpint(rename(filename, targetfilename), ==, 0);
3651
g_assert_cmpint(rmdir(sourcedir), ==, 0);
3654
/* "sufficient to read at least one event." - inotify(7) */
3655
const size_t ievent_size = (sizeof(struct inotify_event)
3657
struct inotify_event *ievent = malloc(ievent_size);
3658
g_assert_nonnull(ievent);
3660
ssize_t read_size = read(added_read_task->fd, ievent, ievent_size);
3662
g_assert_cmpint((int)read_size, >, 0);
3663
g_assert_true(ievent->mask & IN_MOVED_TO);
3664
g_assert_cmpstr(ievent->name, ==, basename(targetfilename));
3668
g_assert_cmpint(unlink(targetfilename), ==, 0);
3669
g_assert_cmpint(rmdir(watchdir), ==, 0);
3673
void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused))
3674
test_fixture *fixture,
3675
__attribute__((unused))
3676
gconstpointer user_data){
3677
__attribute__((cleanup(cleanup_close)))
3678
const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3679
g_assert_cmpint(epoll_fd, >=, 0);
3680
__attribute__((cleanup(cleanup_queue)))
3681
task_queue *queue = create_queue();
3682
g_assert_nonnull(queue);
3683
__attribute__((cleanup(string_set_clear)))
3684
string_set cancelled_filenames = {};
3685
const mono_microsecs current_time = 0;
3687
bool quit_now = false;
3688
buffer password = {};
3689
bool mandos_client_exited = false;
3690
bool password_is_read = false;
3692
__attribute__((cleanup(cleanup_string)))
3693
char *tempdir = make_temporary_directory();
3694
g_assert_nonnull(tempdir);
3696
__attribute__((cleanup(cleanup_string)))
3697
char *tempfile = make_temporary_file_in_directory(tempdir);
3698
g_assert_nonnull(tempfile);
3700
g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
3702
&cancelled_filenames,
3704
&mandos_client_exited,
3705
&password_is_read));
3706
g_assert_cmpint(unlink(tempfile), ==, 0);
3708
g_assert_cmpuint((unsigned int)queue->length, >, 0);
3710
const task_context *const added_read_task
3711
= find_matching_task(queue,
3712
(task_context){ .func=read_inotify_event });
3713
g_assert_nonnull(added_read_task);
3715
g_assert_cmpint(added_read_task->fd, >, 2);
3716
g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
3718
/* "sufficient to read at least one event." - inotify(7) */
3719
const size_t ievent_size = (sizeof(struct inotify_event)
3721
struct inotify_event *ievent = malloc(ievent_size);
3722
g_assert_nonnull(ievent);
3724
ssize_t read_size = 0;
3725
read_size = read(added_read_task->fd, ievent, ievent_size);
3727
g_assert_cmpint((int)read_size, >, 0);
3728
g_assert_true(ievent->mask & IN_DELETE);
3729
g_assert_cmpstr(ievent->name, ==, basename(tempfile));
3733
g_assert_cmpint(rmdir(tempdir), ==, 0);
3736
static void test_read_inotify_event_readerror(__attribute__((unused))
3737
test_fixture *fixture,
3738
__attribute__((unused))