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 <mandos@fukt.bsnet.se>.
31
/* Needed by GPGME, specifically gpgme_data_seek() */
32
#define _LARGEFILE_SOURCE
33
#define _FILE_OFFSET_BITS 64
39
#include <net/if.h> /* if_nametoindex */
40
#include <sys/ioctl.h> // ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS
41
#include <net/if.h> // ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS
43
#include <avahi-core/core.h>
44
#include <avahi-core/lookup.h>
45
#include <avahi-core/log.h>
46
#include <avahi-common/simple-watch.h>
47
#include <avahi-common/malloc.h>
48
#include <avahi-common/error.h>
51
#include <sys/types.h> /* socket(), inet_pton() */
52
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
53
struct in6_addr, inet_pton() */
54
#include <gnutls/gnutls.h> /* All GnuTLS stuff */
55
#include <gnutls/openpgp.h> /* GnuTLS with openpgp stuff */
57
#include <unistd.h> /* close() */
58
#include <netinet/in.h>
59
#include <stdbool.h> /* true */
60
#include <string.h> /* memset */
61
#include <arpa/inet.h> /* inet_pton() */
62
#include <iso646.h> /* not */
65
#include <errno.h> /* perror() */
71
#define BUFFER_SIZE 256
74
static const char *certdir = "/conf/conf.d/mandos";
75
static const char *certfile = "openpgp-client.txt";
76
static const char *certkey = "openpgp-client-key.txt";
81
gnutls_session_t session;
82
gnutls_certificate_credentials_t cred;
83
gnutls_dh_params_t dh_params;
87
static ssize_t pgp_packet_decrypt (char *packet, size_t packet_size,
90
gpgme_data_t dh_crypto, dh_plain;
94
ssize_t new_packet_capacity = 0;
95
ssize_t new_packet_length = 0;
96
gpgme_engine_info_t engine_info;
99
fprintf(stderr, "Trying to decrypt OpenPGP packet\n");
103
gpgme_check_version(NULL);
104
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
105
if (rc != GPG_ERR_NO_ERROR){
106
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
107
gpgme_strsource(rc), gpgme_strerror(rc));
111
/* Set GPGME home directory */
112
rc = gpgme_get_engine_info (&engine_info);
113
if (rc != GPG_ERR_NO_ERROR){
114
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
115
gpgme_strsource(rc), gpgme_strerror(rc));
118
while(engine_info != NULL){
119
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
120
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
121
engine_info->file_name, homedir);
124
engine_info = engine_info->next;
126
if(engine_info == NULL){
127
fprintf(stderr, "Could not set home dir to %s\n", homedir);
131
/* Create new GPGME data buffer from packet buffer */
132
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
133
if (rc != GPG_ERR_NO_ERROR){
134
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
135
gpgme_strsource(rc), gpgme_strerror(rc));
139
/* Create new empty GPGME data buffer for the plaintext */
140
rc = gpgme_data_new(&dh_plain);
141
if (rc != GPG_ERR_NO_ERROR){
142
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
143
gpgme_strsource(rc), gpgme_strerror(rc));
147
/* Create new GPGME "context" */
148
rc = gpgme_new(&ctx);
149
if (rc != GPG_ERR_NO_ERROR){
150
fprintf(stderr, "bad gpgme_new: %s: %s\n",
151
gpgme_strsource(rc), gpgme_strerror(rc));
155
/* Decrypt data from the FILE pointer to the plaintext data
157
rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
158
if (rc != GPG_ERR_NO_ERROR){
159
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
160
gpgme_strsource(rc), gpgme_strerror(rc));
165
fprintf(stderr, "Decryption of OpenPGP packet succeeded\n");
169
gpgme_decrypt_result_t result;
170
result = gpgme_op_decrypt_result(ctx);
172
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
174
fprintf(stderr, "Unsupported algorithm: %s\n",
175
result->unsupported_algorithm);
176
fprintf(stderr, "Wrong key usage: %d\n",
177
result->wrong_key_usage);
178
if(result->file_name != NULL){
179
fprintf(stderr, "File name: %s\n", result->file_name);
181
gpgme_recipient_t recipient;
182
recipient = result->recipients;
184
while(recipient != NULL){
185
fprintf(stderr, "Public key algorithm: %s\n",
186
gpgme_pubkey_algo_name(recipient->pubkey_algo));
187
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
188
fprintf(stderr, "Secret key available: %s\n",
189
recipient->status == GPG_ERR_NO_SECKEY
191
recipient = recipient->next;
197
/* Delete the GPGME FILE pointer cryptotext data buffer */
198
gpgme_data_release(dh_crypto);
200
/* Seek back to the beginning of the GPGME plaintext data buffer */
201
if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){
202
perror("pgpme_data_seek");
207
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
208
*new_packet = realloc(*new_packet,
209
(unsigned int)new_packet_capacity
211
if (*new_packet == NULL){
215
new_packet_capacity += BUFFER_SIZE;
218
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length,
220
/* Print the data, if any */
225
perror("gpgme_data_read");
228
new_packet_length += ret;
231
/* FIXME: check characters before printing to screen so to not print
232
terminal control characters */
234
/* fprintf(stderr, "decrypted password is: "); */
235
/* fwrite(*new_packet, 1, new_packet_length, stderr); */
236
/* fprintf(stderr, "\n"); */
239
/* Delete the GPGME plaintext data buffer */
240
gpgme_data_release(dh_plain);
241
return new_packet_length;
244
static const char * safer_gnutls_strerror (int value) {
245
const char *ret = gnutls_strerror (value);
251
static void debuggnutls(__attribute__((unused)) int level,
253
fprintf(stderr, "%s", string);
256
static int initgnutls(encrypted_session *es){
261
fprintf(stderr, "Initializing GnuTLS\n");
264
if ((ret = gnutls_global_init ())
265
!= GNUTLS_E_SUCCESS) {
266
fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
271
gnutls_global_set_log_level(11);
272
gnutls_global_set_log_function(debuggnutls);
275
/* openpgp credentials */
276
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
277
!= GNUTLS_E_SUCCESS) {
278
fprintf (stderr, "memory error: %s\n",
279
safer_gnutls_strerror(ret));
284
fprintf(stderr, "Attempting to use OpenPGP certificate %s"
285
" and keyfile %s as GnuTLS credentials\n", certfile,
289
ret = gnutls_certificate_set_openpgp_key_file
290
(es->cred, certfile, certkey, GNUTLS_OPENPGP_FMT_BASE64);
291
if (ret != GNUTLS_E_SUCCESS) {
293
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s',"
295
ret, certfile, certkey);
296
fprintf(stdout, "The Error is: %s\n",
297
safer_gnutls_strerror(ret));
301
//GnuTLS server initialization
302
if ((ret = gnutls_dh_params_init (&es->dh_params))
303
!= GNUTLS_E_SUCCESS) {
304
fprintf (stderr, "Error in dh parameter initialization: %s\n",
305
safer_gnutls_strerror(ret));
309
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
310
!= GNUTLS_E_SUCCESS) {
311
fprintf (stderr, "Error in prime generation: %s\n",
312
safer_gnutls_strerror(ret));
316
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
318
// GnuTLS session creation
319
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
320
!= GNUTLS_E_SUCCESS){
321
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
322
safer_gnutls_strerror(ret));
325
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
326
!= GNUTLS_E_SUCCESS) {
327
fprintf(stderr, "Syntax error at: %s\n", err);
328
fprintf(stderr, "GnuTLS error: %s\n",
329
safer_gnutls_strerror(ret));
333
if ((ret = gnutls_credentials_set
334
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
335
!= GNUTLS_E_SUCCESS) {
336
fprintf(stderr, "Error setting a credentials set: %s\n",
337
safer_gnutls_strerror(ret));
341
/* ignore client certificate if any. */
342
gnutls_certificate_server_set_request (es->session,
345
gnutls_dh_set_prime_bits (es->session, DH_BITS);
350
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
351
__attribute__((unused)) const char *txt){}
353
static int start_mandos_communication(const char *ip, uint16_t port,
354
AvahiIfIndex if_index){
356
struct sockaddr_in6 to;
357
encrypted_session es;
359
char *decrypted_buffer;
360
size_t buffer_length = 0;
361
size_t buffer_capacity = 0;
362
ssize_t decrypted_buffer_size;
365
char interface[IF_NAMESIZE];
368
fprintf(stderr, "Setting up a tcp connection to %s, port %d\n",
372
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
379
if(if_indextoname((unsigned int)if_index, interface) == NULL){
381
perror("if_indextoname");
386
fprintf(stderr, "Binding to interface %s\n", interface);
389
memset(&to,0,sizeof(to)); /* Spurious warning */
390
to.sin6_family = AF_INET6;
391
ret = inet_pton(AF_INET6, ip, &to.sin6_addr);
397
fprintf(stderr, "Bad address: %s\n", ip);
400
to.sin6_port = htons(port); /* Spurious warning */
402
to.sin6_scope_id = (uint32_t)if_index;
405
fprintf(stderr, "Connection to: %s, port %d\n", ip, port);
406
/* char addrstr[INET6_ADDRSTRLEN]; */
407
/* if(inet_ntop(to.sin6_family, &(to.sin6_addr), addrstr, */
408
/* sizeof(addrstr)) == NULL){ */
409
/* perror("inet_ntop"); */
411
/* fprintf(stderr, "Really connecting to: %s, port %d\n", */
412
/* addrstr, ntohs(to.sin6_port)); */
416
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
422
ret = initgnutls (&es);
428
gnutls_transport_set_ptr (es.session,
429
(gnutls_transport_ptr_t) tcp_sd);
432
fprintf(stderr, "Establishing TLS session with %s\n", ip);
435
ret = gnutls_handshake (es.session);
437
if (ret != GNUTLS_E_SUCCESS){
439
fprintf(stderr, "\n*** Handshake failed ***\n");
446
//Retrieve OpenPGP packet that contains the wanted password
449
fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
454
if (buffer_length + BUFFER_SIZE > buffer_capacity){
455
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
460
buffer_capacity += BUFFER_SIZE;
463
ret = gnutls_record_recv
464
(es.session, buffer+buffer_length, BUFFER_SIZE);
470
case GNUTLS_E_INTERRUPTED:
473
case GNUTLS_E_REHANDSHAKE:
474
ret = gnutls_handshake (es.session);
476
fprintf(stderr, "\n*** Handshake failed ***\n");
483
fprintf(stderr, "Unknown error while reading data from"
484
" encrypted session with mandos server\n");
486
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
490
buffer_length += (size_t) ret;
494
if (buffer_length > 0){
495
decrypted_buffer_size = pgp_packet_decrypt(buffer,
499
if (decrypted_buffer_size >= 0){
500
while(written < (size_t) decrypted_buffer_size){
501
ret = (int)fwrite (decrypted_buffer + written, 1,
502
(size_t)decrypted_buffer_size - written,
504
if(ret == 0 and ferror(stdout)){
506
fprintf(stderr, "Error writing encrypted data: %s\n",
512
written += (size_t)ret;
514
free(decrypted_buffer);
523
fprintf(stderr, "Closing TLS session\n");
527
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
530
gnutls_deinit (es.session);
531
gnutls_certificate_free_credentials (es.cred);
532
gnutls_global_deinit ();
536
static AvahiSimplePoll *simple_poll = NULL;
537
static AvahiServer *server = NULL;
539
static void resolve_callback(
540
AvahiSServiceResolver *r,
541
AvahiIfIndex interface,
542
AVAHI_GCC_UNUSED AvahiProtocol protocol,
543
AvahiResolverEvent event,
547
const char *host_name,
548
const AvahiAddress *address,
550
AVAHI_GCC_UNUSED AvahiStringList *txt,
551
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
552
AVAHI_GCC_UNUSED void* userdata) {
554
assert(r); /* Spurious warning */
556
/* Called whenever a service has been resolved successfully or
561
case AVAHI_RESOLVER_FAILURE:
562
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of"
563
" type '%s' in domain '%s': %s\n", name, type, domain,
564
avahi_strerror(avahi_server_errno(server)));
567
case AVAHI_RESOLVER_FOUND:
569
char ip[AVAHI_ADDRESS_STR_MAX];
570
avahi_address_snprint(ip, sizeof(ip), address);
572
fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on"
573
" port %d\n", name, host_name, ip, port);
575
int ret = start_mandos_communication(ip, port, interface);
581
avahi_s_service_resolver_free(r);
584
static void browse_callback(
585
AvahiSServiceBrowser *b,
586
AvahiIfIndex interface,
587
AvahiProtocol protocol,
588
AvahiBrowserEvent event,
592
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
595
AvahiServer *s = userdata;
596
assert(b); /* Spurious warning */
598
/* Called whenever a new services becomes available on the LAN or
599
is removed from the LAN */
603
case AVAHI_BROWSER_FAILURE:
605
fprintf(stderr, "(Browser) %s\n",
606
avahi_strerror(avahi_server_errno(server)));
607
avahi_simple_poll_quit(simple_poll);
610
case AVAHI_BROWSER_NEW:
611
/* We ignore the returned resolver object. In the callback
612
function we free it. If the server is terminated before
613
the callback function is called the server will free
614
the resolver for us. */
616
if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
618
AVAHI_PROTO_INET6, 0,
619
resolve_callback, s)))
620
fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
621
avahi_strerror(avahi_server_errno(s)));
624
case AVAHI_BROWSER_REMOVE:
627
case AVAHI_BROWSER_ALL_FOR_NOW:
628
case AVAHI_BROWSER_CACHE_EXHAUSTED:
633
/* Combines file name and path and returns the malloced new
634
string. some sane checks could/should be added */
635
static const char *combinepath(const char *first, const char *second){
636
size_t f_len = strlen(first);
637
size_t s_len = strlen(second);
638
char *tmp = malloc(f_len + s_len + 2);
643
memcpy(tmp, first, f_len);
647
memcpy(tmp + f_len + 1, second, s_len);
649
tmp[f_len + 1 + s_len] = '\0';
654
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
655
AvahiServerConfig config;
656
AvahiSServiceBrowser *sb = NULL;
659
int returncode = EXIT_SUCCESS;
660
const char *interface = "eth0";
661
struct ifreq network;
663
char *connect_to = NULL;
664
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
667
static struct option long_options[] = {
668
{"debug", no_argument, (int *)&debug, 1},
669
{"connect", required_argument, 0, 'C'},
670
{"interface", required_argument, 0, 'i'},
671
{"certdir", required_argument, 0, 'd'},
672
{"certkey", required_argument, 0, 'c'},
673
{"certfile", required_argument, 0, 'k'},
676
int option_index = 0;
677
ret = getopt_long (argc, argv, "i:", long_options,
707
certfile = combinepath(certdir, certfile);
708
if (certfile == NULL){
709
perror("combinepath");
710
returncode = EXIT_FAILURE;
714
certkey = combinepath(certdir, certkey);
715
if (certkey == NULL){
716
perror("combinepath");
717
returncode = EXIT_FAILURE;
721
if_index = (AvahiIfIndex) if_nametoindex(interface);
723
fprintf(stderr, "No such interface: \"%s\"\n", interface);
727
if(connect_to != NULL){
728
/* Connect directly, do not use Zeroconf */
729
/* (Mainly meant for debugging) */
730
char *address = strrchr(connect_to, ':');
732
fprintf(stderr, "No colon in address\n");
736
uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
738
perror("Bad port number");
742
address = connect_to;
743
ret = start_mandos_communication(address, port, if_index);
751
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
754
returncode = EXIT_FAILURE;
757
strcpy(network.ifr_name, interface);
758
ret = ioctl(sd, SIOCGIFFLAGS, &network);
761
perror("ioctl SIOCGIFFLAGS");
762
returncode = EXIT_FAILURE;
765
if((network.ifr_flags & IFF_UP) == 0){
766
network.ifr_flags |= IFF_UP;
767
ret = ioctl(sd, SIOCSIFFLAGS, &network);
769
perror("ioctl SIOCSIFFLAGS");
770
returncode = EXIT_FAILURE;
777
avahi_set_log_function(empty_log);
780
/* Initialize the psuedo-RNG */
781
srand((unsigned int) time(NULL));
783
/* Allocate main loop object */
784
if (!(simple_poll = avahi_simple_poll_new())) {
785
fprintf(stderr, "Failed to create simple poll object.\n");
786
returncode = EXIT_FAILURE;
790
/* Do not publish any local records */
791
avahi_server_config_init(&config);
792
config.publish_hinfo = 0;
793
config.publish_addresses = 0;
794
config.publish_workstation = 0;
795
config.publish_domain = 0;
797
/* Allocate a new server */
798
server = avahi_server_new(avahi_simple_poll_get(simple_poll),
799
&config, NULL, NULL, &error);
801
/* Free the configuration data */
802
avahi_server_config_free(&config);
804
/* Check if creating the server object succeeded */
806
fprintf(stderr, "Failed to create server: %s\n",
807
avahi_strerror(error));
808
returncode = EXIT_FAILURE;
812
/* Create the service browser */
813
sb = avahi_s_service_browser_new(server, if_index,
815
"_mandos._tcp", NULL, 0,
816
browse_callback, server);
818
fprintf(stderr, "Failed to create service browser: %s\n",
819
avahi_strerror(avahi_server_errno(server)));
820
returncode = EXIT_FAILURE;
824
/* Run the main loop */
827
fprintf(stderr, "Starting avahi loop search\n");
830
avahi_simple_poll_loop(simple_poll);
835
fprintf(stderr, "%s exiting\n", argv[0]);
840
avahi_s_service_browser_free(sb);
843
avahi_server_free(server);
846
avahi_simple_poll_free(simple_poll);