/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/password-request.c

removed keyring pre-requirement for starting password-request.
        Keys will now be imported in run-time to a run-time created keyring

Changed seckey and pubkey to be paths to private and public keys of the pgp encrypted password and gnutls authentication credentials.

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