1
/*  -*- coding: utf-8 -*- */
 
 
3
 * Mandos client - get and decrypt data from a Mandos server
 
 
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".
 
 
12
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
 
 
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.
 
 
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.
 
 
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/>.
 
 
28
 * Contact the authors at <https://www.fukt.bsnet.se/~belorn/> and
 
 
29
 * <https://www.fukt.bsnet.se/~teddy/>.
 
 
32
/* Needed by GPGME, specifically gpgme_data_seek() */
 
 
33
#define _LARGEFILE_SOURCE
 
 
34
#define _FILE_OFFSET_BITS 64
 
 
40
#include <net/if.h>             /* if_nametoindex */
 
 
42
#include <avahi-core/core.h>
 
 
43
#include <avahi-core/lookup.h>
 
 
44
#include <avahi-core/log.h>
 
 
45
#include <avahi-common/simple-watch.h>
 
 
46
#include <avahi-common/malloc.h>
 
 
47
#include <avahi-common/error.h>
 
 
50
#include <sys/types.h>          /* socket(), inet_pton() */
 
 
51
#include <sys/socket.h>         /* socket(), struct sockaddr_in6,
 
 
52
                                   struct in6_addr, inet_pton() */
 
 
53
#include <gnutls/gnutls.h>      /* All GnuTLS stuff */
 
 
54
#include <gnutls/openpgp.h>     /* GnuTLS with openpgp stuff */
 
 
56
#include <unistd.h>             /* close() */
 
 
57
#include <netinet/in.h>
 
 
58
#include <stdbool.h>            /* true */
 
 
59
#include <string.h>             /* memset */
 
 
60
#include <arpa/inet.h>          /* inet_pton() */
 
 
61
#include <iso646.h>             /* not */
 
 
64
#include <errno.h>              /* perror() */
 
 
71
#define CERT_ROOT "/conf/conf.d/cryptkeyreq/"
 
 
73
#define CERTFILE CERT_ROOT "openpgp-client.txt"
 
 
74
#define KEYFILE CERT_ROOT "openpgp-client-key.txt"
 
 
75
#define BUFFER_SIZE 256
 
 
81
  gnutls_session_t session;
 
 
82
  gnutls_certificate_credentials_t cred;
 
 
83
  gnutls_dh_params_t dh_params;
 
 
87
ssize_t pgp_packet_decrypt (char *packet, size_t packet_size,
 
 
88
                            char **new_packet, const char *homedir){
 
 
89
  gpgme_data_t dh_crypto, dh_plain;
 
 
93
  ssize_t new_packet_capacity = 0;
 
 
94
  ssize_t new_packet_length = 0;
 
 
95
  gpgme_engine_info_t engine_info;
 
 
98
    fprintf(stderr, "Trying to decrypt OpenPGP packet\n");
 
 
102
  gpgme_check_version(NULL);
 
 
103
  gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
 
 
105
  /* Set GPGME home directory */
 
 
106
  rc = gpgme_get_engine_info (&engine_info);
 
 
107
  if (rc != GPG_ERR_NO_ERROR){
 
 
108
    fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
 
 
109
            gpgme_strsource(rc), gpgme_strerror(rc));
 
 
112
  while(engine_info != NULL){
 
 
113
    if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
 
 
114
      gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
 
 
115
                            engine_info->file_name, homedir);
 
 
118
    engine_info = engine_info->next;
 
 
120
  if(engine_info == NULL){
 
 
121
    fprintf(stderr, "Could not set home dir to %s\n", homedir);
 
 
125
  /* Create new GPGME data buffer from packet buffer */
 
 
126
  rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
 
 
127
  if (rc != GPG_ERR_NO_ERROR){
 
 
128
    fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
 
 
129
            gpgme_strsource(rc), gpgme_strerror(rc));
 
 
133
  /* Create new empty GPGME data buffer for the plaintext */
 
 
134
  rc = gpgme_data_new(&dh_plain);
 
 
135
  if (rc != GPG_ERR_NO_ERROR){
 
 
136
    fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
 
 
137
            gpgme_strsource(rc), gpgme_strerror(rc));
 
 
141
  /* Create new GPGME "context" */
 
 
142
  rc = gpgme_new(&ctx);
 
 
143
  if (rc != GPG_ERR_NO_ERROR){
 
 
144
    fprintf(stderr, "bad gpgme_new: %s: %s\n",
 
 
145
            gpgme_strsource(rc), gpgme_strerror(rc));
 
 
149
  /* Decrypt data from the FILE pointer to the plaintext data
 
 
151
  rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
 
 
152
  if (rc != GPG_ERR_NO_ERROR){
 
 
153
    fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
 
 
154
            gpgme_strsource(rc), gpgme_strerror(rc));
 
 
159
    fprintf(stderr, "Decryption of OpenPGP packet succeeded\n");
 
 
163
    gpgme_decrypt_result_t result;
 
 
164
    result = gpgme_op_decrypt_result(ctx);
 
 
166
      fprintf(stderr, "gpgme_op_decrypt_result failed\n");
 
 
168
      fprintf(stderr, "Unsupported algorithm: %s\n",
 
 
169
              result->unsupported_algorithm);
 
 
170
      fprintf(stderr, "Wrong key usage: %d\n",
 
 
171
              result->wrong_key_usage);
 
 
172
      if(result->file_name != NULL){
 
 
173
        fprintf(stderr, "File name: %s\n", result->file_name);
 
 
175
      gpgme_recipient_t recipient;
 
 
176
      recipient = result->recipients;
 
 
178
        while(recipient != NULL){
 
 
179
          fprintf(stderr, "Public key algorithm: %s\n",
 
 
180
                  gpgme_pubkey_algo_name(recipient->pubkey_algo));
 
 
181
          fprintf(stderr, "Key ID: %s\n", recipient->keyid);
 
 
182
          fprintf(stderr, "Secret key available: %s\n",
 
 
183
                  recipient->status == GPG_ERR_NO_SECKEY
 
 
185
          recipient = recipient->next;
 
 
191
  /* Delete the GPGME FILE pointer cryptotext data buffer */
 
 
192
  gpgme_data_release(dh_crypto);
 
 
194
  /* Seek back to the beginning of the GPGME plaintext data buffer */
 
 
195
  gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET);
 
 
199
    if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
 
 
200
      *new_packet = realloc(*new_packet,
 
 
201
                            (unsigned int)new_packet_capacity
 
 
203
      if (*new_packet == NULL){
 
 
207
      new_packet_capacity += BUFFER_SIZE;
 
 
210
    ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length,
 
 
212
    /* Print the data, if any */
 
 
217
      perror("gpgme_data_read");
 
 
220
    new_packet_length += ret;
 
 
223
  /* FIXME: check characters before printing to screen so to not print
 
 
224
     terminal control characters */
 
 
226
  /*     fprintf(stderr, "decrypted password is: "); */
 
 
227
  /*     fwrite(*new_packet, 1, new_packet_length, stderr); */
 
 
228
  /*     fprintf(stderr, "\n"); */
 
 
231
  /* Delete the GPGME plaintext data buffer */
 
 
232
  gpgme_data_release(dh_plain);
 
 
233
  return new_packet_length;
 
 
236
static const char * safer_gnutls_strerror (int value) {
 
 
237
  const char *ret = gnutls_strerror (value);
 
 
243
void debuggnutls(__attribute__((unused)) int level,
 
 
245
  fprintf(stderr, "%s", string);
 
 
248
int initgnutls(encrypted_session *es){
 
 
253
    fprintf(stderr, "Initializing GnuTLS\n");
 
 
256
  if ((ret = gnutls_global_init ())
 
 
257
      != GNUTLS_E_SUCCESS) {
 
 
258
    fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
 
 
263
    gnutls_global_set_log_level(11);
 
 
264
    gnutls_global_set_log_function(debuggnutls);
 
 
267
  /* openpgp credentials */
 
 
268
  if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
 
 
269
      != GNUTLS_E_SUCCESS) {
 
 
270
    fprintf (stderr, "memory error: %s\n",
 
 
271
             safer_gnutls_strerror(ret));
 
 
276
    fprintf(stderr, "Attempting to use OpenPGP certificate %s"
 
 
277
            " and keyfile %s as GnuTLS credentials\n", CERTFILE,
 
 
281
  ret = gnutls_certificate_set_openpgp_key_file
 
 
282
    (es->cred, CERTFILE, KEYFILE, GNUTLS_OPENPGP_FMT_BASE64);
 
 
283
  if (ret != GNUTLS_E_SUCCESS) {
 
 
285
      (stderr, "Error[%d] while reading the OpenPGP key pair ('%s',"
 
 
287
       ret, CERTFILE, KEYFILE);
 
 
288
    fprintf(stdout, "The Error is: %s\n",
 
 
289
            safer_gnutls_strerror(ret));
 
 
293
  //GnuTLS server initialization
 
 
294
  if ((ret = gnutls_dh_params_init (&es->dh_params))
 
 
295
      != GNUTLS_E_SUCCESS) {
 
 
296
    fprintf (stderr, "Error in dh parameter initialization: %s\n",
 
 
297
             safer_gnutls_strerror(ret));
 
 
301
  if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
 
 
302
      != GNUTLS_E_SUCCESS) {
 
 
303
    fprintf (stderr, "Error in prime generation: %s\n",
 
 
304
             safer_gnutls_strerror(ret));
 
 
308
  gnutls_certificate_set_dh_params (es->cred, es->dh_params);
 
 
310
  // GnuTLS session creation
 
 
311
  if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
 
 
312
      != GNUTLS_E_SUCCESS){
 
 
313
    fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
 
 
314
            safer_gnutls_strerror(ret));
 
 
317
  if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
 
 
318
      != GNUTLS_E_SUCCESS) {
 
 
319
    fprintf(stderr, "Syntax error at: %s\n", err);
 
 
320
    fprintf(stderr, "GnuTLS error: %s\n",
 
 
321
            safer_gnutls_strerror(ret));
 
 
325
  if ((ret = gnutls_credentials_set
 
 
326
       (es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
 
 
327
      != GNUTLS_E_SUCCESS) {
 
 
328
    fprintf(stderr, "Error setting a credentials set: %s\n",
 
 
329
            safer_gnutls_strerror(ret));
 
 
333
  /* ignore client certificate if any. */
 
 
334
  gnutls_certificate_server_set_request (es->session,
 
 
337
  gnutls_dh_set_prime_bits (es->session, DH_BITS);
 
 
342
void empty_log(__attribute__((unused)) AvahiLogLevel level,
 
 
343
               __attribute__((unused)) const char *txt){}
 
 
345
int start_mandos_communication(const char *ip, uint16_t port,
 
 
346
                               unsigned int if_index){
 
 
348
  struct sockaddr_in6 to;
 
 
349
  encrypted_session es;
 
 
351
  char *decrypted_buffer;
 
 
352
  size_t buffer_length = 0;
 
 
353
  size_t buffer_capacity = 0;
 
 
354
  ssize_t decrypted_buffer_size;
 
 
357
  char interface[IF_NAMESIZE];
 
 
360
    fprintf(stderr, "Setting up a tcp connection to %s, port %d\n",
 
 
364
  tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
 
 
370
  if(if_indextoname(if_index, interface) == NULL){
 
 
372
      perror("if_indextoname");
 
 
378
    fprintf(stderr, "Binding to interface %s\n", interface);
 
 
381
  memset(&to,0,sizeof(to));     /* Spurious warning */
 
 
382
  to.sin6_family = AF_INET6;
 
 
383
  ret = inet_pton(AF_INET6, ip, &to.sin6_addr);
 
 
389
    fprintf(stderr, "Bad address: %s\n", ip);
 
 
392
  to.sin6_port = htons(port);   /* Spurious warning */
 
 
394
  to.sin6_scope_id = (uint32_t)if_index;
 
 
397
    fprintf(stderr, "Connection to: %s, port %d\n", ip, port);
 
 
398
/*     char addrstr[INET6_ADDRSTRLEN]; */
 
 
399
/*     if(inet_ntop(to.sin6_family, &(to.sin6_addr), addrstr, */
 
 
400
/*               sizeof(addrstr)) == NULL){ */
 
 
401
/*       perror("inet_ntop"); */
 
 
403
/*       fprintf(stderr, "Really connecting to: %s, port %d\n", */
 
 
404
/*            addrstr, ntohs(to.sin6_port)); */
 
 
408
  ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
 
 
414
  ret = initgnutls (&es);
 
 
420
  gnutls_transport_set_ptr (es.session,
 
 
421
                            (gnutls_transport_ptr_t) tcp_sd);
 
 
424
    fprintf(stderr, "Establishing TLS session with %s\n", ip);
 
 
427
  ret = gnutls_handshake (es.session);
 
 
429
  if (ret != GNUTLS_E_SUCCESS){
 
 
431
      fprintf(stderr, "\n*** Handshake failed ***\n");
 
 
438
  //Retrieve OpenPGP packet that contains the wanted password
 
 
441
    fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
 
 
446
    if (buffer_length + BUFFER_SIZE > buffer_capacity){
 
 
447
      buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
 
 
452
      buffer_capacity += BUFFER_SIZE;
 
 
455
    ret = gnutls_record_recv
 
 
456
      (es.session, buffer+buffer_length, BUFFER_SIZE);
 
 
462
      case GNUTLS_E_INTERRUPTED:
 
 
465
      case GNUTLS_E_REHANDSHAKE:
 
 
466
        ret = gnutls_handshake (es.session);
 
 
468
          fprintf(stderr, "\n*** Handshake failed ***\n");
 
 
475
        fprintf(stderr, "Unknown error while reading data from"
 
 
476
                " encrypted session with mandos server\n");
 
 
478
        gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
 
 
482
      buffer_length += (size_t) ret;
 
 
486
  if (buffer_length > 0){
 
 
487
    decrypted_buffer_size = pgp_packet_decrypt(buffer,
 
 
491
    if (decrypted_buffer_size >= 0){
 
 
492
      while(written < (size_t) decrypted_buffer_size){
 
 
493
        ret = (int)fwrite (decrypted_buffer + written, 1,
 
 
494
                           (size_t)decrypted_buffer_size - written,
 
 
496
        if(ret == 0 and ferror(stdout)){
 
 
498
            fprintf(stderr, "Error writing encrypted data: %s\n",
 
 
504
        written += (size_t)ret;
 
 
506
      free(decrypted_buffer);
 
 
515
    fprintf(stderr, "Closing TLS session\n");
 
 
519
  gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
 
 
522
  gnutls_deinit (es.session);
 
 
523
  gnutls_certificate_free_credentials (es.cred);
 
 
524
  gnutls_global_deinit ();
 
 
528
static AvahiSimplePoll *simple_poll = NULL;
 
 
529
static AvahiServer *server = NULL;
 
 
531
static void resolve_callback(
 
 
532
    AvahiSServiceResolver *r,
 
 
533
    AvahiIfIndex interface,
 
 
534
    AVAHI_GCC_UNUSED AvahiProtocol protocol,
 
 
535
    AvahiResolverEvent event,
 
 
539
    const char *host_name,
 
 
540
    const AvahiAddress *address,
 
 
542
    AVAHI_GCC_UNUSED AvahiStringList *txt,
 
 
543
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
 
 
544
    AVAHI_GCC_UNUSED void* userdata) {
 
 
546
  assert(r);                    /* Spurious warning */
 
 
548
  /* Called whenever a service has been resolved successfully or
 
 
553
  case AVAHI_RESOLVER_FAILURE:
 
 
554
    fprintf(stderr, "(Resolver) Failed to resolve service '%s' of"
 
 
555
            " type '%s' in domain '%s': %s\n", name, type, domain,
 
 
556
            avahi_strerror(avahi_server_errno(server)));
 
 
559
  case AVAHI_RESOLVER_FOUND:
 
 
561
      char ip[AVAHI_ADDRESS_STR_MAX];
 
 
562
      avahi_address_snprint(ip, sizeof(ip), address);
 
 
564
        fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on"
 
 
565
                " port %d\n", name, host_name, ip, port);
 
 
567
      int ret = start_mandos_communication(ip, port,
 
 
568
                                           (unsigned int) interface);
 
 
574
  avahi_s_service_resolver_free(r);
 
 
577
static void browse_callback(
 
 
578
    AvahiSServiceBrowser *b,
 
 
579
    AvahiIfIndex interface,
 
 
580
    AvahiProtocol protocol,
 
 
581
    AvahiBrowserEvent event,
 
 
585
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
 
 
588
    AvahiServer *s = userdata;
 
 
589
    assert(b);                  /* Spurious warning */
 
 
591
    /* Called whenever a new services becomes available on the LAN or
 
 
592
       is removed from the LAN */
 
 
596
    case AVAHI_BROWSER_FAILURE:
 
 
598
      fprintf(stderr, "(Browser) %s\n",
 
 
599
              avahi_strerror(avahi_server_errno(server)));
 
 
600
      avahi_simple_poll_quit(simple_poll);
 
 
603
    case AVAHI_BROWSER_NEW:
 
 
604
      /* We ignore the returned resolver object. In the callback
 
 
605
         function we free it. If the server is terminated before
 
 
606
         the callback function is called the server will free
 
 
607
         the resolver for us. */
 
 
609
      if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
 
 
611
                                         AVAHI_PROTO_INET6, 0,
 
 
612
                                         resolve_callback, s)))
 
 
613
        fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
 
 
614
                avahi_strerror(avahi_server_errno(s)));
 
 
617
    case AVAHI_BROWSER_REMOVE:
 
 
620
    case AVAHI_BROWSER_ALL_FOR_NOW:
 
 
621
    case AVAHI_BROWSER_CACHE_EXHAUSTED:
 
 
626
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
 
 
627
    AvahiServerConfig config;
 
 
628
    AvahiSServiceBrowser *sb = NULL;
 
 
631
    int returncode = EXIT_SUCCESS;
 
 
632
    const char *interface = "eth0";
 
 
633
    unsigned int if_index;
 
 
634
    char *connect_to = NULL;
 
 
637
      static struct option long_options[] = {
 
 
638
        {"debug", no_argument, (int *)&debug, 1},
 
 
639
        {"connect", required_argument, 0, 'c'},
 
 
640
        {"interface", required_argument, 0, 'i'},
 
 
643
      int option_index = 0;
 
 
644
      ret = getopt_long (argc, argv, "i:", long_options,
 
 
665
    if_index = if_nametoindex(interface);
 
 
667
      fprintf(stderr, "No such interface: \"%s\"\n", interface);
 
 
671
    if(connect_to != NULL){
 
 
672
      /* Connect directly, do not use Zeroconf */
 
 
673
      /* (Mainly meant for debugging) */
 
 
674
      char *address = strrchr(connect_to, ':');
 
 
676
        fprintf(stderr, "No colon in address\n");
 
 
680
      uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
 
 
682
        perror("Bad port number");
 
 
686
      address = connect_to;
 
 
687
      ret = start_mandos_communication(address, port, if_index);
 
 
696
      avahi_set_log_function(empty_log);
 
 
699
    /* Initialize the psuedo-RNG */
 
 
700
    srand((unsigned int) time(NULL));
 
 
702
    /* Allocate main loop object */
 
 
703
    if (!(simple_poll = avahi_simple_poll_new())) {
 
 
704
        fprintf(stderr, "Failed to create simple poll object.\n");
 
 
709
    /* Do not publish any local records */
 
 
710
    avahi_server_config_init(&config);
 
 
711
    config.publish_hinfo = 0;
 
 
712
    config.publish_addresses = 0;
 
 
713
    config.publish_workstation = 0;
 
 
714
    config.publish_domain = 0;
 
 
716
    /* Allocate a new server */
 
 
717
    server = avahi_server_new(avahi_simple_poll_get(simple_poll),
 
 
718
                              &config, NULL, NULL, &error);
 
 
720
    /* Free the configuration data */
 
 
721
    avahi_server_config_free(&config);
 
 
723
    /* Check if creating the server object succeeded */
 
 
725
        fprintf(stderr, "Failed to create server: %s\n",
 
 
726
                avahi_strerror(error));
 
 
727
        returncode = EXIT_FAILURE;
 
 
731
    /* Create the service browser */
 
 
732
    sb = avahi_s_service_browser_new(server, (AvahiIfIndex)if_index,
 
 
734
                                     "_mandos._tcp", NULL, 0,
 
 
735
                                     browse_callback, server);
 
 
737
        fprintf(stderr, "Failed to create service browser: %s\n",
 
 
738
                avahi_strerror(avahi_server_errno(server)));
 
 
739
        returncode = EXIT_FAILURE;
 
 
743
    /* Run the main loop */
 
 
746
      fprintf(stderr, "Starting avahi loop search\n");
 
 
749
    avahi_simple_poll_loop(simple_poll);
 
 
754
      fprintf(stderr, "%s exiting\n", argv[0]);
 
 
759
        avahi_s_service_browser_free(sb);
 
 
762
        avahi_server_free(server);
 
 
765
        avahi_simple_poll_free(simple_poll);