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
AvahiIfIndex 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((unsigned int)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, interface);
573
avahi_s_service_resolver_free(r);
576
static void browse_callback(
577
AvahiSServiceBrowser *b,
578
AvahiIfIndex interface,
579
AvahiProtocol protocol,
580
AvahiBrowserEvent event,
584
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
587
AvahiServer *s = userdata;
588
assert(b); /* Spurious warning */
590
/* Called whenever a new services becomes available on the LAN or
591
is removed from the LAN */
595
case AVAHI_BROWSER_FAILURE:
597
fprintf(stderr, "(Browser) %s\n",
598
avahi_strerror(avahi_server_errno(server)));
599
avahi_simple_poll_quit(simple_poll);
602
case AVAHI_BROWSER_NEW:
603
/* We ignore the returned resolver object. In the callback
604
function we free it. If the server is terminated before
605
the callback function is called the server will free
606
the resolver for us. */
608
if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
610
AVAHI_PROTO_INET6, 0,
611
resolve_callback, s)))
612
fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
613
avahi_strerror(avahi_server_errno(s)));
616
case AVAHI_BROWSER_REMOVE:
619
case AVAHI_BROWSER_ALL_FOR_NOW:
620
case AVAHI_BROWSER_CACHE_EXHAUSTED:
625
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
626
AvahiServerConfig config;
627
AvahiSServiceBrowser *sb = NULL;
630
int returncode = EXIT_SUCCESS;
631
const char *interface = NULL;
632
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
633
char *connect_to = NULL;
636
static struct option long_options[] = {
637
{"debug", no_argument, (int *)&debug, 1},
638
{"connect", required_argument, 0, 'c'},
639
{"interface", required_argument, 0, 'i'},
642
int option_index = 0;
643
ret = getopt_long (argc, argv, "i:", long_options,
664
if(interface != NULL){
665
if_index = (AvahiIfIndex) if_nametoindex(interface);
667
fprintf(stderr, "No such interface: \"%s\"\n", interface);
672
if(connect_to != NULL){
673
/* Connect directly, do not use Zeroconf */
674
/* (Mainly meant for debugging) */
675
char *address = strrchr(connect_to, ':');
677
fprintf(stderr, "No colon in address\n");
681
uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
683
perror("Bad port number");
687
address = connect_to;
688
ret = start_mandos_communication(address, port, if_index);
697
avahi_set_log_function(empty_log);
700
/* Initialize the psuedo-RNG */
701
srand((unsigned int) time(NULL));
703
/* Allocate main loop object */
704
if (!(simple_poll = avahi_simple_poll_new())) {
705
fprintf(stderr, "Failed to create simple poll object.\n");
710
/* Do not publish any local records */
711
avahi_server_config_init(&config);
712
config.publish_hinfo = 0;
713
config.publish_addresses = 0;
714
config.publish_workstation = 0;
715
config.publish_domain = 0;
717
/* Allocate a new server */
718
server = avahi_server_new(avahi_simple_poll_get(simple_poll),
719
&config, NULL, NULL, &error);
721
/* Free the configuration data */
722
avahi_server_config_free(&config);
724
/* Check if creating the server object succeeded */
726
fprintf(stderr, "Failed to create server: %s\n",
727
avahi_strerror(error));
728
returncode = EXIT_FAILURE;
732
/* Create the service browser */
733
sb = avahi_s_service_browser_new(server, if_index,
735
"_mandos._tcp", NULL, 0,
736
browse_callback, server);
738
fprintf(stderr, "Failed to create service browser: %s\n",
739
avahi_strerror(avahi_server_errno(server)));
740
returncode = EXIT_FAILURE;
744
/* Run the main loop */
747
fprintf(stderr, "Starting avahi loop search\n");
750
avahi_simple_poll_loop(simple_poll);
755
fprintf(stderr, "%s exiting\n", argv[0]);
760
avahi_s_service_browser_free(sb);
763
avahi_server_free(server);
766
avahi_simple_poll_free(simple_poll);