/mandos/release

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

« back to all changes in this revision

Viewing changes to plugins.d/mandos-client.c

* plugins.d/mandos-client.c: Some white space fixes.
  (runnable_hook): Simplify name rule.  More debug messages.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/***
2
 
  This file is part of avahi.
3
 
 
4
 
  avahi is free software; you can redistribute it and/or modify it
5
 
  under the terms of the GNU Lesser General Public License as
6
 
  published by the Free Software Foundation; either version 2.1 of the
7
 
  License, or (at your option) any later version.
8
 
 
9
 
  avahi is distributed in the hope that it will be useful, but WITHOUT
10
 
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11
 
  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12
 
  Public License for more details.
13
 
 
14
 
  You should have received a copy of the GNU Lesser General Public
15
 
  License along with avahi; if not, write to the Free Software
16
 
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17
 
  USA.
18
 
***/
 
1
/*  -*- coding: utf-8 -*- */
 
2
/*
 
3
 * Mandos-client - get and decrypt data from a Mandos server
 
4
 *
 
5
 * This program is partly derived from an example program for an Avahi
 
6
 * service browser, downloaded from
 
7
 * <http://avahi.org/browser/examples/core-browse-services.c>.  This
 
8
 * includes the following functions: "resolve_callback",
 
9
 * "browse_callback", and parts of "main".
 
10
 * 
 
11
 * Everything else is
 
12
 * Copyright © 2008-2011 Teddy Hogeborn
 
13
 * Copyright © 2008-2011 Björn Påhlsson
 
14
 * 
 
15
 * This program is free software: you can redistribute it and/or
 
16
 * modify it under the terms of the GNU General Public License as
 
17
 * published by the Free Software Foundation, either version 3 of the
 
18
 * License, or (at your option) any later version.
 
19
 * 
 
20
 * This program is distributed in the hope that it will be useful, but
 
21
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
23
 * General Public License for more details.
 
24
 * 
 
25
 * You should have received a copy of the GNU General Public License
 
26
 * along with this program.  If not, see
 
27
 * <http://www.gnu.org/licenses/>.
 
28
 * 
 
29
 * Contact the authors at <mandos@recompile.se>.
 
30
 */
19
31
 
 
32
/* Needed by GPGME, specifically gpgme_data_seek() */
 
33
#ifndef _LARGEFILE_SOURCE
20
34
#define _LARGEFILE_SOURCE
 
35
#endif
 
36
#ifndef _FILE_OFFSET_BITS
21
37
#define _FILE_OFFSET_BITS 64
22
 
 
23
 
#include <stdio.h>
24
 
#include <assert.h>
25
 
#include <stdlib.h>
26
 
#include <time.h>
27
 
#include <net/if.h>             /* if_nametoindex */
28
 
 
 
38
#endif
 
39
 
 
40
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), asprintf() */
 
41
 
 
42
#include <stdio.h>              /* fprintf(), stderr, fwrite(),
 
43
                                   stdout, ferror(), remove() */
 
44
#include <stdint.h>             /* uint16_t, uint32_t */
 
45
#include <stddef.h>             /* NULL, size_t, ssize_t */
 
46
#include <stdlib.h>             /* free(), EXIT_SUCCESS, srand(),
 
47
                                   strtof(), abort() */
 
48
#include <stdbool.h>            /* bool, false, true */
 
49
#include <string.h>             /* memset(), strcmp(), strlen(),
 
50
                                   strerror(), asprintf(), strcpy() */
 
51
#include <sys/ioctl.h>          /* ioctl */
 
52
#include <sys/types.h>          /* socket(), inet_pton(), sockaddr,
 
53
                                   sockaddr_in6, PF_INET6,
 
54
                                   SOCK_STREAM, uid_t, gid_t, open(),
 
55
                                   opendir(), DIR */
 
56
#include <sys/stat.h>           /* open(), S_ISREG */
 
57
#include <sys/socket.h>         /* socket(), struct sockaddr_in6,
 
58
                                   inet_pton(), connect() */
 
59
#include <fcntl.h>              /* open() */
 
60
#include <dirent.h>             /* opendir(), struct dirent, readdir()
 
61
                                 */
 
62
#include <inttypes.h>           /* PRIu16, PRIdMAX, intmax_t,
 
63
                                   strtoimax() */
 
64
#include <assert.h>             /* assert() */
 
65
#include <errno.h>              /* perror(), errno,
 
66
                                   program_invocation_short_name */
 
67
#include <time.h>               /* nanosleep(), time(), sleep() */
 
68
#include <net/if.h>             /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
 
69
                                   SIOCSIFFLAGS, if_indextoname(),
 
70
                                   if_nametoindex(), IF_NAMESIZE */
 
71
#include <netinet/in.h>         /* IN6_IS_ADDR_LINKLOCAL,
 
72
                                   INET_ADDRSTRLEN, INET6_ADDRSTRLEN
 
73
                                */
 
74
#include <unistd.h>             /* close(), SEEK_SET, off_t, write(),
 
75
                                   getuid(), getgid(), seteuid(),
 
76
                                   setgid(), pause() */
 
77
#include <arpa/inet.h>          /* inet_pton(), htons, inet_ntop() */
 
78
#include <iso646.h>             /* not, or, and */
 
79
#include <argp.h>               /* struct argp_option, error_t, struct
 
80
                                   argp_state, struct argp,
 
81
                                   argp_parse(), ARGP_KEY_ARG,
 
82
                                   ARGP_KEY_END, ARGP_ERR_UNKNOWN */
 
83
#include <signal.h>             /* sigemptyset(), sigaddset(),
 
84
                                   sigaction(), SIGTERM, sig_atomic_t,
 
85
                                   raise() */
 
86
#include <sysexits.h>           /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE,
 
87
                                   EX_NOHOST, EX_IOERR, EX_PROTOCOL */
 
88
#include <sys/wait.h>           /* waitpid(), WIFEXITED(),
 
89
                                   WEXITSTATUS(), WTERMSIG() */
 
90
 
 
91
#ifdef __linux__
 
92
#include <sys/klog.h>           /* klogctl() */
 
93
#endif  /* __linux__ */
 
94
 
 
95
/* Avahi */
 
96
/* All Avahi types, constants and functions
 
97
 Avahi*, avahi_*,
 
98
 AVAHI_* */
29
99
#include <avahi-core/core.h>
30
100
#include <avahi-core/lookup.h>
31
101
#include <avahi-core/log.h>
33
103
#include <avahi-common/malloc.h>
34
104
#include <avahi-common/error.h>
35
105
 
36
 
//mandos client part
37
 
#include <sys/types.h>          /* socket(), setsockopt(), inet_pton() */
38
 
#include <sys/socket.h>         /* socket(), setsockopt(), struct sockaddr_in6, struct in6_addr, inet_pton() */
39
 
#include <gnutls/gnutls.h>      /* ALL GNUTLS STUFF */
40
 
#include <gnutls/openpgp.h>     /* gnutls with openpgp stuff */
41
 
 
42
 
#include <unistd.h>             /* close() */
43
 
#include <netinet/in.h>
44
 
#include <stdbool.h>            /* true */
45
 
#include <string.h>             /* memset */
46
 
#include <arpa/inet.h>          /* inet_pton() */
47
 
#include <iso646.h>             /* not */
48
 
 
49
 
// gpgme
50
 
#include <errno.h>              /* perror() */
51
 
#include <gpgme.h>
52
 
 
53
 
 
54
 
#ifndef CERT_ROOT
55
 
#define CERT_ROOT "/conf/conf.d/cryptkeyreq/"
56
 
#endif
57
 
#define CERTFILE CERT_ROOT "openpgp-client.txt"
58
 
#define KEYFILE CERT_ROOT "openpgp-client-key.txt"
 
106
/* GnuTLS */
 
107
#include <gnutls/gnutls.h>      /* All GnuTLS types, constants and
 
108
                                   functions:
 
109
                                   gnutls_*
 
110
                                   init_gnutls_session(),
 
111
                                   GNUTLS_* */
 
112
#include <gnutls/openpgp.h>
 
113
                         /* gnutls_certificate_set_openpgp_key_file(),
 
114
                            GNUTLS_OPENPGP_FMT_BASE64 */
 
115
 
 
116
/* GPGME */
 
117
#include <gpgme.h>              /* All GPGME types, constants and
 
118
                                   functions:
 
119
                                   gpgme_*
 
120
                                   GPGME_PROTOCOL_OpenPGP,
 
121
                                   GPG_ERR_NO_* */
 
122
 
59
123
#define BUFFER_SIZE 256
60
 
#define DH_BITS 1024
 
124
 
 
125
#define PATHDIR "/conf/conf.d/mandos"
 
126
#define SECKEY "seckey.txt"
 
127
#define PUBKEY "pubkey.txt"
 
128
#define HOOKDIR "/lib/mandos/network-hooks.d"
61
129
 
62
130
bool debug = false;
63
 
 
 
131
static const char mandos_protocol_version[] = "1";
 
132
const char *argp_program_version = "mandos-client " VERSION;
 
133
const char *argp_program_bug_address = "<mandos@recompile.se>";
 
134
static const char sys_class_net[] = "/sys/class/net";
 
135
char *connect_to = NULL;
 
136
const char *hookdir = HOOKDIR;
 
137
 
 
138
/* Doubly linked list that need to be circularly linked when used */
 
139
typedef struct server{
 
140
  const char *ip;
 
141
  uint16_t port;
 
142
  AvahiIfIndex if_index;
 
143
  int af;
 
144
  struct timespec last_seen;
 
145
  struct server *next;
 
146
  struct server *prev;
 
147
} server;
 
148
 
 
149
/* Used for passing in values through the Avahi callback functions */
64
150
typedef struct {
65
 
  gnutls_session_t session;
 
151
  AvahiSimplePoll *simple_poll;
 
152
  AvahiServer *server;
66
153
  gnutls_certificate_credentials_t cred;
 
154
  unsigned int dh_bits;
67
155
  gnutls_dh_params_t dh_params;
68
 
} encrypted_session;
69
 
 
70
 
 
71
 
ssize_t gpg_packet_decrypt (char *packet, size_t packet_size, char **new_packet, char *homedir){
72
 
  gpgme_data_t dh_crypto, dh_plain;
 
156
  const char *priority;
73
157
  gpgme_ctx_t ctx;
 
158
  server *current_server;
 
159
} mandos_context;
 
160
 
 
161
/* global context so signal handler can reach it*/
 
162
mandos_context mc = { .simple_poll = NULL, .server = NULL,
 
163
                      .dh_bits = 1024, .priority = "SECURE256"
 
164
                      ":!CTYPE-X.509:+CTYPE-OPENPGP",
 
165
                      .current_server = NULL };
 
166
 
 
167
sig_atomic_t quit_now = 0;
 
168
int signal_received = 0;
 
169
 
 
170
/* Function to use when printing errors */
 
171
void perror_plus(const char *print_text){
 
172
  fprintf(stderr, "Mandos plugin %s: ",
 
173
          program_invocation_short_name);
 
174
  perror(print_text);
 
175
}
 
176
 
 
177
/*
 
178
 * Make additional room in "buffer" for at least BUFFER_SIZE more
 
179
 * bytes. "buffer_capacity" is how much is currently allocated,
 
180
 * "buffer_length" is how much is already used.
 
181
 */
 
182
size_t incbuffer(char **buffer, size_t buffer_length,
 
183
                 size_t buffer_capacity){
 
184
  if(buffer_length + BUFFER_SIZE > buffer_capacity){
 
185
    *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
 
186
    if(buffer == NULL){
 
187
      return 0;
 
188
    }
 
189
    buffer_capacity += BUFFER_SIZE;
 
190
  }
 
191
  return buffer_capacity;
 
192
}
 
193
 
 
194
/* Add server to set of servers to retry periodically */
 
195
int add_server(const char *ip, uint16_t port, AvahiIfIndex if_index,
 
196
               int af){
 
197
  int ret;
 
198
  server *new_server = malloc(sizeof(server));
 
199
  if(new_server == NULL){
 
200
    perror_plus("malloc");
 
201
    return -1;
 
202
  }
 
203
  *new_server = (server){ .ip = strdup(ip),
 
204
                          .port = port,
 
205
                          .if_index = if_index,
 
206
                          .af = af };
 
207
  if(new_server->ip == NULL){
 
208
    perror_plus("strdup");
 
209
    return -1;
 
210
  }
 
211
  /* Special case of first server */
 
212
  if (mc.current_server == NULL){
 
213
    new_server->next = new_server;
 
214
    new_server->prev = new_server;
 
215
    mc.current_server = new_server;
 
216
  /* Place the new server last in the list */
 
217
  } else {
 
218
    new_server->next = mc.current_server;
 
219
    new_server->prev = mc.current_server->prev;
 
220
    new_server->prev->next = new_server;
 
221
    mc.current_server->prev = new_server;
 
222
  }
 
223
  ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
 
224
  if(ret == -1){
 
225
    perror_plus("clock_gettime");
 
226
    return -1;
 
227
  }
 
228
  return 0;
 
229
}
 
230
 
 
231
/* 
 
232
 * Initialize GPGME.
 
233
 */
 
234
static bool init_gpgme(const char *seckey, const char *pubkey,
 
235
                       const char *tempdir){
74
236
  gpgme_error_t rc;
75
 
  ssize_t ret;
76
 
  size_t new_packet_capacity = 0;
77
 
  size_t new_packet_length = 0;
78
237
  gpgme_engine_info_t engine_info;
79
 
 
80
 
  if (debug){
81
 
    fprintf(stderr, "Attempting to decrypt password from gpg packet\n");
 
238
  
 
239
  
 
240
  /*
 
241
   * Helper function to insert pub and seckey to the engine keyring.
 
242
   */
 
243
  bool import_key(const char *filename){
 
244
    int ret;
 
245
    int fd;
 
246
    gpgme_data_t pgp_data;
 
247
    
 
248
    fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
 
249
    if(fd == -1){
 
250
      perror_plus("open");
 
251
      return false;
 
252
    }
 
253
    
 
254
    rc = gpgme_data_new_from_fd(&pgp_data, fd);
 
255
    if(rc != GPG_ERR_NO_ERROR){
 
256
      fprintf(stderr, "Mandos plugin mandos-client: "
 
257
              "bad gpgme_data_new_from_fd: %s: %s\n",
 
258
              gpgme_strsource(rc), gpgme_strerror(rc));
 
259
      return false;
 
260
    }
 
261
    
 
262
    rc = gpgme_op_import(mc.ctx, pgp_data);
 
263
    if(rc != GPG_ERR_NO_ERROR){
 
264
      fprintf(stderr, "Mandos plugin mandos-client: "
 
265
              "bad gpgme_op_import: %s: %s\n",
 
266
              gpgme_strsource(rc), gpgme_strerror(rc));
 
267
      return false;
 
268
    }
 
269
    
 
270
    ret = (int)TEMP_FAILURE_RETRY(close(fd));
 
271
    if(ret == -1){
 
272
      perror_plus("close");
 
273
    }
 
274
    gpgme_data_release(pgp_data);
 
275
    return true;
 
276
  }
 
277
  
 
278
  if(debug){
 
279
    fprintf(stderr, "Mandos plugin mandos-client: "
 
280
            "Initializing GPGME\n");
82
281
  }
83
282
  
84
283
  /* Init GPGME */
85
284
  gpgme_check_version(NULL);
86
 
  gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
 
285
  rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
 
286
  if(rc != GPG_ERR_NO_ERROR){
 
287
    fprintf(stderr, "Mandos plugin mandos-client: "
 
288
            "bad gpgme_engine_check_version: %s: %s\n",
 
289
            gpgme_strsource(rc), gpgme_strerror(rc));
 
290
    return false;
 
291
  }
87
292
  
88
 
  /* Set GPGME home directory */
89
 
  rc = gpgme_get_engine_info (&engine_info);
90
 
  if (rc != GPG_ERR_NO_ERROR){
91
 
    fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
 
293
  /* Set GPGME home directory for the OpenPGP engine only */
 
294
  rc = gpgme_get_engine_info(&engine_info);
 
295
  if(rc != GPG_ERR_NO_ERROR){
 
296
    fprintf(stderr, "Mandos plugin mandos-client: "
 
297
            "bad gpgme_get_engine_info: %s: %s\n",
92
298
            gpgme_strsource(rc), gpgme_strerror(rc));
93
 
    return -1;
 
299
    return false;
94
300
  }
95
301
  while(engine_info != NULL){
96
302
    if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
97
303
      gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
98
 
                            engine_info->file_name, homedir);
 
304
                            engine_info->file_name, tempdir);
99
305
      break;
100
306
    }
101
307
    engine_info = engine_info->next;
102
308
  }
103
309
  if(engine_info == NULL){
104
 
    fprintf(stderr, "Could not set home dir to %s\n", homedir);
105
 
    return -1;
106
 
  }
107
 
  
108
 
  /* Create new GPGME data buffer from packet buffer */
109
 
  rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
110
 
  if (rc != GPG_ERR_NO_ERROR){
111
 
    fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
 
310
    fprintf(stderr, "Mandos plugin mandos-client: "
 
311
            "Could not set GPGME home dir to %s\n", tempdir);
 
312
    return false;
 
313
  }
 
314
  
 
315
  /* Create new GPGME "context" */
 
316
  rc = gpgme_new(&(mc.ctx));
 
317
  if(rc != GPG_ERR_NO_ERROR){
 
318
    fprintf(stderr, "Mandos plugin mandos-client: "
 
319
            "bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
 
320
            gpgme_strerror(rc));
 
321
    return false;
 
322
  }
 
323
  
 
324
  if(not import_key(pubkey) or not import_key(seckey)){
 
325
    return false;
 
326
  }
 
327
  
 
328
  return true;
 
329
}
 
330
 
 
331
/* 
 
332
 * Decrypt OpenPGP data.
 
333
 * Returns -1 on error
 
334
 */
 
335
static ssize_t pgp_packet_decrypt(const char *cryptotext,
 
336
                                  size_t crypto_size,
 
337
                                  char **plaintext){
 
338
  gpgme_data_t dh_crypto, dh_plain;
 
339
  gpgme_error_t rc;
 
340
  ssize_t ret;
 
341
  size_t plaintext_capacity = 0;
 
342
  ssize_t plaintext_length = 0;
 
343
  
 
344
  if(debug){
 
345
    fprintf(stderr, "Mandos plugin mandos-client: "
 
346
            "Trying to decrypt OpenPGP data\n");
 
347
  }
 
348
  
 
349
  /* Create new GPGME data buffer from memory cryptotext */
 
350
  rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
 
351
                               0);
 
352
  if(rc != GPG_ERR_NO_ERROR){
 
353
    fprintf(stderr, "Mandos plugin mandos-client: "
 
354
            "bad gpgme_data_new_from_mem: %s: %s\n",
112
355
            gpgme_strsource(rc), gpgme_strerror(rc));
113
356
    return -1;
114
357
  }
115
358
  
116
359
  /* Create new empty GPGME data buffer for the plaintext */
117
360
  rc = gpgme_data_new(&dh_plain);
118
 
  if (rc != GPG_ERR_NO_ERROR){
119
 
    fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
120
 
            gpgme_strsource(rc), gpgme_strerror(rc));
121
 
    return -1;
122
 
  }
123
 
  
124
 
  /* Create new GPGME "context" */
125
 
  rc = gpgme_new(&ctx);
126
 
  if (rc != GPG_ERR_NO_ERROR){
127
 
    fprintf(stderr, "bad gpgme_new: %s: %s\n",
128
 
            gpgme_strsource(rc), gpgme_strerror(rc));
129
 
    return -1;
130
 
  }
131
 
  
132
 
  /* Decrypt data from the FILE pointer to the plaintext data buffer */
133
 
  rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
134
 
  if (rc != GPG_ERR_NO_ERROR){
135
 
    fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
136
 
            gpgme_strsource(rc), gpgme_strerror(rc));
137
 
    return -1;
138
 
  }
139
 
 
140
 
  if(debug){
141
 
    fprintf(stderr, "decryption of gpg packet succeeded\n");
142
 
  }
143
 
 
144
 
  if (debug){
145
 
    gpgme_decrypt_result_t result;
146
 
    result = gpgme_op_decrypt_result(ctx);
147
 
    if (result == NULL){
148
 
      fprintf(stderr, "gpgme_op_decrypt_result failed\n");
149
 
    } else {
150
 
      fprintf(stderr, "Unsupported algorithm: %s\n", result->unsupported_algorithm);
151
 
      fprintf(stderr, "Wrong key usage: %d\n", result->wrong_key_usage);
152
 
      if(result->file_name != NULL){
153
 
        fprintf(stderr, "File name: %s\n", result->file_name);
154
 
      }
155
 
      gpgme_recipient_t recipient;
156
 
      recipient = result->recipients;
157
 
      if(recipient){
 
361
  if(rc != GPG_ERR_NO_ERROR){
 
362
    fprintf(stderr, "Mandos plugin mandos-client: "
 
363
            "bad gpgme_data_new: %s: %s\n",
 
364
            gpgme_strsource(rc), gpgme_strerror(rc));
 
365
    gpgme_data_release(dh_crypto);
 
366
    return -1;
 
367
  }
 
368
  
 
369
  /* Decrypt data from the cryptotext data buffer to the plaintext
 
370
     data buffer */
 
371
  rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
 
372
  if(rc != GPG_ERR_NO_ERROR){
 
373
    fprintf(stderr, "Mandos plugin mandos-client: "
 
374
            "bad gpgme_op_decrypt: %s: %s\n",
 
375
            gpgme_strsource(rc), gpgme_strerror(rc));
 
376
    plaintext_length = -1;
 
377
    if(debug){
 
378
      gpgme_decrypt_result_t result;
 
379
      result = gpgme_op_decrypt_result(mc.ctx);
 
380
      if(result == NULL){
 
381
        fprintf(stderr, "Mandos plugin mandos-client: "
 
382
                "gpgme_op_decrypt_result failed\n");
 
383
      } else {
 
384
        fprintf(stderr, "Mandos plugin mandos-client: "
 
385
                "Unsupported algorithm: %s\n",
 
386
                result->unsupported_algorithm);
 
387
        fprintf(stderr, "Mandos plugin mandos-client: "
 
388
                "Wrong key usage: %u\n",
 
389
                result->wrong_key_usage);
 
390
        if(result->file_name != NULL){
 
391
          fprintf(stderr, "Mandos plugin mandos-client: "
 
392
                  "File name: %s\n", result->file_name);
 
393
        }
 
394
        gpgme_recipient_t recipient;
 
395
        recipient = result->recipients;
158
396
        while(recipient != NULL){
159
 
          fprintf(stderr, "Public key algorithm: %s\n",
 
397
          fprintf(stderr, "Mandos plugin mandos-client: "
 
398
                  "Public key algorithm: %s\n",
160
399
                  gpgme_pubkey_algo_name(recipient->pubkey_algo));
161
 
          fprintf(stderr, "Key ID: %s\n", recipient->keyid);
162
 
          fprintf(stderr, "Secret key available: %s\n",
163
 
                  recipient->status == GPG_ERR_NO_SECKEY ? "No" : "Yes");
 
400
          fprintf(stderr, "Mandos plugin mandos-client: "
 
401
                  "Key ID: %s\n", recipient->keyid);
 
402
          fprintf(stderr, "Mandos plugin mandos-client: "
 
403
                  "Secret key available: %s\n",
 
404
                  recipient->status == GPG_ERR_NO_SECKEY
 
405
                  ? "No" : "Yes");
164
406
          recipient = recipient->next;
165
407
        }
166
408
      }
167
409
    }
 
410
    goto decrypt_end;
168
411
  }
169
412
  
170
 
  /* Delete the GPGME FILE pointer cryptotext data buffer */
171
 
  gpgme_data_release(dh_crypto);
 
413
  if(debug){
 
414
    fprintf(stderr, "Mandos plugin mandos-client: "
 
415
            "Decryption of OpenPGP data succeeded\n");
 
416
  }
172
417
  
173
418
  /* Seek back to the beginning of the GPGME plaintext data buffer */
174
 
  gpgme_data_seek(dh_plain, 0, SEEK_SET);
175
 
 
176
 
  *new_packet = 0;
 
419
  if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
 
420
    perror_plus("gpgme_data_seek");
 
421
    plaintext_length = -1;
 
422
    goto decrypt_end;
 
423
  }
 
424
  
 
425
  *plaintext = NULL;
177
426
  while(true){
178
 
    if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
179
 
      *new_packet = realloc(*new_packet, new_packet_capacity + BUFFER_SIZE);
180
 
      if (*new_packet == NULL){
181
 
        perror("realloc");
182
 
        return -1;
183
 
      }
184
 
      new_packet_capacity += BUFFER_SIZE;
 
427
    plaintext_capacity = incbuffer(plaintext,
 
428
                                   (size_t)plaintext_length,
 
429
                                   plaintext_capacity);
 
430
    if(plaintext_capacity == 0){
 
431
      perror_plus("incbuffer");
 
432
      plaintext_length = -1;
 
433
      goto decrypt_end;
185
434
    }
186
435
    
187
 
    ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length, BUFFER_SIZE);
 
436
    ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
 
437
                          BUFFER_SIZE);
188
438
    /* Print the data, if any */
189
 
    if (ret == 0){
190
 
      /* If password is empty, then a incorrect error will be printed */
 
439
    if(ret == 0){
 
440
      /* EOF */
191
441
      break;
192
442
    }
193
443
    if(ret < 0){
194
 
      perror("gpgme_data_read");
195
 
      return -1;
 
444
      perror_plus("gpgme_data_read");
 
445
      plaintext_length = -1;
 
446
      goto decrypt_end;
196
447
    }
197
 
    new_packet_length += ret;
 
448
    plaintext_length += ret;
198
449
  }
199
 
 
 
450
  
200
451
  if(debug){
201
 
    fprintf(stderr, "decrypted password is: %s\n", *new_packet);
 
452
    fprintf(stderr, "Mandos plugin mandos-client: "
 
453
            "Decrypted password is: ");
 
454
    for(ssize_t i = 0; i < plaintext_length; i++){
 
455
      fprintf(stderr, "%02hhX ", (*plaintext)[i]);
 
456
    }
 
457
    fprintf(stderr, "\n");
202
458
  }
203
 
 
204
 
   /* Delete the GPGME plaintext data buffer */
 
459
  
 
460
 decrypt_end:
 
461
  
 
462
  /* Delete the GPGME cryptotext data buffer */
 
463
  gpgme_data_release(dh_crypto);
 
464
  
 
465
  /* Delete the GPGME plaintext data buffer */
205
466
  gpgme_data_release(dh_plain);
206
 
  return new_packet_length;
 
467
  return plaintext_length;
207
468
}
208
469
 
209
 
static const char * safer_gnutls_strerror (int value) {
210
 
  const char *ret = gnutls_strerror (value);
211
 
  if (ret == NULL)
 
470
static const char * safer_gnutls_strerror(int value){
 
471
  const char *ret = gnutls_strerror(value); /* Spurious warning from
 
472
                                               -Wunreachable-code */
 
473
  if(ret == NULL)
212
474
    ret = "(unknown)";
213
475
  return ret;
214
476
}
215
477
 
216
 
void debuggnutls(int level, const char* string){
217
 
  fprintf(stderr, "%s", string);
 
478
/* GnuTLS log function callback */
 
479
static void debuggnutls(__attribute__((unused)) int level,
 
480
                        const char* string){
 
481
  fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
218
482
}
219
483
 
220
 
int initgnutls(encrypted_session *es){
221
 
  const char *err;
 
484
static int init_gnutls_global(const char *pubkeyfilename,
 
485
                              const char *seckeyfilename){
222
486
  int ret;
223
 
 
 
487
  
224
488
  if(debug){
225
 
    fprintf(stderr, "Initializing gnutls\n");
 
489
    fprintf(stderr, "Mandos plugin mandos-client: "
 
490
            "Initializing GnuTLS\n");
226
491
  }
227
 
 
228
492
  
229
 
  if ((ret = gnutls_global_init ())
230
 
      != GNUTLS_E_SUCCESS) {
231
 
    fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
 
493
  ret = gnutls_global_init();
 
494
  if(ret != GNUTLS_E_SUCCESS){
 
495
    fprintf(stderr, "Mandos plugin mandos-client: "
 
496
            "GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
232
497
    return -1;
233
498
  }
234
 
 
235
 
  if (debug){
 
499
  
 
500
  if(debug){
 
501
    /* "Use a log level over 10 to enable all debugging options."
 
502
     * - GnuTLS manual
 
503
     */
236
504
    gnutls_global_set_log_level(11);
237
505
    gnutls_global_set_log_function(debuggnutls);
238
506
  }
239
507
  
240
 
 
241
 
  /* openpgp credentials */
242
 
  if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
243
 
      != GNUTLS_E_SUCCESS) {
244
 
    fprintf (stderr, "memory error: %s\n", safer_gnutls_strerror(ret));
 
508
  /* OpenPGP credentials */
 
509
  ret = gnutls_certificate_allocate_credentials(&mc.cred);
 
510
  if(ret != GNUTLS_E_SUCCESS){
 
511
    fprintf(stderr, "Mandos plugin mandos-client: "
 
512
            "GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
 
513
    gnutls_global_deinit();
245
514
    return -1;
246
515
  }
247
 
 
 
516
  
248
517
  if(debug){
249
 
    fprintf(stderr, "Attempting to use openpgp certificate %s"
250
 
            " and keyfile %s as gnutls credentials\n", CERTFILE, KEYFILE);
 
518
    fprintf(stderr, "Mandos plugin mandos-client: "
 
519
            "Attempting to use OpenPGP public key %s and"
 
520
            " secret key %s as GnuTLS credentials\n", pubkeyfilename,
 
521
            seckeyfilename);
251
522
  }
252
 
 
 
523
  
253
524
  ret = gnutls_certificate_set_openpgp_key_file
254
 
    (es->cred, CERTFILE, KEYFILE, GNUTLS_OPENPGP_FMT_BASE64);
255
 
  if (ret != GNUTLS_E_SUCCESS) {
256
 
    fprintf
257
 
      (stderr, "Error[%d] while reading the OpenPGP key pair ('%s', '%s')\n",
258
 
       ret, CERTFILE, KEYFILE);
259
 
    fprintf(stdout, "The Error is: %s\n",
260
 
            safer_gnutls_strerror(ret));
261
 
    return -1;
262
 
  }
263
 
 
264
 
  //Gnutls server initialization
265
 
  if ((ret = gnutls_dh_params_init (&es->dh_params))
266
 
      != GNUTLS_E_SUCCESS) {
267
 
    fprintf (stderr, "Error in dh parameter initialization: %s\n",
268
 
             safer_gnutls_strerror(ret));
269
 
    return -1;
270
 
  }
271
 
 
272
 
  if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
273
 
      != GNUTLS_E_SUCCESS) {
274
 
    fprintf (stderr, "Error in prime generation: %s\n",
275
 
             safer_gnutls_strerror(ret));
276
 
    return -1;
277
 
  }
278
 
 
279
 
  gnutls_certificate_set_dh_params (es->cred, es->dh_params);
280
 
 
281
 
  // Gnutls session creation
282
 
  if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
283
 
      != GNUTLS_E_SUCCESS){
284
 
    fprintf(stderr, "Error in gnutls session initialization: %s\n",
285
 
            safer_gnutls_strerror(ret));
286
 
  }
287
 
 
288
 
  if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
289
 
      != GNUTLS_E_SUCCESS) {
290
 
    fprintf(stderr, "Syntax error at: %s\n", err);
291
 
    fprintf(stderr, "Gnutls error: %s\n",
292
 
            safer_gnutls_strerror(ret));
293
 
    return -1;
294
 
  }
295
 
 
296
 
  if ((ret = gnutls_credentials_set
297
 
       (es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
298
 
      != GNUTLS_E_SUCCESS) {
299
 
    fprintf(stderr, "Error setting a credentials set: %s\n",
300
 
            safer_gnutls_strerror(ret));
301
 
    return -1;
302
 
  }
303
 
 
 
525
    (mc.cred, pubkeyfilename, seckeyfilename,
 
526
     GNUTLS_OPENPGP_FMT_BASE64);
 
527
  if(ret != GNUTLS_E_SUCCESS){
 
528
    fprintf(stderr,
 
529
            "Mandos plugin mandos-client: "
 
530
            "Error[%d] while reading the OpenPGP key pair ('%s',"
 
531
            " '%s')\n", ret, pubkeyfilename, seckeyfilename);
 
532
    fprintf(stderr, "Mandos plugin mandos-client: "
 
533
            "The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
 
534
    goto globalfail;
 
535
  }
 
536
  
 
537
  /* GnuTLS server initialization */
 
538
  ret = gnutls_dh_params_init(&mc.dh_params);
 
539
  if(ret != GNUTLS_E_SUCCESS){
 
540
    fprintf(stderr, "Mandos plugin mandos-client: "
 
541
            "Error in GnuTLS DH parameter initialization:"
 
542
            " %s\n", safer_gnutls_strerror(ret));
 
543
    goto globalfail;
 
544
  }
 
545
  ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
 
546
  if(ret != GNUTLS_E_SUCCESS){
 
547
    fprintf(stderr, "Mandos plugin mandos-client: "
 
548
            "Error in GnuTLS prime generation: %s\n",
 
549
            safer_gnutls_strerror(ret));
 
550
    goto globalfail;
 
551
  }
 
552
  
 
553
  gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
 
554
  
 
555
  return 0;
 
556
  
 
557
 globalfail:
 
558
  
 
559
  gnutls_certificate_free_credentials(mc.cred);
 
560
  gnutls_global_deinit();
 
561
  gnutls_dh_params_deinit(mc.dh_params);
 
562
  return -1;
 
563
}
 
564
 
 
565
static int init_gnutls_session(gnutls_session_t *session){
 
566
  int ret;
 
567
  /* GnuTLS session creation */
 
568
  do {
 
569
    ret = gnutls_init(session, GNUTLS_SERVER);
 
570
    if(quit_now){
 
571
      return -1;
 
572
    }
 
573
  } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
 
574
  if(ret != GNUTLS_E_SUCCESS){
 
575
    fprintf(stderr, "Mandos plugin mandos-client: "
 
576
            "Error in GnuTLS session initialization: %s\n",
 
577
            safer_gnutls_strerror(ret));
 
578
  }
 
579
  
 
580
  {
 
581
    const char *err;
 
582
    do {
 
583
      ret = gnutls_priority_set_direct(*session, mc.priority, &err);
 
584
      if(quit_now){
 
585
        gnutls_deinit(*session);
 
586
        return -1;
 
587
      }
 
588
    } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
 
589
    if(ret != GNUTLS_E_SUCCESS){
 
590
      fprintf(stderr, "Mandos plugin mandos-client: "
 
591
              "Syntax error at: %s\n", err);
 
592
      fprintf(stderr, "Mandos plugin mandos-client: "
 
593
              "GnuTLS error: %s\n", safer_gnutls_strerror(ret));
 
594
      gnutls_deinit(*session);
 
595
      return -1;
 
596
    }
 
597
  }
 
598
  
 
599
  do {
 
600
    ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
 
601
                                 mc.cred);
 
602
    if(quit_now){
 
603
      gnutls_deinit(*session);
 
604
      return -1;
 
605
    }
 
606
  } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
 
607
  if(ret != GNUTLS_E_SUCCESS){
 
608
    fprintf(stderr, "Mandos plugin mandos-client: "
 
609
            "Error setting GnuTLS credentials: %s\n",
 
610
            safer_gnutls_strerror(ret));
 
611
    gnutls_deinit(*session);
 
612
    return -1;
 
613
  }
 
614
  
304
615
  /* ignore client certificate if any. */
305
 
  gnutls_certificate_server_set_request (es->session, GNUTLS_CERT_IGNORE);
 
616
  gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
306
617
  
307
 
  gnutls_dh_set_prime_bits (es->session, DH_BITS);
 
618
  gnutls_dh_set_prime_bits(*session, mc.dh_bits);
308
619
  
309
620
  return 0;
310
621
}
311
622
 
312
 
void empty_log(AvahiLogLevel level, const char *txt){}
 
623
/* Avahi log function callback */
 
624
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
 
625
                      __attribute__((unused)) const char *txt){}
313
626
 
314
 
int start_mandos_communcation(char *ip, uint16_t port){
315
 
  int ret, tcp_sd;
316
 
  struct sockaddr_in6 to;
317
 
  struct in6_addr ip_addr;
318
 
  encrypted_session es;
 
627
/* Called when a Mandos server is found */
 
628
static int start_mandos_communication(const char *ip, uint16_t port,
 
629
                                      AvahiIfIndex if_index,
 
630
                                      int af){
 
631
  int ret, tcp_sd = -1;
 
632
  ssize_t sret;
 
633
  union {
 
634
    struct sockaddr_in in;
 
635
    struct sockaddr_in6 in6;
 
636
  } to;
319
637
  char *buffer = NULL;
320
 
  char *decrypted_buffer;
 
638
  char *decrypted_buffer = NULL;
321
639
  size_t buffer_length = 0;
322
640
  size_t buffer_capacity = 0;
323
 
  ssize_t decrypted_buffer_size;
324
 
  int retval = 0;
325
 
  const char interface[] = "eth0";
326
 
 
327
 
  if(debug){
328
 
    fprintf(stderr, "Setting up a tcp connection to %s\n", ip);
329
 
  }
330
 
  
331
 
  tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
332
 
  if(tcp_sd < 0) {
333
 
    perror("socket");
334
 
    return -1;
335
 
  }
336
 
 
337
 
  if(debug){
338
 
    fprintf(stderr, "Binding to interface %s\n", interface);
339
 
  }
340
 
 
341
 
  ret = setsockopt(tcp_sd, SOL_SOCKET, SO_BINDTODEVICE, interface, 5);
342
 
  if(tcp_sd < 0) {
343
 
    perror("setsockopt bindtodevice");
344
 
    return -1;
345
 
  }
346
 
  
347
 
  memset(&to,0,sizeof(to));
348
 
  to.sin6_family = AF_INET6;
349
 
  ret = inet_pton(AF_INET6, ip, &ip_addr);
350
 
  if (ret < 0 ){
351
 
    perror("inet_pton");
352
 
    return -1;
353
 
  }  
 
641
  size_t written;
 
642
  int retval = -1;
 
643
  gnutls_session_t session;
 
644
  int pf;                       /* Protocol family */
 
645
  
 
646
  errno = 0;
 
647
  
 
648
  if(quit_now){
 
649
    errno = EINTR;
 
650
    return -1;
 
651
  }
 
652
  
 
653
  switch(af){
 
654
  case AF_INET6:
 
655
    pf = PF_INET6;
 
656
    break;
 
657
  case AF_INET:
 
658
    pf = PF_INET;
 
659
    break;
 
660
  default:
 
661
    fprintf(stderr, "Mandos plugin mandos-client: "
 
662
            "Bad address family: %d\n", af);
 
663
    errno = EINVAL;
 
664
    return -1;
 
665
  }
 
666
  
 
667
  ret = init_gnutls_session(&session);
 
668
  if(ret != 0){
 
669
    return -1;
 
670
  }
 
671
  
 
672
  if(debug){
 
673
    fprintf(stderr, "Mandos plugin mandos-client: "
 
674
            "Setting up a TCP connection to %s, port %" PRIu16
 
675
            "\n", ip, port);
 
676
  }
 
677
  
 
678
  tcp_sd = socket(pf, SOCK_STREAM, 0);
 
679
  if(tcp_sd < 0){
 
680
    int e = errno;
 
681
    perror_plus("socket");
 
682
    errno = e;
 
683
    goto mandos_end;
 
684
  }
 
685
  
 
686
  if(quit_now){
 
687
    errno = EINTR;
 
688
    goto mandos_end;
 
689
  }
 
690
  
 
691
  memset(&to, 0, sizeof(to));
 
692
  if(af == AF_INET6){
 
693
    to.in6.sin6_family = (sa_family_t)af;
 
694
    ret = inet_pton(af, ip, &to.in6.sin6_addr);
 
695
  } else {                      /* IPv4 */
 
696
    to.in.sin_family = (sa_family_t)af;
 
697
    ret = inet_pton(af, ip, &to.in.sin_addr);
 
698
  }
 
699
  if(ret < 0 ){
 
700
    int e = errno;
 
701
    perror_plus("inet_pton");
 
702
    errno = e;
 
703
    goto mandos_end;
 
704
  }
354
705
  if(ret == 0){
355
 
    fprintf(stderr, "Bad address: %s\n", ip);
356
 
    return -1;
357
 
  }
358
 
  to.sin6_port = htons(port);
359
 
  to.sin6_scope_id = if_nametoindex(interface);
360
 
 
361
 
  if(debug){
362
 
    fprintf(stderr, "Connection to: %s\n", ip);
363
 
  }
364
 
  
365
 
  ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
366
 
  if (ret < 0){
367
 
    perror("connect");
368
 
    return -1;
369
 
  }
370
 
  
371
 
  ret = initgnutls (&es);
372
 
  if (ret != 0){
373
 
    retval = -1;
374
 
    return -1;
375
 
  }
376
 
    
377
 
  
378
 
  gnutls_transport_set_ptr (es.session, (gnutls_transport_ptr_t) tcp_sd);
379
 
 
380
 
  if(debug){
381
 
    fprintf(stderr, "Establishing tls session with %s\n", ip);
382
 
  }
383
 
 
384
 
  
385
 
  ret = gnutls_handshake (es.session);
386
 
  
387
 
  if (ret != GNUTLS_E_SUCCESS){
388
 
    fprintf(stderr, "\n*** Handshake failed ***\n");
389
 
    gnutls_perror (ret);
390
 
    retval = -1;
391
 
    goto exit;
392
 
  }
393
 
 
394
 
  //Retrieve gpg packet that contains the wanted password
395
 
 
396
 
  if(debug){
397
 
    fprintf(stderr, "Retrieving pgp encrypted password from %s\n", ip);
398
 
  }
399
 
 
400
 
  while(true){
401
 
    if (buffer_length + BUFFER_SIZE > buffer_capacity){
402
 
      buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
403
 
      if (buffer == NULL){
404
 
        perror("realloc");
405
 
        goto exit;
406
 
      }
407
 
      buffer_capacity += BUFFER_SIZE;
408
 
    }
409
 
    
410
 
    ret = gnutls_record_recv
411
 
      (es.session, buffer+buffer_length, BUFFER_SIZE);
412
 
    if (ret == 0){
 
706
    int e = errno;
 
707
    fprintf(stderr, "Mandos plugin mandos-client: "
 
708
            "Bad address: %s\n", ip);
 
709
    errno = e;
 
710
    goto mandos_end;
 
711
  }
 
712
  if(af == AF_INET6){
 
713
    to.in6.sin6_port = htons(port); /* Spurious warnings from
 
714
                                       -Wconversion and
 
715
                                       -Wunreachable-code */
 
716
    
 
717
    if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
 
718
       (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
 
719
                                -Wunreachable-code*/
 
720
      if(if_index == AVAHI_IF_UNSPEC){
 
721
        fprintf(stderr, "Mandos plugin mandos-client: "
 
722
                "An IPv6 link-local address is incomplete"
 
723
                " without a network interface\n");
 
724
        errno = EINVAL;
 
725
        goto mandos_end;
 
726
      }
 
727
      /* Set the network interface number as scope */
 
728
      to.in6.sin6_scope_id = (uint32_t)if_index;
 
729
    }
 
730
  } else {
 
731
    to.in.sin_port = htons(port); /* Spurious warnings from
 
732
                                     -Wconversion and
 
733
                                     -Wunreachable-code */
 
734
  }
 
735
  
 
736
  if(quit_now){
 
737
    errno = EINTR;
 
738
    goto mandos_end;
 
739
  }
 
740
  
 
741
  if(debug){
 
742
    if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
 
743
      char interface[IF_NAMESIZE];
 
744
      if(if_indextoname((unsigned int)if_index, interface) == NULL){
 
745
        perror_plus("if_indextoname");
 
746
      } else {
 
747
        fprintf(stderr, "Mandos plugin mandos-client: "
 
748
                "Connection to: %s%%%s, port %" PRIu16 "\n",
 
749
                ip, interface, port);
 
750
      }
 
751
    } else {
 
752
      fprintf(stderr, "Mandos plugin mandos-client: "
 
753
              "Connection to: %s, port %" PRIu16 "\n", ip, port);
 
754
    }
 
755
    char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
 
756
                 INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
 
757
    const char *pcret;
 
758
    if(af == AF_INET6){
 
759
      pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
 
760
                        sizeof(addrstr));
 
761
    } else {
 
762
      pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
 
763
                        sizeof(addrstr));
 
764
    }
 
765
    if(pcret == NULL){
 
766
      perror_plus("inet_ntop");
 
767
    } else {
 
768
      if(strcmp(addrstr, ip) != 0){
 
769
        fprintf(stderr, "Mandos plugin mandos-client: "
 
770
                "Canonical address form: %s\n", addrstr);
 
771
      }
 
772
    }
 
773
  }
 
774
  
 
775
  if(quit_now){
 
776
    errno = EINTR;
 
777
    goto mandos_end;
 
778
  }
 
779
  
 
780
  if(af == AF_INET6){
 
781
    ret = connect(tcp_sd, &to.in6, sizeof(to));
 
782
  } else {
 
783
    ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
 
784
  }
 
785
  if(ret < 0){
 
786
    if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
 
787
      int e = errno;
 
788
      perror_plus("connect");
 
789
      errno = e;
 
790
    }
 
791
    goto mandos_end;
 
792
  }
 
793
  
 
794
  if(quit_now){
 
795
    errno = EINTR;
 
796
    goto mandos_end;
 
797
  }
 
798
  
 
799
  const char *out = mandos_protocol_version;
 
800
  written = 0;
 
801
  while(true){
 
802
    size_t out_size = strlen(out);
 
803
    ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
 
804
                                        out_size - written));
 
805
    if(ret == -1){
 
806
      int e = errno;
 
807
      perror_plus("write");
 
808
      errno = e;
 
809
      goto mandos_end;
 
810
    }
 
811
    written += (size_t)ret;
 
812
    if(written < out_size){
 
813
      continue;
 
814
    } else {
 
815
      if(out == mandos_protocol_version){
 
816
        written = 0;
 
817
        out = "\r\n";
 
818
      } else {
 
819
        break;
 
820
      }
 
821
    }
 
822
  
 
823
    if(quit_now){
 
824
      errno = EINTR;
 
825
      goto mandos_end;
 
826
    }
 
827
  }
 
828
  
 
829
  if(debug){
 
830
    fprintf(stderr, "Mandos plugin mandos-client: "
 
831
            "Establishing TLS session with %s\n", ip);
 
832
  }
 
833
  
 
834
  if(quit_now){
 
835
    errno = EINTR;
 
836
    goto mandos_end;
 
837
  }
 
838
  
 
839
  /* Spurious warning from -Wint-to-pointer-cast */
 
840
  gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
 
841
  
 
842
  if(quit_now){
 
843
    errno = EINTR;
 
844
    goto mandos_end;
 
845
  }
 
846
  
 
847
  do {
 
848
    ret = gnutls_handshake(session);
 
849
    if(quit_now){
 
850
      errno = EINTR;
 
851
      goto mandos_end;
 
852
    }
 
853
  } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
 
854
  
 
855
  if(ret != GNUTLS_E_SUCCESS){
 
856
    if(debug){
 
857
      fprintf(stderr, "Mandos plugin mandos-client: "
 
858
              "*** GnuTLS Handshake failed ***\n");
 
859
      gnutls_perror(ret);
 
860
    }
 
861
    errno = EPROTO;
 
862
    goto mandos_end;
 
863
  }
 
864
  
 
865
  /* Read OpenPGP packet that contains the wanted password */
 
866
  
 
867
  if(debug){
 
868
    fprintf(stderr, "Mandos plugin mandos-client: "
 
869
            "Retrieving OpenPGP encrypted password from %s\n", ip);
 
870
  }
 
871
  
 
872
  while(true){
 
873
    
 
874
    if(quit_now){
 
875
      errno = EINTR;
 
876
      goto mandos_end;
 
877
    }
 
878
    
 
879
    buffer_capacity = incbuffer(&buffer, buffer_length,
 
880
                                buffer_capacity);
 
881
    if(buffer_capacity == 0){
 
882
      int e = errno;
 
883
      perror_plus("incbuffer");
 
884
      errno = e;
 
885
      goto mandos_end;
 
886
    }
 
887
    
 
888
    if(quit_now){
 
889
      errno = EINTR;
 
890
      goto mandos_end;
 
891
    }
 
892
    
 
893
    sret = gnutls_record_recv(session, buffer+buffer_length,
 
894
                              BUFFER_SIZE);
 
895
    if(sret == 0){
413
896
      break;
414
897
    }
415
 
    if (ret < 0){
416
 
      switch(ret){
 
898
    if(sret < 0){
 
899
      switch(sret){
417
900
      case GNUTLS_E_INTERRUPTED:
418
901
      case GNUTLS_E_AGAIN:
419
902
        break;
420
903
      case GNUTLS_E_REHANDSHAKE:
421
 
        ret = gnutls_handshake (es.session);
422
 
        if (ret < 0){
423
 
          fprintf(stderr, "\n*** Handshake failed ***\n");
424
 
          gnutls_perror (ret);
425
 
          retval = -1;
426
 
          goto exit;
 
904
        do {
 
905
          ret = gnutls_handshake(session);
 
906
          
 
907
          if(quit_now){
 
908
            errno = EINTR;
 
909
            goto mandos_end;
 
910
          }
 
911
        } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
 
912
        if(ret < 0){
 
913
          fprintf(stderr, "Mandos plugin mandos-client: "
 
914
                  "*** GnuTLS Re-handshake failed ***\n");
 
915
          gnutls_perror(ret);
 
916
          errno = EPROTO;
 
917
          goto mandos_end;
427
918
        }
428
919
        break;
429
920
      default:
430
 
        fprintf(stderr, "Unknown error while reading data from encrypted session with mandos server\n");
431
 
        retval = -1;
432
 
        gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
433
 
        goto exit;
434
 
      }
435
 
    } else {
436
 
      buffer_length += ret;
437
 
    }
438
 
  }
439
 
  
440
 
  if (buffer_length > 0){
441
 
    if ((decrypted_buffer_size = gpg_packet_decrypt(buffer, buffer_length, &decrypted_buffer, CERT_ROOT)) >= 0){
442
 
      fwrite (decrypted_buffer, 1, decrypted_buffer_size, stdout);
443
 
      free(decrypted_buffer);
444
 
    } else {
 
921
        fprintf(stderr, "Mandos plugin mandos-client: "
 
922
                "Unknown error while reading data from"
 
923
                " encrypted session with Mandos server\n");
 
924
        gnutls_bye(session, GNUTLS_SHUT_RDWR);
 
925
        errno = EIO;
 
926
        goto mandos_end;
 
927
      }
 
928
    } else {
 
929
      buffer_length += (size_t) sret;
 
930
    }
 
931
  }
 
932
  
 
933
  if(debug){
 
934
    fprintf(stderr, "Mandos plugin mandos-client: "
 
935
            "Closing TLS session\n");
 
936
  }
 
937
  
 
938
  if(quit_now){
 
939
    errno = EINTR;
 
940
    goto mandos_end;
 
941
  }
 
942
  
 
943
  do {
 
944
    ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
 
945
    if(quit_now){
 
946
      errno = EINTR;
 
947
      goto mandos_end;
 
948
    }
 
949
  } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
 
950
  
 
951
  if(buffer_length > 0){
 
952
    ssize_t decrypted_buffer_size;
 
953
    decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length,
 
954
                                               &decrypted_buffer);
 
955
    if(decrypted_buffer_size >= 0){
 
956
      
 
957
      written = 0;
 
958
      while(written < (size_t) decrypted_buffer_size){
 
959
        if(quit_now){
 
960
          errno = EINTR;
 
961
          goto mandos_end;
 
962
        }
 
963
        
 
964
        ret = (int)fwrite(decrypted_buffer + written, 1,
 
965
                          (size_t)decrypted_buffer_size - written,
 
966
                          stdout);
 
967
        if(ret == 0 and ferror(stdout)){
 
968
          int e = errno;
 
969
          if(debug){
 
970
            fprintf(stderr, "Mandos plugin mandos-client: "
 
971
                    "Error writing encrypted data: %s\n",
 
972
                    strerror(errno));
 
973
          }
 
974
          errno = e;
 
975
          goto mandos_end;
 
976
        }
 
977
        written += (size_t)ret;
 
978
      }
 
979
      retval = 0;
 
980
    }
 
981
  }
 
982
  
 
983
  /* Shutdown procedure */
 
984
  
 
985
 mandos_end:
 
986
  {
 
987
    int e = errno;
 
988
    free(decrypted_buffer);
 
989
    free(buffer);
 
990
    if(tcp_sd >= 0){
 
991
      ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
 
992
    }
 
993
    if(ret == -1){
 
994
      if(e == 0){
 
995
        e = errno;
 
996
      }
 
997
      perror_plus("close");
 
998
    }
 
999
    gnutls_deinit(session);
 
1000
    errno = e;
 
1001
    if(quit_now){
 
1002
      errno = EINTR;
445
1003
      retval = -1;
446
1004
    }
447
1005
  }
448
 
 
449
 
  //shutdown procedure
450
 
 
451
 
  if(debug){
452
 
    fprintf(stderr, "Closing tls session\n");
453
 
  }
454
 
 
455
 
  free(buffer);
456
 
  gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
457
 
 exit:
458
 
  close(tcp_sd);
459
 
  gnutls_deinit (es.session);
460
 
  gnutls_certificate_free_credentials (es.cred);
461
 
  gnutls_global_deinit ();
462
1006
  return retval;
463
1007
}
464
1008
 
465
 
static AvahiSimplePoll *simple_poll = NULL;
466
 
static AvahiServer *server = NULL;
467
 
 
468
 
static void resolve_callback(
469
 
    AvahiSServiceResolver *r,
470
 
    AVAHI_GCC_UNUSED AvahiIfIndex interface,
471
 
    AVAHI_GCC_UNUSED AvahiProtocol protocol,
472
 
    AvahiResolverEvent event,
473
 
    const char *name,
474
 
    const char *type,
475
 
    const char *domain,
476
 
    const char *host_name,
477
 
    const AvahiAddress *address,
478
 
    uint16_t port,
479
 
    AvahiStringList *txt,
480
 
    AvahiLookupResultFlags flags,
481
 
    AVAHI_GCC_UNUSED void* userdata) {
482
 
    
483
 
    assert(r);
484
 
 
485
 
    /* Called whenever a service has been resolved successfully or timed out */
486
 
 
487
 
    switch (event) {
488
 
        case AVAHI_RESOLVER_FAILURE:
489
 
            fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_server_errno(server)));
490
 
            break;
491
 
 
492
 
        case AVAHI_RESOLVER_FOUND: {
493
 
          char ip[AVAHI_ADDRESS_STR_MAX];
494
 
            avahi_address_snprint(ip, sizeof(ip), address);
495
 
            if(debug){
496
 
              fprintf(stderr, "Mandos server found at %s on port %d\n", ip, port);
497
 
            }
498
 
            int ret = start_mandos_communcation(ip, port);
499
 
            if (ret == 0){
500
 
              exit(EXIT_SUCCESS);
501
 
            } else {
502
 
              exit(EXIT_FAILURE);
503
 
            }
504
 
        }
505
 
    }
506
 
    avahi_s_service_resolver_free(r);
507
 
}
508
 
 
509
 
static void browse_callback(
510
 
    AvahiSServiceBrowser *b,
511
 
    AvahiIfIndex interface,
512
 
    AvahiProtocol protocol,
513
 
    AvahiBrowserEvent event,
514
 
    const char *name,
515
 
    const char *type,
516
 
    const char *domain,
517
 
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
518
 
    void* userdata) {
519
 
    
520
 
    AvahiServer *s = userdata;
521
 
    assert(b);
522
 
 
523
 
    /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
524
 
 
525
 
    switch (event) {
526
 
 
527
 
        case AVAHI_BROWSER_FAILURE:
528
 
            
529
 
            fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(server)));
530
 
            avahi_simple_poll_quit(simple_poll);
531
 
            return;
532
 
 
533
 
        case AVAHI_BROWSER_NEW:
534
 
            /* We ignore the returned resolver object. In the callback
535
 
               function we free it. If the server is terminated before
536
 
               the callback function is called the server will free
537
 
               the resolver for us. */
538
 
            
539
 
            if (!(avahi_s_service_resolver_new(s, interface, protocol, name, type, domain, AVAHI_PROTO_INET6, 0, resolve_callback, s)))
540
 
                fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(s)));
541
 
            
542
 
            break;
543
 
 
544
 
        case AVAHI_BROWSER_REMOVE:
545
 
            break;
546
 
 
547
 
        case AVAHI_BROWSER_ALL_FOR_NOW:
548
 
        case AVAHI_BROWSER_CACHE_EXHAUSTED:
549
 
            break;
550
 
    }
551
 
}
552
 
 
553
 
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
 
1009
static void resolve_callback(AvahiSServiceResolver *r,
 
1010
                             AvahiIfIndex interface,
 
1011
                             AvahiProtocol proto,
 
1012
                             AvahiResolverEvent event,
 
1013
                             const char *name,
 
1014
                             const char *type,
 
1015
                             const char *domain,
 
1016
                             const char *host_name,
 
1017
                             const AvahiAddress *address,
 
1018
                             uint16_t port,
 
1019
                             AVAHI_GCC_UNUSED AvahiStringList *txt,
 
1020
                             AVAHI_GCC_UNUSED AvahiLookupResultFlags
 
1021
                             flags,
 
1022
                             AVAHI_GCC_UNUSED void* userdata){
 
1023
  assert(r);
 
1024
  
 
1025
  /* Called whenever a service has been resolved successfully or
 
1026
     timed out */
 
1027
  
 
1028
  if(quit_now){
 
1029
    return;
 
1030
  }
 
1031
  
 
1032
  switch(event){
 
1033
  default:
 
1034
  case AVAHI_RESOLVER_FAILURE:
 
1035
    fprintf(stderr, "Mandos plugin mandos-client: "
 
1036
            "(Avahi Resolver) Failed to resolve service '%s'"
 
1037
            " of type '%s' in domain '%s': %s\n", name, type, domain,
 
1038
            avahi_strerror(avahi_server_errno(mc.server)));
 
1039
    break;
 
1040
    
 
1041
  case AVAHI_RESOLVER_FOUND:
 
1042
    {
 
1043
      char ip[AVAHI_ADDRESS_STR_MAX];
 
1044
      avahi_address_snprint(ip, sizeof(ip), address);
 
1045
      if(debug){
 
1046
        fprintf(stderr, "Mandos plugin mandos-client: "
 
1047
                "Mandos server \"%s\" found on %s (%s, %"
 
1048
                PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
 
1049
                ip, (intmax_t)interface, port);
 
1050
      }
 
1051
      int ret = start_mandos_communication(ip, port, interface,
 
1052
                                           avahi_proto_to_af(proto));
 
1053
      if(ret == 0){
 
1054
        avahi_simple_poll_quit(mc.simple_poll);
 
1055
      } else {
 
1056
        ret = add_server(ip, port, interface,
 
1057
                         avahi_proto_to_af(proto));
 
1058
      }
 
1059
    }
 
1060
  }
 
1061
  avahi_s_service_resolver_free(r);
 
1062
}
 
1063
 
 
1064
static void browse_callback(AvahiSServiceBrowser *b,
 
1065
                            AvahiIfIndex interface,
 
1066
                            AvahiProtocol protocol,
 
1067
                            AvahiBrowserEvent event,
 
1068
                            const char *name,
 
1069
                            const char *type,
 
1070
                            const char *domain,
 
1071
                            AVAHI_GCC_UNUSED AvahiLookupResultFlags
 
1072
                            flags,
 
1073
                            AVAHI_GCC_UNUSED void* userdata){
 
1074
  assert(b);
 
1075
  
 
1076
  /* Called whenever a new services becomes available on the LAN or
 
1077
     is removed from the LAN */
 
1078
  
 
1079
  if(quit_now){
 
1080
    return;
 
1081
  }
 
1082
  
 
1083
  switch(event){
 
1084
  default:
 
1085
  case AVAHI_BROWSER_FAILURE:
 
1086
    
 
1087
    fprintf(stderr, "Mandos plugin mandos-client: "
 
1088
            "(Avahi browser) %s\n",
 
1089
            avahi_strerror(avahi_server_errno(mc.server)));
 
1090
    avahi_simple_poll_quit(mc.simple_poll);
 
1091
    return;
 
1092
    
 
1093
  case AVAHI_BROWSER_NEW:
 
1094
    /* We ignore the returned Avahi resolver object. In the callback
 
1095
       function we free it. If the Avahi server is terminated before
 
1096
       the callback function is called the Avahi server will free the
 
1097
       resolver for us. */
 
1098
    
 
1099
    if(avahi_s_service_resolver_new(mc.server, interface, protocol,
 
1100
                                    name, type, domain, protocol, 0,
 
1101
                                    resolve_callback, NULL) == NULL)
 
1102
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1103
              "Avahi: Failed to resolve service '%s': %s\n",
 
1104
              name, avahi_strerror(avahi_server_errno(mc.server)));
 
1105
    break;
 
1106
    
 
1107
  case AVAHI_BROWSER_REMOVE:
 
1108
    break;
 
1109
    
 
1110
  case AVAHI_BROWSER_ALL_FOR_NOW:
 
1111
  case AVAHI_BROWSER_CACHE_EXHAUSTED:
 
1112
    if(debug){
 
1113
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1114
              "No Mandos server found, still searching...\n");
 
1115
    }
 
1116
    break;
 
1117
  }
 
1118
}
 
1119
 
 
1120
/* Signal handler that stops main loop after SIGTERM */
 
1121
static void handle_sigterm(int sig){
 
1122
  if(quit_now){
 
1123
    return;
 
1124
  }
 
1125
  quit_now = 1;
 
1126
  signal_received = sig;
 
1127
  int old_errno = errno;
 
1128
  /* set main loop to exit */
 
1129
  if(mc.simple_poll != NULL){
 
1130
    avahi_simple_poll_quit(mc.simple_poll);
 
1131
  }
 
1132
  errno = old_errno;
 
1133
}
 
1134
 
 
1135
bool get_flags(const char *ifname, struct ifreq *ifr){
 
1136
  int ret;
 
1137
  
 
1138
  int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
 
1139
  if(s < 0){
 
1140
    perror_plus("socket");
 
1141
    return false;
 
1142
  }
 
1143
  strcpy(ifr->ifr_name, ifname);
 
1144
  ret = ioctl(s, SIOCGIFFLAGS, ifr);
 
1145
  if(ret == -1){
 
1146
    if(debug){
 
1147
      perror_plus("ioctl SIOCGIFFLAGS");
 
1148
    }
 
1149
    return false;
 
1150
  }
 
1151
  return true;
 
1152
}
 
1153
 
 
1154
bool good_flags(const char *ifname, const struct ifreq *ifr){
 
1155
  
 
1156
  /* Reject the loopback device */
 
1157
  if(ifr->ifr_flags & IFF_LOOPBACK){
 
1158
    if(debug){
 
1159
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1160
              "Rejecting loopback interface \"%s\"\n", ifname);
 
1161
    }
 
1162
    return false;
 
1163
  }
 
1164
  /* Accept point-to-point devices only if connect_to is specified */
 
1165
  if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
 
1166
    if(debug){
 
1167
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1168
              "Accepting point-to-point interface \"%s\"\n", ifname);
 
1169
    }
 
1170
    return true;
 
1171
  }
 
1172
  /* Otherwise, reject non-broadcast-capable devices */
 
1173
  if(not (ifr->ifr_flags & IFF_BROADCAST)){
 
1174
    if(debug){
 
1175
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1176
              "Rejecting non-broadcast interface \"%s\"\n", ifname);
 
1177
    }
 
1178
    return false;
 
1179
  }
 
1180
  /* Reject non-ARP interfaces (including dummy interfaces) */
 
1181
  if(ifr->ifr_flags & IFF_NOARP){
 
1182
    if(debug){
 
1183
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1184
              "Rejecting non-ARP interface \"%s\"\n", ifname);
 
1185
    }
 
1186
    return false;
 
1187
  }
 
1188
  
 
1189
  /* Accept this device */
 
1190
  if(debug){
 
1191
    fprintf(stderr, "Mandos plugin mandos-client: "
 
1192
            "Interface \"%s\" is good\n", ifname);
 
1193
  }
 
1194
  return true;
 
1195
}
 
1196
 
 
1197
/* 
 
1198
 * This function determines if a directory entry in /sys/class/net
 
1199
 * corresponds to an acceptable network device.
 
1200
 * (This function is passed to scandir(3) as a filter function.)
 
1201
 */
 
1202
int good_interface(const struct dirent *if_entry){
 
1203
  if(if_entry->d_name[0] == '.'){
 
1204
    return 0;
 
1205
  }
 
1206
  
 
1207
  struct ifreq ifr;
 
1208
  if(not get_flags(if_entry->d_name, &ifr)){
 
1209
    if(debug){
 
1210
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1211
              "Failed to get flags for interface \"%s\"\n",
 
1212
              if_entry->d_name);
 
1213
    }
 
1214
    return 0;
 
1215
  }
 
1216
  
 
1217
  if(not good_flags(if_entry->d_name, &ifr)){
 
1218
    return 0;
 
1219
  }
 
1220
  return 1;
 
1221
}
 
1222
 
 
1223
/* 
 
1224
 * This function determines if a directory entry in /sys/class/net
 
1225
 * corresponds to an acceptable network device which is up.
 
1226
 * (This function is passed to scandir(3) as a filter function.)
 
1227
 */
 
1228
int up_interface(const struct dirent *if_entry){
 
1229
  if(if_entry->d_name[0] == '.'){
 
1230
    return 0;
 
1231
  }
 
1232
  
 
1233
  struct ifreq ifr;
 
1234
  if(not get_flags(if_entry->d_name, &ifr)){
 
1235
    if(debug){
 
1236
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1237
              "Failed to get flags for interface \"%s\"\n",
 
1238
              if_entry->d_name);
 
1239
    }
 
1240
    return 0;
 
1241
  }
 
1242
  
 
1243
  /* Reject down interfaces */
 
1244
  if(not (ifr.ifr_flags & IFF_UP)){
 
1245
    if(debug){
 
1246
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1247
              "Rejecting down interface \"%s\"\n",
 
1248
              if_entry->d_name);
 
1249
    }
 
1250
    return 0;
 
1251
  }
 
1252
  
 
1253
  /* Reject non-running interfaces */
 
1254
  if(not (ifr.ifr_flags & IFF_RUNNING)){
 
1255
    if(debug){
 
1256
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1257
              "Rejecting non-running interface \"%s\"\n",
 
1258
              if_entry->d_name);
 
1259
    }
 
1260
    return 0;
 
1261
  }
 
1262
  
 
1263
  if(not good_flags(if_entry->d_name, &ifr)){
 
1264
    return 0;
 
1265
  }
 
1266
  return 1;
 
1267
}
 
1268
 
 
1269
int notdotentries(const struct dirent *direntry){
 
1270
  /* Skip "." and ".." */
 
1271
  if(direntry->d_name[0] == '.'
 
1272
     and (direntry->d_name[1] == '\0'
 
1273
          or (direntry->d_name[1] == '.'
 
1274
              and direntry->d_name[2] == '\0'))){
 
1275
    return 0;
 
1276
  }
 
1277
  return 1;
 
1278
}
 
1279
 
 
1280
/* Is this directory entry a runnable program? */
 
1281
int runnable_hook(const struct dirent *direntry){
 
1282
  int ret;
 
1283
  size_t sret;
 
1284
  struct stat st;
 
1285
  
 
1286
  if((direntry->d_name)[0] == '\0'){
 
1287
    /* Empty name? */
 
1288
    return 0;
 
1289
  }
 
1290
  
 
1291
  sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
1292
                "abcdefghijklmnopqrstuvwxyz"
 
1293
                "0123456789"
 
1294
                "_-");
 
1295
  if((direntry->d_name)[sret] != '\0'){
 
1296
    /* Contains non-allowed characters */
 
1297
    if(debug){
 
1298
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1299
              "Ignoring hook \"%s\" with bad name\n",
 
1300
              direntry->d_name);
 
1301
    }
 
1302
    return 0;
 
1303
  }
 
1304
  
 
1305
  char *fullname = NULL;
 
1306
  ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
 
1307
  if(ret < 0){
 
1308
    perror_plus("asprintf");
 
1309
    return 0;
 
1310
  }
 
1311
  
 
1312
  ret = stat(fullname, &st);
 
1313
  if(ret == -1){
 
1314
    if(debug){
 
1315
      perror_plus("Could not stat hook");
 
1316
    }
 
1317
    return 0;
 
1318
  }
 
1319
  if(not (S_ISREG(st.st_mode))){
 
1320
    /* Not a regular file */
 
1321
    if(debug){
 
1322
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1323
              "Ignoring hook \"%s\" - not a file\n",
 
1324
              direntry->d_name);
 
1325
    }
 
1326
    return 0;
 
1327
  }
 
1328
  if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
 
1329
    /* Not executable */
 
1330
    if(debug){
 
1331
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1332
              "Ignoring hook \"%s\" - not executable\n",
 
1333
              direntry->d_name);
 
1334
    }
 
1335
    return 0;
 
1336
  }
 
1337
  return 1;
 
1338
}
 
1339
 
 
1340
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
 
1341
  int ret;
 
1342
  struct timespec now;
 
1343
  struct timespec waited_time;
 
1344
  intmax_t block_time;
 
1345
  
 
1346
  while(true){
 
1347
    if(mc.current_server == NULL){
 
1348
      if (debug){
 
1349
        fprintf(stderr, "Mandos plugin mandos-client: "
 
1350
                "Wait until first server is found. No timeout!\n");
 
1351
      }
 
1352
      ret = avahi_simple_poll_iterate(s, -1);
 
1353
    } else {
 
1354
      if (debug){
 
1355
        fprintf(stderr, "Mandos plugin mandos-client: "
 
1356
                "Check current_server if we should run it,"
 
1357
                " or wait\n");
 
1358
      }
 
1359
      /* the current time */
 
1360
      ret = clock_gettime(CLOCK_MONOTONIC, &now);
 
1361
      if(ret == -1){
 
1362
        perror_plus("clock_gettime");
 
1363
        return -1;
 
1364
      }
 
1365
      /* Calculating in ms how long time between now and server
 
1366
         who we visted longest time ago. Now - last seen.  */
 
1367
      waited_time.tv_sec = (now.tv_sec
 
1368
                            - mc.current_server->last_seen.tv_sec);
 
1369
      waited_time.tv_nsec = (now.tv_nsec
 
1370
                             - mc.current_server->last_seen.tv_nsec);
 
1371
      /* total time is 10s/10,000ms.
 
1372
         Converting to s from ms by dividing by 1,000,
 
1373
         and ns to ms by dividing by 1,000,000. */
 
1374
      block_time = ((retry_interval
 
1375
                     - ((intmax_t)waited_time.tv_sec * 1000))
 
1376
                    - ((intmax_t)waited_time.tv_nsec / 1000000));
 
1377
      
 
1378
      if (debug){
 
1379
        fprintf(stderr, "Mandos plugin mandos-client: "
 
1380
                "Blocking for %" PRIdMAX " ms\n", block_time);
 
1381
      }
 
1382
      
 
1383
      if(block_time <= 0){
 
1384
        ret = start_mandos_communication(mc.current_server->ip,
 
1385
                                         mc.current_server->port,
 
1386
                                         mc.current_server->if_index,
 
1387
                                         mc.current_server->af);
 
1388
        if(ret == 0){
 
1389
          avahi_simple_poll_quit(mc.simple_poll);
 
1390
          return 0;
 
1391
        }
 
1392
        ret = clock_gettime(CLOCK_MONOTONIC,
 
1393
                            &mc.current_server->last_seen);
 
1394
        if(ret == -1){
 
1395
          perror_plus("clock_gettime");
 
1396
          return -1;
 
1397
        }
 
1398
        mc.current_server = mc.current_server->next;
 
1399
        block_time = 0;         /* Call avahi to find new Mandos
 
1400
                                   servers, but don't block */
 
1401
      }
 
1402
      
 
1403
      ret = avahi_simple_poll_iterate(s, (int)block_time);
 
1404
    }
 
1405
    if(ret != 0){
 
1406
      if (ret > 0 or errno != EINTR){
 
1407
        return (ret != 1) ? ret : 0;
 
1408
      }
 
1409
    }
 
1410
  }
 
1411
}
 
1412
 
 
1413
int main(int argc, char *argv[]){
 
1414
  AvahiSServiceBrowser *sb = NULL;
 
1415
  int error;
 
1416
  int ret;
 
1417
  intmax_t tmpmax;
 
1418
  char *tmp;
 
1419
  int exitcode = EXIT_SUCCESS;
 
1420
  const char *interface = "";
 
1421
  struct ifreq network;
 
1422
  int sd = -1;
 
1423
  bool take_down_interface = false;
 
1424
  uid_t uid;
 
1425
  gid_t gid;
 
1426
  char tempdir[] = "/tmp/mandosXXXXXX";
 
1427
  bool tempdir_created = false;
 
1428
  AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
 
1429
  const char *seckey = PATHDIR "/" SECKEY;
 
1430
  const char *pubkey = PATHDIR "/" PUBKEY;
 
1431
  
 
1432
  bool gnutls_initialized = false;
 
1433
  bool gpgme_initialized = false;
 
1434
  float delay = 2.5f;
 
1435
  double retry_interval = 10; /* 10s between trying a server and
 
1436
                                 retrying the same server again */
 
1437
  
 
1438
  struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
 
1439
  struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
 
1440
  
 
1441
  uid = getuid();
 
1442
  gid = getgid();
 
1443
  
 
1444
  /* Lower any group privileges we might have, just to be safe */
 
1445
  errno = 0;
 
1446
  ret = setgid(gid);
 
1447
  if(ret == -1){
 
1448
    perror_plus("setgid");
 
1449
  }
 
1450
  
 
1451
  /* Lower user privileges (temporarily) */
 
1452
  errno = 0;
 
1453
  ret = seteuid(uid);
 
1454
  if(ret == -1){
 
1455
    perror_plus("seteuid");
 
1456
  }
 
1457
  
 
1458
  if(quit_now){
 
1459
    goto end;
 
1460
  }
 
1461
  
 
1462
  {
 
1463
    struct argp_option options[] = {
 
1464
      { .name = "debug", .key = 128,
 
1465
        .doc = "Debug mode", .group = 3 },
 
1466
      { .name = "connect", .key = 'c',
 
1467
        .arg = "ADDRESS:PORT",
 
1468
        .doc = "Connect directly to a specific Mandos server",
 
1469
        .group = 1 },
 
1470
      { .name = "interface", .key = 'i',
 
1471
        .arg = "NAME",
 
1472
        .doc = "Network interface that will be used to search for"
 
1473
        " Mandos servers",
 
1474
        .group = 1 },
 
1475
      { .name = "seckey", .key = 's',
 
1476
        .arg = "FILE",
 
1477
        .doc = "OpenPGP secret key file base name",
 
1478
        .group = 1 },
 
1479
      { .name = "pubkey", .key = 'p',
 
1480
        .arg = "FILE",
 
1481
        .doc = "OpenPGP public key file base name",
 
1482
        .group = 2 },
 
1483
      { .name = "dh-bits", .key = 129,
 
1484
        .arg = "BITS",
 
1485
        .doc = "Bit length of the prime number used in the"
 
1486
        " Diffie-Hellman key exchange",
 
1487
        .group = 2 },
 
1488
      { .name = "priority", .key = 130,
 
1489
        .arg = "STRING",
 
1490
        .doc = "GnuTLS priority string for the TLS handshake",
 
1491
        .group = 1 },
 
1492
      { .name = "delay", .key = 131,
 
1493
        .arg = "SECONDS",
 
1494
        .doc = "Maximum delay to wait for interface startup",
 
1495
        .group = 2 },
 
1496
      { .name = "retry", .key = 132,
 
1497
        .arg = "SECONDS",
 
1498
        .doc = "Retry interval used when denied by the mandos server",
 
1499
        .group = 2 },
 
1500
      { .name = "network-hook-dir", .key = 133,
 
1501
        .arg = "DIR",
 
1502
        .doc = "Directory where network hooks are located",
 
1503
        .group = 2 },
 
1504
      /*
 
1505
       * These reproduce what we would get without ARGP_NO_HELP
 
1506
       */
 
1507
      { .name = "help", .key = '?',
 
1508
        .doc = "Give this help list", .group = -1 },
 
1509
      { .name = "usage", .key = -3,
 
1510
        .doc = "Give a short usage message", .group = -1 },
 
1511
      { .name = "version", .key = 'V',
 
1512
        .doc = "Print program version", .group = -1 },
 
1513
      { .name = NULL }
 
1514
    };
 
1515
    
 
1516
    error_t parse_opt(int key, char *arg,
 
1517
                      struct argp_state *state){
 
1518
      errno = 0;
 
1519
      switch(key){
 
1520
      case 128:                 /* --debug */
 
1521
        debug = true;
 
1522
        break;
 
1523
      case 'c':                 /* --connect */
 
1524
        connect_to = arg;
 
1525
        break;
 
1526
      case 'i':                 /* --interface */
 
1527
        interface = arg;
 
1528
        break;
 
1529
      case 's':                 /* --seckey */
 
1530
        seckey = arg;
 
1531
        break;
 
1532
      case 'p':                 /* --pubkey */
 
1533
        pubkey = arg;
 
1534
        break;
 
1535
      case 129:                 /* --dh-bits */
 
1536
        errno = 0;
 
1537
        tmpmax = strtoimax(arg, &tmp, 10);
 
1538
        if(errno != 0 or tmp == arg or *tmp != '\0'
 
1539
           or tmpmax != (typeof(mc.dh_bits))tmpmax){
 
1540
          argp_error(state, "Bad number of DH bits");
 
1541
        }
 
1542
        mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
 
1543
        break;
 
1544
      case 130:                 /* --priority */
 
1545
        mc.priority = arg;
 
1546
        break;
 
1547
      case 131:                 /* --delay */
 
1548
        errno = 0;
 
1549
        delay = strtof(arg, &tmp);
 
1550
        if(errno != 0 or tmp == arg or *tmp != '\0'){
 
1551
          argp_error(state, "Bad delay");
 
1552
        }
 
1553
      case 132:                 /* --retry */
 
1554
        errno = 0;
 
1555
        retry_interval = strtod(arg, &tmp);
 
1556
        if(errno != 0 or tmp == arg or *tmp != '\0'
 
1557
           or (retry_interval * 1000) > INT_MAX
 
1558
           or retry_interval < 0){
 
1559
          argp_error(state, "Bad retry interval");
 
1560
        }
 
1561
        break;
 
1562
      case 133:                 /* --network-hook-dir */
 
1563
        hookdir = arg;
 
1564
        break;
 
1565
        /*
 
1566
         * These reproduce what we would get without ARGP_NO_HELP
 
1567
         */
 
1568
      case '?':                 /* --help */
 
1569
        argp_state_help(state, state->out_stream,
 
1570
                        (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
 
1571
                        & ~(unsigned int)ARGP_HELP_EXIT_OK);
 
1572
      case -3:                  /* --usage */
 
1573
        argp_state_help(state, state->out_stream,
 
1574
                        ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
 
1575
      case 'V':                 /* --version */
 
1576
        fprintf(state->out_stream, "Mandos plugin mandos-client: ");
 
1577
        fprintf(state->out_stream, "%s\n", argp_program_version);
 
1578
        exit(argp_err_exit_status);
 
1579
        break;
 
1580
      default:
 
1581
        return ARGP_ERR_UNKNOWN;
 
1582
      }
 
1583
      return errno;
 
1584
    }
 
1585
    
 
1586
    struct argp argp = { .options = options, .parser = parse_opt,
 
1587
                         .args_doc = "",
 
1588
                         .doc = "Mandos client -- Get and decrypt"
 
1589
                         " passwords from a Mandos server" };
 
1590
    ret = argp_parse(&argp, argc, argv,
 
1591
                     ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
 
1592
    switch(ret){
 
1593
    case 0:
 
1594
      break;
 
1595
    case ENOMEM:
 
1596
    default:
 
1597
      errno = ret;
 
1598
      perror_plus("argp_parse");
 
1599
      exitcode = EX_OSERR;
 
1600
      goto end;
 
1601
    case EINVAL:
 
1602
      exitcode = EX_USAGE;
 
1603
      goto end;
 
1604
    }
 
1605
  }
 
1606
    
 
1607
  {
 
1608
    /* Work around Debian bug #633582:
 
1609
       <http://bugs.debian.org/633582> */
 
1610
    struct stat st;
 
1611
    
 
1612
    /* Re-raise priviliges */
 
1613
    errno = 0;
 
1614
    ret = seteuid(0);
 
1615
    if(ret == -1){
 
1616
      perror_plus("seteuid");
 
1617
    }
 
1618
    
 
1619
    if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
 
1620
      int seckey_fd = open(seckey, O_RDONLY);
 
1621
      if(seckey_fd == -1){
 
1622
        perror_plus("open");
 
1623
      } else {
 
1624
        ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
 
1625
        if(ret == -1){
 
1626
          perror_plus("fstat");
 
1627
        } else {
 
1628
          if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
 
1629
            ret = fchown(seckey_fd, uid, gid);
 
1630
            if(ret == -1){
 
1631
              perror_plus("fchown");
 
1632
            }
 
1633
          }
 
1634
        }
 
1635
        TEMP_FAILURE_RETRY(close(seckey_fd));
 
1636
      }
 
1637
    }
 
1638
    
 
1639
    if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
 
1640
      int pubkey_fd = open(pubkey, O_RDONLY);
 
1641
      if(pubkey_fd == -1){
 
1642
        perror_plus("open");
 
1643
      } else {
 
1644
        ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
 
1645
        if(ret == -1){
 
1646
          perror_plus("fstat");
 
1647
        } else {
 
1648
          if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
 
1649
            ret = fchown(pubkey_fd, uid, gid);
 
1650
            if(ret == -1){
 
1651
              perror_plus("fchown");
 
1652
            }
 
1653
          }
 
1654
        }
 
1655
        TEMP_FAILURE_RETRY(close(pubkey_fd));
 
1656
      }
 
1657
    }
 
1658
    
 
1659
    /* Lower privileges */
 
1660
    errno = 0;
 
1661
    ret = seteuid(uid);
 
1662
    if(ret == -1){
 
1663
      perror_plus("seteuid");
 
1664
    }
 
1665
  }
 
1666
  
 
1667
  /* Find network hooks and run them */
 
1668
  {
 
1669
    struct dirent **direntries;
 
1670
    struct dirent *direntry;
 
1671
    int numhooks = scandir(hookdir, &direntries, runnable_hook,
 
1672
                           alphasort);
 
1673
    if(numhooks == -1){
 
1674
      perror_plus("scandir");
 
1675
    } else {
 
1676
      int devnull = open("/dev/null", O_RDONLY);
 
1677
      for(int i = 0; i < numhooks; i++){
 
1678
        direntry = direntries[0];
 
1679
        char *fullname = NULL;
 
1680
        ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
 
1681
        if(ret < 0){
 
1682
          perror_plus("asprintf");
 
1683
          continue;
 
1684
        }
 
1685
        pid_t hook_pid = fork();
 
1686
        if(hook_pid == 0){
 
1687
          /* Child */
 
1688
          dup2(devnull, STDIN_FILENO);
 
1689
          close(devnull);
 
1690
          dup2(STDERR_FILENO, STDOUT_FILENO);
 
1691
          ret = setenv("DEVICE", interface, 1);
 
1692
          if(ret == -1){
 
1693
            perror_plus("setenv");
 
1694
            exit(1);
 
1695
          }
 
1696
          ret = setenv("VERBOSE", debug ? "1" : "0", 1);
 
1697
          if(ret == -1){
 
1698
            perror_plus("setenv");
 
1699
            exit(1);
 
1700
          }
 
1701
          ret = setenv("MODE", "start", 1);
 
1702
          if(ret == -1){
 
1703
            perror_plus("setenv");
 
1704
            exit(1);
 
1705
          }
 
1706
          char *delaystring;
 
1707
          ret = asprintf(&delaystring, "%f", delay);
 
1708
          if(ret == -1){
 
1709
            perror_plus("asprintf");
 
1710
            exit(1);
 
1711
          }
 
1712
          ret = setenv("DELAY", delaystring, 1);
 
1713
          if(ret == -1){
 
1714
            free(delaystring);
 
1715
            perror_plus("setenv");
 
1716
            exit(1);
 
1717
          }
 
1718
          free(delaystring);
 
1719
          ret = execl(fullname, direntry->d_name, "start", NULL);
 
1720
          perror_plus("execl");
 
1721
        } else {
 
1722
          int status;
 
1723
          if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
 
1724
            perror_plus("waitpid");
 
1725
            free(fullname);
 
1726
            continue;
 
1727
          }
 
1728
          if(WIFEXITED(status)){
 
1729
            if(WEXITSTATUS(status) != 0){
 
1730
              fprintf(stderr, "Mandos plugin mandos-client: "
 
1731
                      "Warning: network hook \"%s\" exited"
 
1732
                      " with status %d\n", direntry->d_name,
 
1733
                      WEXITSTATUS(status));
 
1734
              free(fullname);
 
1735
              continue;
 
1736
            }
 
1737
          } else if(WIFSIGNALED(status)){
 
1738
            fprintf(stderr, "Mandos plugin mandos-client: "
 
1739
                    "Warning: network hook \"%s\" died by"
 
1740
                    " signal %d\n", direntry->d_name,
 
1741
                    WTERMSIG(status));
 
1742
            free(fullname);
 
1743
            continue;
 
1744
          } else {
 
1745
            fprintf(stderr, "Mandos plugin mandos-client: "
 
1746
                    "Warning: network hook \"%s\" crashed\n",
 
1747
                    direntry->d_name);
 
1748
            free(fullname);
 
1749
            continue;
 
1750
          }
 
1751
        }
 
1752
        free(fullname);
 
1753
        if(quit_now){
 
1754
          goto end;
 
1755
        }
 
1756
      }
 
1757
      close(devnull);
 
1758
    }
 
1759
  }
 
1760
  
 
1761
  if(not debug){
 
1762
    avahi_set_log_function(empty_log);
 
1763
  }
 
1764
  
 
1765
  if(interface[0] == '\0'){
 
1766
    struct dirent **direntries;
 
1767
    /* First look for interfaces that are up */
 
1768
    ret = scandir(sys_class_net, &direntries, up_interface,
 
1769
                  alphasort);
 
1770
    if(ret == 0){
 
1771
      /* No up interfaces, look for any good interfaces */
 
1772
      free(direntries);
 
1773
      ret = scandir(sys_class_net, &direntries, good_interface,
 
1774
                    alphasort);
 
1775
    }
 
1776
    if(ret >= 1){
 
1777
      /* Pick the first interface returned */
 
1778
      interface = strdup(direntries[0]->d_name);
 
1779
      if(debug){
 
1780
        fprintf(stderr, "Mandos plugin mandos-client: "
 
1781
                "Using interface \"%s\"\n", interface);
 
1782
      }
 
1783
      if(interface == NULL){
 
1784
        perror_plus("malloc");
 
1785
        free(direntries);
 
1786
        exitcode = EXIT_FAILURE;
 
1787
        goto end;
 
1788
      }
 
1789
      free(direntries);
 
1790
    } else {
 
1791
      free(direntries);
 
1792
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1793
              "Could not find a network interface\n");
 
1794
      exitcode = EXIT_FAILURE;
 
1795
      goto end;
 
1796
    }
 
1797
  }
 
1798
  
 
1799
  /* Initialize Avahi early so avahi_simple_poll_quit() can be called
 
1800
     from the signal handler */
 
1801
  /* Initialize the pseudo-RNG for Avahi */
 
1802
  srand((unsigned int) time(NULL));
 
1803
  mc.simple_poll = avahi_simple_poll_new();
 
1804
  if(mc.simple_poll == NULL){
 
1805
    fprintf(stderr, "Mandos plugin mandos-client: "
 
1806
            "Avahi: Failed to create simple poll object.\n");
 
1807
    exitcode = EX_UNAVAILABLE;
 
1808
    goto end;
 
1809
  }
 
1810
  
 
1811
  sigemptyset(&sigterm_action.sa_mask);
 
1812
  ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
 
1813
  if(ret == -1){
 
1814
    perror_plus("sigaddset");
 
1815
    exitcode = EX_OSERR;
 
1816
    goto end;
 
1817
  }
 
1818
  ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
 
1819
  if(ret == -1){
 
1820
    perror_plus("sigaddset");
 
1821
    exitcode = EX_OSERR;
 
1822
    goto end;
 
1823
  }
 
1824
  ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
 
1825
  if(ret == -1){
 
1826
    perror_plus("sigaddset");
 
1827
    exitcode = EX_OSERR;
 
1828
    goto end;
 
1829
  }
 
1830
  /* Need to check if the handler is SIG_IGN before handling:
 
1831
     | [[info:libc:Initial Signal Actions]] |
 
1832
     | [[info:libc:Basic Signal Handling]]  |
 
1833
  */
 
1834
  ret = sigaction(SIGINT, NULL, &old_sigterm_action);
 
1835
  if(ret == -1){
 
1836
    perror_plus("sigaction");
 
1837
    return EX_OSERR;
 
1838
  }
 
1839
  if(old_sigterm_action.sa_handler != SIG_IGN){
 
1840
    ret = sigaction(SIGINT, &sigterm_action, NULL);
 
1841
    if(ret == -1){
 
1842
      perror_plus("sigaction");
 
1843
      exitcode = EX_OSERR;
 
1844
      goto end;
 
1845
    }
 
1846
  }
 
1847
  ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
 
1848
  if(ret == -1){
 
1849
    perror_plus("sigaction");
 
1850
    return EX_OSERR;
 
1851
  }
 
1852
  if(old_sigterm_action.sa_handler != SIG_IGN){
 
1853
    ret = sigaction(SIGHUP, &sigterm_action, NULL);
 
1854
    if(ret == -1){
 
1855
      perror_plus("sigaction");
 
1856
      exitcode = EX_OSERR;
 
1857
      goto end;
 
1858
    }
 
1859
  }
 
1860
  ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
 
1861
  if(ret == -1){
 
1862
    perror_plus("sigaction");
 
1863
    return EX_OSERR;
 
1864
  }
 
1865
  if(old_sigterm_action.sa_handler != SIG_IGN){
 
1866
    ret = sigaction(SIGTERM, &sigterm_action, NULL);
 
1867
    if(ret == -1){
 
1868
      perror_plus("sigaction");
 
1869
      exitcode = EX_OSERR;
 
1870
      goto end;
 
1871
    }
 
1872
  }
 
1873
  
 
1874
  /* If the interface is down, bring it up */
 
1875
  if(strcmp(interface, "none") != 0){
 
1876
    if_index = (AvahiIfIndex) if_nametoindex(interface);
 
1877
    if(if_index == 0){
 
1878
      fprintf(stderr, "Mandos plugin mandos-client: "
 
1879
              "No such interface: \"%s\"\n", interface);
 
1880
      exitcode = EX_UNAVAILABLE;
 
1881
      goto end;
 
1882
    }
 
1883
    
 
1884
    if(quit_now){
 
1885
      goto end;
 
1886
    }
 
1887
    
 
1888
    /* Re-raise priviliges */
 
1889
    errno = 0;
 
1890
    ret = seteuid(0);
 
1891
    if(ret == -1){
 
1892
      perror_plus("seteuid");
 
1893
    }
 
1894
    
 
1895
#ifdef __linux__
 
1896
    /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
 
1897
       messages about the network interface to mess up the prompt */
 
1898
    ret = klogctl(8, NULL, 5);
 
1899
    bool restore_loglevel = true;
 
1900
    if(ret == -1){
 
1901
      restore_loglevel = false;
 
1902
      perror_plus("klogctl");
 
1903
    }
 
1904
#endif  /* __linux__ */
 
1905
    
 
1906
    sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
 
1907
    if(sd < 0){
 
1908
      perror_plus("socket");
 
1909
      exitcode = EX_OSERR;
 
1910
#ifdef __linux__
 
1911
      if(restore_loglevel){
 
1912
        ret = klogctl(7, NULL, 0);
 
1913
        if(ret == -1){
 
1914
          perror_plus("klogctl");
 
1915
        }
 
1916
      }
 
1917
#endif  /* __linux__ */
 
1918
      /* Lower privileges */
 
1919
      errno = 0;
 
1920
      ret = seteuid(uid);
 
1921
      if(ret == -1){
 
1922
        perror_plus("seteuid");
 
1923
      }
 
1924
      goto end;
 
1925
    }
 
1926
    strcpy(network.ifr_name, interface);
 
1927
    ret = ioctl(sd, SIOCGIFFLAGS, &network);
 
1928
    if(ret == -1){
 
1929
      perror_plus("ioctl SIOCGIFFLAGS");
 
1930
#ifdef __linux__
 
1931
      if(restore_loglevel){
 
1932
        ret = klogctl(7, NULL, 0);
 
1933
        if(ret == -1){
 
1934
          perror_plus("klogctl");
 
1935
        }
 
1936
      }
 
1937
#endif  /* __linux__ */
 
1938
      exitcode = EX_OSERR;
 
1939
      /* Lower privileges */
 
1940
      errno = 0;
 
1941
      ret = seteuid(uid);
 
1942
      if(ret == -1){
 
1943
        perror_plus("seteuid");
 
1944
      }
 
1945
      goto end;
 
1946
    }
 
1947
    if((network.ifr_flags & IFF_UP) == 0){
 
1948
      network.ifr_flags |= IFF_UP;
 
1949
      take_down_interface = true;
 
1950
      ret = ioctl(sd, SIOCSIFFLAGS, &network);
 
1951
      if(ret == -1){
 
1952
        take_down_interface = false;
 
1953
        perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
 
1954
        exitcode = EX_OSERR;
 
1955
#ifdef __linux__
 
1956
        if(restore_loglevel){
 
1957
          ret = klogctl(7, NULL, 0);
 
1958
          if(ret == -1){
 
1959
            perror_plus("klogctl");
 
1960
          }
 
1961
        }
 
1962
#endif  /* __linux__ */
 
1963
        /* Lower privileges */
 
1964
        errno = 0;
 
1965
        ret = seteuid(uid);
 
1966
        if(ret == -1){
 
1967
          perror_plus("seteuid");
 
1968
        }
 
1969
        goto end;
 
1970
      }
 
1971
    }
 
1972
    /* Sleep checking until interface is running.
 
1973
       Check every 0.25s, up to total time of delay */
 
1974
    for(int i=0; i < delay * 4; i++){
 
1975
      ret = ioctl(sd, SIOCGIFFLAGS, &network);
 
1976
      if(ret == -1){
 
1977
        perror_plus("ioctl SIOCGIFFLAGS");
 
1978
      } else if(network.ifr_flags & IFF_RUNNING){
 
1979
        break;
 
1980
      }
 
1981
      struct timespec sleeptime = { .tv_nsec = 250000000 };
 
1982
      ret = nanosleep(&sleeptime, NULL);
 
1983
      if(ret == -1 and errno != EINTR){
 
1984
        perror_plus("nanosleep");
 
1985
      }
 
1986
    }
 
1987
    if(not take_down_interface){
 
1988
      /* We won't need the socket anymore */
 
1989
      ret = (int)TEMP_FAILURE_RETRY(close(sd));
 
1990
      if(ret == -1){
 
1991
        perror_plus("close");
 
1992
      }
 
1993
    }
 
1994
#ifdef __linux__
 
1995
    if(restore_loglevel){
 
1996
      /* Restores kernel loglevel to default */
 
1997
      ret = klogctl(7, NULL, 0);
 
1998
      if(ret == -1){
 
1999
        perror_plus("klogctl");
 
2000
      }
 
2001
    }
 
2002
#endif  /* __linux__ */
 
2003
    /* Lower privileges */
 
2004
    errno = 0;
 
2005
    if(take_down_interface){
 
2006
      /* Lower privileges */
 
2007
      ret = seteuid(uid);
 
2008
      if(ret == -1){
 
2009
        perror_plus("seteuid");
 
2010
      }
 
2011
    } else {
 
2012
      /* Lower privileges permanently */
 
2013
      ret = setuid(uid);
 
2014
      if(ret == -1){
 
2015
        perror_plus("setuid");
 
2016
      }
 
2017
    }
 
2018
  }
 
2019
  
 
2020
  if(quit_now){
 
2021
    goto end;
 
2022
  }
 
2023
  
 
2024
  ret = init_gnutls_global(pubkey, seckey);
 
2025
  if(ret == -1){
 
2026
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2027
            "init_gnutls_global failed\n");
 
2028
    exitcode = EX_UNAVAILABLE;
 
2029
    goto end;
 
2030
  } else {
 
2031
    gnutls_initialized = true;
 
2032
  }
 
2033
  
 
2034
  if(quit_now){
 
2035
    goto end;
 
2036
  }
 
2037
  
 
2038
  if(mkdtemp(tempdir) == NULL){
 
2039
    perror_plus("mkdtemp");
 
2040
    goto end;
 
2041
  }
 
2042
  tempdir_created = true;
 
2043
  
 
2044
  if(quit_now){
 
2045
    goto end;
 
2046
  }
 
2047
  
 
2048
  if(not init_gpgme(pubkey, seckey, tempdir)){
 
2049
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2050
            "init_gpgme failed\n");
 
2051
    exitcode = EX_UNAVAILABLE;
 
2052
    goto end;
 
2053
  } else {
 
2054
    gpgme_initialized = true;
 
2055
  }
 
2056
  
 
2057
  if(quit_now){
 
2058
    goto end;
 
2059
  }
 
2060
  
 
2061
  if(connect_to != NULL){
 
2062
    /* Connect directly, do not use Zeroconf */
 
2063
    /* (Mainly meant for debugging) */
 
2064
    char *address = strrchr(connect_to, ':');
 
2065
    if(address == NULL){
 
2066
      fprintf(stderr, "Mandos plugin mandos-client: "
 
2067
              "No colon in address\n");
 
2068
      exitcode = EX_USAGE;
 
2069
      goto end;
 
2070
    }
 
2071
    
 
2072
    if(quit_now){
 
2073
      goto end;
 
2074
    }
 
2075
    
 
2076
    uint16_t port;
 
2077
    errno = 0;
 
2078
    tmpmax = strtoimax(address+1, &tmp, 10);
 
2079
    if(errno != 0 or tmp == address+1 or *tmp != '\0'
 
2080
       or tmpmax != (uint16_t)tmpmax){
 
2081
      fprintf(stderr, "Mandos plugin mandos-client: "
 
2082
              "Bad port number\n");
 
2083
      exitcode = EX_USAGE;
 
2084
      goto end;
 
2085
    }
 
2086
  
 
2087
    if(quit_now){
 
2088
      goto end;
 
2089
    }
 
2090
    
 
2091
    port = (uint16_t)tmpmax;
 
2092
    *address = '\0';
 
2093
    /* Colon in address indicates IPv6 */
 
2094
    int af;
 
2095
    if(strchr(connect_to, ':') != NULL){
 
2096
      af = AF_INET6;
 
2097
      /* Accept [] around IPv6 address - see RFC 5952 */
 
2098
      if(connect_to[0] == '[' and address[-1] == ']')
 
2099
        {
 
2100
          connect_to++;
 
2101
          address[-1] = '\0';
 
2102
        }
 
2103
    } else {
 
2104
      af = AF_INET;
 
2105
    }
 
2106
    address = connect_to;
 
2107
    
 
2108
    if(quit_now){
 
2109
      goto end;
 
2110
    }
 
2111
    
 
2112
    while(not quit_now){
 
2113
      ret = start_mandos_communication(address, port, if_index, af);
 
2114
      if(quit_now or ret == 0){
 
2115
        break;
 
2116
      }
 
2117
      if(debug){
 
2118
        fprintf(stderr, "Mandos plugin mandos-client: "
 
2119
                "Retrying in %d seconds\n", (int)retry_interval);
 
2120
      }
 
2121
      sleep((int)retry_interval);
 
2122
    }
 
2123
    
 
2124
    if (not quit_now){
 
2125
      exitcode = EXIT_SUCCESS;
 
2126
    }
 
2127
    
 
2128
    goto end;
 
2129
  }
 
2130
  
 
2131
  if(quit_now){
 
2132
    goto end;
 
2133
  }
 
2134
  
 
2135
  {
554
2136
    AvahiServerConfig config;
555
 
    AvahiSServiceBrowser *sb = NULL;
556
 
    const char db[] = "--debug";
557
 
    int error;
558
 
    int ret = 1;
559
 
    int returncode = EXIT_SUCCESS;
560
 
    char *basename = rindex(argv[0], '/');
561
 
    if(basename == NULL){
562
 
      basename = argv[0];
563
 
    } else {
564
 
      basename++;
565
 
    }
566
 
    
567
 
    char *program_name = malloc(strlen(basename) + sizeof(db));
568
 
 
569
 
    if (program_name == NULL){
570
 
      perror("argv[0]");
571
 
      return EXIT_FAILURE;
572
 
    }
573
 
    
574
 
    program_name[0] = '\0';
575
 
    
576
 
    for (int i = 1; i < argc; i++){
577
 
      if (not strncmp(argv[i], db, 5)){
578
 
          strcat(strcat(strcat(program_name, db ), "="), basename);
579
 
          if(not strcmp(argv[i], db) or not strcmp(argv[i], program_name)){
580
 
            debug = true;
581
 
          }
582
 
        }
583
 
    }
584
 
    free(program_name);
585
 
 
586
 
    if (not debug){
587
 
      avahi_set_log_function(empty_log);
588
 
    }
589
 
    
590
 
    /* Initialize the psuedo-RNG */
591
 
    srand(time(NULL));
592
 
 
593
 
    /* Allocate main loop object */
594
 
    if (!(simple_poll = avahi_simple_poll_new())) {
595
 
        fprintf(stderr, "Failed to create simple poll object.\n");
596
 
        
597
 
        goto exit;
598
 
    }
599
 
 
600
 
    /* Do not publish any local records */
 
2137
    /* Do not publish any local Zeroconf records */
601
2138
    avahi_server_config_init(&config);
602
2139
    config.publish_hinfo = 0;
603
2140
    config.publish_addresses = 0;
604
2141
    config.publish_workstation = 0;
605
2142
    config.publish_domain = 0;
606
 
 
 
2143
    
607
2144
    /* Allocate a new server */
608
 
    server = avahi_server_new(avahi_simple_poll_get(simple_poll), &config, NULL, NULL, &error);
609
 
 
610
 
    /* Free the configuration data */
 
2145
    mc.server = avahi_server_new(avahi_simple_poll_get
 
2146
                                 (mc.simple_poll), &config, NULL,
 
2147
                                 NULL, &error);
 
2148
    
 
2149
    /* Free the Avahi configuration data */
611
2150
    avahi_server_config_free(&config);
612
 
 
613
 
    /* Check if creating the server object succeeded */
614
 
    if (!server) {
615
 
        fprintf(stderr, "Failed to create server: %s\n", avahi_strerror(error));
616
 
        returncode = EXIT_FAILURE;
617
 
        goto exit;
618
 
    }
619
 
    
620
 
    /* Create the service browser */
621
 
    if (!(sb = avahi_s_service_browser_new(server, if_nametoindex("eth0"), AVAHI_PROTO_INET6, "_mandos._tcp", NULL, 0, browse_callback, server))) {
622
 
        fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_server_errno(server)));
623
 
        returncode = EXIT_FAILURE;
624
 
        goto exit;
625
 
    }
626
 
    
627
 
    /* Run the main loop */
628
 
 
629
 
    if (debug){
630
 
      fprintf(stderr, "Starting avahi loop search\n");
631
 
    }
632
 
    
633
 
    avahi_simple_poll_loop(simple_poll);
634
 
    
635
 
exit:
636
 
 
637
 
    if (debug){
638
 
      fprintf(stderr, "%s exiting\n", argv[0]);
639
 
    }
640
 
    
641
 
    /* Cleanup things */
642
 
    if (sb)
643
 
        avahi_s_service_browser_free(sb);
644
 
    
645
 
    if (server)
646
 
        avahi_server_free(server);
647
 
 
648
 
    if (simple_poll)
649
 
        avahi_simple_poll_free(simple_poll);
650
 
 
651
 
    return ret;
 
2151
  }
 
2152
  
 
2153
  /* Check if creating the Avahi server object succeeded */
 
2154
  if(mc.server == NULL){
 
2155
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2156
            "Failed to create Avahi server: %s\n",
 
2157
            avahi_strerror(error));
 
2158
    exitcode = EX_UNAVAILABLE;
 
2159
    goto end;
 
2160
  }
 
2161
  
 
2162
  if(quit_now){
 
2163
    goto end;
 
2164
  }
 
2165
  
 
2166
  /* Create the Avahi service browser */
 
2167
  sb = avahi_s_service_browser_new(mc.server, if_index,
 
2168
                                   AVAHI_PROTO_UNSPEC, "_mandos._tcp",
 
2169
                                   NULL, 0, browse_callback, NULL);
 
2170
  if(sb == NULL){
 
2171
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2172
            "Failed to create service browser: %s\n",
 
2173
            avahi_strerror(avahi_server_errno(mc.server)));
 
2174
    exitcode = EX_UNAVAILABLE;
 
2175
    goto end;
 
2176
  }
 
2177
  
 
2178
  if(quit_now){
 
2179
    goto end;
 
2180
  }
 
2181
  
 
2182
  /* Run the main loop */
 
2183
  
 
2184
  if(debug){
 
2185
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2186
            "Starting Avahi loop search\n");
 
2187
  }
 
2188
 
 
2189
  ret = avahi_loop_with_timeout(mc.simple_poll,
 
2190
                                (int)(retry_interval * 1000));
 
2191
  if(debug){
 
2192
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2193
            "avahi_loop_with_timeout exited %s\n",
 
2194
            (ret == 0) ? "successfully" : "with error");
 
2195
  }
 
2196
  
 
2197
 end:
 
2198
  
 
2199
  if(debug){
 
2200
    fprintf(stderr, "Mandos plugin mandos-client: "
 
2201
            "%s exiting\n", argv[0]);
 
2202
  }
 
2203
  
 
2204
  /* Cleanup things */
 
2205
  if(sb != NULL)
 
2206
    avahi_s_service_browser_free(sb);
 
2207
  
 
2208
  if(mc.server != NULL)
 
2209
    avahi_server_free(mc.server);
 
2210
  
 
2211
  if(mc.simple_poll != NULL)
 
2212
    avahi_simple_poll_free(mc.simple_poll);
 
2213
  
 
2214
  if(gnutls_initialized){
 
2215
    gnutls_certificate_free_credentials(mc.cred);
 
2216
    gnutls_global_deinit();
 
2217
    gnutls_dh_params_deinit(mc.dh_params);
 
2218
  }
 
2219
  
 
2220
  if(gpgme_initialized){
 
2221
    gpgme_release(mc.ctx);
 
2222
  }
 
2223
 
 
2224
  /* Cleans up the circular linked list of Mandos servers the client
 
2225
     has seen */
 
2226
  if(mc.current_server != NULL){
 
2227
    mc.current_server->prev->next = NULL;
 
2228
    while(mc.current_server != NULL){
 
2229
      server *next = mc.current_server->next;
 
2230
      free(mc.current_server);
 
2231
      mc.current_server = next;
 
2232
    }
 
2233
  }
 
2234
  
 
2235
  /* XXX run network hooks "stop" here  */
 
2236
  
 
2237
  /* Take down the network interface */
 
2238
  if(take_down_interface){
 
2239
    /* Re-raise priviliges */
 
2240
    errno = 0;
 
2241
    ret = seteuid(0);
 
2242
    if(ret == -1){
 
2243
      perror_plus("seteuid");
 
2244
    }
 
2245
    if(geteuid() == 0){
 
2246
      ret = ioctl(sd, SIOCGIFFLAGS, &network);
 
2247
      if(ret == -1){
 
2248
        perror_plus("ioctl SIOCGIFFLAGS");
 
2249
      } else if(network.ifr_flags & IFF_UP){
 
2250
        network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
 
2251
        ret = ioctl(sd, SIOCSIFFLAGS, &network);
 
2252
        if(ret == -1){
 
2253
          perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
 
2254
        }
 
2255
      }
 
2256
      ret = (int)TEMP_FAILURE_RETRY(close(sd));
 
2257
      if(ret == -1){
 
2258
        perror_plus("close");
 
2259
      }
 
2260
      /* Lower privileges permanently */
 
2261
      errno = 0;
 
2262
      ret = setuid(uid);
 
2263
      if(ret == -1){
 
2264
        perror_plus("setuid");
 
2265
      }
 
2266
    }
 
2267
  }
 
2268
  
 
2269
  /* Removes the GPGME temp directory and all files inside */
 
2270
  if(tempdir_created){
 
2271
    struct dirent **direntries = NULL;
 
2272
    struct dirent *direntry = NULL;
 
2273
    int numentries = scandir(tempdir, &direntries, notdotentries,
 
2274
                             alphasort);
 
2275
    if (numentries > 0){
 
2276
      for(int i = 0; i < numentries; i++){
 
2277
        direntry = direntries[i];
 
2278
        char *fullname = NULL;
 
2279
        ret = asprintf(&fullname, "%s/%s", tempdir,
 
2280
                       direntry->d_name);
 
2281
        if(ret < 0){
 
2282
          perror_plus("asprintf");
 
2283
          continue;
 
2284
        }
 
2285
        ret = remove(fullname);
 
2286
        if(ret == -1){
 
2287
          fprintf(stderr, "Mandos plugin mandos-client: "
 
2288
                  "remove(\"%s\"): %s\n", fullname, strerror(errno));
 
2289
        }
 
2290
        free(fullname);
 
2291
      }
 
2292
    }
 
2293
 
 
2294
    /* need to clean even if 0 because man page doesn't specify */
 
2295
    free(direntries);
 
2296
    if (numentries == -1){
 
2297
      perror_plus("scandir");
 
2298
    }
 
2299
    ret = rmdir(tempdir);
 
2300
    if(ret == -1 and errno != ENOENT){
 
2301
      perror_plus("rmdir");
 
2302
    }
 
2303
  }
 
2304
  
 
2305
  if(quit_now){
 
2306
    sigemptyset(&old_sigterm_action.sa_mask);
 
2307
    old_sigterm_action.sa_handler = SIG_DFL;
 
2308
    ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
 
2309
                                            &old_sigterm_action,
 
2310
                                            NULL));
 
2311
    if(ret == -1){
 
2312
      perror_plus("sigaction");
 
2313
    }
 
2314
    do {
 
2315
      ret = raise(signal_received);
 
2316
    } while(ret != 0 and errno == EINTR);
 
2317
    if(ret != 0){
 
2318
      perror_plus("raise");
 
2319
      abort();
 
2320
    }
 
2321
    TEMP_FAILURE_RETRY(pause());
 
2322
  }
 
2323
  
 
2324
  return exitcode;
652
2325
}