/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to dracut-module/password-agent.c

  • Committer: Teddy Hogeborn
  • Date: 2019-07-27 10:11:45 UTC
  • Revision ID: teddy@recompile.se-20190727101145-jnpbpf8220gldbcd
Add dracut(8) support

Add support for the dracut(8) system for generating initramfs image
files; dracut is an alternative to the "initramfs-tools" package.

* .bzrignore (dracut-module/password-agent): Ignore new binary file.
* dracut-module: New directory for the dracut module.
* INSTALL (Prerequisites/Libraries/Mandos Client): Add dracut as an
                                                   alternative to
                                                   initramfs-tools,
                                                   and also add GLib.
* Makefile (DRACUTMODULE, GLIB_CFLAGS, GLIB_LIBS): New.
  (CPROGS): Add "dracut-module/password-agent".
  (DOCS): Add "dracut-module/password-agent.8mandos".
  (dracut-module/password-agent.8mandos): New.
  (dracut-module/password-agent.8mandos.xhtml): - '' -
  (dracut-module/password-agent): - '' -
  (check): Add command to run tests of password-agent(8mandos).
  (install-client-nokey): Also install the dracut module directory,
                          its files, and the password-agent(8mandos)
                          manual page.
  (install-client): To update the initramfs image file, run
                    update-initramfs or dracut depending on what is
                    installed.
  (uninstall-client): - '' - and also uninstall the the files in the
                      dracut module directory, that directory itself,
                      and the password-agent(8mandos) manual page.
* debian/control (Build-Depends): Add "libglib2.0-dev (>=2.40)".
  (Package: mandos-client/Depends): Add "dracut (>= 044+241-3)" as an
                                    alternative dependency to
                                    initramfs-tools.
  (Package: mandos-client/Conflicts): New; set to
                                      "dracut-config-generic".
  (debian/mandos-client.README.Debian): Document alternative commands
                                        to update the initramfs image
                                        for when dracut is used.
* debian/mandos-client.postinst (update_initramfs): Use alternative
                                                    commands to update
                                                    the initramfs
                                                    image for when
                                                    dracut is used.
* debian/tests/control (password-agent, password-agent-suid): Add two
                                                              new tests.
* dracut-module/ask-password-mandos.path: New.
* dracut-module/ask-password-mandos.service: - '' -
* dracut-module/cmdline-mandos.sh: - '' -
* dracut-module/module-setup.sh: - '' -
* dracut-module/password-agent.c: - '' -
* dracut-module/password-agent.xml: - '' -
* initramfs-unpack: Use the dracut "skipcpio" command, if available.
                    Also be more flexible and try hard to detect where
                    compressed data starts.
* plugins.d/mandos-client.xml (SECURITY): Be more precise that the
                                          mandos-client binary might
                                          not always be setuid, but
                                          that the program assumes
                                          that it has been started
                                          that way.
* plugins.d/password-prompt.c: Add new "--prompt" option.
  (conflict_detection): First try to detect the new PID file of
                        plymouth.
  (main): Define and use new "prompt" variable.
* plugins.d/password-prompt.xml (SYNOPSIS): Show new --prompt option.
  (DESCRIPTION): Describe new behavior of looking for plymouth PID
                 file.
  (OPTIONS): Document new "--prompt" option.
  (ENVIRONMENT): Clarify that the CRYPTTAB_SOURCE and CRYPTTAB_NAME
                 environment variables are not used if the --prompt
                 option is used.  Remove unnecessarily specific
                 details about where the CRYPTTAB_SOURCE and
                 CRYPTTAB_NAME comes from, since this can now be
                 either initramfs-tools or dracut.
  (SEE ALSO): Remove superfluous crypttab(5) reference, and add commas
              to separate the other references.
* plugins.d/plymouth.c: Add new "--prompt" and "--debug" options.
  (debug): New global flag.
  (fprintf_plus): New function, used for debug output.
  (exec_and_wait): Add extra "const" to "argv" argument.
  (main): Define and use new "prompt" variable.  Add debug output.
  (main/options, main/parse_opt): New; used to parse options.
* plugins.d/plymouth.xml (SYNOPSIS): Show new options.
  (OPTIONS): Document new options.
  (ENVIRONMENT): Clarify that the cryptsource and crypttarget
                 environment variables are not used if the --prompt
                 option is used.  Remove unnecessarily specific
                 details about where the cryptsource and crypttarget
                 comes from, since this can now be either
                 initramfs-tools or dracut.
  (EXAMPLE): Add an example using an option.
  (SEE ALSO): Remove superfluous crypttab(5) reference.
* plugins.d/splashy.xml (ENVIRONMENT): Clarify that the cryptsource
                                       and crypttarget environment
                                       variables are not used if the
                                       --prompt option is used.
                                       Remove unnecessarily specific
                                       details about where the
                                       cryptsource and crypttarget
                                       comes from, since this can now
                                       be either initramfs-tools or
                                       dracut.
  (SEE ALSO): Remove superfluous crypttab(5) reference.
* plugins.d/usplash.xml (ENVIRONMENT): Clarify that the cryptsource
                                       and crypttarget environment
                                       variables are not used if the
                                       --prompt option is used.
                                       Remove unnecessarily specific
                                       details about where the
                                       cryptsource and crypttarget
                                       comes from, since this can now
                                       be either initramfs-tools or
                                       dracut.
  (SEE ALSO): Remove superfluous crypttab(5) reference.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- mode: c; coding: utf-8; after-save-hook: (lambda () (let* ((find-build-directory (lambda (try-directory &optional base-directory) (let ((base-directory (or base-directory try-directory))) (cond ((equal try-directory "/") base-directory) ((file-readable-p (concat (file-name-as-directory try-directory) "Makefile")) try-directory) ((funcall find-build-directory (directory-file-name (file-name-directory try-directory)) base-directory)))))) (build-directory (funcall find-build-directory (buffer-file-name))) (local-build-directory (if (fboundp 'file-local-name) (file-local-name build-directory) (or (file-remote-p build-directory 'localname) build-directory))) (command (file-relative-name (file-name-sans-extension (buffer-file-name)) build-directory))) (pcase (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (let ((qbdir (shell-quote-argument local-build-directory)) (qcmd (shell-quote-argument command))) (format "cd %s && CFLAGS=-Werror make --silent %s && %s --test --verbose" qbdir qcmd qcmd)) nil "*Test*")) (0 (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)))) (_ (with-current-buffer "*Test*" (compilation-mode) (cd-absolute build-directory)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); -*- */
 
2
/*
 
3
 * Mandos password agent - Simple password agent to run Mandos client
 
4
 *
 
5
 * Copyright © 2019 Teddy Hogeborn
 
6
 * Copyright © 2019 Björn Påhlsson
 
7
 * 
 
8
 * This file is part of Mandos.
 
9
 * 
 
10
 * Mandos is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 * 
 
15
 * Mandos is distributed in the hope that it will be useful, but
 
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
18
 * General Public License for more details.
 
19
 * 
 
20
 * You should have received a copy of the GNU General Public License
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
22
 * 
 
23
 * Contact the authors at <mandos@recompile.se>.
 
24
 */
 
25
 
 
26
#define _GNU_SOURCE
 
27
#include <inttypes.h>           /* uintmax_t, PRIuMAX, PRIdMAX,
 
28
                                   intmax_t, uint32_t, SCNx32,
 
29
                                   SCNuMAX, SCNxMAX */
 
30
#include <stddef.h>             /* size_t */
 
31
#include <sys/types.h>          /* pid_t, uid_t, gid_t, getuid(),
 
32
                                   getpid() */
 
33
#include <stdbool.h>            /* bool, true, false */
 
34
#include <signal.h>             /* struct sigaction, sigset_t,
 
35
                                   sigemptyset(), sigaddset(),
 
36
                                   SIGCHLD, pthread_sigmask(),
 
37
                                   SIG_BLOCK, SIG_SETMASK, SA_RESTART,
 
38
                                   SA_NOCLDSTOP, sigfillset(), kill(),
 
39
                                   SIGTERM, sigdelset(), SIGKILL,
 
40
                                   NSIG, sigismember(), SA_ONSTACK,
 
41
                                   SIG_DFL, SIG_IGN, SIGINT, SIGQUIT,
 
42
                                   SIGHUP, SIGSTOP, SIG_UNBLOCK */
 
43
#include <stdlib.h>             /* EXIT_SUCCESS, EXIT_FAILURE,
 
44
                                   malloc(), free(), strtoumax(),
 
45
                                   realloc(), setenv(), calloc(),
 
46
                                   mkdtemp(), mkostemp() */
 
47
#include <iso646.h>             /* not, or, and, xor */
 
48
#include <error.h>              /* error() */
 
49
#include <sysexits.h>           /* EX_USAGE, EX_OSERR, EX_OSFILE */
 
50
#include <errno.h>              /* errno, error_t, EACCES,
 
51
                                   ENAMETOOLONG, ENOENT, EEXIST,
 
52
                                   ECHILD, EPERM, ENOMEM, EAGAIN,
 
53
                                   EINTR, ENOBUFS, EADDRINUSE,
 
54
                                   ECONNREFUSED, ECONNRESET,
 
55
                                   ETOOMANYREFS, EMSGSIZE, EBADF,
 
56
                                   EINVAL */
 
57
#include <string.h>             /* strdup(), memcpy(),
 
58
                                   explicit_bzero(), memset(),
 
59
                                   strcmp(), strlen(), strncpy(),
 
60
                                   memcmp(), basename() */
 
61
#include <argz.h>               /* argz_create(), argz_count(),
 
62
                                   argz_extract(), argz_next(),
 
63
                                   argz_add() */
 
64
#include <sys/epoll.h>          /* epoll_create1(), EPOLL_CLOEXEC,
 
65
                                   epoll_ctl(), EPOLL_CTL_ADD,
 
66
                                   struct epoll_event, EPOLLIN,
 
67
                                   EPOLLRDHUP, EPOLLOUT,
 
68
                                   epoll_pwait() */
 
69
#include <time.h>               /* struct timespec, clock_gettime(),
 
70
                                   CLOCK_MONOTONIC */
 
71
#include <argp.h>               /* struct argp_option, OPTION_HIDDEN,
 
72
                                   OPTION_ALIAS, struct argp_state,
 
73
                                   ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
 
74
                                   struct argp, argp_parse(),
 
75
                                   ARGP_NO_EXIT */
 
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(),
 
88
                                   WEXITSTATUS() */
 
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(),
 
99
                                   fscanf() */
 
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(),
 
125
                                   socketpair() */
 
126
#include <glob.h>               /* globfree(), glob_t, glob(),
 
127
                                   GLOB_ERR, GLOB_NOSORT, GLOB_MARK,
 
128
                                   GLOB_ABORTED, GLOB_NOMATCH,
 
129
                                   GLOB_NOSPACE */
 
130
 
 
131
/* End of includes */
 
132
 
 
133
/* Start of declarations of private types and functions */
 
134
 
 
135
/* microseconds of CLOCK_MONOTONIC absolute time; 0 means unset */
 
136
typedef uintmax_t mono_microsecs;
 
137
 
 
138
/* "task_queue" - A queue of tasks to be run */
 
139
typedef struct {
 
140
  struct task_struct *tasks;    /* Tasks in this queue */
 
141
  size_t length;                /* Number of tasks */
 
142
  /* Memory allocated for "tasks", in bytes */
 
143
  size_t allocated;             
 
144
  /* Time when this queue should be run, at the latest */
 
145
  mono_microsecs next_run;
 
146
} __attribute__((designated_init)) task_queue;
 
147
 
 
148
/* "func_type" - A function type for task functions
 
149
 
 
150
   I.e. functions for the code which runs when a task is run, all have
 
151
   this type */
 
152
typedef void (task_func) (const struct task_struct,
 
153
                          task_queue *const)
 
154
  __attribute__((nonnull));
 
155
 
 
156
/* "buffer" - A data buffer for a growing array of bytes
 
157
 
 
158
   Used for the "password" variable */
 
159
typedef struct {
 
160
  char *data;
 
161
  size_t length;
 
162
  size_t allocated;
 
163
} __attribute__((designated_init)) buffer;
 
164
 
 
165
/* "string_set" - A set type which can contain strings
 
166
 
 
167
   Used by the "cancelled_filenames" variable */
 
168
typedef struct {
 
169
  char *argz;                   /* Do not access these except in */
 
170
  size_t argz_len;              /* the string_set_* functions */
 
171
} __attribute__((designated_init)) string_set;
 
172
 
 
173
/* "task_context" - local variables for tasks
 
174
 
 
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.
 
178
 
 
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;
 
199
 
 
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,
 
262
                            const char *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 *);
 
280
 
 
281
/* End of declarations of private types and functions */
 
282
 
 
283
/* Start of "main" section; this section LACKS TESTS!
 
284
 
 
285
   Code here should be as simple as possible. */
 
286
 
 
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>";
 
290
 
 
291
int main(int argc, char *argv[]){
 
292
 
 
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 */
 
299
    }
 
300
    return EXIT_FAILURE;        /* Some test(s) failed */
 
301
  }
 
302
 
 
303
  __attribute__((cleanup(cleanup_string)))
 
304
    char *agent_directory = NULL;
 
305
 
 
306
  __attribute__((cleanup(cleanup_string)))
 
307
    char *helper_directory = NULL;
 
308
 
 
309
  uid_t user = 0;
 
310
  gid_t group = 0;
 
311
 
 
312
  __attribute__((cleanup(cleanup_string)))
 
313
    char *mandos_argz = NULL;
 
314
  size_t mandos_argz_length = 0;
 
315
 
 
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");
 
323
  }
 
324
 
 
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 };
 
330
 
 
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()");
 
336
    }
 
337
  }
 
338
  if(helper_directory == NULL){
 
339
    helper_directory = strdup(default_helper_directory);
 
340
    if(helper_directory == NULL){
 
341
      error(EX_OSERR, errno, "Failed strdup()");
 
342
    }
 
343
  }
 
344
  if(user == 0){
 
345
    user = 65534;               /* nobody */
 
346
  }
 
347
  if(group == 0){
 
348
    group = 65534;              /* nogroup */
 
349
  }
 
350
  /* If parse_opt did not create an argz vector, create one with
 
351
     default values */
 
352
  if(mandos_argz == NULL){
 
353
#ifdef __GNUC__
 
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"
 
359
#endif
 
360
    errno = argz_create((char *const *)default_argv, &mandos_argz,
 
361
                        &mandos_argz_length);
 
362
#ifdef __GNUC__
 
363
#pragma GCC diagnostic pop
 
364
#endif
 
365
    if(errno != 0){
 
366
      error(EX_OSERR, errno, "Failed argz_create()");
 
367
    }
 
368
  }
 
369
  /* Use argz vector to create a normal argv, usable by execv() */
 
370
 
 
371
  char **mandos_argv = malloc((argz_count(mandos_argz,
 
372
                                          mandos_argz_length)
 
373
                               + 1) * sizeof(char *));
 
374
  if(mandos_argv == NULL){
 
375
    error_t saved_errno = errno;
 
376
    free(mandos_argz);
 
377
    error(EX_OSERR, saved_errno, "Failed malloc()");
 
378
  }
 
379
  argz_extract(mandos_argz, mandos_argz_length, mandos_argv);
 
380
 
 
381
  sigset_t orig_sigmask;
 
382
  if(not block_sigchld(&orig_sigmask)){
 
383
    return EX_OSERR;
 
384
  }
 
385
 
 
386
  struct sigaction old_sigchld_action;
 
387
  if(not setup_signal_handler(&old_sigchld_action)){
 
388
    return EX_OSERR;
 
389
  }
 
390
 
 
391
  mono_microsecs current_time = 0;
 
392
 
 
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);
 
397
  if(epoll_fd < 0){
 
398
    error(EX_OSERR, errno, "Failed to create epoll set fd");
 
399
  }
 
400
  __attribute__((cleanup(cleanup_queue)))
 
401
    task_queue *queue = create_queue();
 
402
  if(queue == NULL){
 
403
    error(EX_OSERR, errno, "Failed to create task queue");
 
404
  }
 
405
 
 
406
  __attribute__((cleanup(cleanup_buffer)))
 
407
    buffer password = {};
 
408
  bool password_is_read = false;
 
409
 
 
410
  __attribute__((cleanup(string_set_clear)))
 
411
    string_set cancelled_filenames = {};
 
412
 
 
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 */
 
420
  }
 
421
  /* These variables were only for start_mandos_client() and are not
 
422
     needed anymore */
 
423
  free(mandos_argv);
 
424
  free(mandos_argz);
 
425
  mandos_argz = NULL;
 
426
  if(not add_inotify_dir_watch(queue, epoll_fd, &quit_now, &password,
 
427
                               agent_directory, &cancelled_filenames,
 
428
                               &current_time, &mandos_client_exited,
 
429
                               &password_is_read)){
 
430
    switch(errno){              /* Error has already been printed */
 
431
    case EACCES:
 
432
    case ENAMETOOLONG:
 
433
    case ENOENT:
 
434
      return EX_OSFILE;
 
435
    default:
 
436
      return EX_OSERR;
 
437
    }
 
438
  }
 
439
  if(not add_existing_questions(queue, epoll_fd, &password,
 
440
                                &cancelled_filenames, &current_time,
 
441
                                &mandos_client_exited,
 
442
                                &password_is_read, agent_directory)){
 
443
    return EXIT_FAILURE;        /* Error has already been printed */
 
444
  }
 
445
 
 
446
  /* Run queue */
 
447
  do {
 
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"
 
452
            " events");
 
453
    }
 
454
 
 
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");
 
459
    }
 
460
 
 
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));
 
464
 
 
465
  restore_signal_handler(&old_sigchld_action);
 
466
  restore_sigmask(&orig_sigmask);
 
467
 
 
468
  return EXIT_SUCCESS;
 
469
}
 
470
 
 
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");
 
476
    return 0;
 
477
  }
 
478
  return ((mono_microsecs)currtime.tv_sec * 1000000) /* seconds */
 
479
    + ((mono_microsecs)currtime.tv_nsec / 1000);     /* nanoseconds */
 
480
}
 
481
 
 
482
/* End of "main" section */
 
483
 
 
484
/* Start of regular code section; ALL this code has tests */
 
485
 
 
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){
 
491
 
 
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"
 
501
      " user" },
 
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"
 
506
      " group" },
 
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, },
 
512
    { NULL },
 
513
  };
 
514
 
 
515
  __attribute__((nonnull(3)))
 
516
    error_t parse_opt(int key, char *arg, struct argp_state *state){
 
517
    errno = 0;
 
518
    switch(key){
 
519
    case 'd':                   /* --agent-directory */
 
520
      *agent_directory = strdup(arg);
 
521
      break;
 
522
    case 128:                   /* --helper-directory */
 
523
    case 129:                   /* --plugin-helper-dir */
 
524
      *helper_directory = strdup(arg);
 
525
      break;
 
526
    case 'u':                   /* --user */
 
527
    case 130:                   /* --userid */
 
528
      {
 
529
        char *tmp;
 
530
        uintmax_t tmp_id = 0;
 
531
        errno = 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;
 
536
        }
 
537
        *user = (uid_t)tmp_id;
 
538
        errno = 0;
 
539
        break;
 
540
      }
 
541
    case 'g':                   /* --group */
 
542
    case 131:                   /* --groupid */
 
543
      {
 
544
        char *tmp;
 
545
        uintmax_t tmp_id = 0;
 
546
        errno = 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;
 
551
        }
 
552
        *group = (gid_t)tmp_id;
 
553
        errno = 0;
 
554
        break;
 
555
      }
 
556
    case ARGP_KEY_ARGS:
 
557
      /* Copy arguments into argz vector */
 
558
      return argz_create(state->argv + state->next, mandos_argz,
 
559
                         mandos_argz_length);
 
560
    default:
 
561
      return ARGP_ERR_UNKNOWN;
 
562
    }
 
563
    return errno;
 
564
  }
 
565
 
 
566
  const struct argp argp = {
 
567
    .options=options,
 
568
    .parser=parse_opt,
 
569
    .args_doc="[MANDOS_CLIENT [OPTION...]]\n--test",
 
570
    .doc = "Mandos password agent -- runs Mandos client as a"
 
571
    " systemd password agent",
 
572
  };
 
573
 
 
574
  errno = argp_parse(&argp, argc, argv,
 
575
                     exit_failure ? 0 : ARGP_NO_EXIT, NULL, NULL);
 
576
 
 
577
  return errno == 0;
 
578
}
 
579
 
 
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");
 
585
    return false;
 
586
  }
 
587
  if(sigaddset(&sigchld_sigmask, SIGCHLD) < 0){
 
588
    error(0, errno, "Failed to add SIGCHLD to signal set");
 
589
    return false;
 
590
  }
 
591
  if(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, orig_sigmask) != 0){
 
592
    error(0, errno, "Failed to block SIGCHLD signal");
 
593
    return false;
 
594
  }
 
595
  return true;
 
596
}
 
597
 
 
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");
 
602
    return false;
 
603
  }
 
604
  return true;
 
605
}
 
606
 
 
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,
 
612
  };
 
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()");
 
617
    return false;
 
618
  }
 
619
  if(sigaction(SIGCHLD, &sigchld_action, old_sigchld_action) != 0){
 
620
    error(0, errno, "Failed to set SIGCHLD signal handler");
 
621
    return false;
 
622
  }
 
623
  return true;
 
624
}
 
625
 
 
626
__attribute__((nonnull, warn_unused_result))
 
627
bool restore_signal_handler(const struct sigaction *const
 
628
                            old_sigchld_action){
 
629
  if(sigaction(SIGCHLD, old_sigchld_action, NULL) != 0){
 
630
    error(0, errno, "Failed to restore signal handler");
 
631
    return false;
 
632
  }
 
633
  return true;
 
634
}
 
635
 
 
636
__attribute__((warn_unused_result, malloc))
 
637
task_queue *create_queue(void){
 
638
  task_queue *queue = malloc(sizeof(task_queue));
 
639
  if(queue){
 
640
    queue->tasks = NULL;
 
641
    queue->length = 0;
 
642
    queue->allocated = 0;
 
643
    queue->next_run = 0;
 
644
  }
 
645
  return queue;
 
646
}
 
647
 
 
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,
 
653
                                            needed_size);
 
654
    if(new_tasks == NULL){
 
655
      error(0, errno, "Failed to allocate %" PRIuMAX
 
656
            " bytes for queue->tasks", (uintmax_t)needed_size);
 
657
      return false;
 
658
    }
 
659
    queue->tasks = new_tasks;
 
660
    queue->allocated = needed_size;
 
661
  }
 
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));
 
667
  return true;
 
668
}
 
669
 
 
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);
 
677
  }
 
678
  if(task->pid > 0){
 
679
    kill(task->pid, SIGTERM);
 
680
  }
 
681
  if(task->fd > 0){
 
682
    close(task->fd);
 
683
  }
 
684
  errno = saved_errno;
 
685
}
 
686
 
 
687
__attribute__((nonnull))
 
688
void free_queue(task_queue *const queue){
 
689
  free(queue->tasks);
 
690
  free(queue);
 
691
}
 
692
 
 
693
__attribute__((nonnull))
 
694
void cleanup_queue(task_queue *const *const queue){
 
695
  if(*queue == NULL){
 
696
    return;
 
697
  }
 
698
  for(size_t i = 0; i < (*queue)->length; i++){
 
699
    const task_context *const task = ((*queue)->tasks)+i;
 
700
    cleanup_task(task);
 
701
  }
 
702
  free_queue(*queue);
 
703
}
 
704
 
 
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){
 
709
      return true;
 
710
    }
 
711
  }
 
712
  return false;
 
713
}
 
714
 
 
715
__attribute__((nonnull))
 
716
void cleanup_close(const int *const fd){
 
717
  const error_t saved_errno = errno;
 
718
  close(*fd);
 
719
  errno = saved_errno;
 
720
}
 
721
 
 
722
__attribute__((nonnull))
 
723
void cleanup_string(char *const *const ptr){
 
724
  free(*ptr);
 
725
}
 
726
 
 
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);
 
732
#else
 
733
    memset(buf->data, '\0', buf->allocated);
 
734
#endif
 
735
  }
 
736
  if(buf->data != NULL){
 
737
    if(munlock(buf->data, buf->allocated) != 0){
 
738
      error(0, errno, "Failed to unlock memory of old buffer");
 
739
    }
 
740
    free(buf->data);
 
741
    buf->data = NULL;
 
742
  }
 
743
  buf->length = 0;
 
744
  buf->allocated = 0;
 
745
}
 
746
 
 
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){
 
752
      return true;
 
753
    }
 
754
  }
 
755
  return false;
 
756
}
 
757
 
 
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)){
 
761
    return true;
 
762
  }
 
763
  error_t error = argz_add(&set->argz, &set->argz_len, str);
 
764
  if(error == 0){
 
765
    return true;
 
766
  }
 
767
  errno = error;
 
768
  return false;
 
769
}
 
770
 
 
771
__attribute__((nonnull))
 
772
void string_set_clear(string_set *set){
 
773
  free(set->argz);
 
774
  set->argz = NULL;
 
775
  set->argz_len = 0;
 
776
}
 
777
 
 
778
__attribute__((nonnull))
 
779
void string_set_swap(string_set *const set1, string_set *const set2){
 
780
  /* Swap contents of two string sets */
 
781
  {
 
782
    char *const tmp_argz = set1->argz;
 
783
    set1->argz = set2->argz;
 
784
    set2->argz = tmp_argz;
 
785
  }
 
786
  {
 
787
    const size_t tmp_argz_len = set1->argz_len;
 
788
    set1->argz_len = set2->argz_len;
 
789
    set2->argz_len = tmp_argz_len;
 
790
  }
 
791
}
 
792
 
 
793
__attribute__((nonnull, warn_unused_result))
 
794
bool start_mandos_client(task_queue *const queue,
 
795
                         const int epoll_fd,
 
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){
 
804
  int pipefds[2];
 
805
  if(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK) != 0){
 
806
    error(0, errno, "Failed to pipe2(..., O_CLOEXEC | O_NONBLOCK)");
 
807
    return false;
 
808
  }
 
809
 
 
810
  const pid_t pid = fork();
 
811
  if(pid == 0){
 
812
    if(not restore_signal_handler(old_sigchld_action)){
 
813
      _exit(EXIT_FAILURE);
 
814
    }
 
815
    if(not restore_sigmask(&sigmask)){
 
816
      _exit(EXIT_FAILURE);
 
817
    }
 
818
    if(close(pipefds[0]) != 0){
 
819
      error(0, errno, "Failed to close() parent pipe fd");
 
820
      _exit(EXIT_FAILURE);
 
821
    }
 
822
    if(dup2(pipefds[1], STDOUT_FILENO) == -1){
 
823
      error(0, errno, "Failed to dup2() pipe fd to stdout");
 
824
      _exit(EXIT_FAILURE);
 
825
    }
 
826
    if(close(pipefds[1]) != 0){
 
827
      error(0, errno, "Failed to close() old child pipe fd");
 
828
      _exit(EXIT_FAILURE);
 
829
    }
 
830
    if(setenv("MANDOSPLUGINHELPERDIR", helper_directory, 1) != 0){
 
831
      error(0, errno, "Failed to setenv(\"MANDOSPLUGINHELPERDIR\","
 
832
            " \"%s\", 1)", helper_directory);
 
833
      _exit(EXIT_FAILURE);
 
834
    }
 
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);
 
838
      _exit(EXIT_FAILURE);
 
839
    }
 
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);
 
843
      _exit(EXIT_FAILURE);
 
844
    }
 
845
#ifdef __GNUC__
 
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"
 
850
#endif
 
851
    execv(argv[0], (char **)argv);
 
852
#ifdef __GNUC__
 
853
#pragma GCC diagnostic pop
 
854
#endif
 
855
    error(0, errno, "execv(\"%s\", ...) failed", argv[0]);
 
856
    _exit(EXIT_FAILURE);
 
857
  }
 
858
  close(pipefds[1]);
 
859
 
 
860
  if(not add_to_queue(queue, (task_context){
 
861
        .func=wait_for_mandos_client_exit,
 
862
        .pid=pid,
 
863
        .mandos_client_exited=mandos_client_exited,
 
864
        .quit_now=quit_now,
 
865
      })){
 
866
    error(0, errno, "Failed to add wait_for_mandos_client to queue");
 
867
    close(pipefds[0]);
 
868
    return false;
 
869
  }
 
870
 
 
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");
 
876
    close(pipefds[0]);
 
877
    return false;
 
878
  }
 
879
 
 
880
  return add_to_queue(queue, (task_context){
 
881
      .func=read_mandos_client_output,
 
882
      .epoll_fd=epoll_fd,
 
883
      .fd=pipefds[0],
 
884
      .quit_now=quit_now,
 
885
      .password=password,
 
886
      .password_is_read=password_is_read,
 
887
    });
 
888
}
 
889
 
 
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;
 
896
 
 
897
  int status;
 
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");
 
902
      *quit_now = true;
 
903
    }
 
904
    break;
 
905
  case -1:                      /* Error */
 
906
    error(0, errno, "waitpid(%" PRIdMAX ") failed", (intmax_t)pid);
 
907
    if(errno != ECHILD){
 
908
      kill(pid, SIGTERM);
 
909
    }
 
910
    *quit_now = true;
 
911
    break;
 
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");
 
917
      *quit_now = true;
 
918
    }
 
919
  }
 
920
}
 
921
 
 
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;
 
930
 
 
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);
 
937
      *quit_now = true;
 
938
      close(fd);
 
939
      return;
 
940
    }
 
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");
 
945
      }
 
946
    }
 
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);
 
951
#else
 
952
      memset(password->data, '\0', password->allocated);
 
953
#endif
 
954
    }
 
955
    if(password->data != NULL){
 
956
      if(munlock(password->data, password->allocated) != 0){
 
957
        error(0, errno, "Failed to unlock memory of old buffer");
 
958
      }
 
959
      free(password->data);
 
960
    }
 
961
    password->data = new_buffer;
 
962
    password->allocated = new_potential_size;
 
963
  }
 
964
 
 
965
  const ssize_t read_length = read(fd, password->data
 
966
                                   + password->length, PIPE_BUF);
 
967
 
 
968
  if(read_length == 0){ /* EOF */
 
969
    *password_is_read = true;
 
970
    close(fd);
 
971
    return;
 
972
  }
 
973
  if(read_length < 0 and errno != EAGAIN){ /* Actual error */
 
974
    error(0, errno, "Failed to read password from Mandos client");
 
975
    *quit_now = true;
 
976
    close(fd);
 
977
    return;
 
978
  }
 
979
  if(read_length > 0){          /* Data has been read */
 
980
    password->length += (size_t)read_length;
 
981
  }
 
982
 
 
983
  /* Either data was read, or EAGAIN was indicated, meaning no data
 
984
     available yet */
 
985
 
 
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");
 
992
    *quit_now = true;
 
993
    close(fd);
 
994
    return;
 
995
  }
 
996
 
 
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");
 
1000
    *quit_now = true;
 
1001
    close(fd);
 
1002
  }
 
1003
}
 
1004
 
 
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);
 
1015
  if(fd == -1){
 
1016
    error(0, errno, "Failed to create inotify instance");
 
1017
    return false;
 
1018
  }
 
1019
 
 
1020
  if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE
 
1021
                       | IN_MOVED_TO | IN_DELETE)
 
1022
     == -1){
 
1023
    error(0, errno, "Failed to create inotify watch on %s", dir);
 
1024
    return false;
 
1025
  }
 
1026
 
 
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");
 
1033
    close(fd);
 
1034
    return false;
 
1035
  }
 
1036
 
 
1037
  const task_context read_inotify_event_task = {
 
1038
    .func=read_inotify_event,
 
1039
    .epoll_fd=epoll_fd,
 
1040
    .quit_now=quit_now,
 
1041
    .password=password,
 
1042
    .fd=fd,
 
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,
 
1048
  };
 
1049
  if(read_inotify_event_task.filename == NULL){
 
1050
    error(0, errno, "Failed to strdup(\"%s\")", dir);
 
1051
    close(fd);
 
1052
    return false;
 
1053
  }
 
1054
 
 
1055
  return add_to_queue(queue, read_inotify_event_task);
 
1056
}
 
1057
 
 
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;
 
1070
 
 
1071
  /* "sufficient to read at least one event." - inotify(7) */
 
1072
  const size_t ievent_size = (sizeof(struct inotify_event)
 
1073
                              + NAME_MAX + 1);
 
1074
  char ievent_buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
 
1075
  struct inotify_event *ievent = ((struct inotify_event *)
 
1076
                                  ievent_buffer);
 
1077
 
 
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);
 
1081
    *quit_now = true;
 
1082
    cleanup_task(&task);
 
1083
    return;
 
1084
  }
 
1085
  if(read_length < 0 and errno != EAGAIN){ /* Actual error */
 
1086
    error(0, errno, "Failed to read from inotify fd for directory %s",
 
1087
          filename);
 
1088
    *quit_now = true;
 
1089
    cleanup_task(&task);
 
1090
    return;
 
1091
  }
 
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);
 
1100
    } else {
 
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,
 
1104
              .epoll_fd=epoll_fd,
 
1105
              .question_filename=question_filename,
 
1106
              .filename=question_filename,
 
1107
              .password=password,
 
1108
              .cancelled_filenames=cancelled_filenames,
 
1109
              .current_time=current_time,
 
1110
              .mandos_client_exited=mandos_client_exited,
 
1111
              .password_is_read=password_is_read,
 
1112
            })){
 
1113
          error(0, errno, "Failed to add open_and_parse_question task"
 
1114
                " for file name %s to queue", filename);
 
1115
        } else {
 
1116
          /* Force the added task (open_and_parse_question) to run
 
1117
             immediately */
 
1118
          queue->next_run = 1;
 
1119
        }
 
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);
 
1125
          *quit_now = true;
 
1126
          free(question_filename);
 
1127
          cleanup_task(&task);
 
1128
          return;
 
1129
        }
 
1130
        free(question_filename);
 
1131
      }
 
1132
    }
 
1133
  }
 
1134
 
 
1135
  /* Either data was read, or EAGAIN was indicated, meaning no data
 
1136
     available yet */
 
1137
 
 
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);
 
1142
    *quit_now = true;
 
1143
    cleanup_task(&task);
 
1144
    return;
 
1145
  }
 
1146
 
 
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;
 
1159
    }
 
1160
  }
 
1161
}
 
1162
 
 
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;
 
1174
 
 
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);
 
1182
    }
 
1183
  }
 
1184
 
 
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\"",
 
1189
          question_filename);
 
1190
    return;
 
1191
  }
 
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);
 
1200
    }
 
1201
    return;
 
1202
  }
 
1203
 
 
1204
  __attribute__((cleanup(cleanup_string)))
 
1205
    char *socket_name = g_key_file_get_string(key_file, "Ask",
 
1206
                                              "Socket",
 
1207
                                              &glib_error);
 
1208
  if(socket_name == NULL){
 
1209
    error(0, 0, "Question file \"%s\" did not contain \"Socket\": %s",
 
1210
          question_filename, glib_error->message);
 
1211
    return;
 
1212
  }
 
1213
 
 
1214
  if(strlen(socket_name) == 0){
 
1215
    error(0, 0, "Question file \"%s\" had empty \"Socket\" value",
 
1216
          question_filename);
 
1217
    return;
 
1218
  }
 
1219
 
 
1220
  const guint64 pid = g_key_file_get_uint64(key_file, "Ask", "PID",
 
1221
                                            &glib_error);
 
1222
  if(glib_error != NULL){
 
1223
    error(0, 0, "Question file \"%s\" contained bad \"PID\": %s",
 
1224
          question_filename, glib_error->message);
 
1225
    return;
 
1226
  }
 
1227
 
 
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);
 
1232
    return;
 
1233
  }
 
1234
 
 
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);
 
1241
    }
 
1242
    notafter = 0;
 
1243
  }
 
1244
  if(notafter != 0){
 
1245
    if(queue->next_run == 0 or (queue->next_run > notafter)){
 
1246
      queue->next_run = notafter;
 
1247
    }
 
1248
    if(*current_time >= notafter){
 
1249
      return;
 
1250
    }
 
1251
  }
 
1252
 
 
1253
  const task_context connect_question_socket_task = {
 
1254
    .func=connect_question_socket,
 
1255
    .question_filename=strdup(question_filename),
 
1256
    .epoll_fd=epoll_fd,
 
1257
    .password=password,
 
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,
 
1263
  };
 
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,
 
1269
          question_filename);
 
1270
    cleanup_task(&connect_question_socket_task);
 
1271
    return;
 
1272
  }
 
1273
  /* Force the added task (connect_question_socket) to run
 
1274
     immediately */
 
1275
  queue->next_run = 1;
 
1276
 
 
1277
  if(notafter > 0){
 
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,
 
1282
      .notafter=notafter,
 
1283
      .filename=dup_filename,
 
1284
      .cancelled_filenames=cancelled_filenames,
 
1285
      .current_time=current_time,
 
1286
    };
 
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);
 
1292
      return;
 
1293
    }
 
1294
  }
 
1295
}
 
1296
 
 
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;
 
1304
 
 
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",
 
1308
            question_filename);
 
1309
    }
 
1310
    cleanup_task(&task);
 
1311
    return;
 
1312
  }
 
1313
 
 
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);
 
1318
    return;
 
1319
  }
 
1320
 
 
1321
  if((queue->next_run == 0) or (queue->next_run > notafter)){
 
1322
    queue->next_run = notafter;
 
1323
  }
 
1324
}
 
1325
 
 
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;
 
1337
 
 
1338
  struct sockaddr_un sock_name = { .sun_family=AF_LOCAL };
 
1339
 
 
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",
 
1346
            question_filename);
 
1347
    }
 
1348
    cleanup_task(&task);
 
1349
    return;
 
1350
  }
 
1351
 
 
1352
  const int fd = socket(PF_LOCAL, SOCK_DGRAM
 
1353
                        | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
 
1354
  if(fd < 0){
 
1355
    error(0, errno,
 
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,
 
1360
            filename);
 
1361
      cleanup_task(&task);
 
1362
    } else {
 
1363
      /* Force the added task (connect_question_socket) to run
 
1364
         immediately */
 
1365
      queue->next_run = 1;
 
1366
    }
 
1367
    return;
 
1368
  }
 
1369
 
 
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,
 
1377
            filename);
 
1378
      cleanup_task(&task);
 
1379
    } else {
 
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;
 
1385
      }
 
1386
    }
 
1387
    return;
 
1388
  }
 
1389
 
 
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\"",
 
1393
          filename);
 
1394
  }
 
1395
 
 
1396
  /* Add the fd to the epoll set */
 
1397
  if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
 
1398
               &(struct epoll_event){ .events=EPOLLOUT })
 
1399
     != 0){
 
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,
 
1405
            filename);
 
1406
      cleanup_task(&task);
 
1407
    } else {
 
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;
 
1413
      }
 
1414
    }
 
1415
    return;
 
1416
  }
 
1417
 
 
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,
 
1422
    .filename=filename,
 
1423
    .epoll_fd=epoll_fd,
 
1424
    .fd=fd,
 
1425
    .password=password,
 
1426
    .cancelled_filenames=cancelled_filenames,
 
1427
    .mandos_client_exited=mandos_client_exited,
 
1428
    .password_is_read=password_is_read,
 
1429
    .current_time=current_time,
 
1430
  };
 
1431
 
 
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);
 
1437
  }
 
1438
}
 
1439
 
 
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;
 
1452
 
 
1453
  if(*mandos_client_exited and *password_is_read){
 
1454
 
 
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");
 
1459
    } else {
 
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"
 
1464
                " buffer");
 
1465
        }
 
1466
      }
 
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
 
1471
         your message.
 
1472
 
 
1473
         — <https://www.freedesktop.org/wiki/Software/systemd/
 
1474
         PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
 
1475
      */
 
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);
 
1481
      }
 
1482
      errno = 0;
 
1483
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
 
1484
                           MSG_NOSIGNAL);
 
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);
 
1488
#else
 
1489
      memset(send_buffer, '\0', send_buffer_length);
 
1490
#endif
 
1491
      if(munlock(send_buffer, send_buffer_length) != 0){
 
1492
        error(0, errno, "Failed to unlock memory of send buffer");
 
1493
      }
 
1494
      free(send_buffer);
 
1495
      if(ssret < 0 or ssret < (ssize_t)send_buffer_length){
 
1496
        switch(saved_errno){
 
1497
        case EINTR:
 
1498
        case ENOBUFS:
 
1499
        case ENOMEM:
 
1500
        case EADDRINUSE:
 
1501
        case ECONNREFUSED:
 
1502
        case ECONNRESET:
 
1503
        case ENOENT:
 
1504
        case ETOOMANYREFS:
 
1505
        case EAGAIN:
 
1506
          /* Retry, below */
 
1507
          break;
 
1508
        case EMSGSIZE:
 
1509
          error(0, 0, "Password of size %" PRIuMAX " is too big",
 
1510
                (uintmax_t)password->length);
 
1511
#if __GNUC__ < 7
 
1512
          /* FALLTHROUGH */
 
1513
#else
 
1514
          __attribute__((fallthrough));
 
1515
#endif
 
1516
        case 0:
 
1517
          if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
 
1518
            error(0, 0, "Password only partially sent to socket");
 
1519
          }
 
1520
#if __GNUC__ < 7
 
1521
          /* FALLTHROUGH */
 
1522
#else
 
1523
          __attribute__((fallthrough));
 
1524
#endif
 
1525
        default:
 
1526
          error(0, saved_errno, "Failed to send() to socket %s",
 
1527
                filename);
 
1528
          if(not string_set_add(cancelled_filenames,
 
1529
                                question_filename)){
 
1530
            error(0, errno, "Failed to cancel question for file %s",
 
1531
                  question_filename);
 
1532
          }
 
1533
          cleanup_task(&task);
 
1534
          return;
 
1535
        }
 
1536
      } else {
 
1537
        /* Success */
 
1538
        cleanup_task(&task);
 
1539
        return;
 
1540
      }
 
1541
    }
 
1542
  }
 
1543
 
 
1544
  /* We failed or are not ready yet; retry later */
 
1545
 
 
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,
 
1549
          filename);
 
1550
    cleanup_task(&task);
 
1551
  }
 
1552
 
 
1553
  /* Add the fd to the epoll set */
 
1554
  if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd,
 
1555
               &(struct epoll_event){ .events=EPOLLOUT })
 
1556
     != 0){
 
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;
 
1564
    }
 
1565
  }
 
1566
}
 
1567
 
 
1568
__attribute__((warn_unused_result))
 
1569
bool add_existing_questions(task_queue *const queue,
 
1570
                            const int epoll_fd,
 
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",
 
1582
          dirname);
 
1583
    return false;
 
1584
  }
 
1585
  __attribute__((cleanup(globfree)))
 
1586
    glob_t question_filenames = {};
 
1587
  switch(glob(dir_pattern, GLOB_ERR | GLOB_NOSORT | GLOB_MARK,
 
1588
              NULL, &question_filenames)){
 
1589
  case GLOB_ABORTED:
 
1590
  default:
 
1591
    error(0, errno, "Failed to open directory %s", dirname);
 
1592
    return false;
 
1593
  case GLOB_NOMATCH:
 
1594
    error(0, errno, "There are no question files in %s", dirname);
 
1595
    return false;
 
1596
  case GLOB_NOSPACE:
 
1597
    error(0, errno, "Could not allocate memory for question file"
 
1598
          " names in %s", dirname);
 
1599
#if __GNUC__ < 7
 
1600
    /* FALLTHROUGH */
 
1601
#else
 
1602
    __attribute__((fallthrough));
 
1603
#endif
 
1604
  case 0:
 
1605
    for(size_t i = 0; i < question_filenames.gl_pathc; i++){
 
1606
      char *const question_filename = strdup(question_filenames
 
1607
                                             .gl_pathv[i]);
 
1608
      const task_context task = {
 
1609
        .func=open_and_parse_question,
 
1610
        .epoll_fd=epoll_fd,
 
1611
        .question_filename=question_filename,
 
1612
        .filename=question_filename,
 
1613
        .password=password,
 
1614
        .cancelled_filenames=cancelled_filenames,
 
1615
        .current_time=current_time,
 
1616
        .mandos_client_exited=mandos_client_exited,
 
1617
        .password_is_read=password_is_read,
 
1618
      };
 
1619
 
 
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);
 
1626
      } else {
 
1627
        queue->next_run = 1;
 
1628
      }
 
1629
    }
 
1630
    return true;
 
1631
  }
 
1632
}
 
1633
 
 
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){
 
1642
      return 0;
 
1643
    }
 
1644
    const uintmax_t wait_time_ms = (nextrun - currtime) / 1000;
 
1645
    if(wait_time_ms > (uintmax_t)INT_MAX){
 
1646
      return INT_MAX;
 
1647
    }
 
1648
    return (int)wait_time_ms;
 
1649
  }
 
1650
 
 
1651
  const int wait_time_ms = milliseconds_to_wait(current_time,
 
1652
                                                queue_next_run);
 
1653
 
 
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){
 
1658
    return false;
 
1659
  }
 
1660
  /* Remove SIGCHLD from the signal mask */
 
1661
  if(sigdelset(&temporary_unblocked_sigmask, SIGCHLD) != 0){
 
1662
    return false;
 
1663
  }
 
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,"
 
1671
          " ...", epoll_fd,
 
1672
          queue_next_run == 0 ? -1 : (int)wait_time_ms);
 
1673
    return false;
 
1674
  }
 
1675
  return clear_all_fds_from_epoll_set(epoll_fd);
 
1676
}
 
1677
 
 
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){
 
1683
    return false;
 
1684
  }
 
1685
  /* dup3() the new epoll set fd over the old one, replacing it */
 
1686
  if(dup3(new_epoll_fd, epoll_fd, O_CLOEXEC) < 0){
 
1687
    return false;
 
1688
  }
 
1689
  return true;
 
1690
}
 
1691
 
 
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){
 
1696
 
 
1697
  task_queue *new_queue = create_queue();
 
1698
  if(new_queue == NULL){
 
1699
    return false;
 
1700
  }
 
1701
 
 
1702
  __attribute__((cleanup(string_set_clear)))
 
1703
    string_set old_cancelled_filenames = {};
 
1704
  string_set_swap(cancelled_filenames, &old_cancelled_filenames);
 
1705
 
 
1706
  /* Declare i outside the for loop, since we might need i after the
 
1707
     loop in case we aborted in the middle */
 
1708
  size_t i;
 
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)){
 
1716
      cleanup_task(task);
 
1717
      continue;
 
1718
    }
 
1719
    task->func(*task, new_queue);
 
1720
  }
 
1721
 
 
1722
  if(*quit_now){
 
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]));
 
1727
    }
 
1728
    free_queue(*queue);
 
1729
    *queue = new_queue;
 
1730
    new_queue = NULL;
 
1731
    return false;
 
1732
  }
 
1733
  free_queue(*queue);
 
1734
  *queue = new_queue;
 
1735
  new_queue = NULL;
 
1736
 
 
1737
  return true;
 
1738
}
 
1739
 
 
1740
/* End of regular code section */
 
1741
 
 
1742
/* Start of tests section; here are the tests for the above code */
 
1743
 
 
1744
/* This "fixture" data structure is used by the test setup and
 
1745
   teardown functions */
 
1746
typedef struct {
 
1747
  struct sigaction orig_sigaction;
 
1748
  sigset_t orig_sigmask;
 
1749
} test_fixture;
 
1750
 
 
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));
 
1756
}
 
1757
 
 
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));
 
1763
}
 
1764
 
 
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 */
 
1776
 
 
1777
    /* task_func *const func */
 
1778
    if(task.func != NULL and current_task->func != task.func){
 
1779
      continue;
 
1780
    }
 
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)){
 
1786
      continue;
 
1787
    }
 
1788
    /* const pid_t pid; */
 
1789
    if(task.pid != 0 and current_task->pid != task.pid){
 
1790
      continue;
 
1791
    }
 
1792
    /* const int epoll_fd; */
 
1793
    if(task.epoll_fd != 0
 
1794
       and current_task->epoll_fd != task.epoll_fd){
 
1795
      continue;
 
1796
    }
 
1797
    /* bool *const quit_now; */
 
1798
    if(task.quit_now != NULL
 
1799
       and current_task->quit_now != task.quit_now){
 
1800
      continue;
 
1801
    }
 
1802
    /* const int fd; */
 
1803
    if(task.fd != 0 and current_task->fd != task.fd){
 
1804
      continue;
 
1805
    }
 
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){
 
1810
      continue;
 
1811
    }
 
1812
    /* buffer *const password; */
 
1813
    if(task.password != NULL
 
1814
       and current_task->password != task.password){
 
1815
      continue;
 
1816
    }
 
1817
    /* bool *const password_is_read; */
 
1818
    if(task.password_is_read != NULL
 
1819
       and current_task->password_is_read != task.password_is_read){
 
1820
      continue;
 
1821
    }
 
1822
    /* char *filename; */
 
1823
    if(task.filename != NULL
 
1824
       and (current_task->filename == NULL
 
1825
            or strcmp(current_task->filename, task.filename) != 0)){
 
1826
      continue;
 
1827
    }
 
1828
    /* string_set *const cancelled_filenames; */
 
1829
    if(task.cancelled_filenames != NULL
 
1830
       and current_task->cancelled_filenames
 
1831
       != task.cancelled_filenames){
 
1832
      continue;
 
1833
    }
 
1834
    /* const mono_microsecs notafter; */
 
1835
    if(task.notafter != 0
 
1836
       and current_task->notafter != task.notafter){
 
1837
      continue;
 
1838
    }
 
1839
    /* const mono_microsecs *const current_time; */
 
1840
    if(task.current_time != NULL
 
1841
       and current_task->current_time != task.current_time){
 
1842
      continue;
 
1843
    }
 
1844
    /* Current task matches all members; return it */
 
1845
    return current_task;
 
1846
  }
 
1847
  /* No task in queue matches passed pattern task */
 
1848
  return NULL;
 
1849
}
 
1850
 
 
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);
 
1861
}
 
1862
 
 
1863
static task_func dummy_func;
 
1864
 
 
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);
 
1872
 
 
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);
 
1878
}
 
1879
 
 
1880
static void dummy_func(__attribute__((unused))
 
1881
                       const task_context task,
 
1882
                       __attribute__((unused))
 
1883
                       task_queue *const queue){
 
1884
}
 
1885
 
 
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));
 
1894
}
 
1895
 
 
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));
 
1906
}
 
1907
 
 
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 = {
 
1919
    .func=dummy_func,
 
1920
    .question_filename=question_filename,
 
1921
  };
 
1922
  g_assert_true(add_to_queue(queue, task));
 
1923
  g_assert_true(queue_has_question(queue));
 
1924
}
 
1925
 
 
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));
 
1938
}
 
1939
 
 
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 = {
 
1953
    .func=dummy_func,
 
1954
    .question_filename=question_filename,
 
1955
  };
 
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));
 
1959
}
 
1960
 
 
1961
static void test_cleanup_buffer(__attribute__((unused))
 
1962
                                test_fixture *fixture,
 
1963
                                __attribute__((unused))
 
1964
                                gconstpointer user_data){
 
1965
  buffer buf = {};
 
1966
 
 
1967
  const size_t buffersize = 10;
 
1968
 
 
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);
 
1973
  }
 
1974
 
 
1975
  cleanup_buffer(&buf);
 
1976
  g_assert_null(buf.data);
 
1977
}
 
1978
 
 
1979
static
 
1980
void test_string_set_new_set_contains_nothing(__attribute__((unused))
 
1981
                                              test_fixture *fixture,
 
1982
                                              __attribute__((unused))
 
1983
                                              gconstpointer
 
1984
                                              user_data){
 
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"));
 
1989
}
 
1990
 
 
1991
static void
 
1992
test_string_set_with_added_string_contains_it(__attribute__((unused))
 
1993
                                              test_fixture *fixture,
 
1994
                                              __attribute__((unused))
 
1995
                                              gconstpointer
 
1996
                                              user_data){
 
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"));
 
2001
}
 
2002
 
 
2003
static void
 
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"));
 
2013
}
 
2014
 
 
2015
static
 
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"));
 
2028
}
 
2029
 
 
2030
static
 
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"));
 
2043
}
 
2044
 
 
2045
static void test_string_set_swap_one_with_one(__attribute__((unused))
 
2046
                                              test_fixture *fixture,
 
2047
                                              __attribute__((unused))
 
2048
                                              gconstpointer
 
2049
                                              user_data){
 
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"));
 
2061
}
 
2062
 
 
2063
static bool fd_has_cloexec_and_nonblock(const int);
 
2064
 
 
2065
static bool epoll_set_contains(int, int, uint32_t);
 
2066
 
 
2067
static void test_start_mandos_client(test_fixture *fixture,
 
2068
                                     __attribute__((unused))
 
2069
                                     gconstpointer user_data){
 
2070
 
 
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 };
 
2083
 
 
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));
 
2090
 
 
2091
  g_assert_cmpuint((unsigned int)queue->length, >=, 2);
 
2092
 
 
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,
 
2098
      });
 
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);
 
2103
 
 
2104
  const task_context *const added_read_task
 
2105
    = find_matching_task(queue, (task_context){
 
2106
        .func=read_mandos_client_output,
 
2107
        .epoll_fd=epoll_fd,
 
2108
        .password=&password,
 
2109
        .password_is_read=&password_is_read,
 
2110
        .quit_now=&quit_now,
 
2111
      });
 
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));
 
2117
}
 
2118
 
 
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));
 
2126
}
 
2127
 
 
2128
__attribute__((const))
 
2129
bool is_privileged(void){
 
2130
  uid_t user = getuid() + 1;
 
2131
  if(user == 0){                /* Overflow check */
 
2132
    user++;
 
2133
  }
 
2134
  gid_t group = getuid() + 1;
 
2135
  if(group == 0){               /* Overflow check */
 
2136
    group++;
 
2137
  }
 
2138
  const pid_t pid = fork();
 
2139
  if(pid == 0){                 /* Child */
 
2140
    if(setresgid((uid_t)-1, group, group) == -1){
 
2141
      if(errno != EPERM){
 
2142
        error(EXIT_FAILURE, errno, "Failed to setresgid(-1, %" PRIuMAX
 
2143
              ", %" PRIuMAX")", (uintmax_t)group, (uintmax_t)group);
 
2144
      }
 
2145
      exit(EXIT_FAILURE);
 
2146
    }
 
2147
    if(setresuid((uid_t)-1, user, user) == -1){
 
2148
      if(errno != EPERM){
 
2149
        error(EXIT_FAILURE, errno, "Failed to setresuid(-1, %" PRIuMAX
 
2150
              ", %" PRIuMAX")", (uintmax_t)user, (uintmax_t)user);
 
2151
      }
 
2152
      exit(EXIT_FAILURE);
 
2153
    }
 
2154
    exit(EXIT_SUCCESS);
 
2155
  }
 
2156
  int status;
 
2157
  waitpid(pid, &status, 0);
 
2158
  if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
 
2159
    return true;
 
2160
  }
 
2161
  return false;
 
2162
}
 
2163
 
 
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);
 
2172
 
 
2173
  FILE *fdinfo = fopen(fdinfo_name, "r");
 
2174
  g_assert_nonnull(fdinfo);
 
2175
  uint32_t reported_events;
 
2176
  buffer line = {};
 
2177
  int found_fd = -1;
 
2178
 
 
2179
  do {
 
2180
    if(getline(&line.data, &line.allocated, fdinfo) < 0){
 
2181
      break;
 
2182
    }
 
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){
 
2186
      if(found_fd == fd){
 
2187
        break;
 
2188
      }
 
2189
    }
 
2190
  } while(not feof(fdinfo) and not ferror(fdinfo));
 
2191
  g_assert_cmpint(fclose(fdinfo), ==, 0);
 
2192
  free(line.data);
 
2193
  if(found_fd != fd){
 
2194
    return false;
 
2195
  }
 
2196
 
 
2197
  if(events == 0){
 
2198
    /* Don't check events if none are given */
 
2199
    return true;
 
2200
  }
 
2201
  return (reported_events & eventmask) == (events & eventmask);
 
2202
}
 
2203
 
 
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 };
 
2220
 
 
2221
  {
 
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);
 
2229
 
 
2230
    const bool success = start_mandos_client(queue, epoll_fd,
 
2231
                                             &mandos_client_exited,
 
2232
                                             &quit_now,
 
2233
                                             &password,
 
2234
                                             (bool[]){false},
 
2235
                                             &fixture->orig_sigaction,
 
2236
                                             fixture->orig_sigmask,
 
2237
                                             helper_directory, 0, 0,
 
2238
                                             argv);
 
2239
    dup2(real_stderr_fd, STDERR_FILENO);
 
2240
    g_assert_true(success);
 
2241
  }
 
2242
  g_assert_cmpuint((unsigned int)queue->length, ==, 2);
 
2243
 
 
2244
  struct timespec starttime, currtime;
 
2245
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2246
  do {
 
2247
    queue->next_run = 0;
 
2248
    string_set cancelled_filenames = {};
 
2249
 
 
2250
    {
 
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,
 
2261
                                     &quit_now);
 
2262
      dup2(real_stderr_fd, STDERR_FILENO);
 
2263
      if(not success){
 
2264
        break;
 
2265
      }
 
2266
    }
 
2267
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
 
2268
  } while(((queue->length) > 0)
 
2269
          and (not quit_now)
 
2270
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2271
 
 
2272
  g_assert_true(quit_now);
 
2273
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2274
  g_assert_true(mandos_client_exited);
 
2275
}
 
2276
 
 
2277
static void test_start_mandos_client_suid_euid(test_fixture *fixture,
 
2278
                                               __attribute__((unused))
 
2279
                                               gconstpointer
 
2280
                                               user_data){
 
2281
  if(not is_privileged()){
 
2282
    g_test_skip("Not privileged");
 
2283
    return;
 
2284
  }
 
2285
 
 
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 };
 
2299
  uid_t user = 1000;
 
2300
  gid_t group = 1001;
 
2301
 
 
2302
  const bool success = start_mandos_client(queue, epoll_fd,
 
2303
                                           &mandos_client_exited,
 
2304
                                           &quit_now, &password,
 
2305
                                           &password_is_read,
 
2306
                                           &fixture->orig_sigaction,
 
2307
                                           fixture->orig_sigmask,
 
2308
                                           helper_directory, user,
 
2309
                                           group, argv);
 
2310
  g_assert_true(success);
 
2311
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
2312
 
 
2313
  struct timespec starttime, currtime;
 
2314
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2315
  do {
 
2316
    queue->next_run = 0;
 
2317
    string_set cancelled_filenames = {};
 
2318
    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)
 
2322
          and (not quit_now)
 
2323
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2324
 
 
2325
  g_assert_false(quit_now);
 
2326
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2327
  g_assert_true(mandos_client_exited);
 
2328
 
 
2329
  g_assert_true(password_is_read);
 
2330
  g_assert_nonnull(password.data);
 
2331
 
 
2332
  uintmax_t id;
 
2333
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
 
2334
                  ==, 1);
 
2335
  g_assert_true((uid_t)id == id);
 
2336
 
 
2337
  g_assert_cmpuint((unsigned int)id, ==, 0);
 
2338
}
 
2339
 
 
2340
static void test_start_mandos_client_suid_egid(test_fixture *fixture,
 
2341
                                               __attribute__((unused))
 
2342
                                               gconstpointer
 
2343
                                               user_data){
 
2344
  if(not is_privileged()){
 
2345
    g_test_skip("Not privileged");
 
2346
    return;
 
2347
  }
 
2348
 
 
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 };
 
2362
  uid_t user = 1000;
 
2363
  gid_t group = 1001;
 
2364
 
 
2365
  const bool success = start_mandos_client(queue, epoll_fd,
 
2366
                                           &mandos_client_exited,
 
2367
                                           &quit_now, &password,
 
2368
                                           &password_is_read,
 
2369
                                           &fixture->orig_sigaction,
 
2370
                                           fixture->orig_sigmask,
 
2371
                                           helper_directory, user,
 
2372
                                           group, argv);
 
2373
  g_assert_true(success);
 
2374
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
2375
 
 
2376
  struct timespec starttime, currtime;
 
2377
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2378
  do {
 
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)
 
2385
          and (not quit_now)
 
2386
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2387
 
 
2388
  g_assert_false(quit_now);
 
2389
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2390
  g_assert_true(mandos_client_exited);
 
2391
 
 
2392
  g_assert_true(password_is_read);
 
2393
  g_assert_nonnull(password.data);
 
2394
 
 
2395
  uintmax_t id;
 
2396
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
 
2397
                  ==, 1);
 
2398
  g_assert_true((gid_t)id == id);
 
2399
 
 
2400
  g_assert_cmpuint((unsigned int)id, ==, 0);
 
2401
}
 
2402
 
 
2403
static void test_start_mandos_client_suid_ruid(test_fixture *fixture,
 
2404
                                               __attribute__((unused))
 
2405
                                               gconstpointer
 
2406
                                               user_data){
 
2407
  if(not is_privileged()){
 
2408
    g_test_skip("Not privileged");
 
2409
    return;
 
2410
  }
 
2411
 
 
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",
 
2425
    NULL };
 
2426
  uid_t user = 1000;
 
2427
  gid_t group = 1001;
 
2428
 
 
2429
  const bool success = start_mandos_client(queue, epoll_fd,
 
2430
                                           &mandos_client_exited,
 
2431
                                           &quit_now, &password,
 
2432
                                           &password_is_read,
 
2433
                                           &fixture->orig_sigaction,
 
2434
                                           fixture->orig_sigmask,
 
2435
                                           helper_directory, user,
 
2436
                                           group, argv);
 
2437
  g_assert_true(success);
 
2438
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
2439
 
 
2440
  struct timespec starttime, currtime;
 
2441
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2442
  do {
 
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)
 
2449
          and (not quit_now)
 
2450
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2451
 
 
2452
  g_assert_false(quit_now);
 
2453
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2454
  g_assert_true(mandos_client_exited);
 
2455
 
 
2456
  g_assert_true(password_is_read);
 
2457
  g_assert_nonnull(password.data);
 
2458
 
 
2459
  uintmax_t id;
 
2460
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
 
2461
                  ==, 1);
 
2462
  g_assert_true((uid_t)id == id);
 
2463
 
 
2464
  g_assert_cmpuint((unsigned int)id, ==, user);
 
2465
}
 
2466
 
 
2467
static void test_start_mandos_client_suid_rgid(test_fixture *fixture,
 
2468
                                               __attribute__((unused))
 
2469
                                               gconstpointer
 
2470
                                               user_data){
 
2471
  if(not is_privileged()){
 
2472
    g_test_skip("Not privileged");
 
2473
    return;
 
2474
  }
 
2475
 
 
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",
 
2489
    NULL };
 
2490
  uid_t user = 1000;
 
2491
  gid_t group = 1001;
 
2492
 
 
2493
  const bool success = start_mandos_client(queue, epoll_fd,
 
2494
                                           &mandos_client_exited,
 
2495
                                           &quit_now, &password,
 
2496
                                           &password_is_read,
 
2497
                                           &fixture->orig_sigaction,
 
2498
                                           fixture->orig_sigmask,
 
2499
                                           helper_directory, user,
 
2500
                                           group, argv);
 
2501
  g_assert_true(success);
 
2502
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
2503
 
 
2504
  struct timespec starttime, currtime;
 
2505
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2506
  do {
 
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)
 
2513
          and (not quit_now)
 
2514
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2515
 
 
2516
  g_assert_false(quit_now);
 
2517
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2518
  g_assert_true(mandos_client_exited);
 
2519
 
 
2520
  g_assert_true(password_is_read);
 
2521
  g_assert_nonnull(password.data);
 
2522
 
 
2523
  uintmax_t id;
 
2524
  g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id),
 
2525
                  ==, 1);
 
2526
  g_assert_true((gid_t)id == id);
 
2527
 
 
2528
  g_assert_cmpuint((unsigned int)id, ==, group);
 
2529
}
 
2530
 
 
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,
 
2548
    NULL };
 
2549
 
 
2550
  const bool success = start_mandos_client(queue, epoll_fd,
 
2551
                                           &mandos_client_exited,
 
2552
                                           &quit_now, &password,
 
2553
                                           &password_is_read,
 
2554
                                           &fixture->orig_sigaction,
 
2555
                                           fixture->orig_sigmask,
 
2556
                                           helper_directory, 0, 0,
 
2557
                                           argv);
 
2558
  g_assert_true(success);
 
2559
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
2560
 
 
2561
  struct timespec starttime, currtime;
 
2562
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2563
  do {
 
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)
 
2570
          and (not quit_now)
 
2571
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2572
 
 
2573
  g_assert_false(quit_now);
 
2574
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2575
  g_assert_true(mandos_client_exited);
 
2576
 
 
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);
 
2583
}
 
2584
 
 
2585
static
 
2586
void test_start_mandos_client_helper_directory(test_fixture *fixture,
 
2587
                                               __attribute__((unused))
 
2588
                                               gconstpointer
 
2589
                                               user_data){
 
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 };
 
2604
 
 
2605
  const bool success = start_mandos_client(queue, epoll_fd,
 
2606
                                           &mandos_client_exited,
 
2607
                                           &quit_now, &password,
 
2608
                                           &password_is_read,
 
2609
                                           &fixture->orig_sigaction,
 
2610
                                           fixture->orig_sigmask,
 
2611
                                           helper_directory, 0, 0,
 
2612
                                           argv);
 
2613
  g_assert_true(success);
 
2614
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
2615
 
 
2616
  struct timespec starttime, currtime;
 
2617
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2618
  do {
 
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)
 
2625
          and (not quit_now)
 
2626
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2627
 
 
2628
  g_assert_false(quit_now);
 
2629
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2630
  g_assert_true(mandos_client_exited);
 
2631
 
 
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);
 
2638
}
 
2639
 
 
2640
__attribute__((nonnull, warn_unused_result))
 
2641
static bool proc_status_sigblk_to_sigset(const char *const,
 
2642
                                         sigset_t *const);
 
2643
 
 
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 };
 
2662
 
 
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));
 
2669
 
 
2670
  struct timespec starttime, currtime;
 
2671
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2672
  do {
 
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))
 
2679
          and (not quit_now)
 
2680
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2681
  g_assert_true(mandos_client_exited);
 
2682
  g_assert_true(password_is_read);
 
2683
 
 
2684
  sigset_t parsed_sigmask;
 
2685
  g_assert_true(proc_status_sigblk_to_sigset(password.data,
 
2686
                                             &parsed_sigmask));
 
2687
 
 
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);
 
2692
    } else {
 
2693
      g_assert_false(has_signal);
 
2694
    }
 
2695
  }
 
2696
}
 
2697
 
 
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){
 
2704
    return false;
 
2705
  }
 
2706
  if(sigemptyset(sigmask) != 0){
 
2707
    return false;
 
2708
  }
 
2709
  for(int signum = 1; signum < NSIG; signum++){
 
2710
    if(scanned_sigmask & ((uintmax_t)1 << (signum-1))){
 
2711
      if(sigaddset(sigmask, signum) != 0){
 
2712
        return false;
 
2713
      }
 
2714
    }
 
2715
  }
 
2716
  return true;
 
2717
}
 
2718
 
 
2719
static void run_task_with_stderr_to_dev_null(const task_context task,
 
2720
                                             task_queue *const queue);
 
2721
 
 
2722
static
 
2723
void test_wait_for_mandos_client_exit_badpid(__attribute__((unused))
 
2724
                                             test_fixture *fixture,
 
2725
                                             __attribute__((unused))
 
2726
                                             gconstpointer user_data){
 
2727
 
 
2728
  bool mandos_client_exited = false;
 
2729
  bool quit_now = false;
 
2730
 
 
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,
 
2736
    .pid=1,
 
2737
    .mandos_client_exited=&mandos_client_exited,
 
2738
    .quit_now=&quit_now,
 
2739
  };
 
2740
  run_task_with_stderr_to_dev_null(task, queue);
 
2741
 
 
2742
  g_assert_false(mandos_client_exited);
 
2743
  g_assert_true(quit_now);
 
2744
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2745
}
 
2746
 
 
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);
 
2752
 
 
2753
  stderr = devnull;
 
2754
  task.func(task, queue);
 
2755
  stderr = real_stderr;
 
2756
 
 
2757
  g_assert_cmpint(fclose(devnull), ==, 0);
 
2758
}
 
2759
 
 
2760
static
 
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;
 
2766
 
 
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);
 
2772
      }
 
2773
      if(not restore_sigmask(&fixture->orig_sigmask)){
 
2774
        _exit(EXIT_FAILURE);
 
2775
      }
 
2776
      while(true){
 
2777
        pause();
 
2778
      }
 
2779
    }
 
2780
    return pid;
 
2781
  }
 
2782
  pid_t pid = create_eternal_process();
 
2783
  g_assert_true(pid != -1);
 
2784
 
 
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,
 
2790
    .pid=pid,
 
2791
    .mandos_client_exited=&mandos_client_exited,
 
2792
    .quit_now=&quit_now,
 
2793
  };
 
2794
  task.func(task, queue);
 
2795
 
 
2796
  g_assert_false(mandos_client_exited);
 
2797
  g_assert_false(quit_now);
 
2798
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
 
2799
 
 
2800
  g_assert_nonnull(find_matching_task(queue, (task_context){
 
2801
        .func=wait_for_mandos_client_exit,
 
2802
        .pid=task.pid,
 
2803
        .mandos_client_exited=&mandos_client_exited,
 
2804
        .quit_now=&quit_now,
 
2805
      }));
 
2806
}
 
2807
 
 
2808
static
 
2809
void test_wait_for_mandos_client_exit_success(test_fixture *fixture,
 
2810
                                              __attribute__((unused))
 
2811
                                              gconstpointer
 
2812
                                              user_data){
 
2813
  bool mandos_client_exited = false;
 
2814
  bool quit_now = false;
 
2815
 
 
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);
 
2821
      }
 
2822
      if(not restore_sigmask(&fixture->orig_sigmask)){
 
2823
        _exit(EXIT_FAILURE);
 
2824
      }
 
2825
      exit(EXIT_SUCCESS);
 
2826
    }
 
2827
    return pid;
 
2828
  }
 
2829
  const pid_t pid = create_successful_process();
 
2830
  g_assert_true(pid != -1);
 
2831
 
 
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,
 
2837
    .pid=pid,
 
2838
    .mandos_client_exited=&mandos_client_exited,
 
2839
    .quit_now=&quit_now,
 
2840
  };
 
2841
  g_assert_true(add_to_queue(queue, initial_task));
 
2842
 
 
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);
 
2847
  do {
 
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)
 
2853
          and (not quit_now)
 
2854
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2855
 
 
2856
  g_assert_true(mandos_client_exited);
 
2857
  g_assert_false(quit_now);
 
2858
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2859
}
 
2860
 
 
2861
static
 
2862
void test_wait_for_mandos_client_exit_failure(test_fixture *fixture,
 
2863
                                              __attribute__((unused))
 
2864
                                              gconstpointer
 
2865
                                              user_data){
 
2866
  bool mandos_client_exited = false;
 
2867
  bool quit_now = false;
 
2868
 
 
2869
  pid_t create_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);
 
2874
      }
 
2875
      if(not restore_sigmask(&fixture->orig_sigmask)){
 
2876
        _exit(EXIT_FAILURE);
 
2877
      }
 
2878
      exit(EXIT_FAILURE);
 
2879
    }
 
2880
    return pid;
 
2881
  }
 
2882
  const pid_t pid = create_failing_process();
 
2883
  g_assert_true(pid != -1);
 
2884
 
 
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,
 
2895
        .pid=pid,
 
2896
        .mandos_client_exited=&mandos_client_exited,
 
2897
        .quit_now=&quit_now,
 
2898
      }));
 
2899
 
 
2900
  g_assert_true(sigismember(&fixture->orig_sigmask, SIGCHLD) == 0);
 
2901
 
 
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);
 
2909
 
 
2910
  struct timespec starttime, currtime;
 
2911
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2912
  do {
 
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,
 
2916
                                   &quit_now);
 
2917
    dup2(real_stderr_fd, STDERR_FILENO);
 
2918
    if(not success){
 
2919
      break;
 
2920
    }
 
2921
 
 
2922
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
 
2923
  } while((not mandos_client_exited)
 
2924
          and (not quit_now)
 
2925
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2926
 
 
2927
  g_assert_true(quit_now);
 
2928
  g_assert_true(mandos_client_exited);
 
2929
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
2930
}
 
2931
 
 
2932
static
 
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;
 
2938
 
 
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);
 
2944
      }
 
2945
      if(not restore_sigmask(&fixture->orig_sigmask)){
 
2946
        _exit(EXIT_FAILURE);
 
2947
      }
 
2948
      while(true){
 
2949
        pause();
 
2950
      }
 
2951
    }
 
2952
    kill(pid, SIGKILL);
 
2953
    return pid;
 
2954
  }
 
2955
  const pid_t pid = create_killed_process();
 
2956
  g_assert_true(pid != -1);
 
2957
 
 
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,
 
2968
        .pid=pid,
 
2969
        .mandos_client_exited=&mandos_client_exited,
 
2970
        .quit_now=&quit_now,
 
2971
      }));
 
2972
 
 
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);
 
2980
 
 
2981
  struct timespec starttime, currtime;
 
2982
  g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0);
 
2983
  do {
 
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,
 
2987
                                   &quit_now);
 
2988
    dup2(real_stderr_fd, STDERR_FILENO);
 
2989
    if(not success){
 
2990
      break;
 
2991
    }
 
2992
 
 
2993
    g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0);
 
2994
  } while((not mandos_client_exited)
 
2995
          and (not quit_now)
 
2996
          and ((currtime.tv_sec - starttime.tv_sec) < 10));
 
2997
 
 
2998
  g_assert_true(mandos_client_exited);
 
2999
  g_assert_true(quit_now);
 
3000
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
3001
}
 
3002
 
 
3003
static bool epoll_set_does_not_contain(int, int);
 
3004
 
 
3005
static
 
3006
void test_read_mandos_client_output_readerror(__attribute__((unused))
 
3007
                                              test_fixture *fixture,
 
3008
                                              __attribute__((unused))
 
3009
                                              gconstpointer
 
3010
                                              user_data){
 
3011
  __attribute__((cleanup(cleanup_close)))
 
3012
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
 
3013
  g_assert_cmpint(epoll_fd, >=, 0);
 
3014
 
 
3015
  __attribute__((cleanup(cleanup_buffer)))
 
3016
    buffer password = {};
 
3017
 
 
3018
  /* Reading /proc/self/mem from offset 0 will always give EIO */
 
3019
  const int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
 
3020
 
 
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);
 
3026
 
 
3027
  task_context task = {
 
3028
    .func=read_mandos_client_output,
 
3029
    .epoll_fd=epoll_fd,
 
3030
    .fd=fd,
 
3031
    .password=&password,
 
3032
    .password_is_read=&password_is_read,
 
3033
    .quit_now=&quit_now,
 
3034
  };
 
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);
 
3040
 
 
3041
  g_assert_true(epoll_set_does_not_contain(epoll_fd, fd));
 
3042
 
 
3043
  g_assert_cmpint(close(fd), ==, -1);
 
3044
}
 
3045
 
 
3046
static bool epoll_set_does_not_contain(int epoll_fd, int fd){
 
3047
  return not epoll_set_contains(epoll_fd, fd, 0);
 
3048
}
 
3049
 
 
3050
static
 
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);
 
3058
 
 
3059
  int pipefds[2];
 
3060
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
 
3061
 
 
3062
  __attribute__((cleanup(cleanup_buffer)))
 
3063
    buffer password = {};
 
3064
 
 
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);
 
3070
 
 
3071
  task_context task = {
 
3072
    .func=read_mandos_client_output,
 
3073
    .epoll_fd=epoll_fd,
 
3074
    .fd=pipefds[0],
 
3075
    .password=&password,
 
3076
    .password_is_read=&password_is_read,
 
3077
    .quit_now=&quit_now,
 
3078
  };
 
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);
 
3084
 
 
3085
  g_assert_nonnull(find_matching_task(queue, (task_context){
 
3086
        .func=read_mandos_client_output,
 
3087
        .epoll_fd=epoll_fd,
 
3088
        .fd=pipefds[0],
 
3089
        .password=&password,
 
3090
        .password_is_read=&password_is_read,
 
3091
        .quit_now=&quit_now,
 
3092
      }));
 
3093
 
 
3094
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
 
3095
                                   EPOLLIN | EPOLLRDHUP));
 
3096
 
 
3097
  g_assert_cmpint(close(pipefds[1]), ==, 0);
 
3098
}
 
3099
 
 
3100
static void test_read_mandos_client_output_eof(__attribute__((unused))
 
3101
                                               test_fixture *fixture,
 
3102
                                               __attribute__((unused))
 
3103
                                               gconstpointer
 
3104
                                               user_data){
 
3105
  __attribute__((cleanup(cleanup_close)))
 
3106
    const int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
 
3107
  g_assert_cmpint(epoll_fd, >=, 0);
 
3108
 
 
3109
  int pipefds[2];
 
3110
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
 
3111
  g_assert_cmpint(close(pipefds[1]), ==, 0);
 
3112
 
 
3113
  __attribute__((cleanup(cleanup_buffer)))
 
3114
    buffer password = {};
 
3115
 
 
3116
  bool password_is_read = false;
 
3117
  bool quit_now = false;
 
3118
  __attribute__((cleanup(cleanup_queue)))
 
3119
    task_queue *queue = create_queue();
 
3120
  g_assert_nonnull(queue);
 
3121
 
 
3122
  task_context task = {
 
3123
    .func=read_mandos_client_output,
 
3124
    .epoll_fd=epoll_fd,
 
3125
    .fd=pipefds[0],
 
3126
    .password=&password,
 
3127
    .password_is_read=&password_is_read,
 
3128
    .quit_now=&quit_now,
 
3129
  };
 
3130
  task.func(task, queue);
 
3131
  g_assert_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);
 
3135
 
 
3136
  g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0]));
 
3137
 
 
3138
  g_assert_cmpint(close(pipefds[0]), ==, -1);
 
3139
}
 
3140
 
 
3141
static
 
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);
 
3149
 
 
3150
  int pipefds[2];
 
3151
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
 
3152
 
 
3153
  const char dummy_test_password[] = "dummy test password";
 
3154
  /* Start with a pre-allocated buffer */
 
3155
  __attribute__((cleanup(cleanup_buffer)))
 
3156
    buffer password = {
 
3157
    .data=malloc(sizeof(dummy_test_password)),
 
3158
    .length=0,
 
3159
    .allocated=sizeof(dummy_test_password),
 
3160
  };
 
3161
  g_assert_nonnull(password.data);
 
3162
  if(mlock(password.data, password.allocated) != 0){
 
3163
    g_assert_true(errno == EPERM or errno == ENOMEM);
 
3164
  }
 
3165
 
 
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);
 
3171
 
 
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));
 
3176
 
 
3177
  task_context task = {
 
3178
    .func=read_mandos_client_output,
 
3179
    .epoll_fd=epoll_fd,
 
3180
    .fd=pipefds[0],
 
3181
    .password=&password,
 
3182
    .password_is_read=&password_is_read,
 
3183
    .quit_now=&quit_now,
 
3184
  };
 
3185
  task.func(task, queue);
 
3186
 
 
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);
 
3193
 
 
3194
  g_assert_false(quit_now);
 
3195
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
 
3196
 
 
3197
  g_assert_nonnull(find_matching_task(queue, (task_context){
 
3198
        .func=read_mandos_client_output,
 
3199
        .epoll_fd=epoll_fd,
 
3200
        .fd=pipefds[0],
 
3201
        .password=&password,
 
3202
        .password_is_read=&password_is_read,
 
3203
        .quit_now=&quit_now,
 
3204
      }));
 
3205
 
 
3206
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
 
3207
                                   EPOLLIN | EPOLLRDHUP));
 
3208
 
 
3209
  g_assert_cmpint(close(pipefds[1]), ==, 0);
 
3210
}
 
3211
 
 
3212
static
 
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);
 
3220
 
 
3221
  int pipefds[2];
 
3222
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
 
3223
 
 
3224
  const char dummy_test_password[] = "dummy test password";
 
3225
  /* Start with an empty buffer */
 
3226
  __attribute__((cleanup(cleanup_buffer)))
 
3227
    buffer password = {};
 
3228
 
 
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);
 
3234
 
 
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));
 
3239
 
 
3240
  task_context task = {
 
3241
    .func=read_mandos_client_output,
 
3242
    .epoll_fd=epoll_fd,
 
3243
    .fd=pipefds[0],
 
3244
    .password=&password,
 
3245
    .password_is_read=&password_is_read,
 
3246
    .quit_now=&quit_now,
 
3247
  };
 
3248
  task.func(task, queue);
 
3249
 
 
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);
 
3256
 
 
3257
  g_assert_false(quit_now);
 
3258
  g_assert_cmpuint((unsigned int)queue->length, ==, 1);
 
3259
 
 
3260
  g_assert_nonnull(find_matching_task(queue, (task_context){
 
3261
        .func=read_mandos_client_output,
 
3262
        .epoll_fd=epoll_fd,
 
3263
        .fd=pipefds[0],
 
3264
        .password=&password,
 
3265
        .password_is_read=&password_is_read,
 
3266
        .quit_now=&quit_now,
 
3267
      }));
 
3268
 
 
3269
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
 
3270
                                   EPOLLIN | EPOLLRDHUP));
 
3271
 
 
3272
  g_assert_cmpint(close(pipefds[1]), ==, 0);
 
3273
}
 
3274
 
 
3275
static
 
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);
 
3283
 
 
3284
  int pipefds[2];
 
3285
  g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0);
 
3286
 
 
3287
  const char dummy_test_password[] = "dummy test password";
 
3288
  __attribute__((cleanup(cleanup_buffer)))
 
3289
    buffer password = {
 
3290
    .data=malloc(PIPE_BUF),
 
3291
    .length=PIPE_BUF,
 
3292
    .allocated=PIPE_BUF,
 
3293
  };
 
3294
  g_assert_nonnull(password.data);
 
3295
  if(mlock(password.data, password.allocated) != 0){
 
3296
    g_assert_true(errno == EPERM or errno == ENOMEM);
 
3297
  }
 
3298
 
 
3299
  memset(password.data, 'x', PIPE_BUF);
 
3300
  char password_expected[PIPE_BUF];
 
3301
  memcpy(password_expected, password.data, PIPE_BUF);
 
3302
 
 
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);
 
3308
 
 
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));
 
3313
 
 
3314
  task_context task = {
 
3315
    .func=read_mandos_client_output,
 
3316
    .epoll_fd=epoll_fd,
 
3317
    .fd=pipefds[0],
 
3318
    .password=&password,
 
3319
    .password_is_read=&password_is_read,
 
3320
    .quit_now=&quit_now,
 
3321
  };
 
3322
  task.func(task, queue);
 
3323
 
 
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),
 
3329
                  ==, 0);
 
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);
 
3335
 
 
3336
  g_assert_nonnull(find_matching_task(queue, (task_context){
 
3337
        .func=read_mandos_client_output,
 
3338
        .epoll_fd=epoll_fd,
 
3339
        .fd=pipefds[0],
 
3340
        .password=&password,
 
3341
        .password_is_read=&password_is_read,
 
3342
        .quit_now=&quit_now,
 
3343
      }));
 
3344
 
 
3345
  g_assert_true(epoll_set_contains(epoll_fd, pipefds[0],
 
3346
                                   EPOLLIN | EPOLLRDHUP));
 
3347
}
 
3348
 
 
3349
static char *make_temporary_directory(void);
 
3350
 
 
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;
 
3364
 
 
3365
  bool quit_now = false;
 
3366
  buffer password = {};
 
3367
  bool mandos_client_exited = false;
 
3368
  bool password_is_read = false;
 
3369
 
 
3370
  __attribute__((cleanup(cleanup_string)))
 
3371
    char *tempdir = make_temporary_directory();
 
3372
  g_assert_nonnull(tempdir);
 
3373
 
 
3374
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
 
3375
                                      &password, tempdir,
 
3376
                                      &cancelled_filenames,
 
3377
                                      &current_time,
 
3378
                                      &mandos_client_exited,
 
3379
                                      &password_is_read));
 
3380
 
 
3381
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
3382
 
 
3383
  const task_context *const added_read_task
 
3384
    = find_matching_task(queue, (task_context){
 
3385
        .func=read_inotify_event,
 
3386
        .epoll_fd=epoll_fd,
 
3387
        .quit_now=&quit_now,
 
3388
        .password=&password,
 
3389
        .filename=tempdir,
 
3390
        .cancelled_filenames=&cancelled_filenames,
 
3391
        .current_time=&current_time,
 
3392
        .mandos_client_exited=&mandos_client_exited,
 
3393
        .password_is_read=&password_is_read,
 
3394
      });
 
3395
  g_assert_nonnull(added_read_task);
 
3396
 
 
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));
 
3402
 
 
3403
  g_assert_cmpint(rmdir(tempdir), ==, 0);
 
3404
}
 
3405
 
 
3406
static char *make_temporary_directory(void){
 
3407
  char *name = strdup("/tmp/mandosXXXXXX");
 
3408
  g_assert_nonnull(name);
 
3409
  char *result = mkdtemp(name);
 
3410
  if(result == NULL){
 
3411
    free(name);
 
3412
  }
 
3413
  return result;
 
3414
}
 
3415
 
 
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;
 
3429
 
 
3430
  bool quit_now = false;
 
3431
  buffer password = {};
 
3432
  bool mandos_client_exited = false;
 
3433
  bool password_is_read = false;
 
3434
 
 
3435
  const char nonexistent_dir[] = "/nonexistent";
 
3436
 
 
3437
  FILE *real_stderr = stderr;
 
3438
  FILE *devnull = fopen("/dev/null", "we");
 
3439
  g_assert_nonnull(devnull);
 
3440
  stderr = devnull;
 
3441
  g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
 
3442
                                       &password, nonexistent_dir,
 
3443
                                       &cancelled_filenames,
 
3444
                                       &current_time,
 
3445
                                       &mandos_client_exited,
 
3446
                                       &password_is_read));
 
3447
  stderr = real_stderr;
 
3448
  g_assert_cmpint(fclose(devnull), ==, 0);
 
3449
 
 
3450
  g_assert_cmpuint((unsigned int)queue->length, ==, 0);
 
3451
}
 
3452
 
 
3453
static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused))
 
3454
                                              test_fixture *fixture,
 
3455
                                              __attribute__((unused))
 
3456
                                              gconstpointer
 
3457
                                              user_data){
 
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;
 
3467
 
 
3468
  bool quit_now = false;
 
3469
  buffer password = {};
 
3470
  bool mandos_client_exited = false;
 
3471
  bool password_is_read = false;
 
3472
 
 
3473
  __attribute__((cleanup(cleanup_string)))
 
3474
    char *tempdir = make_temporary_directory();
 
3475
  g_assert_nonnull(tempdir);
 
3476
 
 
3477
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
 
3478
                                      &password, tempdir,
 
3479
                                      &cancelled_filenames,
 
3480
                                      &current_time,
 
3481
                                      &mandos_client_exited,
 
3482
                                      &password_is_read));
 
3483
 
 
3484
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
3485
 
 
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);
 
3490
 
 
3491
  g_assert_cmpint(added_read_task->fd, >, 2);
 
3492
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
 
3493
 
 
3494
  /* "sufficient to read at least one event." - inotify(7) */
 
3495
  const size_t ievent_size = (sizeof(struct inotify_event)
 
3496
                              + NAME_MAX + 1);
 
3497
  struct inotify_event *ievent = malloc(ievent_size);
 
3498
  g_assert_nonnull(ievent);
 
3499
 
 
3500
  g_assert_cmpint(read(added_read_task->fd, ievent, ievent_size), ==,
 
3501
                  -1);
 
3502
  g_assert_cmpint(errno, ==, EAGAIN);
 
3503
 
 
3504
  free(ievent);
 
3505
 
 
3506
  g_assert_cmpint(rmdir(tempdir), ==, 0);
 
3507
}
 
3508
 
 
3509
static char *make_temporary_file_in_directory(const char
 
3510
                                              *const dir);
 
3511
 
 
3512
static
 
3513
void test_add_inotify_dir_watch_IN_CLOSE_WRITE(__attribute__((unused))
 
3514
                                               test_fixture *fixture,
 
3515
                                               __attribute__((unused))
 
3516
                                               gconstpointer
 
3517
                                               user_data){
 
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;
 
3527
 
 
3528
  bool quit_now = false;
 
3529
  buffer password = {};
 
3530
  bool mandos_client_exited = false;
 
3531
  bool password_is_read = false;
 
3532
 
 
3533
  __attribute__((cleanup(cleanup_string)))
 
3534
    char *tempdir = make_temporary_directory();
 
3535
  g_assert_nonnull(tempdir);
 
3536
 
 
3537
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
 
3538
                                      &password, tempdir,
 
3539
                                      &cancelled_filenames,
 
3540
                                      &current_time,
 
3541
                                      &mandos_client_exited,
 
3542
                                      &password_is_read));
 
3543
 
 
3544
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
3545
 
 
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);
 
3550
 
 
3551
  g_assert_cmpint(added_read_task->fd, >, 2);
 
3552
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
 
3553
 
 
3554
  __attribute__((cleanup(cleanup_string)))
 
3555
    char *filename = make_temporary_file_in_directory(tempdir);
 
3556
  g_assert_nonnull(filename);
 
3557
 
 
3558
  /* "sufficient to read at least one event." - inotify(7) */
 
3559
  const size_t ievent_size = (sizeof(struct inotify_event)
 
3560
                              + NAME_MAX + 1);
 
3561
  struct inotify_event *ievent = malloc(ievent_size);
 
3562
  g_assert_nonnull(ievent);
 
3563
 
 
3564
  ssize_t read_size = 0;
 
3565
  read_size = read(added_read_task->fd, ievent, ievent_size);
 
3566
 
 
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));
 
3570
 
 
3571
  free(ievent);
 
3572
 
 
3573
  g_assert_cmpint(unlink(filename), ==, 0);
 
3574
  g_assert_cmpint(rmdir(tempdir), ==, 0);
 
3575
}
 
3576
 
 
3577
static char *make_temporary_prefixed_file_in_directory(const char
 
3578
                                                       *const prefix,
 
3579
                                                       const char
 
3580
                                                       *const dir){
 
3581
  char *filename = NULL;
 
3582
  g_assert_cmpint(asprintf(&filename, "%s/%sXXXXXX", dir, prefix),
 
3583
                  >, 0);
 
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);
 
3588
  return filename;
 
3589
}
 
3590
 
 
3591
static char *make_temporary_file_in_directory(const char
 
3592
                                              *const dir){
 
3593
  return make_temporary_prefixed_file_in_directory("temp", dir);
 
3594
}
 
3595
 
 
3596
static
 
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;
 
3610
 
 
3611
  bool quit_now = false;
 
3612
  buffer password = {};
 
3613
  bool mandos_client_exited = false;
 
3614
  bool password_is_read = false;
 
3615
 
 
3616
  __attribute__((cleanup(cleanup_string)))
 
3617
    char *watchdir = make_temporary_directory();
 
3618
  g_assert_nonnull(watchdir);
 
3619
 
 
3620
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
 
3621
                                      &password, watchdir,
 
3622
                                      &cancelled_filenames,
 
3623
                                      &current_time,
 
3624
                                      &mandos_client_exited,
 
3625
                                      &password_is_read));
 
3626
 
 
3627
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
3628
 
 
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);
 
3633
 
 
3634
  g_assert_cmpint(added_read_task->fd, >, 2);
 
3635
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
 
3636
 
 
3637
  char *sourcedir = make_temporary_directory();
 
3638
  g_assert_nonnull(sourcedir);
 
3639
 
 
3640
  __attribute__((cleanup(cleanup_string)))
 
3641
    char *filename = make_temporary_file_in_directory(sourcedir);
 
3642
  g_assert_nonnull(filename);
 
3643
 
 
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);
 
3649
 
 
3650
  g_assert_cmpint(rename(filename, targetfilename), ==, 0);
 
3651
  g_assert_cmpint(rmdir(sourcedir), ==, 0);
 
3652
  free(sourcedir);
 
3653
 
 
3654
  /* "sufficient to read at least one event." - inotify(7) */
 
3655
  const size_t ievent_size = (sizeof(struct inotify_event)
 
3656
                              + NAME_MAX + 1);
 
3657
  struct inotify_event *ievent = malloc(ievent_size);
 
3658
  g_assert_nonnull(ievent);
 
3659
 
 
3660
  ssize_t read_size = read(added_read_task->fd, ievent, ievent_size);
 
3661
 
 
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));
 
3665
 
 
3666
  free(ievent);
 
3667
 
 
3668
  g_assert_cmpint(unlink(targetfilename), ==, 0);
 
3669
  g_assert_cmpint(rmdir(watchdir), ==, 0);
 
3670
}
 
3671
 
 
3672
static
 
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;
 
3686
 
 
3687
  bool quit_now = false;
 
3688
  buffer password = {};
 
3689
  bool mandos_client_exited = false;
 
3690
  bool password_is_read = false;
 
3691
 
 
3692
  __attribute__((cleanup(cleanup_string)))
 
3693
    char *tempdir = make_temporary_directory();
 
3694
  g_assert_nonnull(tempdir);
 
3695
 
 
3696
  __attribute__((cleanup(cleanup_string)))
 
3697
    char *tempfile = make_temporary_file_in_directory(tempdir);
 
3698
  g_assert_nonnull(tempfile);
 
3699
 
 
3700
  g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now,
 
3701
                                      &password, tempdir,
 
3702
                                      &cancelled_filenames,
 
3703
                                      &current_time,
 
3704
                                      &mandos_client_exited,
 
3705
                                      &password_is_read));
 
3706
  g_assert_cmpint(unlink(tempfile), ==, 0);
 
3707
 
 
3708
  g_assert_cmpuint((unsigned int)queue->length, >, 0);
 
3709
 
 
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);
 
3714
 
 
3715
  g_assert_cmpint(added_read_task->fd, >, 2);
 
3716
  g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd));
 
3717
 
 
3718
  /* "sufficient to read at least one event." - inotify(7) */
 
3719
  const size_t ievent_size = (sizeof(struct inotify_event)
 
3720
                              + NAME_MAX + 1);
 
3721
  struct inotify_event *ievent = malloc(ievent_size);
 
3722
  g_assert_nonnull(ievent);
 
3723
 
 
3724
  ssize_t read_size = 0;
 
3725
  read_size = read(added_read_task->fd, ievent, ievent_size);
 
3726
 
 
3727
  g_assert_cmpint((int)read_size, >, 0);
 
3728
  g_assert_true(ievent->mask & IN_DELETE);
 
3729
  g_assert_cmpstr(ievent->name, ==, basename(tempfile));
 
3730
 
 
3731
  free(ievent);
 
3732
 
 
3733
  g_assert_cmpint(rmdir(tempdir), ==, 0);
 
3734
}
 
3735
 
 
3736
static void test_read_inotify_event_readerror(__attribute__((unused))
 
3737
                                              test_fixture *fixture,
 
3738
                                              __attribute__((unused))
</