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 © 2008,2009 Teddy Hogeborn
13
* Copyright © 2008,2009 Björn Påhlsson
15
* This program is free software: you can redistribute it and/or
16
* modify it under the terms of the GNU General Public License as
17
* published by the Free Software Foundation, either version 3 of the
18
* License, or (at your option) any later version.
20
* This program is distributed in the hope that it will be useful, but
21
* WITHOUT ANY WARRANTY; without even the implied warranty of
22
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23
* General Public License for more details.
25
* You should have received a copy of the GNU General Public License
26
* along with this program. If not, see
27
* <http://www.gnu.org/licenses/>.
29
* Contact the authors at <mandos@fukt.bsnet.se>.
32
/* Needed by GPGME, specifically gpgme_data_seek() */
33
#define _LARGEFILE_SOURCE
34
#define _FILE_OFFSET_BITS 64
36
#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */
38
#include <stdio.h> /* fprintf(), stderr, fwrite(),
39
stdout, ferror(), sscanf(),
41
#include <stdint.h> /* uint16_t, uint32_t */
42
#include <stddef.h> /* NULL, size_t, ssize_t */
43
#include <stdlib.h> /* free(), EXIT_SUCCESS, EXIT_FAILURE,
45
#include <stdbool.h> /* bool, false, true */
46
#include <string.h> /* memset(), strcmp(), strlen(),
47
strerror(), asprintf(), strcpy() */
48
#include <sys/ioctl.h> /* ioctl */
49
#include <sys/types.h> /* socket(), inet_pton(), sockaddr,
50
sockaddr_in6, PF_INET6,
51
SOCK_STREAM, uid_t, gid_t, open(),
53
#include <sys/stat.h> /* open() */
54
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
55
inet_pton(), connect() */
56
#include <fcntl.h> /* open() */
57
#include <dirent.h> /* opendir(), struct dirent, readdir()
59
#include <inttypes.h> /* PRIu16, intmax_t, SCNdMAX */
60
#include <assert.h> /* assert() */
61
#include <errno.h> /* perror(), errno */
62
#include <time.h> /* nanosleep(), time() */
63
#include <net/if.h> /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
64
SIOCSIFFLAGS, if_indextoname(),
65
if_nametoindex(), IF_NAMESIZE */
66
#include <netinet/in.h> /* IN6_IS_ADDR_LINKLOCAL,
67
INET_ADDRSTRLEN, INET6_ADDRSTRLEN
69
#include <unistd.h> /* close(), SEEK_SET, off_t, write(),
70
getuid(), getgid(), setuid(),
72
#include <arpa/inet.h> /* inet_pton(), htons */
73
#include <iso646.h> /* not, or, and */
74
#include <argp.h> /* struct argp_option, error_t, struct
75
argp_state, struct argp,
76
argp_parse(), ARGP_KEY_ARG,
77
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
78
#include <signal.h> /* sigemptyset(), sigaddset(),
79
sigaction(), SIGTERM, sigaction */
82
#include <sys/klog.h> /* klogctl() */
86
/* All Avahi types, constants and functions
89
#include <avahi-core/core.h>
90
#include <avahi-core/lookup.h>
91
#include <avahi-core/log.h>
92
#include <avahi-common/simple-watch.h>
93
#include <avahi-common/malloc.h>
94
#include <avahi-common/error.h>
97
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
100
init_gnutls_session(),
102
#include <gnutls/openpgp.h>
103
/* gnutls_certificate_set_openpgp_key_file(),
104
GNUTLS_OPENPGP_FMT_BASE64 */
107
#include <gpgme.h> /* All GPGME types, constants and
110
GPGME_PROTOCOL_OpenPGP,
113
#define BUFFER_SIZE 256
115
#define PATHDIR "/conf/conf.d/mandos"
116
#define SECKEY "seckey.txt"
117
#define PUBKEY "pubkey.txt"
120
static const char mandos_protocol_version[] = "1";
121
const char *argp_program_version = "mandos-client " VERSION;
122
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
124
/* Used for passing in values through the Avahi callback functions */
126
AvahiSimplePoll *simple_poll;
128
gnutls_certificate_credentials_t cred;
129
unsigned int dh_bits;
130
gnutls_dh_params_t dh_params;
131
const char *priority;
135
/* global context so signal handler can reach it*/
139
* Make additional room in "buffer" for at least BUFFER_SIZE
140
* additional bytes. "buffer_capacity" is how much is currently
141
* allocated, "buffer_length" is how much is already used.
143
size_t incbuffer(char **buffer, size_t buffer_length,
144
size_t buffer_capacity){
145
if(buffer_length + BUFFER_SIZE > buffer_capacity){
146
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
150
buffer_capacity += BUFFER_SIZE;
152
return buffer_capacity;
158
static bool init_gpgme(const char *seckey,
159
const char *pubkey, const char *tempdir){
162
gpgme_engine_info_t engine_info;
166
* Helper function to insert pub and seckey to the engine keyring.
168
bool import_key(const char *filename){
170
gpgme_data_t pgp_data;
172
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
178
rc = gpgme_data_new_from_fd(&pgp_data, fd);
179
if(rc != GPG_ERR_NO_ERROR){
180
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
181
gpgme_strsource(rc), gpgme_strerror(rc));
185
rc = gpgme_op_import(mc.ctx, pgp_data);
186
if(rc != GPG_ERR_NO_ERROR){
187
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
188
gpgme_strsource(rc), gpgme_strerror(rc));
192
ret = (int)TEMP_FAILURE_RETRY(close(fd));
196
gpgme_data_release(pgp_data);
201
fprintf(stderr, "Initializing GPGME\n");
205
gpgme_check_version(NULL);
206
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
207
if(rc != GPG_ERR_NO_ERROR){
208
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
209
gpgme_strsource(rc), gpgme_strerror(rc));
213
/* Set GPGME home directory for the OpenPGP engine only */
214
rc = gpgme_get_engine_info(&engine_info);
215
if(rc != GPG_ERR_NO_ERROR){
216
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
217
gpgme_strsource(rc), gpgme_strerror(rc));
220
while(engine_info != NULL){
221
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
222
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
223
engine_info->file_name, tempdir);
226
engine_info = engine_info->next;
228
if(engine_info == NULL){
229
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
233
/* Create new GPGME "context" */
234
rc = gpgme_new(&(mc.ctx));
235
if(rc != GPG_ERR_NO_ERROR){
236
fprintf(stderr, "bad gpgme_new: %s: %s\n",
237
gpgme_strsource(rc), gpgme_strerror(rc));
241
if(not import_key(pubkey) or not import_key(seckey)){
249
* Decrypt OpenPGP data.
250
* Returns -1 on error
252
static ssize_t pgp_packet_decrypt(const char *cryptotext,
255
gpgme_data_t dh_crypto, dh_plain;
258
size_t plaintext_capacity = 0;
259
ssize_t plaintext_length = 0;
262
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
265
/* Create new GPGME data buffer from memory cryptotext */
266
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
268
if(rc != GPG_ERR_NO_ERROR){
269
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
270
gpgme_strsource(rc), gpgme_strerror(rc));
274
/* Create new empty GPGME data buffer for the plaintext */
275
rc = gpgme_data_new(&dh_plain);
276
if(rc != GPG_ERR_NO_ERROR){
277
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
278
gpgme_strsource(rc), gpgme_strerror(rc));
279
gpgme_data_release(dh_crypto);
283
/* Decrypt data from the cryptotext data buffer to the plaintext
285
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
286
if(rc != GPG_ERR_NO_ERROR){
287
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
288
gpgme_strsource(rc), gpgme_strerror(rc));
289
plaintext_length = -1;
291
gpgme_decrypt_result_t result;
292
result = gpgme_op_decrypt_result(mc.ctx);
294
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
296
fprintf(stderr, "Unsupported algorithm: %s\n",
297
result->unsupported_algorithm);
298
fprintf(stderr, "Wrong key usage: %u\n",
299
result->wrong_key_usage);
300
if(result->file_name != NULL){
301
fprintf(stderr, "File name: %s\n", result->file_name);
303
gpgme_recipient_t recipient;
304
recipient = result->recipients;
306
while(recipient != NULL){
307
fprintf(stderr, "Public key algorithm: %s\n",
308
gpgme_pubkey_algo_name(recipient->pubkey_algo));
309
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
310
fprintf(stderr, "Secret key available: %s\n",
311
recipient->status == GPG_ERR_NO_SECKEY
313
recipient = recipient->next;
322
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
325
/* Seek back to the beginning of the GPGME plaintext data buffer */
326
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
327
perror("gpgme_data_seek");
328
plaintext_length = -1;
334
plaintext_capacity = incbuffer(plaintext,
335
(size_t)plaintext_length,
337
if(plaintext_capacity == 0){
339
plaintext_length = -1;
343
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
345
/* Print the data, if any */
351
perror("gpgme_data_read");
352
plaintext_length = -1;
355
plaintext_length += ret;
359
fprintf(stderr, "Decrypted password is: ");
360
for(ssize_t i = 0; i < plaintext_length; i++){
361
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
363
fprintf(stderr, "\n");
368
/* Delete the GPGME cryptotext data buffer */
369
gpgme_data_release(dh_crypto);
371
/* Delete the GPGME plaintext data buffer */
372
gpgme_data_release(dh_plain);
373
return plaintext_length;
376
static const char * safer_gnutls_strerror(int value){
377
const char *ret = gnutls_strerror(value); /* Spurious warning from
378
-Wunreachable-code */
384
/* GnuTLS log function callback */
385
static void debuggnutls(__attribute__((unused)) int level,
387
fprintf(stderr, "GnuTLS: %s", string);
390
static int init_gnutls_global(const char *pubkeyfilename,
391
const char *seckeyfilename){
395
fprintf(stderr, "Initializing GnuTLS\n");
398
ret = gnutls_global_init();
399
if(ret != GNUTLS_E_SUCCESS){
400
fprintf(stderr, "GnuTLS global_init: %s\n",
401
safer_gnutls_strerror(ret));
406
/* "Use a log level over 10 to enable all debugging options."
409
gnutls_global_set_log_level(11);
410
gnutls_global_set_log_function(debuggnutls);
413
/* OpenPGP credentials */
414
gnutls_certificate_allocate_credentials(&mc.cred);
415
if(ret != GNUTLS_E_SUCCESS){
416
fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning
420
safer_gnutls_strerror(ret));
421
gnutls_global_deinit();
426
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
427
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
431
ret = gnutls_certificate_set_openpgp_key_file
432
(mc.cred, pubkeyfilename, seckeyfilename,
433
GNUTLS_OPENPGP_FMT_BASE64);
434
if(ret != GNUTLS_E_SUCCESS){
436
"Error[%d] while reading the OpenPGP key pair ('%s',"
437
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
438
fprintf(stderr, "The GnuTLS error is: %s\n",
439
safer_gnutls_strerror(ret));
443
/* GnuTLS server initialization */
444
ret = gnutls_dh_params_init(&mc.dh_params);
445
if(ret != GNUTLS_E_SUCCESS){
446
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
447
" %s\n", safer_gnutls_strerror(ret));
450
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
451
if(ret != GNUTLS_E_SUCCESS){
452
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
453
safer_gnutls_strerror(ret));
457
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
463
gnutls_certificate_free_credentials(mc.cred);
464
gnutls_global_deinit();
465
gnutls_dh_params_deinit(mc.dh_params);
469
static int init_gnutls_session(gnutls_session_t *session){
471
/* GnuTLS session creation */
472
ret = gnutls_init(session, GNUTLS_SERVER);
473
if(ret != GNUTLS_E_SUCCESS){
474
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
475
safer_gnutls_strerror(ret));
480
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
481
if(ret != GNUTLS_E_SUCCESS){
482
fprintf(stderr, "Syntax error at: %s\n", err);
483
fprintf(stderr, "GnuTLS error: %s\n",
484
safer_gnutls_strerror(ret));
485
gnutls_deinit(*session);
490
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
492
if(ret != GNUTLS_E_SUCCESS){
493
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
494
safer_gnutls_strerror(ret));
495
gnutls_deinit(*session);
499
/* ignore client certificate if any. */
500
gnutls_certificate_server_set_request(*session,
503
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
508
/* Avahi log function callback */
509
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
510
__attribute__((unused)) const char *txt){}
512
/* Called when a Mandos server is found */
513
static int start_mandos_communication(const char *ip, uint16_t port,
514
AvahiIfIndex if_index,
519
struct sockaddr_in in;
520
struct sockaddr_in6 in6;
523
char *decrypted_buffer;
524
size_t buffer_length = 0;
525
size_t buffer_capacity = 0;
526
ssize_t decrypted_buffer_size;
529
gnutls_session_t session;
530
int pf; /* Protocol family */
540
fprintf(stderr, "Bad address family: %d\n", af);
544
ret = init_gnutls_session(&session);
550
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
554
tcp_sd = socket(pf, SOCK_STREAM, 0);
560
memset(&to, 0, sizeof(to));
562
to.in6.sin6_family = (uint16_t)af;
563
ret = inet_pton(af, ip, &to.in6.sin6_addr);
565
to.in.sin_family = (sa_family_t)af;
566
ret = inet_pton(af, ip, &to.in.sin_addr);
573
fprintf(stderr, "Bad address: %s\n", ip);
577
to.in6.sin6_port = htons(port); /* Spurious warnings from
579
-Wunreachable-code */
581
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
582
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
584
if(if_index == AVAHI_IF_UNSPEC){
585
fprintf(stderr, "An IPv6 link-local address is incomplete"
586
" without a network interface\n");
589
/* Set the network interface number as scope */
590
to.in6.sin6_scope_id = (uint32_t)if_index;
593
to.in.sin_port = htons(port); /* Spurious warnings from
595
-Wunreachable-code */
599
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
600
char interface[IF_NAMESIZE];
601
if(if_indextoname((unsigned int)if_index, interface) == NULL){
602
perror("if_indextoname");
604
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
605
ip, interface, port);
608
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
611
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
612
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
615
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
618
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
624
if(strcmp(addrstr, ip) != 0){
625
fprintf(stderr, "Canonical address form: %s\n", addrstr);
631
ret = connect(tcp_sd, &to.in6, sizeof(to));
633
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
640
const char *out = mandos_protocol_version;
643
size_t out_size = strlen(out);
644
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
645
out_size - written));
651
written += (size_t)ret;
652
if(written < out_size){
655
if(out == mandos_protocol_version){
665
fprintf(stderr, "Establishing TLS session with %s\n", ip);
668
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
671
ret = gnutls_handshake(session);
672
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
674
if(ret != GNUTLS_E_SUCCESS){
676
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
683
/* Read OpenPGP packet that contains the wanted password */
686
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
691
buffer_capacity = incbuffer(&buffer, buffer_length,
693
if(buffer_capacity == 0){
699
sret = gnutls_record_recv(session, buffer+buffer_length,
706
case GNUTLS_E_INTERRUPTED:
709
case GNUTLS_E_REHANDSHAKE:
711
ret = gnutls_handshake(session);
712
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
714
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
721
fprintf(stderr, "Unknown error while reading data from"
722
" encrypted session with Mandos server\n");
724
gnutls_bye(session, GNUTLS_SHUT_RDWR);
728
buffer_length += (size_t) sret;
733
fprintf(stderr, "Closing TLS session\n");
736
gnutls_bye(session, GNUTLS_SHUT_RDWR);
738
if(buffer_length > 0){
739
decrypted_buffer_size = pgp_packet_decrypt(buffer,
742
if(decrypted_buffer_size >= 0){
744
while(written < (size_t) decrypted_buffer_size){
745
ret = (int)fwrite(decrypted_buffer + written, 1,
746
(size_t)decrypted_buffer_size - written,
748
if(ret == 0 and ferror(stdout)){
750
fprintf(stderr, "Error writing encrypted data: %s\n",
756
written += (size_t)ret;
758
free(decrypted_buffer);
766
/* Shutdown procedure */
770
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
774
gnutls_deinit(session);
778
static void resolve_callback(AvahiSServiceResolver *r,
779
AvahiIfIndex interface,
781
AvahiResolverEvent event,
785
const char *host_name,
786
const AvahiAddress *address,
788
AVAHI_GCC_UNUSED AvahiStringList *txt,
789
AVAHI_GCC_UNUSED AvahiLookupResultFlags
791
AVAHI_GCC_UNUSED void* userdata){
794
/* Called whenever a service has been resolved successfully or
799
case AVAHI_RESOLVER_FAILURE:
800
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
801
" of type '%s' in domain '%s': %s\n", name, type, domain,
802
avahi_strerror(avahi_server_errno(mc.server)));
805
case AVAHI_RESOLVER_FOUND:
807
char ip[AVAHI_ADDRESS_STR_MAX];
808
avahi_address_snprint(ip, sizeof(ip), address);
810
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
811
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
812
ip, (intmax_t)interface, port);
814
int ret = start_mandos_communication(ip, port, interface,
815
avahi_proto_to_af(proto));
817
avahi_simple_poll_quit(mc.simple_poll);
821
avahi_s_service_resolver_free(r);
824
static void browse_callback(AvahiSServiceBrowser *b,
825
AvahiIfIndex interface,
826
AvahiProtocol protocol,
827
AvahiBrowserEvent event,
831
AVAHI_GCC_UNUSED AvahiLookupResultFlags
833
AVAHI_GCC_UNUSED void* userdata){
836
/* Called whenever a new services becomes available on the LAN or
837
is removed from the LAN */
841
case AVAHI_BROWSER_FAILURE:
843
fprintf(stderr, "(Avahi browser) %s\n",
844
avahi_strerror(avahi_server_errno(mc.server)));
845
avahi_simple_poll_quit(mc.simple_poll);
848
case AVAHI_BROWSER_NEW:
849
/* We ignore the returned Avahi resolver object. In the callback
850
function we free it. If the Avahi server is terminated before
851
the callback function is called the Avahi server will free the
854
if(!(avahi_s_service_resolver_new(mc.server, interface,
855
protocol, name, type, domain,
856
AVAHI_PROTO_INET6, 0,
857
resolve_callback, NULL)))
858
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
859
name, avahi_strerror(avahi_server_errno(mc.server)));
862
case AVAHI_BROWSER_REMOVE:
865
case AVAHI_BROWSER_ALL_FOR_NOW:
866
case AVAHI_BROWSER_CACHE_EXHAUSTED:
868
fprintf(stderr, "No Mandos server found, still searching...\n");
874
static void handle_sigterm(__attribute__((unused)) int sig){
875
int old_errno = errno;
876
avahi_simple_poll_quit(mc.simple_poll);
880
int main(int argc, char *argv[]){
881
AvahiSServiceBrowser *sb = NULL;
886
int exitcode = EXIT_SUCCESS;
887
const char *interface = "eth0";
888
struct ifreq network;
892
char *connect_to = NULL;
893
char tempdir[] = "/tmp/mandosXXXXXX";
894
bool tempdir_created = false;
895
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
896
const char *seckey = PATHDIR "/" SECKEY;
897
const char *pubkey = PATHDIR "/" PUBKEY;
899
/* Initialize Mandos context */
900
mc = (mandos_context){ .simple_poll = NULL, .server = NULL,
901
.dh_bits = 1024, .priority = "SECURE256"
902
":!CTYPE-X.509:+CTYPE-OPENPGP" };
903
bool gnutls_initialized = false;
904
bool gpgme_initialized = false;
907
struct sigaction old_sigterm_action;
908
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
911
struct argp_option options[] = {
912
{ .name = "debug", .key = 128,
913
.doc = "Debug mode", .group = 3 },
914
{ .name = "connect", .key = 'c',
915
.arg = "ADDRESS:PORT",
916
.doc = "Connect directly to a specific Mandos server",
918
{ .name = "interface", .key = 'i',
920
.doc = "Network interface that will be used to search for"
923
{ .name = "seckey", .key = 's',
925
.doc = "OpenPGP secret key file base name",
927
{ .name = "pubkey", .key = 'p',
929
.doc = "OpenPGP public key file base name",
931
{ .name = "dh-bits", .key = 129,
933
.doc = "Bit length of the prime number used in the"
934
" Diffie-Hellman key exchange",
936
{ .name = "priority", .key = 130,
938
.doc = "GnuTLS priority string for the TLS handshake",
940
{ .name = "delay", .key = 131,
942
.doc = "Maximum delay to wait for interface startup",
947
error_t parse_opt(int key, char *arg,
948
struct argp_state *state){
950
case 128: /* --debug */
953
case 'c': /* --connect */
956
case 'i': /* --interface */
959
case 's': /* --seckey */
962
case 'p': /* --pubkey */
965
case 129: /* --dh-bits */
966
ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars);
967
if(ret < 1 or tmpmax != (typeof(mc.dh_bits))tmpmax
968
or arg[numchars] != '\0'){
969
fprintf(stderr, "Bad number of DH bits\n");
972
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
974
case 130: /* --priority */
977
case 131: /* --delay */
978
ret = sscanf(arg, "%lf%n", &delay, &numchars);
979
if(ret < 1 or arg[numchars] != '\0'){
980
fprintf(stderr, "Bad delay\n");
989
return ARGP_ERR_UNKNOWN;
994
struct argp argp = { .options = options, .parser = parse_opt,
996
.doc = "Mandos client -- Get and decrypt"
997
" passwords from a Mandos server" };
998
ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
999
if(ret == ARGP_ERR_UNKNOWN){
1000
fprintf(stderr, "Unknown error while parsing arguments\n");
1001
exitcode = EXIT_FAILURE;
1006
/* If the interface is down, bring it up */
1007
if(interface[0] != '\0'){
1009
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1010
messages to mess up the prompt */
1011
ret = klogctl(8, NULL, 5);
1012
bool restore_loglevel = true;
1014
restore_loglevel = false;
1019
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1022
exitcode = EXIT_FAILURE;
1024
if(restore_loglevel){
1025
ret = klogctl(7, NULL, 0);
1033
strcpy(network.ifr_name, interface);
1034
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1036
perror("ioctl SIOCGIFFLAGS");
1038
if(restore_loglevel){
1039
ret = klogctl(7, NULL, 0);
1045
exitcode = EXIT_FAILURE;
1048
if((network.ifr_flags & IFF_UP) == 0){
1049
network.ifr_flags |= IFF_UP;
1050
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1052
perror("ioctl SIOCSIFFLAGS");
1053
exitcode = EXIT_FAILURE;
1055
if(restore_loglevel){
1056
ret = klogctl(7, NULL, 0);
1065
/* sleep checking until interface is running */
1066
for(int i=0; i < delay * 4; i++){
1067
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1069
perror("ioctl SIOCGIFFLAGS");
1070
} else if(network.ifr_flags & IFF_RUNNING){
1073
struct timespec sleeptime = { .tv_nsec = 250000000 };
1074
ret = nanosleep(&sleeptime, NULL);
1075
if(ret == -1 and errno != EINTR){
1076
perror("nanosleep");
1079
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1084
if(restore_loglevel){
1085
/* Restores kernel loglevel to default */
1086
ret = klogctl(7, NULL, 0);
1108
ret = init_gnutls_global(pubkey, seckey);
1110
fprintf(stderr, "init_gnutls_global failed\n");
1111
exitcode = EXIT_FAILURE;
1114
gnutls_initialized = true;
1117
if(mkdtemp(tempdir) == NULL){
1121
tempdir_created = true;
1123
if(not init_gpgme(pubkey, seckey, tempdir)){
1124
fprintf(stderr, "init_gpgme failed\n");
1125
exitcode = EXIT_FAILURE;
1128
gpgme_initialized = true;
1131
if(interface[0] != '\0'){
1132
if_index = (AvahiIfIndex) if_nametoindex(interface);
1134
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1135
exitcode = EXIT_FAILURE;
1140
if(connect_to != NULL){
1141
/* Connect directly, do not use Zeroconf */
1142
/* (Mainly meant for debugging) */
1143
char *address = strrchr(connect_to, ':');
1144
if(address == NULL){
1145
fprintf(stderr, "No colon in address\n");
1146
exitcode = EXIT_FAILURE;
1150
ret = sscanf(address+1, "%" SCNdMAX "%n", &tmpmax, &numchars);
1151
if(ret < 1 or tmpmax != (uint16_t)tmpmax
1152
or address[numchars+1] != '\0'){
1153
fprintf(stderr, "Bad port number\n");
1154
exitcode = EXIT_FAILURE;
1157
port = (uint16_t)tmpmax;
1159
address = connect_to;
1160
/* Colon in address indicates IPv6 */
1162
if(strchr(address, ':') != NULL){
1167
ret = start_mandos_communication(address, port, if_index, af);
1169
exitcode = EXIT_FAILURE;
1171
exitcode = EXIT_SUCCESS;
1177
avahi_set_log_function(empty_log);
1180
/* Initialize the pseudo-RNG for Avahi */
1181
srand((unsigned int) time(NULL));
1183
/* Allocate main Avahi loop object */
1184
mc.simple_poll = avahi_simple_poll_new();
1185
if(mc.simple_poll == NULL){
1186
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1187
exitcode = EXIT_FAILURE;
1192
AvahiServerConfig config;
1193
/* Do not publish any local Zeroconf records */
1194
avahi_server_config_init(&config);
1195
config.publish_hinfo = 0;
1196
config.publish_addresses = 0;
1197
config.publish_workstation = 0;
1198
config.publish_domain = 0;
1200
/* Allocate a new server */
1201
mc.server = avahi_server_new(avahi_simple_poll_get
1202
(mc.simple_poll), &config, NULL,
1205
/* Free the Avahi configuration data */
1206
avahi_server_config_free(&config);
1209
/* Check if creating the Avahi server object succeeded */
1210
if(mc.server == NULL){
1211
fprintf(stderr, "Failed to create Avahi server: %s\n",
1212
avahi_strerror(error));
1213
exitcode = EXIT_FAILURE;
1217
/* Create the Avahi service browser */
1218
sb = avahi_s_service_browser_new(mc.server, if_index,
1219
AVAHI_PROTO_INET6, "_mandos._tcp",
1220
NULL, 0, browse_callback, NULL);
1222
fprintf(stderr, "Failed to create service browser: %s\n",
1223
avahi_strerror(avahi_server_errno(mc.server)));
1224
exitcode = EXIT_FAILURE;
1228
sigemptyset(&sigterm_action.sa_mask);
1229
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1231
perror("sigaddset");
1232
exitcode = EXIT_FAILURE;
1235
ret = sigaction(SIGTERM, &sigterm_action, &old_sigterm_action);
1237
perror("sigaction");
1238
exitcode = EXIT_FAILURE;
1242
/* Run the main loop */
1245
fprintf(stderr, "Starting Avahi loop search\n");
1248
avahi_simple_poll_loop(mc.simple_poll);
1253
fprintf(stderr, "%s exiting\n", argv[0]);
1256
/* Cleanup things */
1258
avahi_s_service_browser_free(sb);
1260
if(mc.server != NULL)
1261
avahi_server_free(mc.server);
1263
if(mc.simple_poll != NULL)
1264
avahi_simple_poll_free(mc.simple_poll);
1266
if(gnutls_initialized){
1267
gnutls_certificate_free_credentials(mc.cred);
1268
gnutls_global_deinit();
1269
gnutls_dh_params_deinit(mc.dh_params);
1272
if(gpgme_initialized){
1273
gpgme_release(mc.ctx);
1276
/* Removes the temp directory used by GPGME */
1277
if(tempdir_created){
1279
struct dirent *direntry;
1280
d = opendir(tempdir);
1282
if(errno != ENOENT){
1287
direntry = readdir(d);
1288
if(direntry == NULL){
1291
/* Skip "." and ".." */
1292
if(direntry->d_name[0] == '.'
1293
and (direntry->d_name[1] == '\0'
1294
or (direntry->d_name[1] == '.'
1295
and direntry->d_name[2] == '\0'))){
1298
char *fullname = NULL;
1299
ret = asprintf(&fullname, "%s/%s", tempdir,
1305
ret = remove(fullname);
1307
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1314
ret = rmdir(tempdir);
1315
if(ret == -1 and errno != ENOENT){