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);