/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugins.d/mandosclient.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-04 20:43:31 UTC
  • mfrom: (24.1.17 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20080804204331-zgu42g8ii997h1l2
* ca.pem: Removed.
* cert.pem: - '' -
* client-cert.pem: - '' -
* client-key.pem: - '' -
* crl.pem: - '' -
* key.pem: - '' -

* plugins.d/mandosclient.c (start_mandos_communication): Change "to"
                                    to be a union.  All users changed.

* server.py: Changed log severity for many log messages.
  (Client.__init__): Take all config file settins as a dict instead of
                     as keyword arguments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 © 2007-2008 Teddy Hogeborn & Björn Påhlsson
 
13
 * 
 
14
 * This program is free software: you can redistribute it and/or
 
15
 * modify it under the terms of the GNU General Public License as
 
16
 * published by the Free Software Foundation, either version 3 of the
 
17
 * License, or (at your option) any later version.
 
18
 * 
 
19
 * This program is distributed in the hope that it will be useful, but
 
20
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
22
 * General Public License for more details.
 
23
 * 
 
24
 * You should have received a copy of the GNU General Public License
 
25
 * along with this program.  If not, see
 
26
 * <http://www.gnu.org/licenses/>.
 
27
 * 
 
28
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
29
 */
 
30
 
 
31
/* Needed by GPGME, specifically gpgme_data_seek() */
 
32
#define _LARGEFILE_SOURCE
 
33
#define _FILE_OFFSET_BITS 64
 
34
 
 
35
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY() */
 
36
 
 
37
#include <stdio.h>
 
38
#include <assert.h>
 
39
#include <stdlib.h>
 
40
#include <time.h>
 
41
#include <net/if.h>             /* if_nametoindex */
 
42
#include <sys/ioctl.h>          /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
 
43
                                   SIOCSIFFLAGS */
 
44
#include <net/if.h>             /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
 
45
                                   SIOCSIFFLAGS */
 
46
 
 
47
#include <avahi-core/core.h>
 
48
#include <avahi-core/lookup.h>
 
49
#include <avahi-core/log.h>
 
50
#include <avahi-common/simple-watch.h>
 
51
#include <avahi-common/malloc.h>
 
52
#include <avahi-common/error.h>
 
53
 
 
54
/* Mandos client part */
 
55
#include <sys/types.h>          /* socket(), inet_pton() */
 
56
#include <sys/socket.h>         /* socket(), struct sockaddr_in6,
 
57
                                   struct in6_addr, inet_pton() */
 
58
#include <gnutls/gnutls.h>      /* All GnuTLS stuff */
 
59
#include <gnutls/openpgp.h>     /* GnuTLS with openpgp stuff */
 
60
 
 
61
#include <unistd.h>             /* close() */
 
62
#include <netinet/in.h>
 
63
#include <stdbool.h>            /* true */
 
64
#include <string.h>             /* memset */
 
65
#include <arpa/inet.h>          /* inet_pton() */
 
66
#include <iso646.h>             /* not */
 
67
#include <net/if.h>             /* IF_NAMESIZE */
 
68
#include <argp.h>               /* struct argp_option,
 
69
                                   struct argp_state, struct argp,
 
70
                                   argp_parse() */
 
71
/* GPGME */
 
72
#include <errno.h>              /* perror() */
 
73
#include <gpgme.h>
 
74
 
 
75
#define BUFFER_SIZE 256
 
76
 
 
77
bool debug = false;
 
78
static const char *keydir = "/conf/conf.d/mandos";
 
79
static const char mandos_protocol_version[] = "1";
 
80
const char *argp_program_version = "mandosclient 0.9";
 
81
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
82
 
 
83
/* Used for passing in values through the Avahi callback functions */
 
84
typedef struct {
 
85
  AvahiSimplePoll *simple_poll;
 
86
  AvahiServer *server;
 
87
  gnutls_certificate_credentials_t cred;
 
88
  unsigned int dh_bits;
 
89
  gnutls_dh_params_t dh_params;
 
90
  const char *priority;
 
91
} mandos_context;
 
92
 
 
93
/*
 
94
 * Make room in "buffer" for at least BUFFER_SIZE additional bytes.
 
95
 * "buffer_capacity" is how much is currently allocated,
 
96
 * "buffer_length" is how much is already used.
 
97
 */
 
98
size_t adjustbuffer(char **buffer, size_t buffer_length,
 
99
                  size_t buffer_capacity){
 
100
  if (buffer_length + BUFFER_SIZE > buffer_capacity){
 
101
    *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
 
102
    if (buffer == NULL){
 
103
      return 0;
 
104
    }
 
105
    buffer_capacity += BUFFER_SIZE;
 
106
  }
 
107
  return buffer_capacity;
 
108
}
 
109
 
 
110
/* 
 
111
 * Decrypt OpenPGP data using keyrings in HOMEDIR.
 
112
 * Returns -1 on error
 
113
 */
 
114
static ssize_t pgp_packet_decrypt (const char *cryptotext,
 
115
                                   size_t crypto_size,
 
116
                                   char **plaintext,
 
117
                                   const char *homedir){
 
118
  gpgme_data_t dh_crypto, dh_plain;
 
119
  gpgme_ctx_t ctx;
 
120
  gpgme_error_t rc;
 
121
  ssize_t ret;
 
122
  size_t plaintext_capacity = 0;
 
123
  ssize_t plaintext_length = 0;
 
124
  gpgme_engine_info_t engine_info;
 
125
  
 
126
  if (debug){
 
127
    fprintf(stderr, "Trying to decrypt OpenPGP data\n");
 
128
  }
 
129
  
 
130
  /* Init GPGME */
 
131
  gpgme_check_version(NULL);
 
132
  rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
 
133
  if (rc != GPG_ERR_NO_ERROR){
 
134
    fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
 
135
            gpgme_strsource(rc), gpgme_strerror(rc));
 
136
    return -1;
 
137
  }
 
138
  
 
139
  /* Set GPGME home directory for the OpenPGP engine only */
 
140
  rc = gpgme_get_engine_info (&engine_info);
 
141
  if (rc != GPG_ERR_NO_ERROR){
 
142
    fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
 
143
            gpgme_strsource(rc), gpgme_strerror(rc));
 
144
    return -1;
 
145
  }
 
146
  while(engine_info != NULL){
 
147
    if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
 
148
      gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
 
149
                            engine_info->file_name, homedir);
 
150
      break;
 
151
    }
 
152
    engine_info = engine_info->next;
 
153
  }
 
154
  if(engine_info == NULL){
 
155
    fprintf(stderr, "Could not set GPGME home dir to %s\n", homedir);
 
156
    return -1;
 
157
  }
 
158
  
 
159
  /* Create new GPGME data buffer from memory cryptotext */
 
160
  rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
 
161
                               0);
 
162
  if (rc != GPG_ERR_NO_ERROR){
 
163
    fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
 
164
            gpgme_strsource(rc), gpgme_strerror(rc));
 
165
    return -1;
 
166
  }
 
167
  
 
168
  /* Create new empty GPGME data buffer for the plaintext */
 
169
  rc = gpgme_data_new(&dh_plain);
 
170
  if (rc != GPG_ERR_NO_ERROR){
 
171
    fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
 
172
            gpgme_strsource(rc), gpgme_strerror(rc));
 
173
    gpgme_data_release(dh_crypto);
 
174
    return -1;
 
175
  }
 
176
  
 
177
  /* Create new GPGME "context" */
 
178
  rc = gpgme_new(&ctx);
 
179
  if (rc != GPG_ERR_NO_ERROR){
 
180
    fprintf(stderr, "bad gpgme_new: %s: %s\n",
 
181
            gpgme_strsource(rc), gpgme_strerror(rc));
 
182
    plaintext_length = -1;
 
183
    goto decrypt_end;
 
184
  }
 
185
  
 
186
  /* Decrypt data from the cryptotext data buffer to the plaintext
 
187
     data buffer */
 
188
  rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
 
189
  if (rc != GPG_ERR_NO_ERROR){
 
190
    fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
 
191
            gpgme_strsource(rc), gpgme_strerror(rc));
 
192
    plaintext_length = -1;
 
193
    goto decrypt_end;
 
194
  }
 
195
  
 
196
  if(debug){
 
197
    fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
 
198
  }
 
199
  
 
200
  if (debug){
 
201
    gpgme_decrypt_result_t result;
 
202
    result = gpgme_op_decrypt_result(ctx);
 
203
    if (result == NULL){
 
204
      fprintf(stderr, "gpgme_op_decrypt_result failed\n");
 
205
    } else {
 
206
      fprintf(stderr, "Unsupported algorithm: %s\n",
 
207
              result->unsupported_algorithm);
 
208
      fprintf(stderr, "Wrong key usage: %d\n",
 
209
              result->wrong_key_usage);
 
210
      if(result->file_name != NULL){
 
211
        fprintf(stderr, "File name: %s\n", result->file_name);
 
212
      }
 
213
      gpgme_recipient_t recipient;
 
214
      recipient = result->recipients;
 
215
      if(recipient){
 
216
        while(recipient != NULL){
 
217
          fprintf(stderr, "Public key algorithm: %s\n",
 
218
                  gpgme_pubkey_algo_name(recipient->pubkey_algo));
 
219
          fprintf(stderr, "Key ID: %s\n", recipient->keyid);
 
220
          fprintf(stderr, "Secret key available: %s\n",
 
221
                  recipient->status == GPG_ERR_NO_SECKEY
 
222
                  ? "No" : "Yes");
 
223
          recipient = recipient->next;
 
224
        }
 
225
      }
 
226
    }
 
227
  }
 
228
  
 
229
  /* Seek back to the beginning of the GPGME plaintext data buffer */
 
230
  if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){
 
231
    perror("pgpme_data_seek");
 
232
    plaintext_length = -1;
 
233
    goto decrypt_end;
 
234
  }
 
235
  
 
236
  *plaintext = NULL;
 
237
  while(true){
 
238
    plaintext_capacity = adjustbuffer(plaintext,
 
239
                                      (size_t)plaintext_length,
 
240
                                      plaintext_capacity);
 
241
    if (plaintext_capacity == 0){
 
242
        perror("adjustbuffer");
 
243
        plaintext_length = -1;
 
244
        goto decrypt_end;
 
245
    }
 
246
    
 
247
    ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
 
248
                          BUFFER_SIZE);
 
249
    /* Print the data, if any */
 
250
    if (ret == 0){
 
251
      /* EOF */
 
252
      break;
 
253
    }
 
254
    if(ret < 0){
 
255
      perror("gpgme_data_read");
 
256
      plaintext_length = -1;
 
257
      goto decrypt_end;
 
258
    }
 
259
    plaintext_length += ret;
 
260
  }
 
261
 
 
262
  if(debug){
 
263
    fprintf(stderr, "Decrypted password is: ");
 
264
    for(ssize_t i = 0; i < plaintext_length; i++){
 
265
      fprintf(stderr, "%02hhX ", (*plaintext)[i]);
 
266
    }
 
267
    fprintf(stderr, "\n");
 
268
  }
 
269
  
 
270
 decrypt_end:
 
271
  
 
272
  /* Delete the GPGME cryptotext data buffer */
 
273
  gpgme_data_release(dh_crypto);
 
274
  
 
275
  /* Delete the GPGME plaintext data buffer */
 
276
  gpgme_data_release(dh_plain);
 
277
  return plaintext_length;
 
278
}
 
279
 
 
280
static const char * safer_gnutls_strerror (int value) {
 
281
  const char *ret = gnutls_strerror (value);
 
282
  if (ret == NULL)
 
283
    ret = "(unknown)";
 
284
  return ret;
 
285
}
 
286
 
 
287
/* GnuTLS log function callback */
 
288
static void debuggnutls(__attribute__((unused)) int level,
 
289
                        const char* string){
 
290
  fprintf(stderr, "GnuTLS: %s", string);
 
291
}
 
292
 
 
293
static int init_gnutls_global(mandos_context *mc,
 
294
                              const char *pubkeyfile,
 
295
                              const char *seckeyfile){
 
296
  int ret;
 
297
  
 
298
  if(debug){
 
299
    fprintf(stderr, "Initializing GnuTLS\n");
 
300
  }
 
301
 
 
302
  if ((ret = gnutls_global_init ())
 
303
      != GNUTLS_E_SUCCESS) {
 
304
    fprintf (stderr, "GnuTLS global_init: %s\n",
 
305
             safer_gnutls_strerror(ret));
 
306
    return -1;
 
307
  }
 
308
  
 
309
  if (debug){
 
310
    /* "Use a log level over 10 to enable all debugging options."
 
311
     * - GnuTLS manual
 
312
     */
 
313
    gnutls_global_set_log_level(11);
 
314
    gnutls_global_set_log_function(debuggnutls);
 
315
  }
 
316
  
 
317
  /* OpenPGP credentials */
 
318
  if ((ret = gnutls_certificate_allocate_credentials (&mc->cred))
 
319
      != GNUTLS_E_SUCCESS) {
 
320
    fprintf (stderr, "GnuTLS memory error: %s\n",
 
321
             safer_gnutls_strerror(ret));
 
322
    return -1;
 
323
  }
 
324
  
 
325
  if(debug){
 
326
    fprintf(stderr, "Attempting to use OpenPGP certificate %s"
 
327
            " and keyfile %s as GnuTLS credentials\n", pubkeyfile,
 
328
            seckeyfile);
 
329
  }
 
330
  
 
331
  ret = gnutls_certificate_set_openpgp_key_file
 
332
    (mc->cred, pubkeyfile, seckeyfile, GNUTLS_OPENPGP_FMT_BASE64);
 
333
  if (ret != GNUTLS_E_SUCCESS) {
 
334
    fprintf(stderr,
 
335
            "Error[%d] while reading the OpenPGP key pair ('%s',"
 
336
            " '%s')\n", ret, pubkeyfile, seckeyfile);
 
337
    fprintf(stdout, "The GnuTLS error is: %s\n",
 
338
            safer_gnutls_strerror(ret));
 
339
    return -1;
 
340
  }
 
341
  
 
342
  /* GnuTLS server initialization */
 
343
  ret = gnutls_dh_params_init(&mc->dh_params);
 
344
  if (ret != GNUTLS_E_SUCCESS) {
 
345
    fprintf (stderr, "Error in GnuTLS DH parameter initialization:"
 
346
             " %s\n", safer_gnutls_strerror(ret));
 
347
    return -1;
 
348
  }
 
349
  ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits);
 
350
  if (ret != GNUTLS_E_SUCCESS) {
 
351
    fprintf (stderr, "Error in GnuTLS prime generation: %s\n",
 
352
             safer_gnutls_strerror(ret));
 
353
    return -1;
 
354
  }
 
355
  
 
356
  gnutls_certificate_set_dh_params(mc->cred, mc->dh_params);
 
357
 
 
358
  return 0;
 
359
}
 
360
 
 
361
static int init_gnutls_session(mandos_context *mc,
 
362
                               gnutls_session_t *session){
 
363
  int ret;
 
364
  /* GnuTLS session creation */
 
365
  ret = gnutls_init(session, GNUTLS_SERVER);
 
366
  if (ret != GNUTLS_E_SUCCESS){
 
367
    fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
 
368
            safer_gnutls_strerror(ret));
 
369
  }
 
370
  
 
371
  {
 
372
    const char *err;
 
373
    ret = gnutls_priority_set_direct(*session, mc->priority, &err);
 
374
    if (ret != GNUTLS_E_SUCCESS) {
 
375
      fprintf(stderr, "Syntax error at: %s\n", err);
 
376
      fprintf(stderr, "GnuTLS error: %s\n",
 
377
              safer_gnutls_strerror(ret));
 
378
      return -1;
 
379
    }
 
380
  }
 
381
  
 
382
  ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
 
383
                               mc->cred);
 
384
  if (ret != GNUTLS_E_SUCCESS) {
 
385
    fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
 
386
            safer_gnutls_strerror(ret));
 
387
    return -1;
 
388
  }
 
389
  
 
390
  /* ignore client certificate if any. */
 
391
  gnutls_certificate_server_set_request (*session,
 
392
                                         GNUTLS_CERT_IGNORE);
 
393
  
 
394
  gnutls_dh_set_prime_bits (*session, mc->dh_bits);
 
395
  
 
396
  return 0;
 
397
}
 
398
 
 
399
/* Avahi log function callback */
 
400
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
 
401
                      __attribute__((unused)) const char *txt){}
 
402
 
 
403
/* Called when a Mandos server is found */
 
404
static int start_mandos_communication(const char *ip, uint16_t port,
 
405
                                      AvahiIfIndex if_index,
 
406
                                      mandos_context *mc){
 
407
  int ret, tcp_sd;
 
408
  union { struct sockaddr in; struct sockaddr_in6 in6; } to;
 
409
  char *buffer = NULL;
 
410
  char *decrypted_buffer;
 
411
  size_t buffer_length = 0;
 
412
  size_t buffer_capacity = 0;
 
413
  ssize_t decrypted_buffer_size;
 
414
  size_t written;
 
415
  int retval = 0;
 
416
  char interface[IF_NAMESIZE];
 
417
  gnutls_session_t session;
 
418
  
 
419
  ret = init_gnutls_session (mc, &session);
 
420
  if (ret != 0){
 
421
    return -1;
 
422
  }
 
423
  
 
424
  if(debug){
 
425
    fprintf(stderr, "Setting up a tcp connection to %s, port %d\n",
 
426
            ip, port);
 
427
  }
 
428
  
 
429
  tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
 
430
  if(tcp_sd < 0) {
 
431
    perror("socket");
 
432
    return -1;
 
433
  }
 
434
 
 
435
  if(debug){
 
436
    if(if_indextoname((unsigned int)if_index, interface) == NULL){
 
437
      perror("if_indextoname");
 
438
      return -1;
 
439
    }
 
440
    fprintf(stderr, "Binding to interface %s\n", interface);
 
441
  }
 
442
  
 
443
  memset(&to,0,sizeof(to));     /* Spurious warning */
 
444
  to.in6.sin6_family = AF_INET6;
 
445
  /* It would be nice to have a way to detect if we were passed an
 
446
     IPv4 address here.   Now we assume an IPv6 address. */
 
447
  ret = inet_pton(AF_INET6, ip, &to.in6.sin6_addr);
 
448
  if (ret < 0 ){
 
449
    perror("inet_pton");
 
450
    return -1;
 
451
  }
 
452
  if(ret == 0){
 
453
    fprintf(stderr, "Bad address: %s\n", ip);
 
454
    return -1;
 
455
  }
 
456
  to.in6.sin6_port = htons(port);       /* Spurious warning */
 
457
  
 
458
  to.in6.sin6_scope_id = (uint32_t)if_index;
 
459
  
 
460
  if(debug){
 
461
    fprintf(stderr, "Connection to: %s, port %d\n", ip, port);
 
462
    char addrstr[INET6_ADDRSTRLEN] = "";
 
463
    if(inet_ntop(to.in6.sin6_family, &(to.in6.sin6_addr), addrstr,
 
464
                 sizeof(addrstr)) == NULL){
 
465
      perror("inet_ntop");
 
466
    } else {
 
467
      if(strcmp(addrstr, ip) != 0){
 
468
        fprintf(stderr, "Canonical address form: %s\n", addrstr);
 
469
      }
 
470
    }
 
471
  }
 
472
  
 
473
  ret = connect(tcp_sd, &to.in, sizeof(to));
 
474
  if (ret < 0){
 
475
    perror("connect");
 
476
    return -1;
 
477
  }
 
478
 
 
479
  const char *out = mandos_protocol_version;
 
480
  written = 0;
 
481
  while (true){
 
482
    size_t out_size = strlen(out);
 
483
    ret = TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
 
484
                                   out_size - written));
 
485
    if (ret == -1){
 
486
      perror("write");
 
487
      retval = -1;
 
488
      goto mandos_end;
 
489
    }
 
490
    written += (size_t)ret;
 
491
    if(written < out_size){
 
492
      continue;
 
493
    } else {
 
494
      if (out == mandos_protocol_version){
 
495
        written = 0;
 
496
        out = "\r\n";
 
497
      } else {
 
498
        break;
 
499
      }
 
500
    }
 
501
  }
 
502
 
 
503
  if(debug){
 
504
    fprintf(stderr, "Establishing TLS session with %s\n", ip);
 
505
  }
 
506
  
 
507
  gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) tcp_sd);
 
508
  
 
509
  ret = gnutls_handshake (session);
 
510
  
 
511
  if (ret != GNUTLS_E_SUCCESS){
 
512
    if(debug){
 
513
      fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
 
514
      gnutls_perror (ret);
 
515
    }
 
516
    retval = -1;
 
517
    goto mandos_end;
 
518
  }
 
519
  
 
520
  /* Read OpenPGP packet that contains the wanted password */
 
521
  
 
522
  if(debug){
 
523
    fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
 
524
            ip);
 
525
  }
 
526
 
 
527
  while(true){
 
528
    buffer_capacity = adjustbuffer(&buffer, buffer_length,
 
529
                                   buffer_capacity);
 
530
    if (buffer_capacity == 0){
 
531
      perror("adjustbuffer");
 
532
      retval = -1;
 
533
      goto mandos_end;
 
534
    }
 
535
    
 
536
    ret = gnutls_record_recv(session, buffer+buffer_length,
 
537
                             BUFFER_SIZE);
 
538
    if (ret == 0){
 
539
      break;
 
540
    }
 
541
    if (ret < 0){
 
542
      switch(ret){
 
543
      case GNUTLS_E_INTERRUPTED:
 
544
      case GNUTLS_E_AGAIN:
 
545
        break;
 
546
      case GNUTLS_E_REHANDSHAKE:
 
547
        ret = gnutls_handshake (session);
 
548
        if (ret < 0){
 
549
          fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
 
550
          gnutls_perror (ret);
 
551
          retval = -1;
 
552
          goto mandos_end;
 
553
        }
 
554
        break;
 
555
      default:
 
556
        fprintf(stderr, "Unknown error while reading data from"
 
557
                " encrypted session with Mandos server\n");
 
558
        retval = -1;
 
559
        gnutls_bye (session, GNUTLS_SHUT_RDWR);
 
560
        goto mandos_end;
 
561
      }
 
562
    } else {
 
563
      buffer_length += (size_t) ret;
 
564
    }
 
565
  }
 
566
  
 
567
  if(debug){
 
568
    fprintf(stderr, "Closing TLS session\n");
 
569
  }
 
570
  
 
571
  gnutls_bye (session, GNUTLS_SHUT_RDWR);
 
572
  
 
573
  if (buffer_length > 0){
 
574
    decrypted_buffer_size = pgp_packet_decrypt(buffer,
 
575
                                               buffer_length,
 
576
                                               &decrypted_buffer,
 
577
                                               keydir);
 
578
    if (decrypted_buffer_size >= 0){
 
579
      written = 0;
 
580
      while(written < (size_t) decrypted_buffer_size){
 
581
        ret = (int)fwrite (decrypted_buffer + written, 1,
 
582
                           (size_t)decrypted_buffer_size - written,
 
583
                           stdout);
 
584
        if(ret == 0 and ferror(stdout)){
 
585
          if(debug){
 
586
            fprintf(stderr, "Error writing encrypted data: %s\n",
 
587
                    strerror(errno));
 
588
          }
 
589
          retval = -1;
 
590
          break;
 
591
        }
 
592
        written += (size_t)ret;
 
593
      }
 
594
      free(decrypted_buffer);
 
595
    } else {
 
596
      retval = -1;
 
597
    }
 
598
  }
 
599
  
 
600
  /* Shutdown procedure */
 
601
  
 
602
 mandos_end:
 
603
  free(buffer);
 
604
  close(tcp_sd);
 
605
  gnutls_deinit (session);
 
606
  gnutls_certificate_free_credentials (mc->cred);
 
607
  gnutls_global_deinit ();
 
608
  return retval;
 
609
}
 
610
 
 
611
static void resolve_callback(AvahiSServiceResolver *r,
 
612
                             AvahiIfIndex interface,
 
613
                             AVAHI_GCC_UNUSED AvahiProtocol protocol,
 
614
                             AvahiResolverEvent event,
 
615
                             const char *name,
 
616
                             const char *type,
 
617
                             const char *domain,
 
618
                             const char *host_name,
 
619
                             const AvahiAddress *address,
 
620
                             uint16_t port,
 
621
                             AVAHI_GCC_UNUSED AvahiStringList *txt,
 
622
                             AVAHI_GCC_UNUSED AvahiLookupResultFlags
 
623
                             flags,
 
624
                             void* userdata) {
 
625
  mandos_context *mc = userdata;
 
626
  assert(r);                    /* Spurious warning */
 
627
  
 
628
  /* Called whenever a service has been resolved successfully or
 
629
     timed out */
 
630
  
 
631
  switch (event) {
 
632
  default:
 
633
  case AVAHI_RESOLVER_FAILURE:
 
634
    fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
 
635
            " of type '%s' in domain '%s': %s\n", name, type, domain,
 
636
            avahi_strerror(avahi_server_errno(mc->server)));
 
637
    break;
 
638
    
 
639
  case AVAHI_RESOLVER_FOUND:
 
640
    {
 
641
      char ip[AVAHI_ADDRESS_STR_MAX];
 
642
      avahi_address_snprint(ip, sizeof(ip), address);
 
643
      if(debug){
 
644
        fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %d) on"
 
645
                " port %d\n", name, host_name, ip, interface, port);
 
646
      }
 
647
      int ret = start_mandos_communication(ip, port, interface, mc);
 
648
      if (ret == 0){
 
649
        exit(EXIT_SUCCESS);
 
650
      }
 
651
    }
 
652
  }
 
653
  avahi_s_service_resolver_free(r);
 
654
}
 
655
 
 
656
static void browse_callback( AvahiSServiceBrowser *b,
 
657
                             AvahiIfIndex interface,
 
658
                             AvahiProtocol protocol,
 
659
                             AvahiBrowserEvent event,
 
660
                             const char *name,
 
661
                             const char *type,
 
662
                             const char *domain,
 
663
                             AVAHI_GCC_UNUSED AvahiLookupResultFlags
 
664
                             flags,
 
665
                             void* userdata) {
 
666
  mandos_context *mc = userdata;
 
667
  assert(b);                    /* Spurious warning */
 
668
  
 
669
  /* Called whenever a new services becomes available on the LAN or
 
670
     is removed from the LAN */
 
671
  
 
672
  switch (event) {
 
673
  default:
 
674
  case AVAHI_BROWSER_FAILURE:
 
675
    
 
676
    fprintf(stderr, "(Avahi browser) %s\n",
 
677
            avahi_strerror(avahi_server_errno(mc->server)));
 
678
    avahi_simple_poll_quit(mc->simple_poll);
 
679
    return;
 
680
    
 
681
  case AVAHI_BROWSER_NEW:
 
682
    /* We ignore the returned Avahi resolver object. In the callback
 
683
       function we free it. If the Avahi server is terminated before
 
684
       the callback function is called the Avahi server will free the
 
685
       resolver for us. */
 
686
    
 
687
    if (!(avahi_s_service_resolver_new(mc->server, interface,
 
688
                                       protocol, name, type, domain,
 
689
                                       AVAHI_PROTO_INET6, 0,
 
690
                                       resolve_callback, mc)))
 
691
      fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
 
692
              name, avahi_strerror(avahi_server_errno(mc->server)));
 
693
    break;
 
694
    
 
695
  case AVAHI_BROWSER_REMOVE:
 
696
    break;
 
697
    
 
698
  case AVAHI_BROWSER_ALL_FOR_NOW:
 
699
  case AVAHI_BROWSER_CACHE_EXHAUSTED:
 
700
    if(debug){
 
701
      fprintf(stderr, "No Mandos server found, still searching...\n");
 
702
    }
 
703
    break;
 
704
  }
 
705
}
 
706
 
 
707
/* Combines file name and path and returns the malloced new
 
708
   string. some sane checks could/should be added */
 
709
static const char *combinepath(const char *first, const char *second){
 
710
  size_t f_len = strlen(first);
 
711
  size_t s_len = strlen(second);
 
712
  char *tmp = malloc(f_len + s_len + 2);
 
713
  if (tmp == NULL){
 
714
    return NULL;
 
715
  }
 
716
  if(f_len > 0){
 
717
    memcpy(tmp, first, f_len);  /* Spurious warning */
 
718
  }
 
719
  tmp[f_len] = '/';
 
720
  if(s_len > 0){
 
721
    memcpy(tmp + f_len + 1, second, s_len); /* Spurious warning */
 
722
  }
 
723
  tmp[f_len + 1 + s_len] = '\0';
 
724
  return tmp;
 
725
}
 
726
 
 
727
 
 
728
int main(int argc, char *argv[]){
 
729
    AvahiSServiceBrowser *sb = NULL;
 
730
    int error;
 
731
    int ret;
 
732
    int exitcode = EXIT_SUCCESS;
 
733
    const char *interface = "eth0";
 
734
    struct ifreq network;
 
735
    int sd;
 
736
    uid_t uid;
 
737
    gid_t gid;
 
738
    char *connect_to = NULL;
 
739
    AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
 
740
    const char *pubkeyfile = "pubkey.txt";
 
741
    const char *seckeyfile = "seckey.txt";
 
742
    mandos_context mc = { .simple_poll = NULL, .server = NULL,
 
743
                          .dh_bits = 1024, .priority = "SECURE256"};
 
744
    
 
745
    {
 
746
      struct argp_option options[] = {
 
747
        { .name = "debug", .key = 128,
 
748
          .doc = "Debug mode", .group = 3 },
 
749
        { .name = "connect", .key = 'c',
 
750
          .arg = "IP",
 
751
          .doc = "Connect directly to a sepcified mandos server",
 
752
          .group = 1 },
 
753
        { .name = "interface", .key = 'i',
 
754
          .arg = "INTERFACE",
 
755
          .doc = "Interface that Avahi will conntect through",
 
756
          .group = 1 },
 
757
        { .name = "keydir", .key = 'd',
 
758
          .arg = "KEYDIR",
 
759
          .doc = "Directory where the openpgp keyring is",
 
760
          .group = 1 },
 
761
        { .name = "seckey", .key = 's',
 
762
          .arg = "SECKEY",
 
763
          .doc = "Secret openpgp key for gnutls authentication",
 
764
          .group = 1 },
 
765
        { .name = "pubkey", .key = 'p',
 
766
          .arg = "PUBKEY",
 
767
          .doc = "Public openpgp key for gnutls authentication",
 
768
          .group = 2 },
 
769
        { .name = "dh-bits", .key = 129,
 
770
          .arg = "BITS",
 
771
          .doc = "dh-bits to use in gnutls communication",
 
772
          .group = 2 },
 
773
        { .name = "priority", .key = 130,
 
774
          .arg = "PRIORITY",
 
775
          .doc = "GNUTLS priority", .group = 1 },
 
776
        { .name = NULL }
 
777
      };
 
778
 
 
779
      
 
780
      error_t parse_opt (int key, char *arg,
 
781
                         struct argp_state *state) {
 
782
        /* Get the INPUT argument from `argp_parse', which we know is
 
783
           a pointer to our plugin list pointer. */
 
784
        switch (key) {
 
785
        case 128:
 
786
          debug = true;
 
787
          break;
 
788
        case 'c':
 
789
          connect_to = arg;
 
790
          break;
 
791
        case 'i':
 
792
          interface = arg;
 
793
          break;
 
794
        case 'd':
 
795
          keydir = arg;
 
796
          break;
 
797
        case 's':
 
798
          seckeyfile = arg;
 
799
          break;
 
800
        case 'p':
 
801
          pubkeyfile = arg;
 
802
          break;
 
803
        case 129:
 
804
          errno = 0;
 
805
          mc.dh_bits = (unsigned int) strtol(arg, NULL, 10);
 
806
          if (errno){
 
807
            perror("strtol");
 
808
            exit(EXIT_FAILURE);
 
809
          }
 
810
          break;
 
811
        case 130:
 
812
          mc.priority = arg;
 
813
          break;
 
814
        case ARGP_KEY_ARG:
 
815
          argp_usage (state);
 
816
          break;
 
817
          case ARGP_KEY_END:
 
818
            break;
 
819
        default:
 
820
          return ARGP_ERR_UNKNOWN;
 
821
        }
 
822
        return 0;
 
823
      }
 
824
 
 
825
      struct argp argp = { .options = options, .parser = parse_opt,
 
826
                           .args_doc = "",
 
827
                           .doc = "Mandos client -- Get and decrypt"
 
828
                           " passwords from mandos server" };
 
829
      argp_parse (&argp, argc, argv, 0, 0, NULL);
 
830
    }
 
831
      
 
832
    pubkeyfile = combinepath(keydir, pubkeyfile);
 
833
    if (pubkeyfile == NULL){
 
834
      perror("combinepath");
 
835
      exitcode = EXIT_FAILURE;
 
836
      goto end;
 
837
    }
 
838
    
 
839
    seckeyfile = combinepath(keydir, seckeyfile);
 
840
    if (seckeyfile == NULL){
 
841
      perror("combinepath");
 
842
      goto end;
 
843
    }
 
844
 
 
845
    ret = init_gnutls_global(&mc, pubkeyfile, seckeyfile);
 
846
    if (ret == -1){
 
847
      fprintf(stderr, "init_gnutls_global\n");
 
848
      goto end;
 
849
    }
 
850
 
 
851
    uid = getuid();
 
852
    gid = getgid();
 
853
 
 
854
    ret = setuid(uid);
 
855
    if (ret == -1){
 
856
      perror("setuid");
 
857
    }
 
858
    
 
859
    setgid(gid);
 
860
    if (ret == -1){
 
861
      perror("setgid");
 
862
    }
 
863
    
 
864
    if_index = (AvahiIfIndex) if_nametoindex(interface);
 
865
    if(if_index == 0){
 
866
      fprintf(stderr, "No such interface: \"%s\"\n", interface);
 
867
      exit(EXIT_FAILURE);
 
868
    }
 
869
    
 
870
    if(connect_to != NULL){
 
871
      /* Connect directly, do not use Zeroconf */
 
872
      /* (Mainly meant for debugging) */
 
873
      char *address = strrchr(connect_to, ':');
 
874
      if(address == NULL){
 
875
        fprintf(stderr, "No colon in address\n");
 
876
        exitcode = EXIT_FAILURE;
 
877
        goto end;
 
878
      }
 
879
      errno = 0;
 
880
      uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
 
881
      if(errno){
 
882
        perror("Bad port number");
 
883
        exitcode = EXIT_FAILURE;
 
884
        goto end;
 
885
      }
 
886
      *address = '\0';
 
887
      address = connect_to;
 
888
      ret = start_mandos_communication(address, port, if_index, &mc);
 
889
      if(ret < 0){
 
890
        exitcode = EXIT_FAILURE;
 
891
      } else {
 
892
        exitcode = EXIT_SUCCESS;
 
893
      }
 
894
      goto end;
 
895
    }
 
896
    
 
897
    /* If the interface is down, bring it up */
 
898
    {
 
899
      sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
 
900
      if(sd < 0) {
 
901
        perror("socket");
 
902
        exitcode = EXIT_FAILURE;
 
903
        goto end;
 
904
      }
 
905
      strcpy(network.ifr_name, interface); /* Spurious warning */
 
906
      ret = ioctl(sd, SIOCGIFFLAGS, &network);
 
907
      if(ret == -1){
 
908
        perror("ioctl SIOCGIFFLAGS");
 
909
        exitcode = EXIT_FAILURE;
 
910
        goto end;
 
911
      }
 
912
      if((network.ifr_flags & IFF_UP) == 0){
 
913
        network.ifr_flags |= IFF_UP;
 
914
        ret = ioctl(sd, SIOCSIFFLAGS, &network);
 
915
        if(ret == -1){
 
916
          perror("ioctl SIOCSIFFLAGS");
 
917
          exitcode = EXIT_FAILURE;
 
918
          goto end;
 
919
        }
 
920
      }
 
921
      close(sd);
 
922
    }
 
923
    
 
924
    if (not debug){
 
925
      avahi_set_log_function(empty_log);
 
926
    }
 
927
    
 
928
    /* Initialize the pseudo-RNG for Avahi */
 
929
    srand((unsigned int) time(NULL));
 
930
    
 
931
    /* Allocate main Avahi loop object */
 
932
    mc.simple_poll = avahi_simple_poll_new();
 
933
    if (mc.simple_poll == NULL) {
 
934
        fprintf(stderr, "Avahi: Failed to create simple poll"
 
935
                " object.\n");
 
936
        exitcode = EXIT_FAILURE;
 
937
        goto end;
 
938
    }
 
939
 
 
940
    {
 
941
      AvahiServerConfig config;
 
942
      /* Do not publish any local Zeroconf records */
 
943
      avahi_server_config_init(&config);
 
944
      config.publish_hinfo = 0;
 
945
      config.publish_addresses = 0;
 
946
      config.publish_workstation = 0;
 
947
      config.publish_domain = 0;
 
948
 
 
949
      /* Allocate a new server */
 
950
      mc.server = avahi_server_new(avahi_simple_poll_get
 
951
                                   (mc.simple_poll), &config, NULL,
 
952
                                   NULL, &error);
 
953
    
 
954
      /* Free the Avahi configuration data */
 
955
      avahi_server_config_free(&config);
 
956
    }
 
957
    
 
958
    /* Check if creating the Avahi server object succeeded */
 
959
    if (mc.server == NULL) {
 
960
        fprintf(stderr, "Failed to create Avahi server: %s\n",
 
961
                avahi_strerror(error));
 
962
        exitcode = EXIT_FAILURE;
 
963
        goto end;
 
964
    }
 
965
    
 
966
    /* Create the Avahi service browser */
 
967
    sb = avahi_s_service_browser_new(mc.server, if_index,
 
968
                                     AVAHI_PROTO_INET6,
 
969
                                     "_mandos._tcp", NULL, 0,
 
970
                                     browse_callback, &mc);
 
971
    if (sb == NULL) {
 
972
        fprintf(stderr, "Failed to create service browser: %s\n",
 
973
                avahi_strerror(avahi_server_errno(mc.server)));
 
974
        exitcode = EXIT_FAILURE;
 
975
        goto end;
 
976
    }
 
977
    
 
978
    /* Run the main loop */
 
979
 
 
980
    if (debug){
 
981
      fprintf(stderr, "Starting Avahi loop search\n");
 
982
    }
 
983
    
 
984
    avahi_simple_poll_loop(mc.simple_poll);
 
985
    
 
986
 end:
 
987
 
 
988
    if (debug){
 
989
      fprintf(stderr, "%s exiting\n", argv[0]);
 
990
    }
 
991
    
 
992
    /* Cleanup things */
 
993
    if (sb != NULL)
 
994
        avahi_s_service_browser_free(sb);
 
995
    
 
996
    if (mc.server != NULL)
 
997
        avahi_server_free(mc.server);
 
998
 
 
999
    if (mc.simple_poll != NULL)
 
1000
        avahi_simple_poll_free(mc.simple_poll);
 
1001
    free(pubkeyfile);
 
1002
    free(seckeyfile);
 
1003
    
 
1004
    return exitcode;
 
1005
}