/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 plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2019-02-09 23:23:26 UTC
  • Revision ID: teddy@recompile.se-20190209232326-z1z2kzpgfixz7iaj
Add support for using raw public keys in TLS (RFC 7250)

Since GnuTLS removed support for OpenPGP keys in TLS (RFC 6091), and
no other library supports it, we have to change the protocol to use
something else.  We choose to use "raw public keys" (RFC 7250).  Since
we still use OpenPGP keys to decrypt the secret password, this means
that each client will have two keys: One OpenPGP key and one TLS
public/private key, and the key ID of the latter key is used to
identify clients instead of the fingerprint of the OpenPGP key.

Note that this code is still compatible with GnuTLS before version
3.6.0 (when OpenPGP key support was removed).  This commit merely adds
support for using raw pulic keys instead with GnuTLS 3.6.6. or later.

* DBUS-API (Signals/ClientNotFound): Change name of first parameter
                                     from "Fingerprint" to "KeyID".
  (Mandos Client Interface/Properties/KeyID): New.
* INSTALL: Document conflict with GnuTLS 3.6.0 (which removed OpenPGP
           key support) up until 3.6.6, when support for raw public
           keys was added.  Also document new dependency of client on
           "gnutls-bin" package (for certtool).
* Makefile (run-client): Depend on TLS key files, and also pass them
                         as arguments to client.
  (keydir/tls-privkey.pem, keydir/tls-pubkey.pem): New.
  (confdir/clients.conf): Add dependency on TLS public key.
  (purge-client): Add removal of TLS key files.
* clients.conf ([foo]/key_id, [bar]/key_id): New.
* debian/control (Source: mandos/Build-Depends): Also allow
                                                 libgnutls30 (>= 3.6.6)
  (Package: mandos/Depends): - '' -
  (Package: mandos/Description): Alter description to match new
                                 design.
  (Package: mandos-client/Description): - '' -
  (Package: mandos-client/Depends): Move "gnutls-bin | openssl" to
                                    here from "Recommends".
* debian/mandos-client.README.Debian: Add --tls-privkey and
                                      --tls-pubkey options to test
                                      command.
* debian/mandos-client.postinst (create_key): Renamed to "create_keys"
                                             (all callers changed),
                                             and also create TLS key.
* debian/mandos-client.postrm (purge): Also remove TLS key files.
* intro.xml (DESCRIPTION): Describe new dual-key design.
* mandos (GnuTLS): Define different functions depending on whether
                   support for raw public keys is detected.
  (Client.key_id): New attribute.
  (ClientDBus.KeyID_dbus_property): New method.
  (ProxyClient.__init__): Take new "key_id" parameter.
  (ClientHandler.handle): Use key IDs when using raw public keys and
                          use fingerprints when using OpenPGP keys.
  (ClientHandler.peer_certificate): Also handle raw public keys.
  (ClientHandler.key_id): New.
  (MandosServer.handle_ipc): Pass key ID over the pipe IPC.  Also
                             check for key ID matches when looking up
                             clients.
  (main): Default GnuTLS priority string depends on whether we are
          using raw public keys or not.  When unpickling clients, set
          key_id if not set in the pickle.
  (main/MandosDBusService.ClientNotFound): Change name of first
                                           parameter from
                                           "Fingerprint" to "KeyID".
* mandos-clients.conf.xml (OPTIONS): Document new "key_id" option.
  (OPTIONS/secret): Mention new key ID matchning.
  (EXPANSION/RUNTIME EXPANSION): Add new "key_id" option.
  (EXAMPLE): - '' -
* mandos-ctl (tablewords, main/keywords): Add new "KeyID" property.
* mandos-keygen: Create TLS key files.  New "--tls-keytype" (-T)
                 option.  Alter help text to be more clear about key
                 types.  When in password mode, also output "key_id"
                 option.
* mandos-keygen.xml (SYNOPSIS): Add new "--tls-keytype" (-T) option.
  (DESCRIPTION): Alter to match new dual-key design.
  (OVERVIEW): - '' -
  (FILES): Add TLS key files.
* mandos-options.xml (priority): Document new default priority string
                                 when using raw public keys.
* mandos.xml (NETWORK PROTOCOL): Describe new protocol using key ID.
  (BUGS): Remove issue about checking expire times of OpenPGP keys,
          since TLS public keys do not have expiration times.
  (SECURITY/CLIENT): Alter description to match new design.
  (SEE ALSO/GnuTLS): - '' -
  (SEE ALSO): Add reference to RFC 7250, and alter description of when
              RFC 6091 is used.
* overview.xml: Alter text to match new design.
* plugin-runner.xml (EXAMPLE): Add --tls-pubkey and --tls-privkey
                               options to mandos-client options.
* plugins.d/mandos-client.c: Use raw public keys when compiling with
                             supporting GnuTLS versions. Add new
                             "--tls-pubkey" and "--tls-privkey"
                             options (which do nothing if GnuTLS
                             library does not support raw public
                             keys).  Alter text throughout to reflect
                             new design.  Only generate new DH
                             parameters (based on size of OpenPGP key)
                             when using OpenPGP in TLS.  Default
                             GnuTLS priority string depends on whether
                             we are using raw public keys or not.
* plugins.d/mandos-client.xml (SYNOPSIS): Add new "--tls-privkey" (-t)
                                          and "--tls-pubkey" (-T)
                                          options.
  (DESCRIPTION): Describe new dual-key design.
  (OPTIONS): Document new "--tls-privkey" (-t) and "--tls-pubkey" (-T)
             options.
  (OPTIONS/--dh-bits): No longer necessarily depends on OpenPGP key
                       size.
  (FILES): Add default locations for TLS public and private key files.
  (EXAMPLE): Use new --tls-pubkey and --tls-privkey options.
  (SECURITY): Alter wording slightly to reflect new dual-key design.
  (SEE ALSO/GnuTLS): Alter description to match new design.
  (SEE ALSO): Add reference to RFC 7250, and alter description of when
              RFC 6091 is used.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8 -*- */
 
1
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
6
 
 * 
7
 
 * This program is free software: you can redistribute it and/or
8
 
 * modify it under the terms of the GNU General Public License as
9
 
 * published by the Free Software Foundation, either version 3 of the
10
 
 * License, or (at your option) any later version.
11
 
 * 
12
 
 * This program is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008-2018 Teddy Hogeborn
 
6
 * Copyright © 2008-2018 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
13
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
14
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
18
 * General Public License for more details.
16
19
 * 
17
20
 * You should have received a copy of the GNU General Public License
18
 
 * along with this program.  If not, see
19
 
 * <http://www.gnu.org/licenses/>.
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
20
22
 * 
21
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
23
 * Contact the authors at <mandos@recompile.se>.
22
24
 */
23
25
 
24
26
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
25
 
                                   asprintf() */
 
27
                                   O_CLOEXEC, pipe2() */
26
28
#include <stddef.h>             /* size_t, NULL */
27
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
28
 
                                   EXIT_SUCCESS, realloc() */
 
29
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
 
30
                                   realloc() */
29
31
#include <stdbool.h>            /* bool, true, false */
30
 
#include <stdio.h>              /* perror, popen(), fileno(),
31
 
                                   fprintf(), stderr, STDOUT_FILENO */
32
 
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
33
 
                                   stat, waitpid(), WIFEXITED(),
34
 
                                   WEXITSTATUS(), wait(), pid_t,
35
 
                                   uid_t, gid_t, getuid(), getgid(),
36
 
                                   dirfd() */
 
32
#include <stdio.h>              /* fileno(), fprintf(),
 
33
                                   stderr, STDOUT_FILENO, fclose() */
 
34
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
 
35
                                   WIFEXITED(), WEXITSTATUS(), wait(),
 
36
                                   pid_t, uid_t, gid_t, getuid(),
 
37
                                   getgid() */
37
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
38
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
39
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
40
 
                                   WEXITSTATUS() */
41
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
41
                                   WEXITSTATUS(), WTERMSIG() */
 
42
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
42
43
#include <iso646.h>             /* and, or, not */
43
 
#include <dirent.h>             /* DIR, struct dirent, opendir(),
44
 
                                   readdir(), closedir(), dirfd() */
45
 
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
46
 
                                   fcntl(), setuid(), setgid(),
47
 
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
48
 
                                   access(), pipe(), fork(), close()
49
 
                                   dup2, STDOUT_FILENO, _exit(),
50
 
                                   execv(), write(), read(),
51
 
                                   close() */
 
44
#include <dirent.h>             /* struct dirent, scandirat() */
 
45
#include <unistd.h>             /* fcntl(), F_GETFD, F_SETFD,
 
46
                                   FD_CLOEXEC, write(), STDOUT_FILENO,
 
47
                                   struct stat, fstat(), close(),
 
48
                                   setgid(), setuid(), S_ISREG(),
 
49
                                   faccessat() pipe2(), fork(),
 
50
                                   _exit(), dup2(), fexecve(), read()
 
51
                                */
52
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
53
 
                                   FD_CLOEXEC */
54
 
#include <string.h>             /* strsep, strlen(), asprintf() */
 
53
                                   FD_CLOEXEC, openat(), scandirat(),
 
54
                                   pipe2() */
 
55
#include <string.h>             /* strsep, strlen(), strsignal(),
 
56
                                   strcmp(), strncmp() */
55
57
#include <errno.h>              /* errno */
56
58
#include <argp.h>               /* struct argp_option, struct
57
59
                                   argp_state, struct argp,
61
63
#include <signal.h>             /* struct sigaction, sigemptyset(),
62
64
                                   sigaddset(), sigaction(),
63
65
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
64
 
                                   SIG_UNBLOCK, kill() */
 
66
                                   SIG_UNBLOCK, kill(), sig_atomic_t
 
67
                                */
65
68
#include <errno.h>              /* errno, EBADF */
 
69
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
 
70
#include <sysexits.h>           /* EX_OSERR, EX_USAGE, EX_IOERR,
 
71
                                   EX_CONFIG, EX_UNAVAILABLE, EX_OK */
 
72
#include <errno.h>              /* errno */
 
73
#include <error.h>              /* error() */
 
74
#include <fnmatch.h>            /* fnmatch() */
66
75
 
67
76
#define BUFFER_SIZE 256
68
77
 
69
78
#define PDIR "/lib/mandos/plugins.d"
 
79
#define PHDIR "/lib/mandos/plugin-helpers"
70
80
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
71
81
 
72
 
const char *argp_program_version = "plugin-runner 1.0";
73
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
74
 
 
75
 
struct process;
76
 
 
77
 
typedef struct process{
78
 
  pid_t pid;
79
 
  int fd;
80
 
  char *buffer;
81
 
  size_t buffer_size;
82
 
  size_t buffer_length;
83
 
  bool eof;
84
 
  volatile bool completed;
85
 
  volatile int status;
86
 
  struct process *next;
87
 
} process;
 
82
const char *argp_program_version = "plugin-runner " VERSION;
 
83
const char *argp_program_bug_address = "<mandos@recompile.se>";
88
84
 
89
85
typedef struct plugin{
90
86
  char *name;                   /* can be NULL or any plugin name */
93
89
  char **environ;
94
90
  int envc;
95
91
  bool disabled;
 
92
  
 
93
  /* Variables used for running processes*/
 
94
  pid_t pid;
 
95
  int fd;
 
96
  char *buffer;
 
97
  size_t buffer_size;
 
98
  size_t buffer_length;
 
99
  bool eof;
 
100
  volatile sig_atomic_t completed;
 
101
  int status;
96
102
  struct plugin *next;
97
103
} plugin;
98
104
 
99
 
static plugin *getplugin(char *name, plugin **plugin_list){
100
 
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
101
 
    if ((p->name == name)
102
 
        or (p->name and name and (strcmp(p->name, name) == 0))){
 
105
static plugin *plugin_list = NULL;
 
106
 
 
107
/* Gets an existing plugin based on name,
 
108
   or if none is found, creates a new one */
 
109
__attribute__((warn_unused_result))
 
110
static plugin *getplugin(char *name){
 
111
  /* Check for existing plugin with that name */
 
112
  for(plugin *p = plugin_list; p != NULL; p = p->next){
 
113
    if((p->name == name)
 
114
       or (p->name and name and (strcmp(p->name, name) == 0))){
103
115
      return p;
104
116
    }
105
117
  }
106
118
  /* Create a new plugin */
107
 
  plugin *new_plugin = malloc(sizeof(plugin));
108
 
  if (new_plugin == NULL){
 
119
  plugin *new_plugin = NULL;
 
120
  do {
 
121
    new_plugin = malloc(sizeof(plugin));
 
122
  } while(new_plugin == NULL and errno == EINTR);
 
123
  if(new_plugin == NULL){
109
124
    return NULL;
110
125
  }
111
126
  char *copy_name = NULL;
112
127
  if(name != NULL){
113
 
    copy_name = strdup(name);
 
128
    do {
 
129
      copy_name = strdup(name);
 
130
    } while(copy_name == NULL and errno == EINTR);
114
131
    if(copy_name == NULL){
 
132
      int e = errno;
 
133
      free(new_plugin);
 
134
      errno = e;
115
135
      return NULL;
116
136
    }
117
137
  }
118
138
  
119
 
  *new_plugin = (plugin) { .name = copy_name,
120
 
                           .argc = 1,
121
 
                           .envc = 0,
122
 
                           .disabled = false,
123
 
                           .next = *plugin_list };
 
139
  *new_plugin = (plugin){ .name = copy_name,
 
140
                          .argc = 1,
 
141
                          .disabled = false,
 
142
                          .next = plugin_list };
124
143
  
125
 
  new_plugin->argv = malloc(sizeof(char *) * 2);
126
 
  if (new_plugin->argv == NULL){
 
144
  do {
 
145
    new_plugin->argv = malloc(sizeof(char *) * 2);
 
146
  } while(new_plugin->argv == NULL and errno == EINTR);
 
147
  if(new_plugin->argv == NULL){
 
148
    int e = errno;
127
149
    free(copy_name);
128
150
    free(new_plugin);
 
151
    errno = e;
129
152
    return NULL;
130
153
  }
131
154
  new_plugin->argv[0] = copy_name;
132
155
  new_plugin->argv[1] = NULL;
133
 
 
134
 
  new_plugin->environ = malloc(sizeof(char *));
 
156
  
 
157
  do {
 
158
    new_plugin->environ = malloc(sizeof(char *));
 
159
  } while(new_plugin->environ == NULL and errno == EINTR);
135
160
  if(new_plugin->environ == NULL){
 
161
    int e = errno;
136
162
    free(copy_name);
137
163
    free(new_plugin->argv);
138
164
    free(new_plugin);
 
165
    errno = e;
139
166
    return NULL;
140
167
  }
141
168
  new_plugin->environ[0] = NULL;
 
169
  
142
170
  /* Append the new plugin to the list */
143
 
  *plugin_list = new_plugin;
 
171
  plugin_list = new_plugin;
144
172
  return new_plugin;
145
173
}
146
174
 
147
175
/* Helper function for add_argument and add_environment */
 
176
__attribute__((nonnull, warn_unused_result))
148
177
static bool add_to_char_array(const char *new, char ***array,
149
178
                              int *len){
150
179
  /* Resize the pointed-to array to hold one more pointer */
151
 
  *array = realloc(*array, sizeof(char *)
152
 
                   * (size_t) ((*len) + 2));
 
180
  char **new_array = NULL;
 
181
  do {
 
182
    new_array = realloc(*array, sizeof(char *)
 
183
                        * (size_t) ((*len) + 2));
 
184
  } while(new_array == NULL and errno == EINTR);
153
185
  /* Malloc check */
154
 
  if(*array == NULL){
 
186
  if(new_array == NULL){
155
187
    return false;
156
188
  }
 
189
  *array = new_array;
157
190
  /* Make a copy of the new string */
158
 
  char *copy = strdup(new);
 
191
  char *copy;
 
192
  do {
 
193
    copy = strdup(new);
 
194
  } while(copy == NULL and errno == EINTR);
159
195
  if(copy == NULL){
160
196
    return false;
161
197
  }
168
204
}
169
205
 
170
206
/* Add to a plugin's argument vector */
 
207
__attribute__((nonnull(2), warn_unused_result))
171
208
static bool add_argument(plugin *p, const char *arg){
172
209
  if(p == NULL){
173
210
    return false;
176
213
}
177
214
 
178
215
/* Add to a plugin's environment */
179
 
static bool add_environment(plugin *p, const char *def){
 
216
__attribute__((nonnull(2), warn_unused_result))
 
217
static bool add_environment(plugin *p, const char *def, bool replace){
180
218
  if(p == NULL){
181
219
    return false;
182
220
  }
 
221
  /* namelen = length of name of environment variable */
 
222
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
 
223
  /* Search for this environment variable */
 
224
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
225
    if(strncmp(*envdef, def, namelen + 1) == 0){
 
226
      /* It already exists */
 
227
      if(replace){
 
228
        char *new_envdef;
 
229
        do {
 
230
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
231
        } while(new_envdef == NULL and errno == EINTR);
 
232
        if(new_envdef == NULL){
 
233
          return false;
 
234
        }
 
235
        *envdef = new_envdef;
 
236
        strcpy(*envdef, def);
 
237
      }
 
238
      return true;
 
239
    }
 
240
  }
183
241
  return add_to_char_array(def, &(p->environ), &(p->envc));
184
242
}
185
243
 
186
 
 
 
244
#ifndef O_CLOEXEC
187
245
/*
188
246
 * Based on the example in the GNU LibC manual chapter 13.13 "File
189
247
 * Descriptor Flags".
190
 
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
 
248
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
191
249
 */
192
 
static int set_cloexec_flag(int fd)
193
 
{
194
 
  int ret = fcntl(fd, F_GETFD, 0);
 
250
__attribute__((warn_unused_result))
 
251
static int set_cloexec_flag(int fd){
 
252
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
195
253
  /* If reading the flags failed, return error indication now. */
196
254
  if(ret < 0){
197
255
    return ret;
198
256
  }
199
257
  /* Store modified flag word in the descriptor. */
200
 
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
 
258
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
 
259
                                       ret | FD_CLOEXEC));
201
260
}
 
261
#endif  /* not O_CLOEXEC */
202
262
 
203
 
process *process_list = NULL;
204
263
 
205
264
/* Mark processes as completed when they exit, and save their exit
206
265
   status. */
207
 
void handle_sigchld(__attribute__((unused)) int sig){
 
266
static void handle_sigchld(__attribute__((unused)) int sig){
 
267
  int old_errno = errno;
208
268
  while(true){
209
 
    process *proc = process_list;
 
269
    plugin *proc = plugin_list;
210
270
    int status;
211
271
    pid_t pid = waitpid(-1, &status, WNOHANG);
212
272
    if(pid == 0){
214
274
      break;
215
275
    }
216
276
    if(pid == -1){
217
 
      if (errno != ECHILD){
218
 
        perror("waitpid");
 
277
      if(errno == ECHILD){
 
278
        /* No child processes */
 
279
        break;
219
280
      }
220
 
      /* No child processes */
221
 
      break;
 
281
      error(0, errno, "waitpid");
222
282
    }
223
 
 
 
283
    
224
284
    /* A child exited, find it in process_list */
225
285
    while(proc != NULL and proc->pid != pid){
226
286
      proc = proc->next;
230
290
      continue;
231
291
    }
232
292
    proc->status = status;
233
 
    proc->completed = true;
 
293
    proc->completed = 1;
234
294
  }
 
295
  errno = old_errno;
235
296
}
236
297
 
237
 
bool print_out_password(const char *buffer, size_t length){
 
298
/* Prints out a password to stdout */
 
299
__attribute__((nonnull, warn_unused_result))
 
300
static bool print_out_password(const char *buffer, size_t length){
238
301
  ssize_t ret;
239
 
  if(length>0 and buffer[length-1] == '\n'){
240
 
    length--;
241
 
  }
242
302
  for(size_t written = 0; written < length; written += (size_t)ret){
243
303
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
244
304
                                   length - written));
249
309
  return true;
250
310
}
251
311
 
252
 
static void free_plugin_list(plugin *plugin_list){
253
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
254
 
    next = plugin_list->next;
255
 
    for(char **arg = plugin_list->argv; *arg != NULL; arg++){
256
 
      free(*arg);
257
 
    }
258
 
    free(plugin_list->argv);
259
 
    for(char **env = plugin_list->environ; *env != NULL; env++){
260
 
      free(*env);
261
 
    }
262
 
    free(plugin_list->environ);
263
 
    free(plugin_list);
 
312
/* Removes and free a plugin from the plugin list */
 
313
__attribute__((nonnull))
 
314
static void free_plugin(plugin *plugin_node){
 
315
  
 
316
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
317
    free(*arg);
 
318
  }
 
319
  free(plugin_node->argv);
 
320
  for(char **env = plugin_node->environ; *env != NULL; env++){
 
321
    free(*env);
 
322
  }
 
323
  free(plugin_node->environ);
 
324
  free(plugin_node->buffer);
 
325
  
 
326
  /* Removes the plugin from the singly-linked list */
 
327
  if(plugin_node == plugin_list){
 
328
    /* First one - simple */
 
329
    plugin_list = plugin_list->next;
 
330
  } else {
 
331
    /* Second one or later */
 
332
    for(plugin *p = plugin_list; p != NULL; p = p->next){
 
333
      if(p->next == plugin_node){
 
334
        p->next = plugin_node->next;
 
335
        break;
 
336
      }
 
337
    }
 
338
  }
 
339
  
 
340
  free(plugin_node);
 
341
}
 
342
 
 
343
static void free_plugin_list(void){
 
344
  while(plugin_list != NULL){
 
345
    free_plugin(plugin_list);
264
346
  }
265
347
}
266
348
 
267
349
int main(int argc, char *argv[]){
268
350
  char *plugindir = NULL;
 
351
  char *pluginhelperdir = NULL;
269
352
  char *argfile = NULL;
270
353
  FILE *conffp;
271
 
  size_t d_name_len;
272
 
  DIR *dir = NULL;
273
 
  struct dirent *dirst;
 
354
  struct dirent **direntries = NULL;
274
355
  struct stat st;
275
356
  fd_set rfds_all;
276
357
  int ret, maxfd = 0;
 
358
  ssize_t sret;
277
359
  uid_t uid = 65534;
278
360
  gid_t gid = 65534;
279
361
  bool debug = false;
283
365
                                      .sa_flags = SA_NOCLDSTOP };
284
366
  char **custom_argv = NULL;
285
367
  int custom_argc = 0;
 
368
  int dir_fd = -1;
286
369
  
287
370
  /* Establish a signal handler */
288
371
  sigemptyset(&sigchld_action.sa_mask);
289
372
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
290
373
  if(ret == -1){
291
 
    perror("sigaddset");
292
 
    exitstatus = EXIT_FAILURE;
 
374
    error(0, errno, "sigaddset");
 
375
    exitstatus = EX_OSERR;
293
376
    goto fallback;
294
377
  }
295
378
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
296
379
  if(ret == -1){
297
 
    perror("sigaction");
298
 
    exitstatus = EXIT_FAILURE;
 
380
    error(0, errno, "sigaction");
 
381
    exitstatus = EX_OSERR;
299
382
    goto fallback;
300
383
  }
301
384
  
304
387
    { .name = "global-options", .key = 'g',
305
388
      .arg = "OPTION[,OPTION[,...]]",
306
389
      .doc = "Options passed to all plugins" },
307
 
    { .name = "global-envs", .key = 'e',
 
390
    { .name = "global-env", .key = 'G',
308
391
      .arg = "VAR=value",
309
392
      .doc = "Environment variable passed to all plugins" },
310
393
    { .name = "options-for", .key = 'o',
311
394
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
312
395
      .doc = "Options passed only to specified plugin" },
313
 
    { .name = "envs-for", .key = 'f',
 
396
    { .name = "env-for", .key = 'E',
314
397
      .arg = "PLUGIN:ENV=value",
315
398
      .doc = "Environment variable passed to specified plugin" },
316
399
    { .name = "disable", .key = 'd',
317
400
      .arg = "PLUGIN",
318
401
      .doc = "Disable a specific plugin", .group = 1 },
 
402
    { .name = "enable", .key = 'e',
 
403
      .arg = "PLUGIN",
 
404
      .doc = "Enable a specific plugin", .group = 1 },
319
405
    { .name = "plugin-dir", .key = 128,
320
406
      .arg = "DIRECTORY",
321
407
      .doc = "Specify a different plugin directory", .group = 2 },
330
416
      .doc = "Group ID the plugins will run as", .group = 3 },
331
417
    { .name = "debug", .key = 132,
332
418
      .doc = "Debug mode", .group = 4 },
 
419
    { .name = "plugin-helper-dir", .key = 133,
 
420
      .arg = "DIRECTORY",
 
421
      .doc = "Specify a different plugin helper directory",
 
422
      .group = 2 },
 
423
    /*
 
424
     * These reproduce what we would get without ARGP_NO_HELP
 
425
     */
 
426
    { .name = "help", .key = '?',
 
427
      .doc = "Give this help list", .group = -1 },
 
428
    { .name = "usage", .key = -3,
 
429
      .doc = "Give a short usage message", .group = -1 },
 
430
    { .name = "version", .key = 'V',
 
431
      .doc = "Print program version", .group = -1 },
333
432
    { .name = NULL }
334
433
  };
335
434
  
336
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
337
 
    /* Get the INPUT argument from `argp_parse', which we know is a
338
 
       pointer to our plugin list pointer. */
339
 
    plugin **plugins = state->input;
340
 
    switch (key) {
341
 
    case 'g':
342
 
      if (arg != NULL){
343
 
        char *p;
344
 
        while((p = strsep(&arg, ",")) != NULL){
345
 
          if(p[0] == '\0'){
346
 
            continue;
347
 
          }
348
 
          if(not add_argument(getplugin(NULL, plugins), p)){
349
 
            perror("add_argument");
350
 
            return ARGP_ERR_UNKNOWN;
351
 
          }
352
 
        }
353
 
      }
354
 
      break;
355
 
    case 'e':
356
 
      if(arg == NULL){
357
 
        break;
358
 
      }
359
 
      {
360
 
        char *envdef = strdup(arg);
361
 
        if(envdef == NULL){
362
 
          break;
363
 
        }
364
 
        if(not add_environment(getplugin(NULL, plugins), envdef)){
365
 
          perror("add_environment");
366
 
        }
367
 
      }
368
 
      break;
369
 
    case 'o':
370
 
      if (arg != NULL){
371
 
        char *p_name = strsep(&arg, ":");
372
 
        if(p_name[0] == '\0'){
373
 
          break;
374
 
        }
375
 
        char *opt = strsep(&arg, ":");
376
 
        if(opt[0] == '\0'){
377
 
          break;
378
 
        }
379
 
        if(opt != NULL){
380
 
          char *p;
381
 
          while((p = strsep(&opt, ",")) != NULL){
382
 
            if(p[0] == '\0'){
383
 
              continue;
384
 
            }
385
 
            if(not add_argument(getplugin(p_name, plugins), p)){
386
 
              perror("add_argument");
387
 
              return ARGP_ERR_UNKNOWN;
388
 
            }
389
 
          }
390
 
        }
391
 
      }
392
 
      break;
393
 
    case 'f':
394
 
      if(arg == NULL){
395
 
        break;
396
 
      }
 
435
  __attribute__((nonnull(3)))
 
436
  error_t parse_opt(int key, char *arg, struct argp_state *state){
 
437
    errno = 0;
 
438
    switch(key){
 
439
      char *tmp;
 
440
      intmax_t tmp_id;
 
441
    case 'g':                   /* --global-options */
 
442
      {
 
443
        char *plugin_option;
 
444
        while((plugin_option = strsep(&arg, ",")) != NULL){
 
445
          if(not add_argument(getplugin(NULL), plugin_option)){
 
446
            break;
 
447
          }
 
448
        }
 
449
        errno = 0;
 
450
      }
 
451
      break;
 
452
    case 'G':                   /* --global-env */
 
453
      if(add_environment(getplugin(NULL), arg, true)){
 
454
        errno = 0;
 
455
      }
 
456
      break;
 
457
    case 'o':                   /* --options-for */
 
458
      {
 
459
        char *option_list = strchr(arg, ':');
 
460
        if(option_list == NULL){
 
461
          argp_error(state, "No colon in \"%s\"", arg);
 
462
          errno = EINVAL;
 
463
          break;
 
464
        }
 
465
        *option_list = '\0';
 
466
        option_list++;
 
467
        if(arg[0] == '\0'){
 
468
          argp_error(state, "Empty plugin name");
 
469
          errno = EINVAL;
 
470
          break;
 
471
        }
 
472
        char *option;
 
473
        while((option = strsep(&option_list, ",")) != NULL){
 
474
          if(not add_argument(getplugin(arg), option)){
 
475
            break;
 
476
          }
 
477
        }
 
478
        errno = 0;
 
479
      }
 
480
      break;
 
481
    case 'E':                   /* --env-for */
397
482
      {
398
483
        char *envdef = strchr(arg, ':');
399
484
        if(envdef == NULL){
400
 
          break;
401
 
        }
402
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
403
 
        if(p_name == NULL){
404
 
          break;
405
 
        }
 
485
          argp_error(state, "No colon in \"%s\"", arg);
 
486
          errno = EINVAL;
 
487
          break;
 
488
        }
 
489
        *envdef = '\0';
406
490
        envdef++;
407
 
        if(not add_environment(getplugin(p_name, plugins), envdef)){
408
 
          perror("add_environment");
409
 
        }
410
 
      }
411
 
      break;
412
 
    case 'd':
413
 
      if (arg != NULL){
414
 
        plugin *p = getplugin(arg, plugins);
415
 
        if(p == NULL){
416
 
          return ARGP_ERR_UNKNOWN;
417
 
        }
418
 
        p->disabled = true;
419
 
      }
420
 
      break;
421
 
    case 128:
 
491
        if(arg[0] == '\0'){
 
492
          argp_error(state, "Empty plugin name");
 
493
          errno = EINVAL;
 
494
          break;
 
495
        }
 
496
        if(add_environment(getplugin(arg), envdef, true)){
 
497
          errno = 0;
 
498
        }
 
499
      }
 
500
      break;
 
501
    case 'd':                   /* --disable */
 
502
      {
 
503
        plugin *p = getplugin(arg);
 
504
        if(p != NULL){
 
505
          p->disabled = true;
 
506
          errno = 0;
 
507
        }
 
508
      }
 
509
      break;
 
510
    case 'e':                   /* --enable */
 
511
      {
 
512
        plugin *p = getplugin(arg);
 
513
        if(p != NULL){
 
514
          p->disabled = false;
 
515
          errno = 0;
 
516
        }
 
517
      }
 
518
      break;
 
519
    case 128:                   /* --plugin-dir */
 
520
      free(plugindir);
422
521
      plugindir = strdup(arg);
423
 
      if(plugindir == NULL){
424
 
        perror("strdup");
425
 
      }      
426
 
      break;
427
 
    case 129:
 
522
      if(plugindir != NULL){
 
523
        errno = 0;
 
524
      }
 
525
      break;
 
526
    case 129:                   /* --config-file */
 
527
      /* This is already done by parse_opt_config_file() */
 
528
      break;
 
529
    case 130:                   /* --userid */
 
530
      tmp_id = strtoimax(arg, &tmp, 10);
 
531
      if(errno != 0 or tmp == arg or *tmp != '\0'
 
532
         or tmp_id != (uid_t)tmp_id){
 
533
        argp_error(state, "Bad user ID number: \"%s\", using %"
 
534
                   PRIdMAX, arg, (intmax_t)uid);
 
535
        break;
 
536
      }
 
537
      uid = (uid_t)tmp_id;
 
538
      errno = 0;
 
539
      break;
 
540
    case 131:                   /* --groupid */
 
541
      tmp_id = strtoimax(arg, &tmp, 10);
 
542
      if(errno != 0 or tmp == arg or *tmp != '\0'
 
543
         or tmp_id != (gid_t)tmp_id){
 
544
        argp_error(state, "Bad group ID number: \"%s\", using %"
 
545
                   PRIdMAX, arg, (intmax_t)gid);
 
546
        break;
 
547
      }
 
548
      gid = (gid_t)tmp_id;
 
549
      errno = 0;
 
550
      break;
 
551
    case 132:                   /* --debug */
 
552
      debug = true;
 
553
      break;
 
554
    case 133:                   /* --plugin-helper-dir */
 
555
      free(pluginhelperdir);
 
556
      pluginhelperdir = strdup(arg);
 
557
      if(pluginhelperdir != NULL){
 
558
        errno = 0;
 
559
      }
 
560
      break;
 
561
      /*
 
562
       * These reproduce what we would get without ARGP_NO_HELP
 
563
       */
 
564
    case '?':                   /* --help */
 
565
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
 
566
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
567
    case -3:                    /* --usage */
 
568
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
 
569
      argp_state_help(state, state->out_stream,
 
570
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
571
    case 'V':                   /* --version */
 
572
      fprintf(state->out_stream, "%s\n", argp_program_version);
 
573
      exit(EXIT_SUCCESS);
 
574
      break;
 
575
/*
 
576
 * When adding more options before this line, remember to also add a
 
577
 * "case" to the "parse_opt_config_file" function below.
 
578
 */
 
579
    case ARGP_KEY_ARG:
 
580
      /* Cryptsetup always passes an argument, which is an empty
 
581
         string if "none" was specified in /etc/crypttab.  So if
 
582
         argument was empty, we ignore it silently. */
 
583
      if(arg[0] == '\0'){
 
584
        break;
 
585
      }
 
586
      /* FALLTHROUGH */
 
587
    default:
 
588
      return ARGP_ERR_UNKNOWN;
 
589
    }
 
590
    return errno;               /* Set to 0 at start */
 
591
  }
 
592
  
 
593
  /* This option parser is the same as parse_opt() above, except it
 
594
     ignores everything but the --config-file option. */
 
595
  error_t parse_opt_config_file(int key, char *arg,
 
596
                                __attribute__((unused))
 
597
                                struct argp_state *state){
 
598
    errno = 0;
 
599
    switch(key){
 
600
    case 'g':                   /* --global-options */
 
601
    case 'G':                   /* --global-env */
 
602
    case 'o':                   /* --options-for */
 
603
    case 'E':                   /* --env-for */
 
604
    case 'd':                   /* --disable */
 
605
    case 'e':                   /* --enable */
 
606
    case 128:                   /* --plugin-dir */
 
607
      break;
 
608
    case 129:                   /* --config-file */
 
609
      free(argfile);
428
610
      argfile = strdup(arg);
429
 
      if(argfile == NULL){
430
 
        perror("strdup");
 
611
      if(argfile != NULL){
 
612
        errno = 0;
431
613
      }
432
 
      break;      
433
 
    case 130:
434
 
      uid = (uid_t)strtol(arg, NULL, 10);
435
 
      break;
436
 
    case 131:
437
 
      gid = (gid_t)strtol(arg, NULL, 10);
438
 
      break;
439
 
    case 132:
440
 
      debug = true;
441
 
      break;
 
614
      break;
 
615
    case 130:                   /* --userid */
 
616
    case 131:                   /* --groupid */
 
617
    case 132:                   /* --debug */
 
618
    case 133:                   /* --plugin-helper-dir */
 
619
    case '?':                   /* --help */
 
620
    case -3:                    /* --usage */
 
621
    case 'V':                   /* --version */
442
622
    case ARGP_KEY_ARG:
443
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
444
 
      break;
445
 
    case ARGP_KEY_END:
446
623
      break;
447
624
    default:
448
625
      return ARGP_ERR_UNKNOWN;
449
626
    }
450
 
    return 0;
 
627
    return errno;
451
628
  }
452
629
  
453
 
  plugin *plugin_list = NULL;
454
 
  
455
 
  struct argp argp = { .options = options, .parser = parse_opt,
456
 
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
 
630
  struct argp argp = { .options = options,
 
631
                       .parser = parse_opt_config_file,
 
632
                       .args_doc = "",
457
633
                       .doc = "Mandos plugin runner -- Run plugins" };
458
634
  
459
 
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
460
 
  if (ret == ARGP_ERR_UNKNOWN){
461
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
462
 
    exitstatus = EXIT_FAILURE;
 
635
  /* Parse using parse_opt_config_file() in order to get the custom
 
636
     config file location, if any. */
 
637
  ret = argp_parse(&argp, argc, argv,
 
638
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
 
639
                   NULL, NULL);
 
640
  switch(ret){
 
641
  case 0:
 
642
    break;
 
643
  case ENOMEM:
 
644
  default:
 
645
    errno = ret;
 
646
    error(0, errno, "argp_parse");
 
647
    exitstatus = EX_OSERR;
 
648
    goto fallback;
 
649
  case EINVAL:
 
650
    exitstatus = EX_USAGE;
463
651
    goto fallback;
464
652
  }
465
 
 
466
 
  if (argfile == NULL){
 
653
  
 
654
  /* Reset to the normal argument parser */
 
655
  argp.parser = parse_opt;
 
656
  
 
657
  /* Open the configfile if available */
 
658
  if(argfile == NULL){
467
659
    conffp = fopen(AFILE, "r");
468
660
  } else {
469
661
    conffp = fopen(argfile, "r");
470
662
  }
471
 
  
472
663
  if(conffp != NULL){
473
664
    char *org_line = NULL;
474
665
    char *p, *arg, *new_arg, *line;
475
666
    size_t size = 0;
476
 
    ssize_t sret;
477
667
    const char whitespace_delims[] = " \r\t\f\v\n";
478
668
    const char comment_delim[] = "#";
479
 
 
 
669
    
480
670
    custom_argc = 1;
481
671
    custom_argv = malloc(sizeof(char*) * 2);
482
672
    if(custom_argv == NULL){
483
 
      perror("malloc");
484
 
      exitstatus = EXIT_FAILURE;
 
673
      error(0, errno, "malloc");
 
674
      exitstatus = EX_OSERR;
485
675
      goto fallback;
486
676
    }
487
677
    custom_argv[0] = argv[0];
488
678
    custom_argv[1] = NULL;
489
679
    
 
680
    /* for each line in the config file, strip whitespace and ignore
 
681
       commented text */
490
682
    while(true){
491
683
      sret = getline(&org_line, &size, conffp);
492
684
      if(sret == -1){
493
685
        break;
494
686
      }
495
 
 
 
687
      
496
688
      line = org_line;
497
689
      arg = strsep(&line, comment_delim);
498
690
      while((p = strsep(&arg, whitespace_delims)) != NULL){
501
693
        }
502
694
        new_arg = strdup(p);
503
695
        if(new_arg == NULL){
504
 
          perror("strdup");
505
 
          exitstatus = EXIT_FAILURE;
 
696
          error(0, errno, "strdup");
 
697
          exitstatus = EX_OSERR;
506
698
          free(org_line);
507
699
          goto fallback;
508
700
        }
509
701
        
510
702
        custom_argc += 1;
511
 
        custom_argv = realloc(custom_argv, sizeof(char *)
512
 
                              * ((unsigned int) custom_argc + 1));
513
 
        if(custom_argv == NULL){
514
 
          perror("realloc");
515
 
          exitstatus = EXIT_FAILURE;
516
 
          free(org_line);
517
 
          goto fallback;
 
703
        {
 
704
          char **new_argv = realloc(custom_argv, sizeof(char *)
 
705
                                    * ((size_t)custom_argc + 1));
 
706
          if(new_argv == NULL){
 
707
            error(0, errno, "realloc");
 
708
            exitstatus = EX_OSERR;
 
709
            free(new_arg);
 
710
            free(org_line);
 
711
            goto fallback;
 
712
          } else {
 
713
            custom_argv = new_argv;
 
714
          }
518
715
        }
519
716
        custom_argv[custom_argc-1] = new_arg;
520
 
        custom_argv[custom_argc] = NULL;        
 
717
        custom_argv[custom_argc] = NULL;
521
718
      }
522
719
    }
 
720
    do {
 
721
      ret = fclose(conffp);
 
722
    } while(ret == EOF and errno == EINTR);
 
723
    if(ret == EOF){
 
724
      error(0, errno, "fclose");
 
725
      exitstatus = EX_IOERR;
 
726
      goto fallback;
 
727
    }
523
728
    free(org_line);
524
 
  } else{
 
729
  } else {
525
730
    /* Check for harmful errors and go to fallback. Other errors might
526
731
       not affect opening plugins */
527
 
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
528
 
      perror("fopen");
529
 
      exitstatus = EXIT_FAILURE;
 
732
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
733
      error(0, errno, "fopen");
 
734
      exitstatus = EX_OSERR;
530
735
      goto fallback;
531
736
    }
532
737
  }
533
 
 
 
738
  /* If there were any arguments from the configuration file, pass
 
739
     them to parser as command line arguments */
534
740
  if(custom_argv != NULL){
535
 
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
536
 
    if (ret == ARGP_ERR_UNKNOWN){
537
 
      fprintf(stderr, "Unknown error while parsing arguments\n");
538
 
      exitstatus = EXIT_FAILURE;
539
 
      goto fallback;
 
741
    ret = argp_parse(&argp, custom_argc, custom_argv,
 
742
                     ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
 
743
                     NULL, NULL);
 
744
    switch(ret){
 
745
    case 0:
 
746
      break;
 
747
    case ENOMEM:
 
748
    default:
 
749
      errno = ret;
 
750
      error(0, errno, "argp_parse");
 
751
      exitstatus = EX_OSERR;
 
752
      goto fallback;
 
753
    case EINVAL:
 
754
      exitstatus = EX_CONFIG;
 
755
      goto fallback;
 
756
    }
 
757
  }
 
758
  
 
759
  /* Parse actual command line arguments, to let them override the
 
760
     config file */
 
761
  ret = argp_parse(&argp, argc, argv,
 
762
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
 
763
                   NULL, NULL);
 
764
  switch(ret){
 
765
  case 0:
 
766
    break;
 
767
  case ENOMEM:
 
768
  default:
 
769
    errno = ret;
 
770
    error(0, errno, "argp_parse");
 
771
    exitstatus = EX_OSERR;
 
772
    goto fallback;
 
773
  case EINVAL:
 
774
    exitstatus = EX_USAGE;
 
775
    goto fallback;
 
776
  }
 
777
  
 
778
  {
 
779
    char *pluginhelperenv;
 
780
    bool bret = true;
 
781
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
782
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
783
    if(ret != -1){
 
784
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
785
    }
 
786
    if(ret == -1 or not bret){
 
787
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
788
            " environment variable to \"%s\" for all plugins\n",
 
789
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
790
    }
 
791
    if(ret != -1){
 
792
      free(pluginhelperenv);
540
793
    }
541
794
  }
542
795
  
543
796
  if(debug){
544
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
797
    for(plugin *p = plugin_list; p != NULL; p = p->next){
545
798
      fprintf(stderr, "Plugin: %s has %d arguments\n",
546
799
              p->name ? p->name : "Global", p->argc - 1);
547
800
      for(char **a = p->argv; *a != NULL; a++){
548
801
        fprintf(stderr, "\tArg: %s\n", *a);
549
802
      }
550
 
      fprintf(stderr, "...and %u environment variables\n", p->envc);
 
803
      fprintf(stderr, "...and %d environment variables\n", p->envc);
551
804
      for(char **a = p->environ; *a != NULL; a++){
552
805
        fprintf(stderr, "\t%s\n", *a);
553
806
      }
554
807
    }
555
808
  }
556
809
  
 
810
  if(getuid() == 0){
 
811
    /* Work around Debian bug #633582:
 
812
       <https://bugs.debian.org/633582> */
 
813
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
 
814
    if(plugindir_fd == -1){
 
815
      if(errno != ENOENT){
 
816
        error(0, errno, "open(\"" PDIR "\")");
 
817
      }
 
818
    } else {
 
819
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
 
820
      if(ret == -1){
 
821
        error(0, errno, "fstat");
 
822
      } else {
 
823
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
 
824
          ret = fchown(plugindir_fd, uid, gid);
 
825
          if(ret == -1){
 
826
            error(0, errno, "fchown");
 
827
          }
 
828
        }
 
829
      }
 
830
      close(plugindir_fd);
 
831
    }
 
832
  }
 
833
  
 
834
  /* Lower permissions */
 
835
  ret = setgid(gid);
 
836
  if(ret == -1){
 
837
    error(0, errno, "setgid");
 
838
  }
557
839
  ret = setuid(uid);
558
 
  if (ret == -1){
559
 
    perror("setuid");
560
 
  }
561
 
  
562
 
  setgid(gid);
563
 
  if (ret == -1){
564
 
    perror("setgid");
565
 
  }
566
 
 
567
 
  if (plugindir == NULL){
568
 
    dir = opendir(PDIR);
569
 
  } else {
570
 
    dir = opendir(plugindir);
571
 
  }
572
 
  
573
 
  if(dir == NULL){
574
 
    perror("Could not open plugin dir");
575
 
    exitstatus = EXIT_FAILURE;
576
 
    goto fallback;
577
 
  }
578
 
  
579
 
  /* Set the FD_CLOEXEC flag on the directory, if possible */
 
840
  if(ret == -1){
 
841
    error(0, errno, "setuid");
 
842
  }
 
843
  
 
844
  /* Open plugin directory with close_on_exec flag */
580
845
  {
581
 
    int dir_fd = dirfd(dir);
582
 
    if(dir_fd >= 0){
583
 
      ret = set_cloexec_flag(dir_fd);
584
 
      if(ret < 0){
585
 
        perror("set_cloexec_flag");
586
 
        exitstatus = EXIT_FAILURE;
587
 
        goto fallback;
 
846
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
847
#ifdef O_CLOEXEC
 
848
                  O_CLOEXEC
 
849
#else  /* not O_CLOEXEC */
 
850
                  0
 
851
#endif  /* not O_CLOEXEC */
 
852
                  );
 
853
    if(dir_fd == -1){
 
854
      error(0, errno, "Could not open plugin dir");
 
855
      exitstatus = EX_UNAVAILABLE;
 
856
      goto fallback;
 
857
    }
 
858
    
 
859
#ifndef O_CLOEXEC
 
860
  /* Set the FD_CLOEXEC flag on the directory */
 
861
    ret = set_cloexec_flag(dir_fd);
 
862
    if(ret < 0){
 
863
      error(0, errno, "set_cloexec_flag");
 
864
      exitstatus = EX_OSERR;
 
865
      goto fallback;
 
866
    }
 
867
#endif  /* O_CLOEXEC */
 
868
  }
 
869
  
 
870
  int good_name(const struct dirent * const dirent){
 
871
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
872
                                      "*.dpkg-old", "*.dpkg-bak",
 
873
                                      "*.dpkg-divert", NULL };
 
874
#ifdef __GNUC__
 
875
#pragma GCC diagnostic push
 
876
#pragma GCC diagnostic ignored "-Wcast-qual"
 
877
#endif
 
878
    for(const char **pat = (const char **)patterns;
 
879
        *pat != NULL; pat++){
 
880
#ifdef __GNUC__
 
881
#pragma GCC diagnostic pop
 
882
#endif
 
883
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
884
         != FNM_NOMATCH){
 
885
        if(debug){
 
886
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
887
                    " matching pattern %s\n", dirent->d_name, *pat);
 
888
        }
 
889
        return 0;
588
890
      }
589
891
    }
 
892
    return 1;
 
893
  }
 
894
  
 
895
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
896
                             alphasort);
 
897
  if(numplugins == -1){
 
898
    error(0, errno, "Could not scan plugin dir");
 
899
    direntries = NULL;
 
900
    exitstatus = EX_OSERR;
 
901
    goto fallback;
590
902
  }
591
903
  
592
904
  FD_ZERO(&rfds_all);
593
905
  
594
 
  while(true){
595
 
    dirst = readdir(dir);
596
 
    
597
 
    // All directory entries have been processed
598
 
    if(dirst == NULL){
599
 
      if (errno == EBADF){
600
 
        perror("readdir");
601
 
        exitstatus = EXIT_FAILURE;
602
 
        goto fallback;
603
 
      }
604
 
      break;
605
 
    }
606
 
    
607
 
    d_name_len = strlen(dirst->d_name);
608
 
    
609
 
    // Ignore dotfiles, backup files and other junk
610
 
    {
611
 
      bool bad_name = false;
612
 
      
613
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
614
 
      
615
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
616
 
                                           ".dpkg-old",
617
 
                                           ".dpkg-divert", NULL };
618
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
619
 
        size_t pre_len = strlen(*pre);
620
 
        if((d_name_len >= pre_len)
621
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
622
 
          if(debug){
623
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
624
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
625
 
          }
626
 
          bad_name = true;
627
 
          break;
628
 
        }
629
 
      }
630
 
      
631
 
      if(bad_name){
632
 
        continue;
633
 
      }
634
 
      
635
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
636
 
        size_t suf_len = strlen(*suf);
637
 
        if((d_name_len >= suf_len)
638
 
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
639
 
                == 0)){
640
 
          if(debug){
641
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
642
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
643
 
          }
644
 
          bad_name = true;
645
 
          break;
646
 
        }
647
 
      }
648
 
      
649
 
      if(bad_name){
650
 
        continue;
651
 
      }
652
 
    }
653
 
 
654
 
    char *filename;
655
 
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
656
 
    if(ret < 0){
657
 
      perror("asprintf");
658
 
      continue;
659
 
    }
660
 
    
661
 
    ret = stat(filename, &st);
662
 
    if (ret == -1){
663
 
      perror("stat");
664
 
      free(filename);
665
 
      continue;
666
 
    }
667
 
    
668
 
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
 
906
  /* Read and execute any executable in the plugin directory*/
 
907
  for(int i = 0; i < numplugins; i++){
 
908
    
 
909
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
910
    if(plugin_fd == -1){
 
911
      error(0, errno, "Could not open plugin");
 
912
      free(direntries[i]);
 
913
      continue;
 
914
    }
 
915
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
 
916
    if(ret == -1){
 
917
      error(0, errno, "stat");
 
918
      close(plugin_fd);
 
919
      free(direntries[i]);
 
920
      continue;
 
921
    }
 
922
    
 
923
    /* Ignore non-executable files */
 
924
    if(not S_ISREG(st.st_mode)
 
925
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
926
                                        X_OK, 0)) != 0)){
669
927
      if(debug){
670
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
671
 
                " with bad type or mode\n", filename);
 
928
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
929
                " with bad type or mode\n",
 
930
                plugindir != NULL ? plugindir : PDIR,
 
931
                direntries[i]->d_name);
672
932
      }
673
 
      free(filename);
 
933
      close(plugin_fd);
 
934
      free(direntries[i]);
674
935
      continue;
675
936
    }
676
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
 
937
    
 
938
    plugin *p = getplugin(direntries[i]->d_name);
677
939
    if(p == NULL){
678
 
      perror("getplugin");
679
 
      free(filename);
 
940
      error(0, errno, "getplugin");
 
941
      close(plugin_fd);
 
942
      free(direntries[i]);
680
943
      continue;
681
944
    }
682
945
    if(p->disabled){
683
946
      if(debug){
684
947
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
685
 
                dirst->d_name);
 
948
                direntries[i]->d_name);
686
949
      }
687
 
      free(filename);
 
950
      close(plugin_fd);
 
951
      free(direntries[i]);
688
952
      continue;
689
953
    }
690
954
    {
691
955
      /* Add global arguments to argument list for this plugin */
692
 
      plugin *g = getplugin(NULL, &plugin_list);
 
956
      plugin *g = getplugin(NULL);
693
957
      if(g != NULL){
694
958
        for(char **a = g->argv + 1; *a != NULL; a++){
695
959
          if(not add_argument(p, *a)){
696
 
            perror("add_argument");
 
960
            error(0, errno, "add_argument");
697
961
          }
698
962
        }
699
963
        /* Add global environment variables */
700
964
        for(char **e = g->environ; *e != NULL; e++){
701
 
          if(not add_environment(p, *e)){
702
 
            perror("add_environment");
 
965
          if(not add_environment(p, *e, false)){
 
966
            error(0, errno, "add_environment");
703
967
          }
704
968
        }
705
969
      }
706
970
    }
707
 
    /* If this plugin has any environment variables, we will call
708
 
       using execve and need to duplicate the environment from this
709
 
       process, too. */
 
971
    /* If this plugin has any environment variables, we need to
 
972
       duplicate the environment from this process, too. */
710
973
    if(p->environ[0] != NULL){
711
974
      for(char **e = environ; *e != NULL; e++){
712
 
        char *copy = strdup(*e);
713
 
        if(copy == NULL){
714
 
          perror("strdup");
715
 
          continue;
716
 
        }
717
 
        if(not add_environment(p, copy)){
718
 
          perror("add_environment");
 
975
        if(not add_environment(p, *e, false)){
 
976
          error(0, errno, "add_environment");
719
977
        }
720
978
      }
721
979
    }
722
980
    
723
981
    int pipefd[2];
724
 
    ret = pipe(pipefd);
725
 
    if (ret == -1){
726
 
      perror("pipe");
727
 
      exitstatus = EXIT_FAILURE;
728
 
      goto fallback;
729
 
    }
 
982
#ifndef O_CLOEXEC
 
983
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
984
#else  /* O_CLOEXEC */
 
985
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
986
#endif  /* O_CLOEXEC */
 
987
    if(ret == -1){
 
988
      error(0, errno, "pipe");
 
989
      exitstatus = EX_OSERR;
 
990
      free(direntries[i]);
 
991
      goto fallback;
 
992
    }
 
993
    if(pipefd[0] >= FD_SETSIZE){
 
994
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
995
              FD_SETSIZE);
 
996
      close(pipefd[0]);
 
997
      close(pipefd[1]);
 
998
      exitstatus = EX_OSERR;
 
999
      free(direntries[i]);
 
1000
      goto fallback;
 
1001
    }
 
1002
#ifndef O_CLOEXEC
 
1003
    /* Ask OS to automatic close the pipe on exec */
730
1004
    ret = set_cloexec_flag(pipefd[0]);
731
1005
    if(ret < 0){
732
 
      perror("set_cloexec_flag");
733
 
      exitstatus = EXIT_FAILURE;
 
1006
      error(0, errno, "set_cloexec_flag");
 
1007
      close(pipefd[0]);
 
1008
      close(pipefd[1]);
 
1009
      exitstatus = EX_OSERR;
 
1010
      free(direntries[i]);
734
1011
      goto fallback;
735
1012
    }
736
1013
    ret = set_cloexec_flag(pipefd[1]);
737
1014
    if(ret < 0){
738
 
      perror("set_cloexec_flag");
739
 
      exitstatus = EXIT_FAILURE;
 
1015
      error(0, errno, "set_cloexec_flag");
 
1016
      close(pipefd[0]);
 
1017
      close(pipefd[1]);
 
1018
      exitstatus = EX_OSERR;
 
1019
      free(direntries[i]);
740
1020
      goto fallback;
741
1021
    }
 
1022
#endif  /* not O_CLOEXEC */
742
1023
    /* Block SIGCHLD until process is safely in process list */
743
 
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
1024
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
 
1025
                                              &sigchld_action.sa_mask,
 
1026
                                              NULL));
744
1027
    if(ret < 0){
745
 
      perror("sigprocmask");
746
 
      exitstatus = EXIT_FAILURE;
 
1028
      error(0, errno, "sigprocmask");
 
1029
      exitstatus = EX_OSERR;
 
1030
      free(direntries[i]);
747
1031
      goto fallback;
748
1032
    }
749
 
    // Starting a new process to be watched
750
 
    pid_t pid = fork();
 
1033
    /* Starting a new process to be watched */
 
1034
    pid_t pid;
 
1035
    do {
 
1036
      pid = fork();
 
1037
    } while(pid == -1 and errno == EINTR);
751
1038
    if(pid == -1){
752
 
      perror("fork");
753
 
      exitstatus = EXIT_FAILURE;
 
1039
      error(0, errno, "fork");
 
1040
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1041
                                     &sigchld_action.sa_mask, NULL));
 
1042
      close(pipefd[0]);
 
1043
      close(pipefd[1]);
 
1044
      exitstatus = EX_OSERR;
 
1045
      free(direntries[i]);
754
1046
      goto fallback;
755
1047
    }
756
1048
    if(pid == 0){
757
1049
      /* this is the child process */
758
1050
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
759
1051
      if(ret < 0){
760
 
        perror("sigaction");
761
 
        _exit(EXIT_FAILURE);
 
1052
        error(0, errno, "sigaction");
 
1053
        _exit(EX_OSERR);
762
1054
      }
763
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
1055
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
764
1056
      if(ret < 0){
765
 
        perror("sigprocmask");
766
 
        _exit(EXIT_FAILURE);
 
1057
        error(0, errno, "sigprocmask");
 
1058
        _exit(EX_OSERR);
767
1059
      }
768
 
 
 
1060
      
769
1061
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
770
1062
      if(ret == -1){
771
 
        perror("dup2");
772
 
        _exit(EXIT_FAILURE);
 
1063
        error(0, errno, "dup2");
 
1064
        _exit(EX_OSERR);
773
1065
      }
774
1066
      
775
 
      if(dirfd(dir) < 0){
776
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
777
 
           above and must now close it manually here. */
778
 
        closedir(dir);
779
 
      }
780
 
      if(p->environ[0] == NULL){
781
 
        if(execv(filename, p->argv) < 0){
782
 
          perror("execv");
783
 
          _exit(EXIT_FAILURE);
784
 
        }
785
 
      } else {
786
 
        if(execve(filename, p->argv, p->environ) < 0){
787
 
          perror("execve");
788
 
          _exit(EXIT_FAILURE);
789
 
        }
 
1067
      if(fexecve(plugin_fd, p->argv,
 
1068
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1069
        error(0, errno, "fexecve for %s/%s",
 
1070
              plugindir != NULL ? plugindir : PDIR,
 
1071
              direntries[i]->d_name);
 
1072
        _exit(EX_OSERR);
790
1073
      }
791
1074
      /* no return */
792
1075
    }
793
 
    /* parent process */
794
 
    free(filename);
795
 
    close(pipefd[1]);           /* close unused write end of pipe */
796
 
    process *new_process = malloc(sizeof(process));
797
 
    if (new_process == NULL){
798
 
      perror("malloc");
799
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
1076
    /* Parent process */
 
1077
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1078
    close(plugin_fd);
 
1079
    plugin *new_plugin = getplugin(direntries[i]->d_name);
 
1080
    if(new_plugin == NULL){
 
1081
      error(0, errno, "getplugin");
 
1082
      ret = (int)(TEMP_FAILURE_RETRY
 
1083
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
 
1084
                               NULL)));
800
1085
      if(ret < 0){
801
 
        perror("sigprocmask");
 
1086
        error(0, errno, "sigprocmask");
802
1087
      }
803
 
      exitstatus = EXIT_FAILURE;
 
1088
      exitstatus = EX_OSERR;
 
1089
      free(direntries[i]);
804
1090
      goto fallback;
805
1091
    }
 
1092
    free(direntries[i]);
806
1093
    
807
 
    *new_process = (struct process){ .pid = pid,
808
 
                                     .fd = pipefd[0],
809
 
                                     .next = process_list };
810
 
    // List handling
811
 
    process_list = new_process;
 
1094
    new_plugin->pid = pid;
 
1095
    new_plugin->fd = pipefd[0];
 
1096
 
 
1097
    if(debug){
 
1098
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1099
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1100
    }
 
1101
 
812
1102
    /* Unblock SIGCHLD so signal handler can be run if this process
813
1103
       has already completed */
814
 
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
1104
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1105
                                              &sigchld_action.sa_mask,
 
1106
                                              NULL));
815
1107
    if(ret < 0){
816
 
      perror("sigprocmask");
817
 
      exitstatus = EXIT_FAILURE;
 
1108
      error(0, errno, "sigprocmask");
 
1109
      exitstatus = EX_OSERR;
818
1110
      goto fallback;
819
1111
    }
820
1112
    
821
 
    FD_SET(new_process->fd, &rfds_all);
822
 
    
823
 
    if (maxfd < new_process->fd){
824
 
      maxfd = new_process->fd;
825
 
    }
826
 
    
827
 
  }
828
 
 
829
 
  free_plugin_list(plugin_list);
830
 
  plugin_list = NULL;
831
 
  
832
 
  closedir(dir);
833
 
  dir = NULL;
834
 
    
835
 
  if (process_list == NULL){
836
 
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
837
 
            " directory?\n");
838
 
    process_list = NULL;
839
 
  }
840
 
  while(process_list){
 
1113
    FD_SET(new_plugin->fd, &rfds_all);
 
1114
    
 
1115
    if(maxfd < new_plugin->fd){
 
1116
      maxfd = new_plugin->fd;
 
1117
    }
 
1118
  }
 
1119
  
 
1120
  free(direntries);
 
1121
  direntries = NULL;
 
1122
  close(dir_fd);
 
1123
  dir_fd = -1;
 
1124
  free_plugin(getplugin(NULL));
 
1125
  
 
1126
  for(plugin *p = plugin_list; p != NULL; p = p->next){
 
1127
    if(p->pid != 0){
 
1128
      break;
 
1129
    }
 
1130
    if(p->next == NULL){
 
1131
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
1132
              " directory?\n");
 
1133
      free_plugin_list();
 
1134
    }
 
1135
  }
 
1136
  
 
1137
  /* Main loop while running plugins exist */
 
1138
  while(plugin_list){
841
1139
    fd_set rfds = rfds_all;
842
1140
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
843
 
    if (select_ret == -1){
844
 
      perror("select");
845
 
      exitstatus = EXIT_FAILURE;
 
1141
    if(select_ret == -1 and errno != EINTR){
 
1142
      error(0, errno, "select");
 
1143
      exitstatus = EX_OSERR;
846
1144
      goto fallback;
847
1145
    }
848
1146
    /* OK, now either a process completed, or something can be read
849
1147
       from one of them */
850
 
    for(process *proc = process_list; proc ; proc = proc->next){
 
1148
    for(plugin *proc = plugin_list; proc != NULL;){
851
1149
      /* Is this process completely done? */
852
 
      if(proc->eof and proc->completed){
 
1150
      if(proc->completed and proc->eof){
853
1151
        /* Only accept the plugin output if it exited cleanly */
854
1152
        if(not WIFEXITED(proc->status)
855
1153
           or WEXITSTATUS(proc->status) != 0){
856
1154
          /* Bad exit by plugin */
 
1155
          
857
1156
          if(debug){
858
1157
            if(WIFEXITED(proc->status)){
859
 
              fprintf(stderr, "Plugin %u exited with status %d\n",
860
 
                      (unsigned int) (proc->pid),
 
1158
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
 
1159
                      " status %d\n", proc->name,
 
1160
                      (intmax_t) (proc->pid),
861
1161
                      WEXITSTATUS(proc->status));
862
 
            } else if(WIFSIGNALED(proc->status)) {
863
 
              fprintf(stderr, "Plugin %u killed by signal %d\n",
864
 
                      (unsigned int) (proc->pid),
865
 
                      WTERMSIG(proc->status));
866
 
            } else if(WCOREDUMP(proc->status)){
867
 
              fprintf(stderr, "Plugin %d dumped core\n",
868
 
                      (unsigned int) (proc->pid));
 
1162
            } else if(WIFSIGNALED(proc->status)){
 
1163
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
 
1164
                      " signal %d: %s\n", proc->name,
 
1165
                      (intmax_t) (proc->pid),
 
1166
                      WTERMSIG(proc->status),
 
1167
                      strsignal(WTERMSIG(proc->status)));
869
1168
            }
870
1169
          }
 
1170
          
871
1171
          /* Remove the plugin */
872
1172
          FD_CLR(proc->fd, &rfds_all);
 
1173
          
873
1174
          /* Block signal while modifying process_list */
874
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
1175
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
 
1176
                                        (SIG_BLOCK,
 
1177
                                         &sigchld_action.sa_mask,
 
1178
                                         NULL));
875
1179
          if(ret < 0){
876
 
            perror("sigprocmask");
877
 
            exitstatus = EXIT_FAILURE;
 
1180
            error(0, errno, "sigprocmask");
 
1181
            exitstatus = EX_OSERR;
878
1182
            goto fallback;
879
1183
          }
880
 
          /* Delete this process entry from the list */
881
 
          if(process_list == proc){
882
 
            /* First one - simple */
883
 
            process_list = proc->next;
884
 
          } else {
885
 
            /* Second one or later */
886
 
            for(process *p = process_list; p != NULL; p = p->next){
887
 
              if(p->next == proc){
888
 
                p->next = proc->next;
889
 
                break;
890
 
              }
891
 
            }
892
 
          }
 
1184
          
 
1185
          plugin *next_plugin = proc->next;
 
1186
          free_plugin(proc);
 
1187
          proc = next_plugin;
 
1188
          
893
1189
          /* We are done modifying process list, so unblock signal */
894
 
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
895
 
                             NULL);
 
1190
          ret = (int)(TEMP_FAILURE_RETRY
 
1191
                      (sigprocmask(SIG_UNBLOCK,
 
1192
                                   &sigchld_action.sa_mask, NULL)));
896
1193
          if(ret < 0){
897
 
            perror("sigprocmask");
898
 
          }
899
 
          free(proc->buffer);
900
 
          free(proc);
901
 
          /* We deleted this process from the list, so we can't go
902
 
             proc->next.  Therefore, start over from the beginning of
903
 
             the process list */
904
 
          break;
 
1194
            error(0, errno, "sigprocmask");
 
1195
            exitstatus = EX_OSERR;
 
1196
            goto fallback;
 
1197
          }
 
1198
          
 
1199
          if(plugin_list == NULL){
 
1200
            break;
 
1201
          }
 
1202
          
 
1203
          continue;
905
1204
        }
 
1205
        
906
1206
        /* This process exited nicely, so print its buffer */
907
 
 
 
1207
        
908
1208
        bool bret = print_out_password(proc->buffer,
909
1209
                                       proc->buffer_length);
910
1210
        if(not bret){
911
 
          perror("print_out_password");
912
 
          exitstatus = EXIT_FAILURE;
 
1211
          error(0, errno, "print_out_password");
 
1212
          exitstatus = EX_IOERR;
913
1213
        }
914
1214
        goto fallback;
915
1215
      }
 
1216
      
916
1217
      /* This process has not completed.  Does it have any output? */
917
1218
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
918
1219
        /* This process had nothing to say at this time */
 
1220
        proc = proc->next;
919
1221
        continue;
920
1222
      }
921
1223
      /* Before reading, make the process' data buffer large enough */
922
1224
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
923
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
924
 
                               + (size_t) BUFFER_SIZE);
925
 
        if (proc->buffer == NULL){
926
 
          perror("malloc");
927
 
          exitstatus = EXIT_FAILURE;
 
1225
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1226
                                   + (size_t) BUFFER_SIZE);
 
1227
        if(new_buffer == NULL){
 
1228
          error(0, errno, "malloc");
 
1229
          exitstatus = EX_OSERR;
928
1230
          goto fallback;
929
1231
        }
 
1232
        proc->buffer = new_buffer;
930
1233
        proc->buffer_size += BUFFER_SIZE;
931
1234
      }
932
1235
      /* Read from the process */
933
 
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
934
 
                 BUFFER_SIZE);
935
 
      if(ret < 0){
 
1236
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
 
1237
                                     proc->buffer
 
1238
                                     + proc->buffer_length,
 
1239
                                     BUFFER_SIZE));
 
1240
      if(sret < 0){
936
1241
        /* Read error from this process; ignore the error */
 
1242
        proc = proc->next;
937
1243
        continue;
938
1244
      }
939
 
      if(ret == 0){
 
1245
      if(sret == 0){
940
1246
        /* got EOF */
941
1247
        proc->eof = true;
942
1248
      } else {
943
 
        proc->buffer_length += (size_t) ret;
 
1249
        proc->buffer_length += (size_t) sret;
944
1250
      }
945
1251
    }
946
1252
  }
947
 
 
948
 
 
 
1253
  
 
1254
  
949
1255
 fallback:
950
1256
  
951
 
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
1257
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
 
1258
                             and exitstatus != EX_OK)){
952
1259
    /* Fallback if all plugins failed, none are found or an error
953
1260
       occured */
954
1261
    bool bret;
955
1262
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
956
1263
    char *passwordbuffer = getpass("Password: ");
957
 
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
 
1264
    size_t len = strlen(passwordbuffer);
 
1265
    /* Strip trailing newline */
 
1266
    if(len > 0 and passwordbuffer[len-1] == '\n'){
 
1267
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
 
1268
      len--;
 
1269
    }
 
1270
    bret = print_out_password(passwordbuffer, len);
958
1271
    if(not bret){
959
 
      perror("print_out_password");
960
 
      exitstatus = EXIT_FAILURE;
 
1272
      error(0, errno, "print_out_password");
 
1273
      exitstatus = EX_IOERR;
961
1274
    }
962
1275
  }
963
1276
  
964
1277
  /* Restore old signal handler */
965
1278
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
966
1279
  if(ret == -1){
967
 
    perror("sigaction");
968
 
    exitstatus = EXIT_FAILURE;
 
1280
    error(0, errno, "sigaction");
 
1281
    exitstatus = EX_OSERR;
969
1282
  }
970
 
 
 
1283
  
971
1284
  if(custom_argv != NULL){
972
1285
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
973
1286
      free(*arg);
974
1287
    }
975
1288
    free(custom_argv);
976
1289
  }
977
 
  free_plugin_list(plugin_list);
978
 
  
979
 
  if(dir != NULL){
980
 
    closedir(dir);
 
1290
  
 
1291
  free(direntries);
 
1292
  
 
1293
  if(dir_fd != -1){
 
1294
    close(dir_fd);
981
1295
  }
982
1296
  
983
 
  /* Free the process list and kill the processes */
984
 
  for(process *next; process_list != NULL; process_list = next){
985
 
    next = process_list->next;
986
 
    close(process_list->fd);
987
 
    ret = kill(process_list->pid, SIGTERM);
988
 
    if(ret == -1 and errno != ESRCH){
989
 
      /* set-uid proccesses migth not get closed */
990
 
      perror("kill");
 
1297
  /* Kill the processes */
 
1298
  for(plugin *p = plugin_list; p != NULL; p = p->next){
 
1299
    if(p->pid != 0){
 
1300
      close(p->fd);
 
1301
      ret = kill(p->pid, SIGTERM);
 
1302
      if(ret == -1 and errno != ESRCH){
 
1303
        /* Set-uid proccesses might not get closed */
 
1304
        error(0, errno, "kill");
 
1305
      }
991
1306
    }
992
 
    free(process_list->buffer);
993
 
    free(process_list);
994
1307
  }
995
1308
  
996
1309
  /* Wait for any remaining child processes to terminate */
997
 
  do{
 
1310
  do {
998
1311
    ret = wait(NULL);
999
1312
  } while(ret >= 0);
1000
1313
  if(errno != ECHILD){
1001
 
    perror("wait");
 
1314
    error(0, errno, "wait");
1002
1315
  }
1003
 
 
 
1316
  
 
1317
  free_plugin_list();
 
1318
  
1004
1319
  free(plugindir);
 
1320
  free(pluginhelperdir);
1005
1321
  free(argfile);
1006
1322
  
1007
1323
  return exitstatus;