/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/mandosclient.c

  • Committer: Björn Påhlsson
  • Date: 2007-10-20 21:38:25 UTC
  • Revision ID: belorn@tower-20071020213825-abf6f0d1c33ee961
First working version with: IPv6, GnuTLS, X.509 certificates, DN
retrieval.

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