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-2011 Teddy Hogeborn
13
* Copyright © 2008-2011 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
#ifndef _LARGEFILE_SOURCE
34
#define _LARGEFILE_SOURCE
36
#ifndef _FILE_OFFSET_BITS
37
#define _FILE_OFFSET_BITS 64
40
#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */
42
#include <stdio.h> /* fprintf(), stderr, fwrite(),
43
stdout, ferror(), remove() */
44
#include <stdint.h> /* uint16_t, uint32_t */
45
#include <stddef.h> /* NULL, size_t, ssize_t */
46
#include <stdlib.h> /* free(), EXIT_SUCCESS, srand(),
48
#include <stdbool.h> /* bool, false, true */
49
#include <string.h> /* memset(), strcmp(), strlen(),
50
strerror(), asprintf(), strcpy() */
51
#include <sys/ioctl.h> /* ioctl */
52
#include <sys/types.h> /* socket(), inet_pton(), sockaddr,
53
sockaddr_in6, PF_INET6,
54
SOCK_STREAM, uid_t, gid_t, open(),
56
#include <sys/stat.h> /* open() */
57
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
58
inet_pton(), connect() */
59
#include <fcntl.h> /* open() */
60
#include <dirent.h> /* opendir(), struct dirent, readdir()
62
#include <inttypes.h> /* PRIu16, PRIdMAX, intmax_t,
64
#include <assert.h> /* assert() */
65
#include <errno.h> /* perror(), errno */
66
#include <time.h> /* nanosleep(), time(), sleep() */
67
#include <net/if.h> /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
68
SIOCSIFFLAGS, if_indextoname(),
69
if_nametoindex(), IF_NAMESIZE */
70
#include <netinet/in.h> /* IN6_IS_ADDR_LINKLOCAL,
71
INET_ADDRSTRLEN, INET6_ADDRSTRLEN
73
#include <unistd.h> /* close(), SEEK_SET, off_t, write(),
74
getuid(), getgid(), seteuid(),
76
#include <arpa/inet.h> /* inet_pton(), htons, inet_ntop() */
77
#include <iso646.h> /* not, or, and */
78
#include <argp.h> /* struct argp_option, error_t, struct
79
argp_state, struct argp,
80
argp_parse(), ARGP_KEY_ARG,
81
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
82
#include <signal.h> /* sigemptyset(), sigaddset(),
83
sigaction(), SIGTERM, sig_atomic_t,
85
#include <sysexits.h> /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE,
86
EX_NOHOST, EX_IOERR, EX_PROTOCOL */
89
#include <sys/klog.h> /* klogctl() */
90
#endif /* __linux__ */
93
/* All Avahi types, constants and functions
96
#include <avahi-core/core.h>
97
#include <avahi-core/lookup.h>
98
#include <avahi-core/log.h>
99
#include <avahi-common/simple-watch.h>
100
#include <avahi-common/malloc.h>
101
#include <avahi-common/error.h>
104
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
107
init_gnutls_session(),
109
#include <gnutls/openpgp.h>
110
/* gnutls_certificate_set_openpgp_key_file(),
111
GNUTLS_OPENPGP_FMT_BASE64 */
114
#include <gpgme.h> /* All GPGME types, constants and
117
GPGME_PROTOCOL_OpenPGP,
120
#define BUFFER_SIZE 256
122
#define PATHDIR "/conf/conf.d/mandos"
123
#define SECKEY "seckey.txt"
124
#define PUBKEY "pubkey.txt"
127
static const char mandos_protocol_version[] = "1";
128
const char *argp_program_version = "mandos-client " VERSION;
129
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
130
static const char sys_class_net[] = "/sys/class/net";
131
char *connect_to = NULL;
133
/* Used for passing in values through the Avahi callback functions */
135
AvahiSimplePoll *simple_poll;
137
gnutls_certificate_credentials_t cred;
138
unsigned int dh_bits;
139
gnutls_dh_params_t dh_params;
140
const char *priority;
144
/* global context so signal handler can reach it*/
145
mandos_context mc = { .simple_poll = NULL, .server = NULL,
146
.dh_bits = 1024, .priority = "SECURE256"
147
":!CTYPE-X.509:+CTYPE-OPENPGP" };
149
sig_atomic_t quit_now = 0;
150
int signal_received = 0;
153
* Make additional room in "buffer" for at least BUFFER_SIZE more
154
* bytes. "buffer_capacity" is how much is currently allocated,
155
* "buffer_length" is how much is already used.
157
size_t incbuffer(char **buffer, size_t buffer_length,
158
size_t buffer_capacity){
159
if(buffer_length + BUFFER_SIZE > buffer_capacity){
160
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
164
buffer_capacity += BUFFER_SIZE;
166
return buffer_capacity;
172
static bool init_gpgme(const char *seckey,
173
const char *pubkey, const char *tempdir){
175
gpgme_engine_info_t engine_info;
179
* Helper function to insert pub and seckey to the engine keyring.
181
bool import_key(const char *filename){
184
gpgme_data_t pgp_data;
186
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
192
rc = gpgme_data_new_from_fd(&pgp_data, fd);
193
if(rc != GPG_ERR_NO_ERROR){
194
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
195
gpgme_strsource(rc), gpgme_strerror(rc));
199
rc = gpgme_op_import(mc.ctx, pgp_data);
200
if(rc != GPG_ERR_NO_ERROR){
201
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
202
gpgme_strsource(rc), gpgme_strerror(rc));
206
ret = (int)TEMP_FAILURE_RETRY(close(fd));
210
gpgme_data_release(pgp_data);
215
fprintf(stderr, "Initializing GPGME\n");
219
gpgme_check_version(NULL);
220
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
221
if(rc != GPG_ERR_NO_ERROR){
222
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
223
gpgme_strsource(rc), gpgme_strerror(rc));
227
/* Set GPGME home directory for the OpenPGP engine only */
228
rc = gpgme_get_engine_info(&engine_info);
229
if(rc != GPG_ERR_NO_ERROR){
230
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
231
gpgme_strsource(rc), gpgme_strerror(rc));
234
while(engine_info != NULL){
235
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
236
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
237
engine_info->file_name, tempdir);
240
engine_info = engine_info->next;
242
if(engine_info == NULL){
243
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
247
/* Create new GPGME "context" */
248
rc = gpgme_new(&(mc.ctx));
249
if(rc != GPG_ERR_NO_ERROR){
250
fprintf(stderr, "bad gpgme_new: %s: %s\n",
251
gpgme_strsource(rc), gpgme_strerror(rc));
255
if(not import_key(pubkey) or not import_key(seckey)){
263
* Decrypt OpenPGP data.
264
* Returns -1 on error
266
static ssize_t pgp_packet_decrypt(const char *cryptotext,
269
gpgme_data_t dh_crypto, dh_plain;
272
size_t plaintext_capacity = 0;
273
ssize_t plaintext_length = 0;
276
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
279
/* Create new GPGME data buffer from memory cryptotext */
280
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
282
if(rc != GPG_ERR_NO_ERROR){
283
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
284
gpgme_strsource(rc), gpgme_strerror(rc));
288
/* Create new empty GPGME data buffer for the plaintext */
289
rc = gpgme_data_new(&dh_plain);
290
if(rc != GPG_ERR_NO_ERROR){
291
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
292
gpgme_strsource(rc), gpgme_strerror(rc));
293
gpgme_data_release(dh_crypto);
297
/* Decrypt data from the cryptotext data buffer to the plaintext
299
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
300
if(rc != GPG_ERR_NO_ERROR){
301
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
302
gpgme_strsource(rc), gpgme_strerror(rc));
303
plaintext_length = -1;
305
gpgme_decrypt_result_t result;
306
result = gpgme_op_decrypt_result(mc.ctx);
308
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
310
fprintf(stderr, "Unsupported algorithm: %s\n",
311
result->unsupported_algorithm);
312
fprintf(stderr, "Wrong key usage: %u\n",
313
result->wrong_key_usage);
314
if(result->file_name != NULL){
315
fprintf(stderr, "File name: %s\n", result->file_name);
317
gpgme_recipient_t recipient;
318
recipient = result->recipients;
319
while(recipient != NULL){
320
fprintf(stderr, "Public key algorithm: %s\n",
321
gpgme_pubkey_algo_name(recipient->pubkey_algo));
322
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
323
fprintf(stderr, "Secret key available: %s\n",
324
recipient->status == GPG_ERR_NO_SECKEY
326
recipient = recipient->next;
334
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
337
/* Seek back to the beginning of the GPGME plaintext data buffer */
338
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
339
perror("gpgme_data_seek");
340
plaintext_length = -1;
346
plaintext_capacity = incbuffer(plaintext,
347
(size_t)plaintext_length,
349
if(plaintext_capacity == 0){
351
plaintext_length = -1;
355
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
357
/* Print the data, if any */
363
perror("gpgme_data_read");
364
plaintext_length = -1;
367
plaintext_length += ret;
371
fprintf(stderr, "Decrypted password is: ");
372
for(ssize_t i = 0; i < plaintext_length; i++){
373
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
375
fprintf(stderr, "\n");
380
/* Delete the GPGME cryptotext data buffer */
381
gpgme_data_release(dh_crypto);
383
/* Delete the GPGME plaintext data buffer */
384
gpgme_data_release(dh_plain);
385
return plaintext_length;
388
static const char * safer_gnutls_strerror(int value){
389
const char *ret = gnutls_strerror(value); /* Spurious warning from
390
-Wunreachable-code */
396
/* GnuTLS log function callback */
397
static void debuggnutls(__attribute__((unused)) int level,
399
fprintf(stderr, "GnuTLS: %s", string);
402
static int init_gnutls_global(const char *pubkeyfilename,
403
const char *seckeyfilename){
407
fprintf(stderr, "Initializing GnuTLS\n");
410
ret = gnutls_global_init();
411
if(ret != GNUTLS_E_SUCCESS){
412
fprintf(stderr, "GnuTLS global_init: %s\n",
413
safer_gnutls_strerror(ret));
418
/* "Use a log level over 10 to enable all debugging options."
421
gnutls_global_set_log_level(11);
422
gnutls_global_set_log_function(debuggnutls);
425
/* OpenPGP credentials */
426
ret = gnutls_certificate_allocate_credentials(&mc.cred);
427
if(ret != GNUTLS_E_SUCCESS){
428
fprintf(stderr, "GnuTLS memory error: %s\n",
429
safer_gnutls_strerror(ret));
430
gnutls_global_deinit();
435
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
436
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
440
ret = gnutls_certificate_set_openpgp_key_file
441
(mc.cred, pubkeyfilename, seckeyfilename,
442
GNUTLS_OPENPGP_FMT_BASE64);
443
if(ret != GNUTLS_E_SUCCESS){
445
"Error[%d] while reading the OpenPGP key pair ('%s',"
446
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
447
fprintf(stderr, "The GnuTLS error is: %s\n",
448
safer_gnutls_strerror(ret));
452
/* GnuTLS server initialization */
453
ret = gnutls_dh_params_init(&mc.dh_params);
454
if(ret != GNUTLS_E_SUCCESS){
455
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
456
" %s\n", safer_gnutls_strerror(ret));
459
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
460
if(ret != GNUTLS_E_SUCCESS){
461
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
462
safer_gnutls_strerror(ret));
466
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
472
gnutls_certificate_free_credentials(mc.cred);
473
gnutls_global_deinit();
474
gnutls_dh_params_deinit(mc.dh_params);
478
static int init_gnutls_session(gnutls_session_t *session){
480
/* GnuTLS session creation */
482
ret = gnutls_init(session, GNUTLS_SERVER);
486
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
487
if(ret != GNUTLS_E_SUCCESS){
488
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
489
safer_gnutls_strerror(ret));
495
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
497
gnutls_deinit(*session);
500
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
501
if(ret != GNUTLS_E_SUCCESS){
502
fprintf(stderr, "Syntax error at: %s\n", err);
503
fprintf(stderr, "GnuTLS error: %s\n",
504
safer_gnutls_strerror(ret));
505
gnutls_deinit(*session);
511
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
514
gnutls_deinit(*session);
517
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
518
if(ret != GNUTLS_E_SUCCESS){
519
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
520
safer_gnutls_strerror(ret));
521
gnutls_deinit(*session);
525
/* ignore client certificate if any. */
526
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
528
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
533
/* Avahi log function callback */
534
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
535
__attribute__((unused)) const char *txt){}
537
/* Called when a Mandos server is found */
538
static int start_mandos_communication(const char *ip, uint16_t port,
539
AvahiIfIndex if_index,
541
int ret, tcp_sd = -1;
544
struct sockaddr_in in;
545
struct sockaddr_in6 in6;
548
char *decrypted_buffer = NULL;
549
size_t buffer_length = 0;
550
size_t buffer_capacity = 0;
553
gnutls_session_t session;
554
int pf; /* Protocol family */
571
fprintf(stderr, "Bad address family: %d\n", af);
576
ret = init_gnutls_session(&session);
582
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
586
tcp_sd = socket(pf, SOCK_STREAM, 0);
599
memset(&to, 0, sizeof(to));
601
to.in6.sin6_family = (sa_family_t)af;
602
ret = inet_pton(af, ip, &to.in6.sin6_addr);
604
to.in.sin_family = (sa_family_t)af;
605
ret = inet_pton(af, ip, &to.in.sin_addr);
615
fprintf(stderr, "Bad address: %s\n", ip);
620
to.in6.sin6_port = htons(port); /* Spurious warnings from
622
-Wunreachable-code */
624
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
625
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
627
if(if_index == AVAHI_IF_UNSPEC){
628
fprintf(stderr, "An IPv6 link-local address is incomplete"
629
" without a network interface\n");
633
/* Set the network interface number as scope */
634
to.in6.sin6_scope_id = (uint32_t)if_index;
637
to.in.sin_port = htons(port); /* Spurious warnings from
639
-Wunreachable-code */
648
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
649
char interface[IF_NAMESIZE];
650
if(if_indextoname((unsigned int)if_index, interface) == NULL){
651
perror("if_indextoname");
653
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
654
ip, interface, port);
657
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
660
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
661
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
664
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
667
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
673
if(strcmp(addrstr, ip) != 0){
674
fprintf(stderr, "Canonical address form: %s\n", addrstr);
685
ret = connect(tcp_sd, &to.in6, sizeof(to));
687
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
690
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
703
const char *out = mandos_protocol_version;
706
size_t out_size = strlen(out);
707
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
708
out_size - written));
715
written += (size_t)ret;
716
if(written < out_size){
719
if(out == mandos_protocol_version){
734
fprintf(stderr, "Establishing TLS session with %s\n", ip);
742
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
750
ret = gnutls_handshake(session);
755
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
757
if(ret != GNUTLS_E_SUCCESS){
759
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
766
/* Read OpenPGP packet that contains the wanted password */
769
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
780
buffer_capacity = incbuffer(&buffer, buffer_length,
782
if(buffer_capacity == 0){
794
sret = gnutls_record_recv(session, buffer+buffer_length,
801
case GNUTLS_E_INTERRUPTED:
804
case GNUTLS_E_REHANDSHAKE:
806
ret = gnutls_handshake(session);
812
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
814
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
821
fprintf(stderr, "Unknown error while reading data from"
822
" encrypted session with Mandos server\n");
823
gnutls_bye(session, GNUTLS_SHUT_RDWR);
828
buffer_length += (size_t) sret;
833
fprintf(stderr, "Closing TLS session\n");
842
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
847
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
849
if(buffer_length > 0){
850
ssize_t decrypted_buffer_size;
851
decrypted_buffer_size = pgp_packet_decrypt(buffer,
854
if(decrypted_buffer_size >= 0){
857
while(written < (size_t) decrypted_buffer_size){
863
ret = (int)fwrite(decrypted_buffer + written, 1,
864
(size_t)decrypted_buffer_size - written,
866
if(ret == 0 and ferror(stdout)){
869
fprintf(stderr, "Error writing encrypted data: %s\n",
875
written += (size_t)ret;
881
/* Shutdown procedure */
886
free(decrypted_buffer);
889
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
897
gnutls_deinit(session);
907
static void resolve_callback(AvahiSServiceResolver *r,
908
AvahiIfIndex interface,
910
AvahiResolverEvent event,
914
const char *host_name,
915
const AvahiAddress *address,
917
AVAHI_GCC_UNUSED AvahiStringList *txt,
918
AVAHI_GCC_UNUSED AvahiLookupResultFlags
920
AVAHI_GCC_UNUSED void* userdata){
923
/* Called whenever a service has been resolved successfully or
932
case AVAHI_RESOLVER_FAILURE:
933
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
934
" of type '%s' in domain '%s': %s\n", name, type, domain,
935
avahi_strerror(avahi_server_errno(mc.server)));
938
case AVAHI_RESOLVER_FOUND:
940
char ip[AVAHI_ADDRESS_STR_MAX];
941
avahi_address_snprint(ip, sizeof(ip), address);
943
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
944
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
945
ip, (intmax_t)interface, port);
947
int ret = start_mandos_communication(ip, port, interface,
948
avahi_proto_to_af(proto));
950
avahi_simple_poll_quit(mc.simple_poll);
954
avahi_s_service_resolver_free(r);
957
static void browse_callback(AvahiSServiceBrowser *b,
958
AvahiIfIndex interface,
959
AvahiProtocol protocol,
960
AvahiBrowserEvent event,
964
AVAHI_GCC_UNUSED AvahiLookupResultFlags
966
AVAHI_GCC_UNUSED void* userdata){
969
/* Called whenever a new services becomes available on the LAN or
970
is removed from the LAN */
978
case AVAHI_BROWSER_FAILURE:
980
fprintf(stderr, "(Avahi browser) %s\n",
981
avahi_strerror(avahi_server_errno(mc.server)));
982
avahi_simple_poll_quit(mc.simple_poll);
985
case AVAHI_BROWSER_NEW:
986
/* We ignore the returned Avahi resolver object. In the callback
987
function we free it. If the Avahi server is terminated before
988
the callback function is called the Avahi server will free the
991
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
992
name, type, domain, protocol, 0,
993
resolve_callback, NULL) == NULL)
994
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
995
name, avahi_strerror(avahi_server_errno(mc.server)));
998
case AVAHI_BROWSER_REMOVE:
1001
case AVAHI_BROWSER_ALL_FOR_NOW:
1002
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1004
fprintf(stderr, "No Mandos server found, still searching...\n");
1010
/* stop main loop after sigterm has been called */
1011
static void handle_sigterm(int sig){
1016
signal_received = sig;
1017
int old_errno = errno;
1018
if(mc.simple_poll != NULL){
1019
avahi_simple_poll_quit(mc.simple_poll);
1025
* This function determines if a directory entry in /sys/class/net
1026
* corresponds to an acceptable network device.
1027
* (This function is passed to scandir(3) as a filter function.)
1029
int good_interface(const struct dirent *if_entry){
1031
char *flagname = NULL;
1032
if(if_entry->d_name[0] == '.'){
1035
int ret = asprintf(&flagname, "%s/%s/flags", sys_class_net,
1041
int flags_fd = (int)TEMP_FAILURE_RETRY(open(flagname, O_RDONLY));
1048
typedef short ifreq_flags; /* ifreq.ifr_flags in netdevice(7) */
1049
/* read line from flags_fd */
1050
ssize_t to_read = 2+(sizeof(ifreq_flags)*2)+1; /* "0x1003\n" */
1051
char *flagstring = malloc((size_t)to_read+1); /* +1 for final \0 */
1052
flagstring[(size_t)to_read] = '\0';
1053
if(flagstring == NULL){
1059
ssret = (ssize_t)TEMP_FAILURE_RETRY(read(flags_fd, flagstring,
1076
tmpmax = strtoimax(flagstring, &tmp, 0);
1077
if(errno != 0 or tmp == flagstring or (*tmp != '\0'
1078
and not (isspace(*tmp)))
1079
or tmpmax != (ifreq_flags)tmpmax){
1081
fprintf(stderr, "Invalid flags \"%s\" for interface \"%s\"\n",
1082
flagstring, if_entry->d_name);
1088
ifreq_flags flags = (ifreq_flags)tmpmax;
1089
/* Reject the loopback device */
1090
if(flags & IFF_LOOPBACK){
1092
fprintf(stderr, "Rejecting loopback interface \"%s\"\n",
1097
/* Accept point-to-point devices only if connect_to is specified */
1098
if(connect_to != NULL and (flags & IFF_POINTOPOINT)){
1100
fprintf(stderr, "Accepting point-to-point interface \"%s\"\n",
1105
/* Otherwise, reject non-broadcast-capable devices */
1106
if(not (flags & IFF_BROADCAST)){
1108
fprintf(stderr, "Rejecting non-broadcast interface \"%s\"\n",
1113
/* Reject non-ARP interfaces (including dummy interfaces) */
1114
if(flags & IFF_NOARP){
1116
fprintf(stderr, "Rejecting non-ARP interface \"%s\"\n",
1121
/* Accept this device */
1123
fprintf(stderr, "Interface \"%s\" is acceptable\n",
1129
int main(int argc, char *argv[]){
1130
AvahiSServiceBrowser *sb = NULL;
1135
int exitcode = EXIT_SUCCESS;
1136
const char *interface = "";
1137
struct ifreq network;
1139
bool take_down_interface = false;
1142
char tempdir[] = "/tmp/mandosXXXXXX";
1143
bool tempdir_created = false;
1144
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1145
const char *seckey = PATHDIR "/" SECKEY;
1146
const char *pubkey = PATHDIR "/" PUBKEY;
1148
bool gnutls_initialized = false;
1149
bool gpgme_initialized = false;
1152
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1153
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1158
/* Lower any group privileges we might have, just to be safe */
1165
/* Lower user privileges (temporarily) */
1177
struct argp_option options[] = {
1178
{ .name = "debug", .key = 128,
1179
.doc = "Debug mode", .group = 3 },
1180
{ .name = "connect", .key = 'c',
1181
.arg = "ADDRESS:PORT",
1182
.doc = "Connect directly to a specific Mandos server",
1184
{ .name = "interface", .key = 'i',
1186
.doc = "Network interface that will be used to search for"
1189
{ .name = "seckey", .key = 's',
1191
.doc = "OpenPGP secret key file base name",
1193
{ .name = "pubkey", .key = 'p',
1195
.doc = "OpenPGP public key file base name",
1197
{ .name = "dh-bits", .key = 129,
1199
.doc = "Bit length of the prime number used in the"
1200
" Diffie-Hellman key exchange",
1202
{ .name = "priority", .key = 130,
1204
.doc = "GnuTLS priority string for the TLS handshake",
1206
{ .name = "delay", .key = 131,
1208
.doc = "Maximum delay to wait for interface startup",
1211
* These reproduce what we would get without ARGP_NO_HELP
1213
{ .name = "help", .key = '?',
1214
.doc = "Give this help list", .group = -1 },
1215
{ .name = "usage", .key = -3,
1216
.doc = "Give a short usage message", .group = -1 },
1217
{ .name = "version", .key = 'V',
1218
.doc = "Print program version", .group = -1 },
1222
error_t parse_opt(int key, char *arg,
1223
struct argp_state *state){
1226
case 128: /* --debug */
1229
case 'c': /* --connect */
1232
case 'i': /* --interface */
1235
case 's': /* --seckey */
1238
case 'p': /* --pubkey */
1241
case 129: /* --dh-bits */
1243
tmpmax = strtoimax(arg, &tmp, 10);
1244
if(errno != 0 or tmp == arg or *tmp != '\0'
1245
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1246
argp_error(state, "Bad number of DH bits");
1248
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1250
case 130: /* --priority */
1253
case 131: /* --delay */
1255
delay = strtof(arg, &tmp);
1256
if(errno != 0 or tmp == arg or *tmp != '\0'){
1257
argp_error(state, "Bad delay");
1261
* These reproduce what we would get without ARGP_NO_HELP
1263
case '?': /* --help */
1264
argp_state_help(state, state->out_stream,
1265
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1266
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1267
case -3: /* --usage */
1268
argp_state_help(state, state->out_stream,
1269
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1270
case 'V': /* --version */
1271
fprintf(state->out_stream, "%s\n", argp_program_version);
1272
exit(argp_err_exit_status);
1275
return ARGP_ERR_UNKNOWN;
1280
struct argp argp = { .options = options, .parser = parse_opt,
1282
.doc = "Mandos client -- Get and decrypt"
1283
" passwords from a Mandos server" };
1284
ret = argp_parse(&argp, argc, argv,
1285
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1292
perror("argp_parse");
1293
exitcode = EX_OSERR;
1296
exitcode = EX_USAGE;
1302
avahi_set_log_function(empty_log);
1305
if(interface[0] == '\0'){
1306
struct dirent **direntries;
1307
ret = scandir(sys_class_net, &direntries, good_interface,
1310
/* Pick the first good interface */
1311
interface = strdup(direntries[0]->d_name);
1313
fprintf(stderr, "Using interface \"%s\"\n", interface);
1315
if(interface == NULL){
1318
exitcode = EXIT_FAILURE;
1324
fprintf(stderr, "Could not find a network interface\n");
1325
exitcode = EXIT_FAILURE;
1330
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1331
from the signal handler */
1332
/* Initialize the pseudo-RNG for Avahi */
1333
srand((unsigned int) time(NULL));
1334
mc.simple_poll = avahi_simple_poll_new();
1335
if(mc.simple_poll == NULL){
1336
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1337
exitcode = EX_UNAVAILABLE;
1341
sigemptyset(&sigterm_action.sa_mask);
1342
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1344
perror("sigaddset");
1345
exitcode = EX_OSERR;
1348
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1350
perror("sigaddset");
1351
exitcode = EX_OSERR;
1354
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1356
perror("sigaddset");
1357
exitcode = EX_OSERR;
1360
/* Need to check if the handler is SIG_IGN before handling:
1361
| [[info:libc:Initial Signal Actions]] |
1362
| [[info:libc:Basic Signal Handling]] |
1364
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1366
perror("sigaction");
1369
if(old_sigterm_action.sa_handler != SIG_IGN){
1370
ret = sigaction(SIGINT, &sigterm_action, NULL);
1372
perror("sigaction");
1373
exitcode = EX_OSERR;
1377
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1379
perror("sigaction");
1382
if(old_sigterm_action.sa_handler != SIG_IGN){
1383
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1385
perror("sigaction");
1386
exitcode = EX_OSERR;
1390
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1392
perror("sigaction");
1395
if(old_sigterm_action.sa_handler != SIG_IGN){
1396
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1398
perror("sigaction");
1399
exitcode = EX_OSERR;
1404
/* If the interface is down, bring it up */
1405
if(strcmp(interface, "none") != 0){
1406
if_index = (AvahiIfIndex) if_nametoindex(interface);
1408
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1409
exitcode = EX_UNAVAILABLE;
1417
/* Re-raise priviliges */
1425
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1426
messages about the network interface to mess up the prompt */
1427
ret = klogctl(8, NULL, 5);
1428
bool restore_loglevel = true;
1430
restore_loglevel = false;
1433
#endif /* __linux__ */
1435
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1438
exitcode = EX_OSERR;
1440
if(restore_loglevel){
1441
ret = klogctl(7, NULL, 0);
1446
#endif /* __linux__ */
1447
/* Lower privileges */
1455
strcpy(network.ifr_name, interface);
1456
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1458
perror("ioctl SIOCGIFFLAGS");
1460
if(restore_loglevel){
1461
ret = klogctl(7, NULL, 0);
1466
#endif /* __linux__ */
1467
exitcode = EX_OSERR;
1468
/* Lower privileges */
1476
if((network.ifr_flags & IFF_UP) == 0){
1477
network.ifr_flags |= IFF_UP;
1478
take_down_interface = true;
1479
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1481
take_down_interface = false;
1482
perror("ioctl SIOCSIFFLAGS +IFF_UP");
1483
exitcode = EX_OSERR;
1485
if(restore_loglevel){
1486
ret = klogctl(7, NULL, 0);
1491
#endif /* __linux__ */
1492
/* Lower privileges */
1501
/* sleep checking until interface is running */
1502
for(int i=0; i < delay * 4; i++){
1503
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1505
perror("ioctl SIOCGIFFLAGS");
1506
} else if(network.ifr_flags & IFF_RUNNING){
1509
struct timespec sleeptime = { .tv_nsec = 250000000 };
1510
ret = nanosleep(&sleeptime, NULL);
1511
if(ret == -1 and errno != EINTR){
1512
perror("nanosleep");
1515
if(not take_down_interface){
1516
/* We won't need the socket anymore */
1517
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1523
if(restore_loglevel){
1524
/* Restores kernel loglevel to default */
1525
ret = klogctl(7, NULL, 0);
1530
#endif /* __linux__ */
1531
/* Lower privileges */
1533
if(take_down_interface){
1534
/* Lower privileges */
1540
/* Lower privileges permanently */
1552
ret = init_gnutls_global(pubkey, seckey);
1554
fprintf(stderr, "init_gnutls_global failed\n");
1555
exitcode = EX_UNAVAILABLE;
1558
gnutls_initialized = true;
1565
tempdir_created = true;
1566
if(mkdtemp(tempdir) == NULL){
1567
tempdir_created = false;
1576
if(not init_gpgme(pubkey, seckey, tempdir)){
1577
fprintf(stderr, "init_gpgme failed\n");
1578
exitcode = EX_UNAVAILABLE;
1581
gpgme_initialized = true;
1588
if(connect_to != NULL){
1589
/* Connect directly, do not use Zeroconf */
1590
/* (Mainly meant for debugging) */
1591
char *address = strrchr(connect_to, ':');
1592
if(address == NULL){
1593
fprintf(stderr, "No colon in address\n");
1594
exitcode = EX_USAGE;
1604
tmpmax = strtoimax(address+1, &tmp, 10);
1605
if(errno != 0 or tmp == address+1 or *tmp != '\0'
1606
or tmpmax != (uint16_t)tmpmax){
1607
fprintf(stderr, "Bad port number\n");
1608
exitcode = EX_USAGE;
1616
port = (uint16_t)tmpmax;
1618
address = connect_to;
1619
/* Colon in address indicates IPv6 */
1621
if(strchr(address, ':') != NULL){
1631
while(not quit_now){
1632
ret = start_mandos_communication(address, port, if_index, af);
1633
if(quit_now or ret == 0){
1640
exitcode = EXIT_SUCCESS;
1651
AvahiServerConfig config;
1652
/* Do not publish any local Zeroconf records */
1653
avahi_server_config_init(&config);
1654
config.publish_hinfo = 0;
1655
config.publish_addresses = 0;
1656
config.publish_workstation = 0;
1657
config.publish_domain = 0;
1659
/* Allocate a new server */
1660
mc.server = avahi_server_new(avahi_simple_poll_get
1661
(mc.simple_poll), &config, NULL,
1664
/* Free the Avahi configuration data */
1665
avahi_server_config_free(&config);
1668
/* Check if creating the Avahi server object succeeded */
1669
if(mc.server == NULL){
1670
fprintf(stderr, "Failed to create Avahi server: %s\n",
1671
avahi_strerror(error));
1672
exitcode = EX_UNAVAILABLE;
1680
/* Create the Avahi service browser */
1681
sb = avahi_s_service_browser_new(mc.server, if_index,
1682
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
1683
NULL, 0, browse_callback, NULL);
1685
fprintf(stderr, "Failed to create service browser: %s\n",
1686
avahi_strerror(avahi_server_errno(mc.server)));
1687
exitcode = EX_UNAVAILABLE;
1695
/* Run the main loop */
1698
fprintf(stderr, "Starting Avahi loop search\n");
1701
avahi_simple_poll_loop(mc.simple_poll);
1706
fprintf(stderr, "%s exiting\n", argv[0]);
1709
/* Cleanup things */
1711
avahi_s_service_browser_free(sb);
1713
if(mc.server != NULL)
1714
avahi_server_free(mc.server);
1716
if(mc.simple_poll != NULL)
1717
avahi_simple_poll_free(mc.simple_poll);
1719
if(gnutls_initialized){
1720
gnutls_certificate_free_credentials(mc.cred);
1721
gnutls_global_deinit();
1722
gnutls_dh_params_deinit(mc.dh_params);
1725
if(gpgme_initialized){
1726
gpgme_release(mc.ctx);
1729
/* Take down the network interface */
1730
if(take_down_interface){
1731
/* Re-raise priviliges */
1738
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1740
perror("ioctl SIOCGIFFLAGS");
1741
} else if(network.ifr_flags & IFF_UP) {
1742
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
1743
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1745
perror("ioctl SIOCSIFFLAGS -IFF_UP");
1748
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1752
/* Lower privileges permanently */
1761
/* Removes the temp directory used by GPGME */
1762
if(tempdir_created){
1764
struct dirent *direntry;
1765
d = opendir(tempdir);
1767
if(errno != ENOENT){
1772
direntry = readdir(d);
1773
if(direntry == NULL){
1776
/* Skip "." and ".." */
1777
if(direntry->d_name[0] == '.'
1778
and (direntry->d_name[1] == '\0'
1779
or (direntry->d_name[1] == '.'
1780
and direntry->d_name[2] == '\0'))){
1783
char *fullname = NULL;
1784
ret = asprintf(&fullname, "%s/%s", tempdir,
1790
ret = remove(fullname);
1792
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1799
ret = rmdir(tempdir);
1800
if(ret == -1 and errno != ENOENT){
1806
sigemptyset(&old_sigterm_action.sa_mask);
1807
old_sigterm_action.sa_handler = SIG_DFL;
1808
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
1809
&old_sigterm_action,
1812
perror("sigaction");
1815
ret = raise(signal_received);
1816
} while(ret != 0 and errno == EINTR);
1821
TEMP_FAILURE_RETRY(pause());