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@recompile.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(), S_ISREG */
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
program_invocation_short_name */
67
#include <time.h> /* nanosleep(), time(), sleep() */
68
#include <net/if.h> /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
69
SIOCSIFFLAGS, if_indextoname(),
70
if_nametoindex(), IF_NAMESIZE */
71
#include <netinet/in.h> /* IN6_IS_ADDR_LINKLOCAL,
72
INET_ADDRSTRLEN, INET6_ADDRSTRLEN
74
#include <unistd.h> /* close(), SEEK_SET, off_t, write(),
75
getuid(), getgid(), seteuid(),
77
#include <arpa/inet.h> /* inet_pton(), htons, inet_ntop() */
78
#include <iso646.h> /* not, or, and */
79
#include <argp.h> /* struct argp_option, error_t, struct
80
argp_state, struct argp,
81
argp_parse(), ARGP_KEY_ARG,
82
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
83
#include <signal.h> /* sigemptyset(), sigaddset(),
84
sigaction(), SIGTERM, sig_atomic_t,
86
#include <sysexits.h> /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE,
87
EX_NOHOST, EX_IOERR, EX_PROTOCOL */
88
#include <sys/wait.h> /* waitpid(), WIFEXITED(),
89
WEXITSTATUS(), WTERMSIG() */
92
#include <sys/klog.h> /* klogctl() */
93
#endif /* __linux__ */
96
/* All Avahi types, constants and functions
99
#include <avahi-core/core.h>
100
#include <avahi-core/lookup.h>
101
#include <avahi-core/log.h>
102
#include <avahi-common/simple-watch.h>
103
#include <avahi-common/malloc.h>
104
#include <avahi-common/error.h>
107
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
110
init_gnutls_session(),
112
#include <gnutls/openpgp.h>
113
/* gnutls_certificate_set_openpgp_key_file(),
114
GNUTLS_OPENPGP_FMT_BASE64 */
117
#include <gpgme.h> /* All GPGME types, constants and
120
GPGME_PROTOCOL_OpenPGP,
123
#define BUFFER_SIZE 256
125
#define PATHDIR "/conf/conf.d/mandos"
126
#define SECKEY "seckey.txt"
127
#define PUBKEY "pubkey.txt"
128
#define HOOKDIR "/lib/mandos/network-hooks.d"
131
static const char mandos_protocol_version[] = "1";
132
const char *argp_program_version = "mandos-client " VERSION;
133
const char *argp_program_bug_address = "<mandos@recompile.se>";
134
static const char sys_class_net[] = "/sys/class/net";
135
char *connect_to = NULL;
136
const char *hookdir = HOOKDIR;
138
/* Doubly linked list that need to be circularly linked when used */
139
typedef struct server{
142
AvahiIfIndex if_index;
144
struct timespec last_seen;
149
/* Used for passing in values through the Avahi callback functions */
151
AvahiSimplePoll *simple_poll;
153
gnutls_certificate_credentials_t cred;
154
unsigned int dh_bits;
155
gnutls_dh_params_t dh_params;
156
const char *priority;
158
server *current_server;
161
/* global context so signal handler can reach it*/
162
mandos_context mc = { .simple_poll = NULL, .server = NULL,
163
.dh_bits = 1024, .priority = "SECURE256"
164
":!CTYPE-X.509:+CTYPE-OPENPGP",
165
.current_server = NULL };
167
sig_atomic_t quit_now = 0;
168
int signal_received = 0;
170
/* Function to use when printing errors */
171
void perror_plus(const char *print_text){
172
fprintf(stderr, "Mandos plugin %s: ",
173
program_invocation_short_name);
178
* Make additional room in "buffer" for at least BUFFER_SIZE more
179
* bytes. "buffer_capacity" is how much is currently allocated,
180
* "buffer_length" is how much is already used.
182
size_t incbuffer(char **buffer, size_t buffer_length,
183
size_t buffer_capacity){
184
if(buffer_length + BUFFER_SIZE > buffer_capacity){
185
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
189
buffer_capacity += BUFFER_SIZE;
191
return buffer_capacity;
194
/* Add server to set of servers to retry periodically */
195
int add_server(const char *ip, uint16_t port,
196
AvahiIfIndex if_index,
199
server *new_server = malloc(sizeof(server));
200
if(new_server == NULL){
201
perror_plus("malloc");
204
*new_server = (server){ .ip = strdup(ip),
206
.if_index = if_index,
208
if(new_server->ip == NULL){
209
perror_plus("strdup");
212
/* Special case of first server */
213
if (mc.current_server == NULL){
214
new_server->next = new_server;
215
new_server->prev = new_server;
216
mc.current_server = new_server;
217
/* Place the new server last in the list */
219
new_server->next = mc.current_server;
220
new_server->prev = mc.current_server->prev;
221
new_server->prev->next = new_server;
222
mc.current_server->prev = new_server;
224
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
226
perror_plus("clock_gettime");
235
static bool init_gpgme(const char *seckey,
236
const char *pubkey, const char *tempdir){
238
gpgme_engine_info_t engine_info;
242
* Helper function to insert pub and seckey to the engine keyring.
244
bool import_key(const char *filename){
247
gpgme_data_t pgp_data;
249
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
255
rc = gpgme_data_new_from_fd(&pgp_data, fd);
256
if(rc != GPG_ERR_NO_ERROR){
257
fprintf(stderr, "Mandos plugin mandos-client: "
258
"bad gpgme_data_new_from_fd: %s: %s\n",
259
gpgme_strsource(rc), gpgme_strerror(rc));
263
rc = gpgme_op_import(mc.ctx, pgp_data);
264
if(rc != GPG_ERR_NO_ERROR){
265
fprintf(stderr, "Mandos plugin mandos-client: "
266
"bad gpgme_op_import: %s: %s\n",
267
gpgme_strsource(rc), gpgme_strerror(rc));
271
ret = (int)TEMP_FAILURE_RETRY(close(fd));
273
perror_plus("close");
275
gpgme_data_release(pgp_data);
280
fprintf(stderr, "Mandos plugin mandos-client: "
281
"Initializing GPGME\n");
285
gpgme_check_version(NULL);
286
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
287
if(rc != GPG_ERR_NO_ERROR){
288
fprintf(stderr, "Mandos plugin mandos-client: "
289
"bad gpgme_engine_check_version: %s: %s\n",
290
gpgme_strsource(rc), gpgme_strerror(rc));
294
/* Set GPGME home directory for the OpenPGP engine only */
295
rc = gpgme_get_engine_info(&engine_info);
296
if(rc != GPG_ERR_NO_ERROR){
297
fprintf(stderr, "Mandos plugin mandos-client: "
298
"bad gpgme_get_engine_info: %s: %s\n",
299
gpgme_strsource(rc), gpgme_strerror(rc));
302
while(engine_info != NULL){
303
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
304
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
305
engine_info->file_name, tempdir);
308
engine_info = engine_info->next;
310
if(engine_info == NULL){
311
fprintf(stderr, "Mandos plugin mandos-client: "
312
"Could not set GPGME home dir to %s\n", tempdir);
316
/* Create new GPGME "context" */
317
rc = gpgme_new(&(mc.ctx));
318
if(rc != GPG_ERR_NO_ERROR){
319
fprintf(stderr, "Mandos plugin mandos-client: "
320
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
325
if(not import_key(pubkey) or not import_key(seckey)){
333
* Decrypt OpenPGP data.
334
* Returns -1 on error
336
static ssize_t pgp_packet_decrypt(const char *cryptotext,
339
gpgme_data_t dh_crypto, dh_plain;
342
size_t plaintext_capacity = 0;
343
ssize_t plaintext_length = 0;
346
fprintf(stderr, "Mandos plugin mandos-client: "
347
"Trying to decrypt OpenPGP data\n");
350
/* Create new GPGME data buffer from memory cryptotext */
351
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
353
if(rc != GPG_ERR_NO_ERROR){
354
fprintf(stderr, "Mandos plugin mandos-client: "
355
"bad gpgme_data_new_from_mem: %s: %s\n",
356
gpgme_strsource(rc), gpgme_strerror(rc));
360
/* Create new empty GPGME data buffer for the plaintext */
361
rc = gpgme_data_new(&dh_plain);
362
if(rc != GPG_ERR_NO_ERROR){
363
fprintf(stderr, "Mandos plugin mandos-client: "
364
"bad gpgme_data_new: %s: %s\n",
365
gpgme_strsource(rc), gpgme_strerror(rc));
366
gpgme_data_release(dh_crypto);
370
/* Decrypt data from the cryptotext data buffer to the plaintext
372
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
373
if(rc != GPG_ERR_NO_ERROR){
374
fprintf(stderr, "Mandos plugin mandos-client: "
375
"bad gpgme_op_decrypt: %s: %s\n",
376
gpgme_strsource(rc), gpgme_strerror(rc));
377
plaintext_length = -1;
379
gpgme_decrypt_result_t result;
380
result = gpgme_op_decrypt_result(mc.ctx);
382
fprintf(stderr, "Mandos plugin mandos-client: "
383
"gpgme_op_decrypt_result failed\n");
385
fprintf(stderr, "Mandos plugin mandos-client: "
386
"Unsupported algorithm: %s\n",
387
result->unsupported_algorithm);
388
fprintf(stderr, "Mandos plugin mandos-client: "
389
"Wrong key usage: %u\n",
390
result->wrong_key_usage);
391
if(result->file_name != NULL){
392
fprintf(stderr, "Mandos plugin mandos-client: "
393
"File name: %s\n", result->file_name);
395
gpgme_recipient_t recipient;
396
recipient = result->recipients;
397
while(recipient != NULL){
398
fprintf(stderr, "Mandos plugin mandos-client: "
399
"Public key algorithm: %s\n",
400
gpgme_pubkey_algo_name(recipient->pubkey_algo));
401
fprintf(stderr, "Mandos plugin mandos-client: "
402
"Key ID: %s\n", recipient->keyid);
403
fprintf(stderr, "Mandos plugin mandos-client: "
404
"Secret key available: %s\n",
405
recipient->status == GPG_ERR_NO_SECKEY
407
recipient = recipient->next;
415
fprintf(stderr, "Mandos plugin mandos-client: "
416
"Decryption of OpenPGP data succeeded\n");
419
/* Seek back to the beginning of the GPGME plaintext data buffer */
420
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
421
perror_plus("gpgme_data_seek");
422
plaintext_length = -1;
428
plaintext_capacity = incbuffer(plaintext,
429
(size_t)plaintext_length,
431
if(plaintext_capacity == 0){
432
perror_plus("incbuffer");
433
plaintext_length = -1;
437
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
439
/* Print the data, if any */
445
perror_plus("gpgme_data_read");
446
plaintext_length = -1;
449
plaintext_length += ret;
453
fprintf(stderr, "Mandos plugin mandos-client: "
454
"Decrypted password is: ");
455
for(ssize_t i = 0; i < plaintext_length; i++){
456
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
458
fprintf(stderr, "\n");
463
/* Delete the GPGME cryptotext data buffer */
464
gpgme_data_release(dh_crypto);
466
/* Delete the GPGME plaintext data buffer */
467
gpgme_data_release(dh_plain);
468
return plaintext_length;
471
static const char * safer_gnutls_strerror(int value){
472
const char *ret = gnutls_strerror(value); /* Spurious warning from
473
-Wunreachable-code */
479
/* GnuTLS log function callback */
480
static void debuggnutls(__attribute__((unused)) int level,
482
fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
485
static int init_gnutls_global(const char *pubkeyfilename,
486
const char *seckeyfilename){
490
fprintf(stderr, "Mandos plugin mandos-client: "
491
"Initializing GnuTLS\n");
494
ret = gnutls_global_init();
495
if(ret != GNUTLS_E_SUCCESS){
496
fprintf(stderr, "Mandos plugin mandos-client: "
497
"GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
502
/* "Use a log level over 10 to enable all debugging options."
505
gnutls_global_set_log_level(11);
506
gnutls_global_set_log_function(debuggnutls);
509
/* OpenPGP credentials */
510
ret = gnutls_certificate_allocate_credentials(&mc.cred);
511
if(ret != GNUTLS_E_SUCCESS){
512
fprintf(stderr, "Mandos plugin mandos-client: "
513
"GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
514
gnutls_global_deinit();
519
fprintf(stderr, "Mandos plugin mandos-client: "
520
"Attempting to use OpenPGP public key %s and"
521
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
525
ret = gnutls_certificate_set_openpgp_key_file
526
(mc.cred, pubkeyfilename, seckeyfilename,
527
GNUTLS_OPENPGP_FMT_BASE64);
528
if(ret != GNUTLS_E_SUCCESS){
530
"Mandos plugin mandos-client: "
531
"Error[%d] while reading the OpenPGP key pair ('%s',"
532
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
533
fprintf(stderr, "Mandos plugin mandos-client: "
534
"The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
538
/* GnuTLS server initialization */
539
ret = gnutls_dh_params_init(&mc.dh_params);
540
if(ret != GNUTLS_E_SUCCESS){
541
fprintf(stderr, "Mandos plugin mandos-client: "
542
"Error in GnuTLS DH parameter initialization:"
543
" %s\n", safer_gnutls_strerror(ret));
546
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
547
if(ret != GNUTLS_E_SUCCESS){
548
fprintf(stderr, "Mandos plugin mandos-client: "
549
"Error in GnuTLS prime generation: %s\n",
550
safer_gnutls_strerror(ret));
554
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
560
gnutls_certificate_free_credentials(mc.cred);
561
gnutls_global_deinit();
562
gnutls_dh_params_deinit(mc.dh_params);
566
static int init_gnutls_session(gnutls_session_t *session){
568
/* GnuTLS session creation */
570
ret = gnutls_init(session, GNUTLS_SERVER);
574
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
575
if(ret != GNUTLS_E_SUCCESS){
576
fprintf(stderr, "Mandos plugin mandos-client: "
577
"Error in GnuTLS session initialization: %s\n",
578
safer_gnutls_strerror(ret));
584
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
586
gnutls_deinit(*session);
589
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
590
if(ret != GNUTLS_E_SUCCESS){
591
fprintf(stderr, "Mandos plugin mandos-client: "
592
"Syntax error at: %s\n", err);
593
fprintf(stderr, "Mandos plugin mandos-client: "
594
"GnuTLS error: %s\n", safer_gnutls_strerror(ret));
595
gnutls_deinit(*session);
601
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
604
gnutls_deinit(*session);
607
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
608
if(ret != GNUTLS_E_SUCCESS){
609
fprintf(stderr, "Mandos plugin mandos-client: "
610
"Error setting GnuTLS credentials: %s\n",
611
safer_gnutls_strerror(ret));
612
gnutls_deinit(*session);
616
/* ignore client certificate if any. */
617
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
619
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
624
/* Avahi log function callback */
625
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
626
__attribute__((unused)) const char *txt){}
628
/* Called when a Mandos server is found */
629
static int start_mandos_communication(const char *ip, uint16_t port,
630
AvahiIfIndex if_index,
632
int ret, tcp_sd = -1;
635
struct sockaddr_in in;
636
struct sockaddr_in6 in6;
639
char *decrypted_buffer = NULL;
640
size_t buffer_length = 0;
641
size_t buffer_capacity = 0;
644
gnutls_session_t session;
645
int pf; /* Protocol family */
662
fprintf(stderr, "Mandos plugin mandos-client: "
663
"Bad address family: %d\n", af);
668
ret = init_gnutls_session(&session);
674
fprintf(stderr, "Mandos plugin mandos-client: "
675
"Setting up a TCP connection to %s, port %" PRIu16
679
tcp_sd = socket(pf, SOCK_STREAM, 0);
682
perror_plus("socket");
692
memset(&to, 0, sizeof(to));
694
to.in6.sin6_family = (sa_family_t)af;
695
ret = inet_pton(af, ip, &to.in6.sin6_addr);
697
to.in.sin_family = (sa_family_t)af;
698
ret = inet_pton(af, ip, &to.in.sin_addr);
702
perror_plus("inet_pton");
708
fprintf(stderr, "Mandos plugin mandos-client: "
709
"Bad address: %s\n", ip);
714
to.in6.sin6_port = htons(port); /* Spurious warnings from
716
-Wunreachable-code */
718
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
719
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
721
if(if_index == AVAHI_IF_UNSPEC){
722
fprintf(stderr, "Mandos plugin mandos-client: "
723
"An IPv6 link-local address is incomplete"
724
" without a network interface\n");
728
/* Set the network interface number as scope */
729
to.in6.sin6_scope_id = (uint32_t)if_index;
732
to.in.sin_port = htons(port); /* Spurious warnings from
734
-Wunreachable-code */
743
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
744
char interface[IF_NAMESIZE];
745
if(if_indextoname((unsigned int)if_index, interface) == NULL){
746
perror_plus("if_indextoname");
748
fprintf(stderr, "Mandos plugin mandos-client: "
749
"Connection to: %s%%%s, port %" PRIu16 "\n",
750
ip, interface, port);
753
fprintf(stderr, "Mandos plugin mandos-client: "
754
"Connection to: %s, port %" PRIu16 "\n", ip, port);
756
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
757
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
760
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
763
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
767
perror_plus("inet_ntop");
769
if(strcmp(addrstr, ip) != 0){
770
fprintf(stderr, "Mandos plugin mandos-client: "
771
"Canonical address form: %s\n", addrstr);
782
ret = connect(tcp_sd, &to.in6, sizeof(to));
784
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
787
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
789
perror_plus("connect");
800
const char *out = mandos_protocol_version;
803
size_t out_size = strlen(out);
804
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
805
out_size - written));
808
perror_plus("write");
812
written += (size_t)ret;
813
if(written < out_size){
816
if(out == mandos_protocol_version){
831
fprintf(stderr, "Mandos plugin mandos-client: "
832
"Establishing TLS session with %s\n", ip);
840
/* Spurious warning from -Wint-to-pointer-cast */
841
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
849
ret = gnutls_handshake(session);
854
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
856
if(ret != GNUTLS_E_SUCCESS){
858
fprintf(stderr, "Mandos plugin mandos-client: "
859
"*** GnuTLS Handshake failed ***\n");
866
/* Read OpenPGP packet that contains the wanted password */
869
fprintf(stderr, "Mandos plugin mandos-client: "
870
"Retrieving OpenPGP encrypted password from %s\n", ip);
880
buffer_capacity = incbuffer(&buffer, buffer_length,
882
if(buffer_capacity == 0){
884
perror_plus("incbuffer");
894
sret = gnutls_record_recv(session, buffer+buffer_length,
901
case GNUTLS_E_INTERRUPTED:
904
case GNUTLS_E_REHANDSHAKE:
906
ret = gnutls_handshake(session);
912
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
914
fprintf(stderr, "Mandos plugin mandos-client: "
915
"*** GnuTLS Re-handshake failed ***\n");
922
fprintf(stderr, "Mandos plugin mandos-client: "
923
"Unknown error while reading data from"
924
" encrypted session with Mandos server\n");
925
gnutls_bye(session, GNUTLS_SHUT_RDWR);
930
buffer_length += (size_t) sret;
935
fprintf(stderr, "Mandos plugin mandos-client: "
936
"Closing TLS session\n");
945
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
950
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
952
if(buffer_length > 0){
953
ssize_t decrypted_buffer_size;
954
decrypted_buffer_size = pgp_packet_decrypt(buffer,
957
if(decrypted_buffer_size >= 0){
960
while(written < (size_t) decrypted_buffer_size){
966
ret = (int)fwrite(decrypted_buffer + written, 1,
967
(size_t)decrypted_buffer_size - written,
969
if(ret == 0 and ferror(stdout)){
972
fprintf(stderr, "Mandos plugin mandos-client: "
973
"Error writing encrypted data: %s\n",
979
written += (size_t)ret;
985
/* Shutdown procedure */
990
free(decrypted_buffer);
993
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
999
perror_plus("close");
1001
gnutls_deinit(session);
1011
static void resolve_callback(AvahiSServiceResolver *r,
1012
AvahiIfIndex interface,
1013
AvahiProtocol proto,
1014
AvahiResolverEvent event,
1018
const char *host_name,
1019
const AvahiAddress *address,
1021
AVAHI_GCC_UNUSED AvahiStringList *txt,
1022
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1024
AVAHI_GCC_UNUSED void* userdata){
1027
/* Called whenever a service has been resolved successfully or
1036
case AVAHI_RESOLVER_FAILURE:
1037
fprintf(stderr, "Mandos plugin mandos-client: "
1038
"(Avahi Resolver) Failed to resolve service '%s'"
1039
" of type '%s' in domain '%s': %s\n", name, type, domain,
1040
avahi_strerror(avahi_server_errno(mc.server)));
1043
case AVAHI_RESOLVER_FOUND:
1045
char ip[AVAHI_ADDRESS_STR_MAX];
1046
avahi_address_snprint(ip, sizeof(ip), address);
1048
fprintf(stderr, "Mandos plugin mandos-client: "
1049
"Mandos server \"%s\" found on %s (%s, %"
1050
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1051
ip, (intmax_t)interface, port);
1053
int ret = start_mandos_communication(ip, port, interface,
1054
avahi_proto_to_af(proto));
1056
avahi_simple_poll_quit(mc.simple_poll);
1058
ret = add_server(ip, port, interface,
1059
avahi_proto_to_af(proto));
1063
avahi_s_service_resolver_free(r);
1066
static void browse_callback(AvahiSServiceBrowser *b,
1067
AvahiIfIndex interface,
1068
AvahiProtocol protocol,
1069
AvahiBrowserEvent event,
1073
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1075
AVAHI_GCC_UNUSED void* userdata){
1078
/* Called whenever a new services becomes available on the LAN or
1079
is removed from the LAN */
1087
case AVAHI_BROWSER_FAILURE:
1089
fprintf(stderr, "Mandos plugin mandos-client: "
1090
"(Avahi browser) %s\n",
1091
avahi_strerror(avahi_server_errno(mc.server)));
1092
avahi_simple_poll_quit(mc.simple_poll);
1095
case AVAHI_BROWSER_NEW:
1096
/* We ignore the returned Avahi resolver object. In the callback
1097
function we free it. If the Avahi server is terminated before
1098
the callback function is called the Avahi server will free the
1101
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1102
name, type, domain, protocol, 0,
1103
resolve_callback, NULL) == NULL)
1104
fprintf(stderr, "Mandos plugin mandos-client: "
1105
"Avahi: Failed to resolve service '%s': %s\n",
1106
name, avahi_strerror(avahi_server_errno(mc.server)));
1109
case AVAHI_BROWSER_REMOVE:
1112
case AVAHI_BROWSER_ALL_FOR_NOW:
1113
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1115
fprintf(stderr, "Mandos plugin mandos-client: "
1116
"No Mandos server found, still searching...\n");
1122
/* Signal handler that stops main loop after SIGTERM */
1123
static void handle_sigterm(int sig){
1128
signal_received = sig;
1129
int old_errno = errno;
1130
/* set main loop to exit */
1131
if(mc.simple_poll != NULL){
1132
avahi_simple_poll_quit(mc.simple_poll);
1137
bool get_flags(const char *ifname, struct ifreq *ifr){
1140
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1142
perror_plus("socket");
1145
strcpy(ifr->ifr_name, ifname);
1146
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1149
perror_plus("ioctl SIOCGIFFLAGS");
1156
bool good_flags(const char *ifname, const struct ifreq *ifr){
1158
/* Reject the loopback device */
1159
if(ifr->ifr_flags & IFF_LOOPBACK){
1161
fprintf(stderr, "Mandos plugin mandos-client: "
1162
"Rejecting loopback interface \"%s\"\n", ifname);
1166
/* Accept point-to-point devices only if connect_to is specified */
1167
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1169
fprintf(stderr, "Mandos plugin mandos-client: "
1170
"Accepting point-to-point interface \"%s\"\n", ifname);
1174
/* Otherwise, reject non-broadcast-capable devices */
1175
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1177
fprintf(stderr, "Mandos plugin mandos-client: "
1178
"Rejecting non-broadcast interface \"%s\"\n", ifname);
1182
/* Reject non-ARP interfaces (including dummy interfaces) */
1183
if(ifr->ifr_flags & IFF_NOARP){
1185
fprintf(stderr, "Mandos plugin mandos-client: "
1186
"Rejecting non-ARP interface \"%s\"\n", ifname);
1191
/* Accept this device */
1193
fprintf(stderr, "Mandos plugin mandos-client: "
1194
"Interface \"%s\" is good\n", ifname);
1200
* This function determines if a directory entry in /sys/class/net
1201
* corresponds to an acceptable network device.
1202
* (This function is passed to scandir(3) as a filter function.)
1204
int good_interface(const struct dirent *if_entry){
1205
if(if_entry->d_name[0] == '.'){
1210
if(not get_flags(if_entry->d_name, &ifr)){
1212
fprintf(stderr, "Mandos plugin mandos-client: "
1213
"Failed to get flags for interface \"%s\"\n",
1219
if(not good_flags(if_entry->d_name, &ifr)){
1226
* This function determines if a directory entry in /sys/class/net
1227
* corresponds to an acceptable network device which is up.
1228
* (This function is passed to scandir(3) as a filter function.)
1230
int up_interface(const struct dirent *if_entry){
1231
if(if_entry->d_name[0] == '.'){
1236
if(not get_flags(if_entry->d_name, &ifr)){
1238
fprintf(stderr, "Mandos plugin mandos-client: "
1239
"Failed to get flags for interface \"%s\"\n",
1245
/* Reject down interfaces */
1246
if(not (ifr.ifr_flags & IFF_UP)){
1248
fprintf(stderr, "Mandos plugin mandos-client: "
1249
"Rejecting down interface \"%s\"\n",
1255
/* Reject non-running interfaces */
1256
if(not (ifr.ifr_flags & IFF_RUNNING)){
1258
fprintf(stderr, "Mandos plugin mandos-client: "
1259
"Rejecting non-running interface \"%s\"\n",
1265
if(not good_flags(if_entry->d_name, &ifr)){
1271
int notdotentries(const struct dirent *direntry){
1272
/* Skip "." and ".." */
1273
if(direntry->d_name[0] == '.'
1274
and (direntry->d_name[1] == '\0'
1275
or (direntry->d_name[1] == '.'
1276
and direntry->d_name[2] == '\0'))){
1282
/* Is this directory entry a runnable program? */
1283
int runnable_hook(const struct dirent *direntry){
1287
if((direntry->d_name)[0] == '\0'){
1292
/* Save pointer to last character */
1293
char *end = strchr(direntry->d_name, '\0')-1;
1300
if(((direntry->d_name)[0] == '#')
1302
/* Temporary #name# */
1306
/* XXX more rules here */
1308
char *fullname = NULL;
1309
ret = asprintf(&fullname, "%s/%s", hookdir,
1312
perror_plus("asprintf");
1316
ret = stat(fullname, &st);
1319
perror_plus("Could not stat plugin");
1323
if(not (S_ISREG(st.st_mode))){
1324
/* Not a regular file */
1327
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1328
/* Not executable */
1334
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1336
struct timespec now;
1337
struct timespec waited_time;
1338
intmax_t block_time;
1341
if(mc.current_server == NULL){
1343
fprintf(stderr, "Mandos plugin mandos-client: "
1344
"Wait until first server is found. No timeout!\n");
1346
ret = avahi_simple_poll_iterate(s, -1);
1349
fprintf(stderr, "Mandos plugin mandos-client: "
1350
"Check current_server if we should run it,"
1353
/* the current time */
1354
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1356
perror_plus("clock_gettime");
1359
/* Calculating in ms how long time between now and server
1360
who we visted longest time ago. Now - last seen. */
1361
waited_time.tv_sec = (now.tv_sec
1362
- mc.current_server->last_seen.tv_sec);
1363
waited_time.tv_nsec = (now.tv_nsec
1364
- mc.current_server->last_seen.tv_nsec);
1365
/* total time is 10s/10,000ms.
1366
Converting to s from ms by dividing by 1,000,
1367
and ns to ms by dividing by 1,000,000. */
1368
block_time = ((retry_interval
1369
- ((intmax_t)waited_time.tv_sec * 1000))
1370
- ((intmax_t)waited_time.tv_nsec / 1000000));
1373
fprintf(stderr, "Mandos plugin mandos-client: "
1374
"Blocking for %" PRIdMAX " ms\n", block_time);
1377
if(block_time <= 0){
1378
ret = start_mandos_communication(mc.current_server->ip,
1379
mc.current_server->port,
1380
mc.current_server->if_index,
1381
mc.current_server->af);
1383
avahi_simple_poll_quit(mc.simple_poll);
1386
ret = clock_gettime(CLOCK_MONOTONIC,
1387
&mc.current_server->last_seen);
1389
perror_plus("clock_gettime");
1392
mc.current_server = mc.current_server->next;
1393
block_time = 0; /* Call avahi to find new Mandos
1394
servers, but don't block */
1397
ret = avahi_simple_poll_iterate(s, (int)block_time);
1400
if (ret > 0 or errno != EINTR){
1401
return (ret != 1) ? ret : 0;
1407
int main(int argc, char *argv[]){
1408
AvahiSServiceBrowser *sb = NULL;
1413
int exitcode = EXIT_SUCCESS;
1414
const char *interface = "";
1415
struct ifreq network;
1417
bool take_down_interface = false;
1420
char tempdir[] = "/tmp/mandosXXXXXX";
1421
bool tempdir_created = false;
1422
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1423
const char *seckey = PATHDIR "/" SECKEY;
1424
const char *pubkey = PATHDIR "/" PUBKEY;
1426
bool gnutls_initialized = false;
1427
bool gpgme_initialized = false;
1429
double retry_interval = 10; /* 10s between trying a server and
1430
retrying the same server again */
1432
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1433
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1438
/* Lower any group privileges we might have, just to be safe */
1442
perror_plus("setgid");
1445
/* Lower user privileges (temporarily) */
1449
perror_plus("seteuid");
1457
struct argp_option options[] = {
1458
{ .name = "debug", .key = 128,
1459
.doc = "Debug mode", .group = 3 },
1460
{ .name = "connect", .key = 'c',
1461
.arg = "ADDRESS:PORT",
1462
.doc = "Connect directly to a specific Mandos server",
1464
{ .name = "interface", .key = 'i',
1466
.doc = "Network interface that will be used to search for"
1469
{ .name = "seckey", .key = 's',
1471
.doc = "OpenPGP secret key file base name",
1473
{ .name = "pubkey", .key = 'p',
1475
.doc = "OpenPGP public key file base name",
1477
{ .name = "dh-bits", .key = 129,
1479
.doc = "Bit length of the prime number used in the"
1480
" Diffie-Hellman key exchange",
1482
{ .name = "priority", .key = 130,
1484
.doc = "GnuTLS priority string for the TLS handshake",
1486
{ .name = "delay", .key = 131,
1488
.doc = "Maximum delay to wait for interface startup",
1490
{ .name = "retry", .key = 132,
1492
.doc = "Retry interval used when denied by the mandos server",
1494
{ .name = "network-hook-dir", .key = 133,
1496
.doc = "Directory where network hooks are located",
1499
* These reproduce what we would get without ARGP_NO_HELP
1501
{ .name = "help", .key = '?',
1502
.doc = "Give this help list", .group = -1 },
1503
{ .name = "usage", .key = -3,
1504
.doc = "Give a short usage message", .group = -1 },
1505
{ .name = "version", .key = 'V',
1506
.doc = "Print program version", .group = -1 },
1510
error_t parse_opt(int key, char *arg,
1511
struct argp_state *state){
1514
case 128: /* --debug */
1517
case 'c': /* --connect */
1520
case 'i': /* --interface */
1523
case 's': /* --seckey */
1526
case 'p': /* --pubkey */
1529
case 129: /* --dh-bits */
1531
tmpmax = strtoimax(arg, &tmp, 10);
1532
if(errno != 0 or tmp == arg or *tmp != '\0'
1533
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1534
argp_error(state, "Bad number of DH bits");
1536
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1538
case 130: /* --priority */
1541
case 131: /* --delay */
1543
delay = strtof(arg, &tmp);
1544
if(errno != 0 or tmp == arg or *tmp != '\0'){
1545
argp_error(state, "Bad delay");
1547
case 132: /* --retry */
1549
retry_interval = strtod(arg, &tmp);
1550
if(errno != 0 or tmp == arg or *tmp != '\0'
1551
or (retry_interval * 1000) > INT_MAX
1552
or retry_interval < 0){
1553
argp_error(state, "Bad retry interval");
1556
case 133: /* --network-hook-dir */
1560
* These reproduce what we would get without ARGP_NO_HELP
1562
case '?': /* --help */
1563
argp_state_help(state, state->out_stream,
1564
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1565
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1566
case -3: /* --usage */
1567
argp_state_help(state, state->out_stream,
1568
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1569
case 'V': /* --version */
1570
fprintf(state->out_stream, "Mandos plugin mandos-client: ");
1571
fprintf(state->out_stream, "%s\n", argp_program_version);
1572
exit(argp_err_exit_status);
1575
return ARGP_ERR_UNKNOWN;
1580
struct argp argp = { .options = options, .parser = parse_opt,
1582
.doc = "Mandos client -- Get and decrypt"
1583
" passwords from a Mandos server" };
1584
ret = argp_parse(&argp, argc, argv,
1585
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1592
perror_plus("argp_parse");
1593
exitcode = EX_OSERR;
1596
exitcode = EX_USAGE;
1602
/* Work around Debian bug #633582:
1603
<http://bugs.debian.org/633582> */
1606
/* Re-raise priviliges */
1610
perror_plus("seteuid");
1613
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
1614
int seckey_fd = open(seckey, O_RDONLY);
1615
if(seckey_fd == -1){
1616
perror_plus("open");
1618
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
1620
perror_plus("fstat");
1622
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1623
ret = fchown(seckey_fd, uid, gid);
1625
perror_plus("fchown");
1629
TEMP_FAILURE_RETRY(close(seckey_fd));
1633
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
1634
int pubkey_fd = open(pubkey, O_RDONLY);
1635
if(pubkey_fd == -1){
1636
perror_plus("open");
1638
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
1640
perror_plus("fstat");
1642
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1643
ret = fchown(pubkey_fd, uid, gid);
1645
perror_plus("fchown");
1649
TEMP_FAILURE_RETRY(close(pubkey_fd));
1653
/* Lower privileges */
1657
perror_plus("seteuid");
1661
/* Find network hooks and run them */
1663
struct dirent **direntries;
1664
struct dirent *direntry;
1665
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1668
perror_plus("scandir");
1670
int devnull = open("/dev/null", O_RDONLY);
1671
for(int i = 0; i < numhooks; i++){
1672
direntry = direntries[0];
1673
char *fullname = NULL;
1674
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1676
perror_plus("asprintf");
1679
pid_t hook_pid = fork();
1682
dup2(devnull, STDIN_FILENO);
1684
dup2(STDERR_FILENO, STDOUT_FILENO);
1685
ret = setenv("DEVICE", interface, 1);
1687
perror_plus("setenv");
1690
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1692
perror_plus("setenv");
1695
ret = setenv("MODE", "start", 1);
1697
perror_plus("setenv");
1701
ret = asprintf(&delaystring, "%f", delay);
1703
perror_plus("asprintf");
1706
ret = setenv("DELAY", delaystring, 1);
1709
perror_plus("setenv");
1713
ret = execl(fullname, direntry->d_name, "start", NULL);
1714
perror_plus("execl");
1717
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1718
perror_plus("waitpid");
1722
if(WIFEXITED(status)){
1723
if(WEXITSTATUS(status) != 0){
1724
fprintf(stderr, "Mandos plugin mandos-client: "
1725
"Warning: network hook \"%s\" exited"
1726
" with status %d\n", direntry->d_name,
1727
WEXITSTATUS(status));
1731
} else if(WIFSIGNALED(status)){
1732
fprintf(stderr, "Mandos plugin mandos-client: "
1733
"Warning: network hook \"%s\" died by"
1734
" signal %d\n", direntry->d_name,
1739
fprintf(stderr, "Mandos plugin mandos-client: "
1740
"Warning: network hook \"%s\" crashed\n",
1756
avahi_set_log_function(empty_log);
1759
if(interface[0] == '\0'){
1760
struct dirent **direntries;
1761
/* First look for interfaces that are up */
1762
ret = scandir(sys_class_net, &direntries, up_interface,
1765
/* No up interfaces, look for any good interfaces */
1767
ret = scandir(sys_class_net, &direntries, good_interface,
1771
/* Pick the first interface returned */
1772
interface = strdup(direntries[0]->d_name);
1774
fprintf(stderr, "Mandos plugin mandos-client: "
1775
"Using interface \"%s\"\n", interface);
1777
if(interface == NULL){
1778
perror_plus("malloc");
1780
exitcode = EXIT_FAILURE;
1786
fprintf(stderr, "Mandos plugin mandos-client: "
1787
"Could not find a network interface\n");
1788
exitcode = EXIT_FAILURE;
1793
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1794
from the signal handler */
1795
/* Initialize the pseudo-RNG for Avahi */
1796
srand((unsigned int) time(NULL));
1797
mc.simple_poll = avahi_simple_poll_new();
1798
if(mc.simple_poll == NULL){
1799
fprintf(stderr, "Mandos plugin mandos-client: "
1800
"Avahi: Failed to create simple poll object.\n");
1801
exitcode = EX_UNAVAILABLE;
1805
sigemptyset(&sigterm_action.sa_mask);
1806
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1808
perror_plus("sigaddset");
1809
exitcode = EX_OSERR;
1812
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1814
perror_plus("sigaddset");
1815
exitcode = EX_OSERR;
1818
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1820
perror_plus("sigaddset");
1821
exitcode = EX_OSERR;
1824
/* Need to check if the handler is SIG_IGN before handling:
1825
| [[info:libc:Initial Signal Actions]] |
1826
| [[info:libc:Basic Signal Handling]] |
1828
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1830
perror_plus("sigaction");
1833
if(old_sigterm_action.sa_handler != SIG_IGN){
1834
ret = sigaction(SIGINT, &sigterm_action, NULL);
1836
perror_plus("sigaction");
1837
exitcode = EX_OSERR;
1841
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1843
perror_plus("sigaction");
1846
if(old_sigterm_action.sa_handler != SIG_IGN){
1847
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1849
perror_plus("sigaction");
1850
exitcode = EX_OSERR;
1854
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1856
perror_plus("sigaction");
1859
if(old_sigterm_action.sa_handler != SIG_IGN){
1860
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1862
perror_plus("sigaction");
1863
exitcode = EX_OSERR;
1868
/* If the interface is down, bring it up */
1869
if(strcmp(interface, "none") != 0){
1870
if_index = (AvahiIfIndex) if_nametoindex(interface);
1872
fprintf(stderr, "Mandos plugin mandos-client: "
1873
"No such interface: \"%s\"\n", interface);
1874
exitcode = EX_UNAVAILABLE;
1882
/* Re-raise priviliges */
1886
perror_plus("seteuid");
1890
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1891
messages about the network interface to mess up the prompt */
1892
ret = klogctl(8, NULL, 5);
1893
bool restore_loglevel = true;
1895
restore_loglevel = false;
1896
perror_plus("klogctl");
1898
#endif /* __linux__ */
1900
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1902
perror_plus("socket");
1903
exitcode = EX_OSERR;
1905
if(restore_loglevel){
1906
ret = klogctl(7, NULL, 0);
1908
perror_plus("klogctl");
1911
#endif /* __linux__ */
1912
/* Lower privileges */
1916
perror_plus("seteuid");
1920
strcpy(network.ifr_name, interface);
1921
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1923
perror_plus("ioctl SIOCGIFFLAGS");
1925
if(restore_loglevel){
1926
ret = klogctl(7, NULL, 0);
1928
perror_plus("klogctl");
1931
#endif /* __linux__ */
1932
exitcode = EX_OSERR;
1933
/* Lower privileges */
1937
perror_plus("seteuid");
1941
if((network.ifr_flags & IFF_UP) == 0){
1942
network.ifr_flags |= IFF_UP;
1943
take_down_interface = true;
1944
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1946
take_down_interface = false;
1947
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1948
exitcode = EX_OSERR;
1950
if(restore_loglevel){
1951
ret = klogctl(7, NULL, 0);
1953
perror_plus("klogctl");
1956
#endif /* __linux__ */
1957
/* Lower privileges */
1961
perror_plus("seteuid");
1966
/* Sleep checking until interface is running.
1967
Check every 0.25s, up to total time of delay */
1968
for(int i=0; i < delay * 4; i++){
1969
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1971
perror_plus("ioctl SIOCGIFFLAGS");
1972
} else if(network.ifr_flags & IFF_RUNNING){
1975
struct timespec sleeptime = { .tv_nsec = 250000000 };
1976
ret = nanosleep(&sleeptime, NULL);
1977
if(ret == -1 and errno != EINTR){
1978
perror_plus("nanosleep");
1981
if(not take_down_interface){
1982
/* We won't need the socket anymore */
1983
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1985
perror_plus("close");
1989
if(restore_loglevel){
1990
/* Restores kernel loglevel to default */
1991
ret = klogctl(7, NULL, 0);
1993
perror_plus("klogctl");
1996
#endif /* __linux__ */
1997
/* Lower privileges */
1999
if(take_down_interface){
2000
/* Lower privileges */
2003
perror_plus("seteuid");
2006
/* Lower privileges permanently */
2009
perror_plus("setuid");
2018
ret = init_gnutls_global(pubkey, seckey);
2020
fprintf(stderr, "Mandos plugin mandos-client: "
2021
"init_gnutls_global failed\n");
2022
exitcode = EX_UNAVAILABLE;
2025
gnutls_initialized = true;
2032
if(mkdtemp(tempdir) == NULL){
2033
perror_plus("mkdtemp");
2036
tempdir_created = true;
2042
if(not init_gpgme(pubkey, seckey, tempdir)){
2043
fprintf(stderr, "Mandos plugin mandos-client: "
2044
"init_gpgme failed\n");
2045
exitcode = EX_UNAVAILABLE;
2048
gpgme_initialized = true;
2055
if(connect_to != NULL){
2056
/* Connect directly, do not use Zeroconf */
2057
/* (Mainly meant for debugging) */
2058
char *address = strrchr(connect_to, ':');
2059
if(address == NULL){
2060
fprintf(stderr, "Mandos plugin mandos-client: "
2061
"No colon in address\n");
2062
exitcode = EX_USAGE;
2072
tmpmax = strtoimax(address+1, &tmp, 10);
2073
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2074
or tmpmax != (uint16_t)tmpmax){
2075
fprintf(stderr, "Mandos plugin mandos-client: "
2076
"Bad port number\n");
2077
exitcode = EX_USAGE;
2085
port = (uint16_t)tmpmax;
2087
/* Colon in address indicates IPv6 */
2089
if(strchr(connect_to, ':') != NULL){
2091
/* Accept [] around IPv6 address - see RFC 5952 */
2092
if(connect_to[0] == '[' and address[-1] == ']')
2100
address = connect_to;
2106
while(not quit_now){
2107
ret = start_mandos_communication(address, port, if_index, af);
2108
if(quit_now or ret == 0){
2112
fprintf(stderr, "Mandos plugin mandos-client: "
2113
"Retrying in %d seconds\n", (int)retry_interval);
2115
sleep((int)retry_interval);
2119
exitcode = EXIT_SUCCESS;
2130
AvahiServerConfig config;
2131
/* Do not publish any local Zeroconf records */
2132
avahi_server_config_init(&config);
2133
config.publish_hinfo = 0;
2134
config.publish_addresses = 0;
2135
config.publish_workstation = 0;
2136
config.publish_domain = 0;
2138
/* Allocate a new server */
2139
mc.server = avahi_server_new(avahi_simple_poll_get
2140
(mc.simple_poll), &config, NULL,
2143
/* Free the Avahi configuration data */
2144
avahi_server_config_free(&config);
2147
/* Check if creating the Avahi server object succeeded */
2148
if(mc.server == NULL){
2149
fprintf(stderr, "Mandos plugin mandos-client: "
2150
"Failed to create Avahi server: %s\n",
2151
avahi_strerror(error));
2152
exitcode = EX_UNAVAILABLE;
2160
/* Create the Avahi service browser */
2161
sb = avahi_s_service_browser_new(mc.server, if_index,
2162
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2163
NULL, 0, browse_callback, NULL);
2165
fprintf(stderr, "Mandos plugin mandos-client: "
2166
"Failed to create service browser: %s\n",
2167
avahi_strerror(avahi_server_errno(mc.server)));
2168
exitcode = EX_UNAVAILABLE;
2176
/* Run the main loop */
2179
fprintf(stderr, "Mandos plugin mandos-client: "
2180
"Starting Avahi loop search\n");
2183
ret = avahi_loop_with_timeout(mc.simple_poll,
2184
(int)(retry_interval * 1000));
2186
fprintf(stderr, "Mandos plugin mandos-client: "
2187
"avahi_loop_with_timeout exited %s\n",
2188
(ret == 0) ? "successfully" : "with error");
2194
fprintf(stderr, "Mandos plugin mandos-client: "
2195
"%s exiting\n", argv[0]);
2198
/* Cleanup things */
2200
avahi_s_service_browser_free(sb);
2202
if(mc.server != NULL)
2203
avahi_server_free(mc.server);
2205
if(mc.simple_poll != NULL)
2206
avahi_simple_poll_free(mc.simple_poll);
2208
if(gnutls_initialized){
2209
gnutls_certificate_free_credentials(mc.cred);
2210
gnutls_global_deinit();
2211
gnutls_dh_params_deinit(mc.dh_params);
2214
if(gpgme_initialized){
2215
gpgme_release(mc.ctx);
2218
/* Cleans up the circular linked list of Mandos servers the client
2220
if(mc.current_server != NULL){
2221
mc.current_server->prev->next = NULL;
2222
while(mc.current_server != NULL){
2223
server *next = mc.current_server->next;
2224
free(mc.current_server);
2225
mc.current_server = next;
2229
/* XXX run network hooks "stop" here */
2231
/* Take down the network interface */
2232
if(take_down_interface){
2233
/* Re-raise priviliges */
2237
perror_plus("seteuid");
2240
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2242
perror_plus("ioctl SIOCGIFFLAGS");
2243
} else if(network.ifr_flags & IFF_UP){
2244
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2245
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2247
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2250
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2252
perror_plus("close");
2254
/* Lower privileges permanently */
2258
perror_plus("setuid");
2263
/* Removes the GPGME temp directory and all files inside */
2264
if(tempdir_created){
2265
struct dirent **direntries = NULL;
2266
struct dirent *direntry = NULL;
2267
int numentries = scandir(tempdir, &direntries, notdotentries,
2269
if (numentries > 0){
2270
for(int i = 0; i < numentries; i++){
2271
direntry = direntries[i];
2272
char *fullname = NULL;
2273
ret = asprintf(&fullname, "%s/%s", tempdir,
2276
perror_plus("asprintf");
2279
ret = remove(fullname);
2281
fprintf(stderr, "Mandos plugin mandos-client: "
2282
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2288
/* need to clean even if 0 because man page doesn't specify */
2290
if (numentries == -1){
2291
perror_plus("scandir");
2293
ret = rmdir(tempdir);
2294
if(ret == -1 and errno != ENOENT){
2295
perror_plus("rmdir");
2300
sigemptyset(&old_sigterm_action.sa_mask);
2301
old_sigterm_action.sa_handler = SIG_DFL;
2302
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2303
&old_sigterm_action,
2306
perror_plus("sigaction");
2309
ret = raise(signal_received);
2310
} while(ret != 0 and errno == EINTR);
2312
perror_plus("raise");
2315
TEMP_FAILURE_RETRY(pause());