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
#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() */
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 */
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>";
131
/* Used for passing in values through the Avahi callback functions */
133
AvahiSimplePoll *simple_poll;
135
gnutls_certificate_credentials_t cred;
136
unsigned int dh_bits;
137
gnutls_dh_params_t dh_params;
138
const char *priority;
142
/* global context so signal handler can reach it*/
143
mandos_context mc = { .simple_poll = NULL, .server = NULL,
144
.dh_bits = 1024, .priority = "SECURE256"
145
":!CTYPE-X.509:+CTYPE-OPENPGP" };
147
sig_atomic_t quit_now = 0;
148
int signal_received = 0;
151
* Make additional room in "buffer" for at least BUFFER_SIZE more
152
* bytes. "buffer_capacity" is how much is currently allocated,
153
* "buffer_length" is how much is already used.
155
size_t incbuffer(char **buffer, size_t buffer_length,
156
size_t buffer_capacity){
157
if(buffer_length + BUFFER_SIZE > buffer_capacity){
158
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
162
buffer_capacity += BUFFER_SIZE;
164
return buffer_capacity;
170
static bool init_gpgme(const char *seckey,
171
const char *pubkey, const char *tempdir){
173
gpgme_engine_info_t engine_info;
177
* Helper function to insert pub and seckey to the engine keyring.
179
bool import_key(const char *filename){
182
gpgme_data_t pgp_data;
184
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
190
rc = gpgme_data_new_from_fd(&pgp_data, fd);
191
if(rc != GPG_ERR_NO_ERROR){
192
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
193
gpgme_strsource(rc), gpgme_strerror(rc));
197
rc = gpgme_op_import(mc.ctx, pgp_data);
198
if(rc != GPG_ERR_NO_ERROR){
199
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
200
gpgme_strsource(rc), gpgme_strerror(rc));
204
ret = (int)TEMP_FAILURE_RETRY(close(fd));
208
gpgme_data_release(pgp_data);
213
fprintf(stderr, "Initializing GPGME\n");
217
gpgme_check_version(NULL);
218
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
219
if(rc != GPG_ERR_NO_ERROR){
220
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
221
gpgme_strsource(rc), gpgme_strerror(rc));
225
/* Set GPGME home directory for the OpenPGP engine only */
226
rc = gpgme_get_engine_info(&engine_info);
227
if(rc != GPG_ERR_NO_ERROR){
228
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
229
gpgme_strsource(rc), gpgme_strerror(rc));
232
while(engine_info != NULL){
233
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
234
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
235
engine_info->file_name, tempdir);
238
engine_info = engine_info->next;
240
if(engine_info == NULL){
241
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
245
/* Create new GPGME "context" */
246
rc = gpgme_new(&(mc.ctx));
247
if(rc != GPG_ERR_NO_ERROR){
248
fprintf(stderr, "bad gpgme_new: %s: %s\n",
249
gpgme_strsource(rc), gpgme_strerror(rc));
253
if(not import_key(pubkey) or not import_key(seckey)){
261
* Decrypt OpenPGP data.
262
* Returns -1 on error
264
static ssize_t pgp_packet_decrypt(const char *cryptotext,
267
gpgme_data_t dh_crypto, dh_plain;
270
size_t plaintext_capacity = 0;
271
ssize_t plaintext_length = 0;
274
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
277
/* Create new GPGME data buffer from memory cryptotext */
278
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
280
if(rc != GPG_ERR_NO_ERROR){
281
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
282
gpgme_strsource(rc), gpgme_strerror(rc));
286
/* Create new empty GPGME data buffer for the plaintext */
287
rc = gpgme_data_new(&dh_plain);
288
if(rc != GPG_ERR_NO_ERROR){
289
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
290
gpgme_strsource(rc), gpgme_strerror(rc));
291
gpgme_data_release(dh_crypto);
295
/* Decrypt data from the cryptotext data buffer to the plaintext
297
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
298
if(rc != GPG_ERR_NO_ERROR){
299
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
300
gpgme_strsource(rc), gpgme_strerror(rc));
301
plaintext_length = -1;
303
gpgme_decrypt_result_t result;
304
result = gpgme_op_decrypt_result(mc.ctx);
306
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
308
fprintf(stderr, "Unsupported algorithm: %s\n",
309
result->unsupported_algorithm);
310
fprintf(stderr, "Wrong key usage: %u\n",
311
result->wrong_key_usage);
312
if(result->file_name != NULL){
313
fprintf(stderr, "File name: %s\n", result->file_name);
315
gpgme_recipient_t recipient;
316
recipient = result->recipients;
317
while(recipient != NULL){
318
fprintf(stderr, "Public key algorithm: %s\n",
319
gpgme_pubkey_algo_name(recipient->pubkey_algo));
320
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
321
fprintf(stderr, "Secret key available: %s\n",
322
recipient->status == GPG_ERR_NO_SECKEY
324
recipient = recipient->next;
332
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
335
/* Seek back to the beginning of the GPGME plaintext data buffer */
336
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
337
perror("gpgme_data_seek");
338
plaintext_length = -1;
344
plaintext_capacity = incbuffer(plaintext,
345
(size_t)plaintext_length,
347
if(plaintext_capacity == 0){
349
plaintext_length = -1;
353
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
355
/* Print the data, if any */
361
perror("gpgme_data_read");
362
plaintext_length = -1;
365
plaintext_length += ret;
369
fprintf(stderr, "Decrypted password is: ");
370
for(ssize_t i = 0; i < plaintext_length; i++){
371
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
373
fprintf(stderr, "\n");
378
/* Delete the GPGME cryptotext data buffer */
379
gpgme_data_release(dh_crypto);
381
/* Delete the GPGME plaintext data buffer */
382
gpgme_data_release(dh_plain);
383
return plaintext_length;
386
static const char * safer_gnutls_strerror(int value){
387
const char *ret = gnutls_strerror(value); /* Spurious warning from
388
-Wunreachable-code */
394
/* GnuTLS log function callback */
395
static void debuggnutls(__attribute__((unused)) int level,
397
fprintf(stderr, "GnuTLS: %s", string);
400
static int init_gnutls_global(const char *pubkeyfilename,
401
const char *seckeyfilename){
405
fprintf(stderr, "Initializing GnuTLS\n");
408
ret = gnutls_global_init();
409
if(ret != GNUTLS_E_SUCCESS){
410
fprintf(stderr, "GnuTLS global_init: %s\n",
411
safer_gnutls_strerror(ret));
416
/* "Use a log level over 10 to enable all debugging options."
419
gnutls_global_set_log_level(11);
420
gnutls_global_set_log_function(debuggnutls);
423
/* OpenPGP credentials */
424
gnutls_certificate_allocate_credentials(&mc.cred);
425
if(ret != GNUTLS_E_SUCCESS){
426
fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning
430
safer_gnutls_strerror(ret));
431
gnutls_global_deinit();
436
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
437
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
441
ret = gnutls_certificate_set_openpgp_key_file
442
(mc.cred, pubkeyfilename, seckeyfilename,
443
GNUTLS_OPENPGP_FMT_BASE64);
444
if(ret != GNUTLS_E_SUCCESS){
446
"Error[%d] while reading the OpenPGP key pair ('%s',"
447
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
448
fprintf(stderr, "The GnuTLS error is: %s\n",
449
safer_gnutls_strerror(ret));
453
/* GnuTLS server initialization */
454
ret = gnutls_dh_params_init(&mc.dh_params);
455
if(ret != GNUTLS_E_SUCCESS){
456
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
457
" %s\n", safer_gnutls_strerror(ret));
460
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
461
if(ret != GNUTLS_E_SUCCESS){
462
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
463
safer_gnutls_strerror(ret));
467
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
473
gnutls_certificate_free_credentials(mc.cred);
474
gnutls_global_deinit();
475
gnutls_dh_params_deinit(mc.dh_params);
479
static int init_gnutls_session(gnutls_session_t *session){
481
/* GnuTLS session creation */
483
ret = gnutls_init(session, GNUTLS_SERVER);
487
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
488
if(ret != GNUTLS_E_SUCCESS){
489
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
490
safer_gnutls_strerror(ret));
496
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
498
gnutls_deinit(*session);
501
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
502
if(ret != GNUTLS_E_SUCCESS){
503
fprintf(stderr, "Syntax error at: %s\n", err);
504
fprintf(stderr, "GnuTLS error: %s\n",
505
safer_gnutls_strerror(ret));
506
gnutls_deinit(*session);
512
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
515
gnutls_deinit(*session);
518
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
519
if(ret != GNUTLS_E_SUCCESS){
520
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
521
safer_gnutls_strerror(ret));
522
gnutls_deinit(*session);
526
/* ignore client certificate if any. */
527
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
529
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
534
/* Avahi log function callback */
535
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
536
__attribute__((unused)) const char *txt){}
538
/* Called when a Mandos server is found */
539
static int start_mandos_communication(const char *ip, uint16_t port,
540
AvahiIfIndex if_index,
542
int ret, tcp_sd = -1;
545
struct sockaddr_in in;
546
struct sockaddr_in6 in6;
549
char *decrypted_buffer = NULL;
550
size_t buffer_length = 0;
551
size_t buffer_capacity = 0;
554
gnutls_session_t session;
555
int pf; /* Protocol family */
572
fprintf(stderr, "Bad address family: %d\n", af);
577
ret = init_gnutls_session(&session);
583
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
587
tcp_sd = socket(pf, SOCK_STREAM, 0);
600
memset(&to, 0, sizeof(to));
602
to.in6.sin6_family = (sa_family_t)af;
603
ret = inet_pton(af, ip, &to.in6.sin6_addr);
605
to.in.sin_family = (sa_family_t)af;
606
ret = inet_pton(af, ip, &to.in.sin_addr);
616
fprintf(stderr, "Bad address: %s\n", ip);
621
to.in6.sin6_port = htons(port); /* Spurious warnings from
623
-Wunreachable-code */
625
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
626
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
628
if(if_index == AVAHI_IF_UNSPEC){
629
fprintf(stderr, "An IPv6 link-local address is incomplete"
630
" without a network interface\n");
634
/* Set the network interface number as scope */
635
to.in6.sin6_scope_id = (uint32_t)if_index;
638
to.in.sin_port = htons(port); /* Spurious warnings from
640
-Wunreachable-code */
649
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
650
char interface[IF_NAMESIZE];
651
if(if_indextoname((unsigned int)if_index, interface) == NULL){
652
perror("if_indextoname");
654
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
655
ip, interface, port);
658
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
661
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
662
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
665
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
668
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
674
if(strcmp(addrstr, ip) != 0){
675
fprintf(stderr, "Canonical address form: %s\n", addrstr);
686
ret = connect(tcp_sd, &to.in6, sizeof(to));
688
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
702
const char *out = mandos_protocol_version;
705
size_t out_size = strlen(out);
706
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
707
out_size - written));
714
written += (size_t)ret;
715
if(written < out_size){
718
if(out == mandos_protocol_version){
733
fprintf(stderr, "Establishing TLS session with %s\n", ip);
741
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
749
ret = gnutls_handshake(session);
754
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
756
if(ret != GNUTLS_E_SUCCESS){
758
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
765
/* Read OpenPGP packet that contains the wanted password */
768
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
779
buffer_capacity = incbuffer(&buffer, buffer_length,
781
if(buffer_capacity == 0){
793
sret = gnutls_record_recv(session, buffer+buffer_length,
800
case GNUTLS_E_INTERRUPTED:
803
case GNUTLS_E_REHANDSHAKE:
805
ret = gnutls_handshake(session);
811
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
813
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
820
fprintf(stderr, "Unknown error while reading data from"
821
" encrypted session with Mandos server\n");
822
gnutls_bye(session, GNUTLS_SHUT_RDWR);
827
buffer_length += (size_t) sret;
832
fprintf(stderr, "Closing TLS session\n");
841
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
846
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
848
if(buffer_length > 0){
849
ssize_t decrypted_buffer_size;
850
decrypted_buffer_size = pgp_packet_decrypt(buffer,
853
if(decrypted_buffer_size >= 0){
856
while(written < (size_t) decrypted_buffer_size){
862
ret = (int)fwrite(decrypted_buffer + written, 1,
863
(size_t)decrypted_buffer_size - written,
865
if(ret == 0 and ferror(stdout)){
868
fprintf(stderr, "Error writing encrypted data: %s\n",
874
written += (size_t)ret;
880
/* Shutdown procedure */
885
free(decrypted_buffer);
888
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
896
gnutls_deinit(session);
906
static void resolve_callback(AvahiSServiceResolver *r,
907
AvahiIfIndex interface,
909
AvahiResolverEvent event,
913
const char *host_name,
914
const AvahiAddress *address,
916
AVAHI_GCC_UNUSED AvahiStringList *txt,
917
AVAHI_GCC_UNUSED AvahiLookupResultFlags
919
AVAHI_GCC_UNUSED void* userdata){
922
/* Called whenever a service has been resolved successfully or
931
case AVAHI_RESOLVER_FAILURE:
932
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
933
" of type '%s' in domain '%s': %s\n", name, type, domain,
934
avahi_strerror(avahi_server_errno(mc.server)));
937
case AVAHI_RESOLVER_FOUND:
939
char ip[AVAHI_ADDRESS_STR_MAX];
940
avahi_address_snprint(ip, sizeof(ip), address);
942
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
943
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
944
ip, (intmax_t)interface, port);
946
int ret = start_mandos_communication(ip, port, interface,
947
avahi_proto_to_af(proto));
949
avahi_simple_poll_quit(mc.simple_poll);
953
avahi_s_service_resolver_free(r);
956
static void browse_callback(AvahiSServiceBrowser *b,
957
AvahiIfIndex interface,
958
AvahiProtocol protocol,
959
AvahiBrowserEvent event,
963
AVAHI_GCC_UNUSED AvahiLookupResultFlags
965
AVAHI_GCC_UNUSED void* userdata){
968
/* Called whenever a new services becomes available on the LAN or
969
is removed from the LAN */
977
case AVAHI_BROWSER_FAILURE:
979
fprintf(stderr, "(Avahi browser) %s\n",
980
avahi_strerror(avahi_server_errno(mc.server)));
981
avahi_simple_poll_quit(mc.simple_poll);
984
case AVAHI_BROWSER_NEW:
985
/* We ignore the returned Avahi resolver object. In the callback
986
function we free it. If the Avahi server is terminated before
987
the callback function is called the Avahi server will free the
990
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
991
name, type, domain, protocol, 0,
992
resolve_callback, NULL) == NULL)
993
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
994
name, avahi_strerror(avahi_server_errno(mc.server)));
997
case AVAHI_BROWSER_REMOVE:
1000
case AVAHI_BROWSER_ALL_FOR_NOW:
1001
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1003
fprintf(stderr, "No Mandos server found, still searching...\n");
1009
/* stop main loop after sigterm has been called */
1010
static void handle_sigterm(int sig){
1015
signal_received = sig;
1016
int old_errno = errno;
1017
if(mc.simple_poll != NULL){
1018
avahi_simple_poll_quit(mc.simple_poll);
1023
int main(int argc, char *argv[]){
1024
AvahiSServiceBrowser *sb = NULL;
1029
int exitcode = EXIT_SUCCESS;
1030
const char *interface = "eth0";
1031
struct ifreq network;
1033
bool take_down_interface = false;
1036
char *connect_to = NULL;
1037
char tempdir[] = "/tmp/mandosXXXXXX";
1038
bool tempdir_created = false;
1039
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1040
const char *seckey = PATHDIR "/" SECKEY;
1041
const char *pubkey = PATHDIR "/" PUBKEY;
1043
bool gnutls_initialized = false;
1044
bool gpgme_initialized = false;
1047
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1048
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1053
/* Lower any group privileges we might have, just to be safe */
1060
/* Lower user privileges (temporarily) */
1072
struct argp_option options[] = {
1073
{ .name = "debug", .key = 128,
1074
.doc = "Debug mode", .group = 3 },
1075
{ .name = "connect", .key = 'c',
1076
.arg = "ADDRESS:PORT",
1077
.doc = "Connect directly to a specific Mandos server",
1079
{ .name = "interface", .key = 'i',
1081
.doc = "Network interface that will be used to search for"
1084
{ .name = "seckey", .key = 's',
1086
.doc = "OpenPGP secret key file base name",
1088
{ .name = "pubkey", .key = 'p',
1090
.doc = "OpenPGP public key file base name",
1092
{ .name = "dh-bits", .key = 129,
1094
.doc = "Bit length of the prime number used in the"
1095
" Diffie-Hellman key exchange",
1097
{ .name = "priority", .key = 130,
1099
.doc = "GnuTLS priority string for the TLS handshake",
1101
{ .name = "delay", .key = 131,
1103
.doc = "Maximum delay to wait for interface startup",
1106
* These reproduce what we would get without ARGP_NO_HELP
1108
{ .name = "help", .key = '?',
1109
.doc = "Give this help list", .group = -1 },
1110
{ .name = "usage", .key = -3,
1111
.doc = "Give a short usage message", .group = -1 },
1112
{ .name = "version", .key = 'V',
1113
.doc = "Print program version", .group = -1 },
1117
error_t parse_opt(int key, char *arg,
1118
struct argp_state *state){
1121
case 128: /* --debug */
1124
case 'c': /* --connect */
1127
case 'i': /* --interface */
1130
case 's': /* --seckey */
1133
case 'p': /* --pubkey */
1136
case 129: /* --dh-bits */
1138
tmpmax = strtoimax(arg, &tmp, 10);
1139
if(errno != 0 or tmp == arg or *tmp != '\0'
1140
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1141
argp_error(state, "Bad number of DH bits");
1143
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1145
case 130: /* --priority */
1148
case 131: /* --delay */
1150
delay = strtof(arg, &tmp);
1151
if(errno != 0 or tmp == arg or *tmp != '\0'){
1152
argp_error(state, "Bad delay");
1156
* These reproduce what we would get without ARGP_NO_HELP
1158
case '?': /* --help */
1159
argp_state_help(state, state->out_stream,
1160
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1161
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1162
case -3: /* --usage */
1163
argp_state_help(state, state->out_stream,
1164
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1165
case 'V': /* --version */
1166
fprintf(state->out_stream, "%s\n", argp_program_version);
1167
exit(argp_err_exit_status);
1170
return ARGP_ERR_UNKNOWN;
1175
struct argp argp = { .options = options, .parser = parse_opt,
1177
.doc = "Mandos client -- Get and decrypt"
1178
" passwords from a Mandos server" };
1179
ret = argp_parse(&argp, argc, argv,
1180
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1187
perror("argp_parse");
1188
exitcode = EX_OSERR;
1191
exitcode = EX_USAGE;
1197
avahi_set_log_function(empty_log);
1200
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1201
from the signal handler */
1202
/* Initialize the pseudo-RNG for Avahi */
1203
srand((unsigned int) time(NULL));
1204
mc.simple_poll = avahi_simple_poll_new();
1205
if(mc.simple_poll == NULL){
1206
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1207
exitcode = EX_UNAVAILABLE;
1211
sigemptyset(&sigterm_action.sa_mask);
1212
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1214
perror("sigaddset");
1215
exitcode = EX_OSERR;
1218
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1220
perror("sigaddset");
1221
exitcode = EX_OSERR;
1224
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1226
perror("sigaddset");
1227
exitcode = EX_OSERR;
1230
/* Need to check if the handler is SIG_IGN before handling:
1231
| [[info:libc:Initial Signal Actions]] |
1232
| [[info:libc:Basic Signal Handling]] |
1234
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1236
perror("sigaction");
1239
if(old_sigterm_action.sa_handler != SIG_IGN){
1240
ret = sigaction(SIGINT, &sigterm_action, NULL);
1242
perror("sigaction");
1243
exitcode = EX_OSERR;
1247
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1249
perror("sigaction");
1252
if(old_sigterm_action.sa_handler != SIG_IGN){
1253
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1255
perror("sigaction");
1256
exitcode = EX_OSERR;
1260
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1262
perror("sigaction");
1265
if(old_sigterm_action.sa_handler != SIG_IGN){
1266
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1268
perror("sigaction");
1269
exitcode = EX_OSERR;
1274
/* If the interface is down, bring it up */
1275
if(interface[0] != '\0'){
1276
if_index = (AvahiIfIndex) if_nametoindex(interface);
1278
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1279
exitcode = EX_UNAVAILABLE;
1287
/* Re-raise priviliges */
1295
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1296
messages about the network interface to mess up the prompt */
1297
ret = klogctl(8, NULL, 5);
1298
bool restore_loglevel = true;
1300
restore_loglevel = false;
1303
#endif /* __linux__ */
1305
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1308
exitcode = EX_OSERR;
1310
if(restore_loglevel){
1311
ret = klogctl(7, NULL, 0);
1316
#endif /* __linux__ */
1317
/* Lower privileges */
1325
strcpy(network.ifr_name, interface);
1326
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1328
perror("ioctl SIOCGIFFLAGS");
1330
if(restore_loglevel){
1331
ret = klogctl(7, NULL, 0);
1336
#endif /* __linux__ */
1337
exitcode = EX_OSERR;
1338
/* Lower privileges */
1346
if((network.ifr_flags & IFF_UP) == 0){
1347
network.ifr_flags |= IFF_UP;
1348
take_down_interface = true;
1349
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1351
take_down_interface = false;
1352
perror("ioctl SIOCSIFFLAGS");
1353
exitcode = EX_OSERR;
1355
if(restore_loglevel){
1356
ret = klogctl(7, NULL, 0);
1361
#endif /* __linux__ */
1362
/* Lower privileges */
1371
/* sleep checking until interface is running */
1372
for(int i=0; i < delay * 4; i++){
1373
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1375
perror("ioctl SIOCGIFFLAGS");
1376
} else if(network.ifr_flags & IFF_RUNNING){
1379
struct timespec sleeptime = { .tv_nsec = 250000000 };
1380
ret = nanosleep(&sleeptime, NULL);
1381
if(ret == -1 and errno != EINTR){
1382
perror("nanosleep");
1385
if(not take_down_interface){
1386
/* We won't need the socket anymore */
1387
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1393
if(restore_loglevel){
1394
/* Restores kernel loglevel to default */
1395
ret = klogctl(7, NULL, 0);
1400
#endif /* __linux__ */
1401
/* Lower privileges */
1403
if(take_down_interface){
1404
/* Lower privileges */
1410
/* Lower privileges permanently */
1422
ret = init_gnutls_global(pubkey, seckey);
1424
fprintf(stderr, "init_gnutls_global failed\n");
1425
exitcode = EX_UNAVAILABLE;
1428
gnutls_initialized = true;
1435
tempdir_created = true;
1436
if(mkdtemp(tempdir) == NULL){
1437
tempdir_created = false;
1446
if(not init_gpgme(pubkey, seckey, tempdir)){
1447
fprintf(stderr, "init_gpgme failed\n");
1448
exitcode = EX_UNAVAILABLE;
1451
gpgme_initialized = true;
1458
if(connect_to != NULL){
1459
/* Connect directly, do not use Zeroconf */
1460
/* (Mainly meant for debugging) */
1461
char *address = strrchr(connect_to, ':');
1462
if(address == NULL){
1463
fprintf(stderr, "No colon in address\n");
1464
exitcode = EX_USAGE;
1474
tmpmax = strtoimax(address+1, &tmp, 10);
1475
if(errno != 0 or tmp == address+1 or *tmp != '\0'
1476
or tmpmax != (uint16_t)tmpmax){
1477
fprintf(stderr, "Bad port number\n");
1478
exitcode = EX_USAGE;
1486
port = (uint16_t)tmpmax;
1488
address = connect_to;
1489
/* Colon in address indicates IPv6 */
1491
if(strchr(address, ':') != NULL){
1501
ret = start_mandos_communication(address, port, if_index, af);
1507
exitcode = EX_NOHOST;
1510
exitcode = EX_USAGE;
1513
exitcode = EX_IOERR;
1516
exitcode = EX_PROTOCOL;
1519
exitcode = EX_OSERR;
1523
exitcode = EXIT_SUCCESS;
1533
AvahiServerConfig config;
1534
/* Do not publish any local Zeroconf records */
1535
avahi_server_config_init(&config);
1536
config.publish_hinfo = 0;
1537
config.publish_addresses = 0;
1538
config.publish_workstation = 0;
1539
config.publish_domain = 0;
1541
/* Allocate a new server */
1542
mc.server = avahi_server_new(avahi_simple_poll_get
1543
(mc.simple_poll), &config, NULL,
1546
/* Free the Avahi configuration data */
1547
avahi_server_config_free(&config);
1550
/* Check if creating the Avahi server object succeeded */
1551
if(mc.server == NULL){
1552
fprintf(stderr, "Failed to create Avahi server: %s\n",
1553
avahi_strerror(error));
1554
exitcode = EX_UNAVAILABLE;
1562
/* Create the Avahi service browser */
1563
sb = avahi_s_service_browser_new(mc.server, if_index,
1564
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
1565
NULL, 0, browse_callback, NULL);
1567
fprintf(stderr, "Failed to create service browser: %s\n",
1568
avahi_strerror(avahi_server_errno(mc.server)));
1569
exitcode = EX_UNAVAILABLE;
1577
/* Run the main loop */
1580
fprintf(stderr, "Starting Avahi loop search\n");
1583
avahi_simple_poll_loop(mc.simple_poll);
1588
fprintf(stderr, "%s exiting\n", argv[0]);
1591
/* Cleanup things */
1593
avahi_s_service_browser_free(sb);
1595
if(mc.server != NULL)
1596
avahi_server_free(mc.server);
1598
if(mc.simple_poll != NULL)
1599
avahi_simple_poll_free(mc.simple_poll);
1601
if(gnutls_initialized){
1602
gnutls_certificate_free_credentials(mc.cred);
1603
gnutls_global_deinit();
1604
gnutls_dh_params_deinit(mc.dh_params);
1607
if(gpgme_initialized){
1608
gpgme_release(mc.ctx);
1611
/* Take down the network interface */
1612
if(take_down_interface){
1613
/* Re-raise priviliges */
1620
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1622
perror("ioctl SIOCGIFFLAGS");
1623
} else if(network.ifr_flags & IFF_UP) {
1624
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
1625
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1627
perror("ioctl SIOCSIFFLAGS");
1630
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1634
/* Lower privileges permanently */
1643
/* Removes the temp directory used by GPGME */
1644
if(tempdir_created){
1646
struct dirent *direntry;
1647
d = opendir(tempdir);
1649
if(errno != ENOENT){
1654
direntry = readdir(d);
1655
if(direntry == NULL){
1658
/* Skip "." and ".." */
1659
if(direntry->d_name[0] == '.'
1660
and (direntry->d_name[1] == '\0'
1661
or (direntry->d_name[1] == '.'
1662
and direntry->d_name[2] == '\0'))){
1665
char *fullname = NULL;
1666
ret = asprintf(&fullname, "%s/%s", tempdir,
1672
ret = remove(fullname);
1674
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1681
ret = rmdir(tempdir);
1682
if(ret == -1 and errno != ENOENT){
1688
sigemptyset(&old_sigterm_action.sa_mask);
1689
old_sigterm_action.sa_handler = SIG_DFL;
1690
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
1691
&old_sigterm_action,
1694
perror("sigaction");
1697
ret = raise(signal_received);
1698
} while(ret != 0 and errno == EINTR);
1703
TEMP_FAILURE_RETRY(pause());