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, AvahiIfIndex if_index,
198
server *new_server = malloc(sizeof(server));
199
if(new_server == NULL){
200
perror_plus("malloc");
203
*new_server = (server){ .ip = strdup(ip),
205
.if_index = if_index,
207
if(new_server->ip == NULL){
208
perror_plus("strdup");
211
/* Special case of first server */
212
if (mc.current_server == NULL){
213
new_server->next = new_server;
214
new_server->prev = new_server;
215
mc.current_server = new_server;
216
/* Place the new server last in the list */
218
new_server->next = mc.current_server;
219
new_server->prev = mc.current_server->prev;
220
new_server->prev->next = new_server;
221
mc.current_server->prev = new_server;
223
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
225
perror_plus("clock_gettime");
234
static bool init_gpgme(const char *seckey, const char *pubkey,
235
const char *tempdir){
237
gpgme_engine_info_t engine_info;
241
* Helper function to insert pub and seckey to the engine keyring.
243
bool import_key(const char *filename){
246
gpgme_data_t pgp_data;
248
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
254
rc = gpgme_data_new_from_fd(&pgp_data, fd);
255
if(rc != GPG_ERR_NO_ERROR){
256
fprintf(stderr, "Mandos plugin mandos-client: "
257
"bad gpgme_data_new_from_fd: %s: %s\n",
258
gpgme_strsource(rc), gpgme_strerror(rc));
262
rc = gpgme_op_import(mc.ctx, pgp_data);
263
if(rc != GPG_ERR_NO_ERROR){
264
fprintf(stderr, "Mandos plugin mandos-client: "
265
"bad gpgme_op_import: %s: %s\n",
266
gpgme_strsource(rc), gpgme_strerror(rc));
270
ret = (int)TEMP_FAILURE_RETRY(close(fd));
272
perror_plus("close");
274
gpgme_data_release(pgp_data);
279
fprintf(stderr, "Mandos plugin mandos-client: "
280
"Initializing GPGME\n");
284
gpgme_check_version(NULL);
285
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
286
if(rc != GPG_ERR_NO_ERROR){
287
fprintf(stderr, "Mandos plugin mandos-client: "
288
"bad gpgme_engine_check_version: %s: %s\n",
289
gpgme_strsource(rc), gpgme_strerror(rc));
293
/* Set GPGME home directory for the OpenPGP engine only */
294
rc = gpgme_get_engine_info(&engine_info);
295
if(rc != GPG_ERR_NO_ERROR){
296
fprintf(stderr, "Mandos plugin mandos-client: "
297
"bad gpgme_get_engine_info: %s: %s\n",
298
gpgme_strsource(rc), gpgme_strerror(rc));
301
while(engine_info != NULL){
302
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
303
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
304
engine_info->file_name, tempdir);
307
engine_info = engine_info->next;
309
if(engine_info == NULL){
310
fprintf(stderr, "Mandos plugin mandos-client: "
311
"Could not set GPGME home dir to %s\n", tempdir);
315
/* Create new GPGME "context" */
316
rc = gpgme_new(&(mc.ctx));
317
if(rc != GPG_ERR_NO_ERROR){
318
fprintf(stderr, "Mandos plugin mandos-client: "
319
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
324
if(not import_key(pubkey) or not import_key(seckey)){
332
* Decrypt OpenPGP data.
333
* Returns -1 on error
335
static ssize_t pgp_packet_decrypt(const char *cryptotext,
338
gpgme_data_t dh_crypto, dh_plain;
341
size_t plaintext_capacity = 0;
342
ssize_t plaintext_length = 0;
345
fprintf(stderr, "Mandos plugin mandos-client: "
346
"Trying to decrypt OpenPGP data\n");
349
/* Create new GPGME data buffer from memory cryptotext */
350
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
352
if(rc != GPG_ERR_NO_ERROR){
353
fprintf(stderr, "Mandos plugin mandos-client: "
354
"bad gpgme_data_new_from_mem: %s: %s\n",
355
gpgme_strsource(rc), gpgme_strerror(rc));
359
/* Create new empty GPGME data buffer for the plaintext */
360
rc = gpgme_data_new(&dh_plain);
361
if(rc != GPG_ERR_NO_ERROR){
362
fprintf(stderr, "Mandos plugin mandos-client: "
363
"bad gpgme_data_new: %s: %s\n",
364
gpgme_strsource(rc), gpgme_strerror(rc));
365
gpgme_data_release(dh_crypto);
369
/* Decrypt data from the cryptotext data buffer to the plaintext
371
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
372
if(rc != GPG_ERR_NO_ERROR){
373
fprintf(stderr, "Mandos plugin mandos-client: "
374
"bad gpgme_op_decrypt: %s: %s\n",
375
gpgme_strsource(rc), gpgme_strerror(rc));
376
plaintext_length = -1;
378
gpgme_decrypt_result_t result;
379
result = gpgme_op_decrypt_result(mc.ctx);
381
fprintf(stderr, "Mandos plugin mandos-client: "
382
"gpgme_op_decrypt_result failed\n");
384
fprintf(stderr, "Mandos plugin mandos-client: "
385
"Unsupported algorithm: %s\n",
386
result->unsupported_algorithm);
387
fprintf(stderr, "Mandos plugin mandos-client: "
388
"Wrong key usage: %u\n",
389
result->wrong_key_usage);
390
if(result->file_name != NULL){
391
fprintf(stderr, "Mandos plugin mandos-client: "
392
"File name: %s\n", result->file_name);
394
gpgme_recipient_t recipient;
395
recipient = result->recipients;
396
while(recipient != NULL){
397
fprintf(stderr, "Mandos plugin mandos-client: "
398
"Public key algorithm: %s\n",
399
gpgme_pubkey_algo_name(recipient->pubkey_algo));
400
fprintf(stderr, "Mandos plugin mandos-client: "
401
"Key ID: %s\n", recipient->keyid);
402
fprintf(stderr, "Mandos plugin mandos-client: "
403
"Secret key available: %s\n",
404
recipient->status == GPG_ERR_NO_SECKEY
406
recipient = recipient->next;
414
fprintf(stderr, "Mandos plugin mandos-client: "
415
"Decryption of OpenPGP data succeeded\n");
418
/* Seek back to the beginning of the GPGME plaintext data buffer */
419
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
420
perror_plus("gpgme_data_seek");
421
plaintext_length = -1;
427
plaintext_capacity = incbuffer(plaintext,
428
(size_t)plaintext_length,
430
if(plaintext_capacity == 0){
431
perror_plus("incbuffer");
432
plaintext_length = -1;
436
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
438
/* Print the data, if any */
444
perror_plus("gpgme_data_read");
445
plaintext_length = -1;
448
plaintext_length += ret;
452
fprintf(stderr, "Mandos plugin mandos-client: "
453
"Decrypted password is: ");
454
for(ssize_t i = 0; i < plaintext_length; i++){
455
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
457
fprintf(stderr, "\n");
462
/* Delete the GPGME cryptotext data buffer */
463
gpgme_data_release(dh_crypto);
465
/* Delete the GPGME plaintext data buffer */
466
gpgme_data_release(dh_plain);
467
return plaintext_length;
470
static const char * safer_gnutls_strerror(int value){
471
const char *ret = gnutls_strerror(value); /* Spurious warning from
472
-Wunreachable-code */
478
/* GnuTLS log function callback */
479
static void debuggnutls(__attribute__((unused)) int level,
481
fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
484
static int init_gnutls_global(const char *pubkeyfilename,
485
const char *seckeyfilename){
489
fprintf(stderr, "Mandos plugin mandos-client: "
490
"Initializing GnuTLS\n");
493
ret = gnutls_global_init();
494
if(ret != GNUTLS_E_SUCCESS){
495
fprintf(stderr, "Mandos plugin mandos-client: "
496
"GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
501
/* "Use a log level over 10 to enable all debugging options."
504
gnutls_global_set_log_level(11);
505
gnutls_global_set_log_function(debuggnutls);
508
/* OpenPGP credentials */
509
ret = gnutls_certificate_allocate_credentials(&mc.cred);
510
if(ret != GNUTLS_E_SUCCESS){
511
fprintf(stderr, "Mandos plugin mandos-client: "
512
"GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
513
gnutls_global_deinit();
518
fprintf(stderr, "Mandos plugin mandos-client: "
519
"Attempting to use OpenPGP public key %s and"
520
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
524
ret = gnutls_certificate_set_openpgp_key_file
525
(mc.cred, pubkeyfilename, seckeyfilename,
526
GNUTLS_OPENPGP_FMT_BASE64);
527
if(ret != GNUTLS_E_SUCCESS){
529
"Mandos plugin mandos-client: "
530
"Error[%d] while reading the OpenPGP key pair ('%s',"
531
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
532
fprintf(stderr, "Mandos plugin mandos-client: "
533
"The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
537
/* GnuTLS server initialization */
538
ret = gnutls_dh_params_init(&mc.dh_params);
539
if(ret != GNUTLS_E_SUCCESS){
540
fprintf(stderr, "Mandos plugin mandos-client: "
541
"Error in GnuTLS DH parameter initialization:"
542
" %s\n", safer_gnutls_strerror(ret));
545
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
546
if(ret != GNUTLS_E_SUCCESS){
547
fprintf(stderr, "Mandos plugin mandos-client: "
548
"Error in GnuTLS prime generation: %s\n",
549
safer_gnutls_strerror(ret));
553
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
559
gnutls_certificate_free_credentials(mc.cred);
560
gnutls_global_deinit();
561
gnutls_dh_params_deinit(mc.dh_params);
565
static int init_gnutls_session(gnutls_session_t *session){
567
/* GnuTLS session creation */
569
ret = gnutls_init(session, GNUTLS_SERVER);
573
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
574
if(ret != GNUTLS_E_SUCCESS){
575
fprintf(stderr, "Mandos plugin mandos-client: "
576
"Error in GnuTLS session initialization: %s\n",
577
safer_gnutls_strerror(ret));
583
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
585
gnutls_deinit(*session);
588
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
589
if(ret != GNUTLS_E_SUCCESS){
590
fprintf(stderr, "Mandos plugin mandos-client: "
591
"Syntax error at: %s\n", err);
592
fprintf(stderr, "Mandos plugin mandos-client: "
593
"GnuTLS error: %s\n", safer_gnutls_strerror(ret));
594
gnutls_deinit(*session);
600
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
603
gnutls_deinit(*session);
606
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
607
if(ret != GNUTLS_E_SUCCESS){
608
fprintf(stderr, "Mandos plugin mandos-client: "
609
"Error setting GnuTLS credentials: %s\n",
610
safer_gnutls_strerror(ret));
611
gnutls_deinit(*session);
615
/* ignore client certificate if any. */
616
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
618
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
623
/* Avahi log function callback */
624
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
625
__attribute__((unused)) const char *txt){}
627
/* Called when a Mandos server is found */
628
static int start_mandos_communication(const char *ip, uint16_t port,
629
AvahiIfIndex if_index,
631
int ret, tcp_sd = -1;
634
struct sockaddr_in in;
635
struct sockaddr_in6 in6;
638
char *decrypted_buffer = NULL;
639
size_t buffer_length = 0;
640
size_t buffer_capacity = 0;
643
gnutls_session_t session;
644
int pf; /* Protocol family */
661
fprintf(stderr, "Mandos plugin mandos-client: "
662
"Bad address family: %d\n", af);
667
ret = init_gnutls_session(&session);
673
fprintf(stderr, "Mandos plugin mandos-client: "
674
"Setting up a TCP connection to %s, port %" PRIu16
678
tcp_sd = socket(pf, SOCK_STREAM, 0);
681
perror_plus("socket");
691
memset(&to, 0, sizeof(to));
693
to.in6.sin6_family = (sa_family_t)af;
694
ret = inet_pton(af, ip, &to.in6.sin6_addr);
696
to.in.sin_family = (sa_family_t)af;
697
ret = inet_pton(af, ip, &to.in.sin_addr);
701
perror_plus("inet_pton");
707
fprintf(stderr, "Mandos plugin mandos-client: "
708
"Bad address: %s\n", ip);
713
to.in6.sin6_port = htons(port); /* Spurious warnings from
715
-Wunreachable-code */
717
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
718
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
720
if(if_index == AVAHI_IF_UNSPEC){
721
fprintf(stderr, "Mandos plugin mandos-client: "
722
"An IPv6 link-local address is incomplete"
723
" without a network interface\n");
727
/* Set the network interface number as scope */
728
to.in6.sin6_scope_id = (uint32_t)if_index;
731
to.in.sin_port = htons(port); /* Spurious warnings from
733
-Wunreachable-code */
742
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
743
char interface[IF_NAMESIZE];
744
if(if_indextoname((unsigned int)if_index, interface) == NULL){
745
perror_plus("if_indextoname");
747
fprintf(stderr, "Mandos plugin mandos-client: "
748
"Connection to: %s%%%s, port %" PRIu16 "\n",
749
ip, interface, port);
752
fprintf(stderr, "Mandos plugin mandos-client: "
753
"Connection to: %s, port %" PRIu16 "\n", ip, port);
755
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
756
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
759
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
762
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
766
perror_plus("inet_ntop");
768
if(strcmp(addrstr, ip) != 0){
769
fprintf(stderr, "Mandos plugin mandos-client: "
770
"Canonical address form: %s\n", addrstr);
781
ret = connect(tcp_sd, &to.in6, sizeof(to));
783
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
786
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
788
perror_plus("connect");
799
const char *out = mandos_protocol_version;
802
size_t out_size = strlen(out);
803
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
804
out_size - written));
807
perror_plus("write");
811
written += (size_t)ret;
812
if(written < out_size){
815
if(out == mandos_protocol_version){
830
fprintf(stderr, "Mandos plugin mandos-client: "
831
"Establishing TLS session with %s\n", ip);
839
/* Spurious warning from -Wint-to-pointer-cast */
840
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
848
ret = gnutls_handshake(session);
853
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
855
if(ret != GNUTLS_E_SUCCESS){
857
fprintf(stderr, "Mandos plugin mandos-client: "
858
"*** GnuTLS Handshake failed ***\n");
865
/* Read OpenPGP packet that contains the wanted password */
868
fprintf(stderr, "Mandos plugin mandos-client: "
869
"Retrieving OpenPGP encrypted password from %s\n", ip);
879
buffer_capacity = incbuffer(&buffer, buffer_length,
881
if(buffer_capacity == 0){
883
perror_plus("incbuffer");
893
sret = gnutls_record_recv(session, buffer+buffer_length,
900
case GNUTLS_E_INTERRUPTED:
903
case GNUTLS_E_REHANDSHAKE:
905
ret = gnutls_handshake(session);
911
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
913
fprintf(stderr, "Mandos plugin mandos-client: "
914
"*** GnuTLS Re-handshake failed ***\n");
921
fprintf(stderr, "Mandos plugin mandos-client: "
922
"Unknown error while reading data from"
923
" encrypted session with Mandos server\n");
924
gnutls_bye(session, GNUTLS_SHUT_RDWR);
929
buffer_length += (size_t) sret;
934
fprintf(stderr, "Mandos plugin mandos-client: "
935
"Closing TLS session\n");
944
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
949
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
951
if(buffer_length > 0){
952
ssize_t decrypted_buffer_size;
953
decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length,
955
if(decrypted_buffer_size >= 0){
958
while(written < (size_t) decrypted_buffer_size){
964
ret = (int)fwrite(decrypted_buffer + written, 1,
965
(size_t)decrypted_buffer_size - written,
967
if(ret == 0 and ferror(stdout)){
970
fprintf(stderr, "Mandos plugin mandos-client: "
971
"Error writing encrypted data: %s\n",
977
written += (size_t)ret;
983
/* Shutdown procedure */
988
free(decrypted_buffer);
991
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
997
perror_plus("close");
999
gnutls_deinit(session);
1009
static void resolve_callback(AvahiSServiceResolver *r,
1010
AvahiIfIndex interface,
1011
AvahiProtocol proto,
1012
AvahiResolverEvent event,
1016
const char *host_name,
1017
const AvahiAddress *address,
1019
AVAHI_GCC_UNUSED AvahiStringList *txt,
1020
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1022
AVAHI_GCC_UNUSED void* userdata){
1025
/* Called whenever a service has been resolved successfully or
1034
case AVAHI_RESOLVER_FAILURE:
1035
fprintf(stderr, "Mandos plugin mandos-client: "
1036
"(Avahi Resolver) Failed to resolve service '%s'"
1037
" of type '%s' in domain '%s': %s\n", name, type, domain,
1038
avahi_strerror(avahi_server_errno(mc.server)));
1041
case AVAHI_RESOLVER_FOUND:
1043
char ip[AVAHI_ADDRESS_STR_MAX];
1044
avahi_address_snprint(ip, sizeof(ip), address);
1046
fprintf(stderr, "Mandos plugin mandos-client: "
1047
"Mandos server \"%s\" found on %s (%s, %"
1048
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1049
ip, (intmax_t)interface, port);
1051
int ret = start_mandos_communication(ip, port, interface,
1052
avahi_proto_to_af(proto));
1054
avahi_simple_poll_quit(mc.simple_poll);
1056
ret = add_server(ip, port, interface,
1057
avahi_proto_to_af(proto));
1061
avahi_s_service_resolver_free(r);
1064
static void browse_callback(AvahiSServiceBrowser *b,
1065
AvahiIfIndex interface,
1066
AvahiProtocol protocol,
1067
AvahiBrowserEvent event,
1071
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1073
AVAHI_GCC_UNUSED void* userdata){
1076
/* Called whenever a new services becomes available on the LAN or
1077
is removed from the LAN */
1085
case AVAHI_BROWSER_FAILURE:
1087
fprintf(stderr, "Mandos plugin mandos-client: "
1088
"(Avahi browser) %s\n",
1089
avahi_strerror(avahi_server_errno(mc.server)));
1090
avahi_simple_poll_quit(mc.simple_poll);
1093
case AVAHI_BROWSER_NEW:
1094
/* We ignore the returned Avahi resolver object. In the callback
1095
function we free it. If the Avahi server is terminated before
1096
the callback function is called the Avahi server will free the
1099
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1100
name, type, domain, protocol, 0,
1101
resolve_callback, NULL) == NULL)
1102
fprintf(stderr, "Mandos plugin mandos-client: "
1103
"Avahi: Failed to resolve service '%s': %s\n",
1104
name, avahi_strerror(avahi_server_errno(mc.server)));
1107
case AVAHI_BROWSER_REMOVE:
1110
case AVAHI_BROWSER_ALL_FOR_NOW:
1111
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1113
fprintf(stderr, "Mandos plugin mandos-client: "
1114
"No Mandos server found, still searching...\n");
1120
/* Signal handler that stops main loop after SIGTERM */
1121
static void handle_sigterm(int sig){
1126
signal_received = sig;
1127
int old_errno = errno;
1128
/* set main loop to exit */
1129
if(mc.simple_poll != NULL){
1130
avahi_simple_poll_quit(mc.simple_poll);
1135
bool get_flags(const char *ifname, struct ifreq *ifr){
1138
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1140
perror_plus("socket");
1143
strcpy(ifr->ifr_name, ifname);
1144
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1147
perror_plus("ioctl SIOCGIFFLAGS");
1154
bool good_flags(const char *ifname, const struct ifreq *ifr){
1156
/* Reject the loopback device */
1157
if(ifr->ifr_flags & IFF_LOOPBACK){
1159
fprintf(stderr, "Mandos plugin mandos-client: "
1160
"Rejecting loopback interface \"%s\"\n", ifname);
1164
/* Accept point-to-point devices only if connect_to is specified */
1165
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1167
fprintf(stderr, "Mandos plugin mandos-client: "
1168
"Accepting point-to-point interface \"%s\"\n", ifname);
1172
/* Otherwise, reject non-broadcast-capable devices */
1173
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1175
fprintf(stderr, "Mandos plugin mandos-client: "
1176
"Rejecting non-broadcast interface \"%s\"\n", ifname);
1180
/* Reject non-ARP interfaces (including dummy interfaces) */
1181
if(ifr->ifr_flags & IFF_NOARP){
1183
fprintf(stderr, "Mandos plugin mandos-client: "
1184
"Rejecting non-ARP interface \"%s\"\n", ifname);
1189
/* Accept this device */
1191
fprintf(stderr, "Mandos plugin mandos-client: "
1192
"Interface \"%s\" is good\n", ifname);
1198
* This function determines if a directory entry in /sys/class/net
1199
* corresponds to an acceptable network device.
1200
* (This function is passed to scandir(3) as a filter function.)
1202
int good_interface(const struct dirent *if_entry){
1203
if(if_entry->d_name[0] == '.'){
1208
if(not get_flags(if_entry->d_name, &ifr)){
1210
fprintf(stderr, "Mandos plugin mandos-client: "
1211
"Failed to get flags for interface \"%s\"\n",
1217
if(not good_flags(if_entry->d_name, &ifr)){
1224
* This function determines if a directory entry in /sys/class/net
1225
* corresponds to an acceptable network device which is up.
1226
* (This function is passed to scandir(3) as a filter function.)
1228
int up_interface(const struct dirent *if_entry){
1229
if(if_entry->d_name[0] == '.'){
1234
if(not get_flags(if_entry->d_name, &ifr)){
1236
fprintf(stderr, "Mandos plugin mandos-client: "
1237
"Failed to get flags for interface \"%s\"\n",
1243
/* Reject down interfaces */
1244
if(not (ifr.ifr_flags & IFF_UP)){
1246
fprintf(stderr, "Mandos plugin mandos-client: "
1247
"Rejecting down interface \"%s\"\n",
1253
/* Reject non-running interfaces */
1254
if(not (ifr.ifr_flags & IFF_RUNNING)){
1256
fprintf(stderr, "Mandos plugin mandos-client: "
1257
"Rejecting non-running interface \"%s\"\n",
1263
if(not good_flags(if_entry->d_name, &ifr)){
1269
int notdotentries(const struct dirent *direntry){
1270
/* Skip "." and ".." */
1271
if(direntry->d_name[0] == '.'
1272
and (direntry->d_name[1] == '\0'
1273
or (direntry->d_name[1] == '.'
1274
and direntry->d_name[2] == '\0'))){
1280
/* Is this directory entry a runnable program? */
1281
int runnable_hook(const struct dirent *direntry){
1286
if((direntry->d_name)[0] == '\0'){
1291
sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1292
"abcdefghijklmnopqrstuvwxyz"
1295
if((direntry->d_name)[sret] != '\0'){
1296
/* Contains non-allowed characters */
1298
fprintf(stderr, "Mandos plugin mandos-client: "
1299
"Ignoring hook \"%s\" with bad name\n",
1305
char *fullname = NULL;
1306
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1308
perror_plus("asprintf");
1312
ret = stat(fullname, &st);
1315
perror_plus("Could not stat hook");
1319
if(not (S_ISREG(st.st_mode))){
1320
/* Not a regular file */
1322
fprintf(stderr, "Mandos plugin mandos-client: "
1323
"Ignoring hook \"%s\" - not a file\n",
1328
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1329
/* Not executable */
1331
fprintf(stderr, "Mandos plugin mandos-client: "
1332
"Ignoring hook \"%s\" - not executable\n",
1340
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1342
struct timespec now;
1343
struct timespec waited_time;
1344
intmax_t block_time;
1347
if(mc.current_server == NULL){
1349
fprintf(stderr, "Mandos plugin mandos-client: "
1350
"Wait until first server is found. No timeout!\n");
1352
ret = avahi_simple_poll_iterate(s, -1);
1355
fprintf(stderr, "Mandos plugin mandos-client: "
1356
"Check current_server if we should run it,"
1359
/* the current time */
1360
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1362
perror_plus("clock_gettime");
1365
/* Calculating in ms how long time between now and server
1366
who we visted longest time ago. Now - last seen. */
1367
waited_time.tv_sec = (now.tv_sec
1368
- mc.current_server->last_seen.tv_sec);
1369
waited_time.tv_nsec = (now.tv_nsec
1370
- mc.current_server->last_seen.tv_nsec);
1371
/* total time is 10s/10,000ms.
1372
Converting to s from ms by dividing by 1,000,
1373
and ns to ms by dividing by 1,000,000. */
1374
block_time = ((retry_interval
1375
- ((intmax_t)waited_time.tv_sec * 1000))
1376
- ((intmax_t)waited_time.tv_nsec / 1000000));
1379
fprintf(stderr, "Mandos plugin mandos-client: "
1380
"Blocking for %" PRIdMAX " ms\n", block_time);
1383
if(block_time <= 0){
1384
ret = start_mandos_communication(mc.current_server->ip,
1385
mc.current_server->port,
1386
mc.current_server->if_index,
1387
mc.current_server->af);
1389
avahi_simple_poll_quit(mc.simple_poll);
1392
ret = clock_gettime(CLOCK_MONOTONIC,
1393
&mc.current_server->last_seen);
1395
perror_plus("clock_gettime");
1398
mc.current_server = mc.current_server->next;
1399
block_time = 0; /* Call avahi to find new Mandos
1400
servers, but don't block */
1403
ret = avahi_simple_poll_iterate(s, (int)block_time);
1406
if (ret > 0 or errno != EINTR){
1407
return (ret != 1) ? ret : 0;
1413
bool run_network_hooks(const char *mode, const char *interface,
1415
struct dirent **direntries;
1416
struct dirent *direntry;
1418
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1421
perror_plus("scandir");
1423
int devnull = open("/dev/null", O_RDONLY);
1424
for(int i = 0; i < numhooks; i++){
1425
direntry = direntries[0];
1426
char *fullname = NULL;
1427
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1429
perror_plus("asprintf");
1432
pid_t hook_pid = fork();
1435
dup2(devnull, STDIN_FILENO);
1437
dup2(STDERR_FILENO, STDOUT_FILENO);
1438
ret = setenv("MANDOSNETHOOKDIR", hookdir, 1);
1440
perror_plus("setenv");
1443
ret = setenv("DEVICE", interface, 1);
1445
perror_plus("setenv");
1448
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1450
perror_plus("setenv");
1453
ret = setenv("MODE", mode, 1);
1455
perror_plus("setenv");
1459
ret = asprintf(&delaystring, "%f", delay);
1461
perror_plus("asprintf");
1464
ret = setenv("DELAY", delaystring, 1);
1467
perror_plus("setenv");
1471
ret = execl(fullname, direntry->d_name, mode, NULL);
1472
perror_plus("execl");
1475
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1476
perror_plus("waitpid");
1480
if(WIFEXITED(status)){
1481
if(WEXITSTATUS(status) != 0){
1482
fprintf(stderr, "Mandos plugin mandos-client: "
1483
"Warning: network hook \"%s\" exited"
1484
" with status %d\n", direntry->d_name,
1485
WEXITSTATUS(status));
1489
} else if(WIFSIGNALED(status)){
1490
fprintf(stderr, "Mandos plugin mandos-client: "
1491
"Warning: network hook \"%s\" died by"
1492
" signal %d\n", direntry->d_name,
1497
fprintf(stderr, "Mandos plugin mandos-client: "
1498
"Warning: network hook \"%s\" crashed\n",
1514
int main(int argc, char *argv[]){
1515
AvahiSServiceBrowser *sb = NULL;
1520
int exitcode = EXIT_SUCCESS;
1521
const char *interface = "";
1522
struct ifreq network;
1524
bool take_down_interface = false;
1527
char tempdir[] = "/tmp/mandosXXXXXX";
1528
bool tempdir_created = false;
1529
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1530
const char *seckey = PATHDIR "/" SECKEY;
1531
const char *pubkey = PATHDIR "/" PUBKEY;
1533
bool gnutls_initialized = false;
1534
bool gpgme_initialized = false;
1536
double retry_interval = 10; /* 10s between trying a server and
1537
retrying the same server again */
1539
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1540
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1545
/* Lower any group privileges we might have, just to be safe */
1549
perror_plus("setgid");
1552
/* Lower user privileges (temporarily) */
1556
perror_plus("seteuid");
1564
struct argp_option options[] = {
1565
{ .name = "debug", .key = 128,
1566
.doc = "Debug mode", .group = 3 },
1567
{ .name = "connect", .key = 'c',
1568
.arg = "ADDRESS:PORT",
1569
.doc = "Connect directly to a specific Mandos server",
1571
{ .name = "interface", .key = 'i',
1573
.doc = "Network interface that will be used to search for"
1576
{ .name = "seckey", .key = 's',
1578
.doc = "OpenPGP secret key file base name",
1580
{ .name = "pubkey", .key = 'p',
1582
.doc = "OpenPGP public key file base name",
1584
{ .name = "dh-bits", .key = 129,
1586
.doc = "Bit length of the prime number used in the"
1587
" Diffie-Hellman key exchange",
1589
{ .name = "priority", .key = 130,
1591
.doc = "GnuTLS priority string for the TLS handshake",
1593
{ .name = "delay", .key = 131,
1595
.doc = "Maximum delay to wait for interface startup",
1597
{ .name = "retry", .key = 132,
1599
.doc = "Retry interval used when denied by the mandos server",
1601
{ .name = "network-hook-dir", .key = 133,
1603
.doc = "Directory where network hooks are located",
1606
* These reproduce what we would get without ARGP_NO_HELP
1608
{ .name = "help", .key = '?',
1609
.doc = "Give this help list", .group = -1 },
1610
{ .name = "usage", .key = -3,
1611
.doc = "Give a short usage message", .group = -1 },
1612
{ .name = "version", .key = 'V',
1613
.doc = "Print program version", .group = -1 },
1617
error_t parse_opt(int key, char *arg,
1618
struct argp_state *state){
1621
case 128: /* --debug */
1624
case 'c': /* --connect */
1627
case 'i': /* --interface */
1630
case 's': /* --seckey */
1633
case 'p': /* --pubkey */
1636
case 129: /* --dh-bits */
1638
tmpmax = strtoimax(arg, &tmp, 10);
1639
if(errno != 0 or tmp == arg or *tmp != '\0'
1640
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1641
argp_error(state, "Bad number of DH bits");
1643
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1645
case 130: /* --priority */
1648
case 131: /* --delay */
1650
delay = strtof(arg, &tmp);
1651
if(errno != 0 or tmp == arg or *tmp != '\0'){
1652
argp_error(state, "Bad delay");
1654
case 132: /* --retry */
1656
retry_interval = strtod(arg, &tmp);
1657
if(errno != 0 or tmp == arg or *tmp != '\0'
1658
or (retry_interval * 1000) > INT_MAX
1659
or retry_interval < 0){
1660
argp_error(state, "Bad retry interval");
1663
case 133: /* --network-hook-dir */
1667
* These reproduce what we would get without ARGP_NO_HELP
1669
case '?': /* --help */
1670
argp_state_help(state, state->out_stream,
1671
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1672
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1673
case -3: /* --usage */
1674
argp_state_help(state, state->out_stream,
1675
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1676
case 'V': /* --version */
1677
fprintf(state->out_stream, "Mandos plugin mandos-client: ");
1678
fprintf(state->out_stream, "%s\n", argp_program_version);
1679
exit(argp_err_exit_status);
1682
return ARGP_ERR_UNKNOWN;
1687
struct argp argp = { .options = options, .parser = parse_opt,
1689
.doc = "Mandos client -- Get and decrypt"
1690
" passwords from a Mandos server" };
1691
ret = argp_parse(&argp, argc, argv,
1692
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1699
perror_plus("argp_parse");
1700
exitcode = EX_OSERR;
1703
exitcode = EX_USAGE;
1709
/* Work around Debian bug #633582:
1710
<http://bugs.debian.org/633582> */
1713
/* Re-raise priviliges */
1717
perror_plus("seteuid");
1720
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
1721
int seckey_fd = open(seckey, O_RDONLY);
1722
if(seckey_fd == -1){
1723
perror_plus("open");
1725
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
1727
perror_plus("fstat");
1729
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1730
ret = fchown(seckey_fd, uid, gid);
1732
perror_plus("fchown");
1736
TEMP_FAILURE_RETRY(close(seckey_fd));
1740
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
1741
int pubkey_fd = open(pubkey, O_RDONLY);
1742
if(pubkey_fd == -1){
1743
perror_plus("open");
1745
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
1747
perror_plus("fstat");
1749
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1750
ret = fchown(pubkey_fd, uid, gid);
1752
perror_plus("fchown");
1756
TEMP_FAILURE_RETRY(close(pubkey_fd));
1760
/* Lower privileges */
1764
perror_plus("seteuid");
1768
/* Run network hooks */
1770
/* Re-raise priviliges */
1774
perror_plus("seteuid");
1776
if(not run_network_hooks("start", interface, delay)){
1779
/* Lower privileges */
1783
perror_plus("seteuid");
1788
avahi_set_log_function(empty_log);
1791
if(interface[0] == '\0'){
1792
struct dirent **direntries;
1793
/* First look for interfaces that are up */
1794
ret = scandir(sys_class_net, &direntries, up_interface,
1797
/* No up interfaces, look for any good interfaces */
1799
ret = scandir(sys_class_net, &direntries, good_interface,
1803
/* Pick the first interface returned */
1804
interface = strdup(direntries[0]->d_name);
1806
fprintf(stderr, "Mandos plugin mandos-client: "
1807
"Using interface \"%s\"\n", interface);
1809
if(interface == NULL){
1810
perror_plus("malloc");
1812
exitcode = EXIT_FAILURE;
1818
fprintf(stderr, "Mandos plugin mandos-client: "
1819
"Could not find a network interface\n");
1820
exitcode = EXIT_FAILURE;
1825
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1826
from the signal handler */
1827
/* Initialize the pseudo-RNG for Avahi */
1828
srand((unsigned int) time(NULL));
1829
mc.simple_poll = avahi_simple_poll_new();
1830
if(mc.simple_poll == NULL){
1831
fprintf(stderr, "Mandos plugin mandos-client: "
1832
"Avahi: Failed to create simple poll object.\n");
1833
exitcode = EX_UNAVAILABLE;
1837
sigemptyset(&sigterm_action.sa_mask);
1838
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1840
perror_plus("sigaddset");
1841
exitcode = EX_OSERR;
1844
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1846
perror_plus("sigaddset");
1847
exitcode = EX_OSERR;
1850
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1852
perror_plus("sigaddset");
1853
exitcode = EX_OSERR;
1856
/* Need to check if the handler is SIG_IGN before handling:
1857
| [[info:libc:Initial Signal Actions]] |
1858
| [[info:libc:Basic Signal Handling]] |
1860
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1862
perror_plus("sigaction");
1865
if(old_sigterm_action.sa_handler != SIG_IGN){
1866
ret = sigaction(SIGINT, &sigterm_action, NULL);
1868
perror_plus("sigaction");
1869
exitcode = EX_OSERR;
1873
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1875
perror_plus("sigaction");
1878
if(old_sigterm_action.sa_handler != SIG_IGN){
1879
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1881
perror_plus("sigaction");
1882
exitcode = EX_OSERR;
1886
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1888
perror_plus("sigaction");
1891
if(old_sigterm_action.sa_handler != SIG_IGN){
1892
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1894
perror_plus("sigaction");
1895
exitcode = EX_OSERR;
1900
/* If the interface is down, bring it up */
1901
if(strcmp(interface, "none") != 0){
1902
if_index = (AvahiIfIndex) if_nametoindex(interface);
1904
fprintf(stderr, "Mandos plugin mandos-client: "
1905
"No such interface: \"%s\"\n", interface);
1906
exitcode = EX_UNAVAILABLE;
1914
/* Re-raise priviliges */
1918
perror_plus("seteuid");
1922
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1923
messages about the network interface to mess up the prompt */
1924
ret = klogctl(8, NULL, 5);
1925
bool restore_loglevel = true;
1927
restore_loglevel = false;
1928
perror_plus("klogctl");
1930
#endif /* __linux__ */
1932
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1934
perror_plus("socket");
1935
exitcode = EX_OSERR;
1937
if(restore_loglevel){
1938
ret = klogctl(7, NULL, 0);
1940
perror_plus("klogctl");
1943
#endif /* __linux__ */
1944
/* Lower privileges */
1948
perror_plus("seteuid");
1952
strcpy(network.ifr_name, interface);
1953
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1955
perror_plus("ioctl SIOCGIFFLAGS");
1957
if(restore_loglevel){
1958
ret = klogctl(7, NULL, 0);
1960
perror_plus("klogctl");
1963
#endif /* __linux__ */
1964
exitcode = EX_OSERR;
1965
/* Lower privileges */
1969
perror_plus("seteuid");
1973
if((network.ifr_flags & IFF_UP) == 0){
1974
network.ifr_flags |= IFF_UP;
1975
take_down_interface = true;
1976
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1978
take_down_interface = false;
1979
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1980
exitcode = EX_OSERR;
1982
if(restore_loglevel){
1983
ret = klogctl(7, NULL, 0);
1985
perror_plus("klogctl");
1988
#endif /* __linux__ */
1989
/* Lower privileges */
1993
perror_plus("seteuid");
1998
/* Sleep checking until interface is running.
1999
Check every 0.25s, up to total time of delay */
2000
for(int i=0; i < delay * 4; i++){
2001
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2003
perror_plus("ioctl SIOCGIFFLAGS");
2004
} else if(network.ifr_flags & IFF_RUNNING){
2007
struct timespec sleeptime = { .tv_nsec = 250000000 };
2008
ret = nanosleep(&sleeptime, NULL);
2009
if(ret == -1 and errno != EINTR){
2010
perror_plus("nanosleep");
2013
if(not take_down_interface){
2014
/* We won't need the socket anymore */
2015
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2017
perror_plus("close");
2021
if(restore_loglevel){
2022
/* Restores kernel loglevel to default */
2023
ret = klogctl(7, NULL, 0);
2025
perror_plus("klogctl");
2028
#endif /* __linux__ */
2029
/* Lower privileges */
2031
/* Lower privileges */
2034
perror_plus("seteuid");
2042
ret = init_gnutls_global(pubkey, seckey);
2044
fprintf(stderr, "Mandos plugin mandos-client: "
2045
"init_gnutls_global failed\n");
2046
exitcode = EX_UNAVAILABLE;
2049
gnutls_initialized = true;
2056
if(mkdtemp(tempdir) == NULL){
2057
perror_plus("mkdtemp");
2060
tempdir_created = true;
2066
if(not init_gpgme(pubkey, seckey, tempdir)){
2067
fprintf(stderr, "Mandos plugin mandos-client: "
2068
"init_gpgme failed\n");
2069
exitcode = EX_UNAVAILABLE;
2072
gpgme_initialized = true;
2079
if(connect_to != NULL){
2080
/* Connect directly, do not use Zeroconf */
2081
/* (Mainly meant for debugging) */
2082
char *address = strrchr(connect_to, ':');
2083
if(address == NULL){
2084
fprintf(stderr, "Mandos plugin mandos-client: "
2085
"No colon in address\n");
2086
exitcode = EX_USAGE;
2096
tmpmax = strtoimax(address+1, &tmp, 10);
2097
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2098
or tmpmax != (uint16_t)tmpmax){
2099
fprintf(stderr, "Mandos plugin mandos-client: "
2100
"Bad port number\n");
2101
exitcode = EX_USAGE;
2109
port = (uint16_t)tmpmax;
2111
/* Colon in address indicates IPv6 */
2113
if(strchr(connect_to, ':') != NULL){
2115
/* Accept [] around IPv6 address - see RFC 5952 */
2116
if(connect_to[0] == '[' and address[-1] == ']')
2124
address = connect_to;
2130
while(not quit_now){
2131
ret = start_mandos_communication(address, port, if_index, af);
2132
if(quit_now or ret == 0){
2136
fprintf(stderr, "Mandos plugin mandos-client: "
2137
"Retrying in %d seconds\n", (int)retry_interval);
2139
sleep((int)retry_interval);
2143
exitcode = EXIT_SUCCESS;
2154
AvahiServerConfig config;
2155
/* Do not publish any local Zeroconf records */
2156
avahi_server_config_init(&config);
2157
config.publish_hinfo = 0;
2158
config.publish_addresses = 0;
2159
config.publish_workstation = 0;
2160
config.publish_domain = 0;
2162
/* Allocate a new server */
2163
mc.server = avahi_server_new(avahi_simple_poll_get
2164
(mc.simple_poll), &config, NULL,
2167
/* Free the Avahi configuration data */
2168
avahi_server_config_free(&config);
2171
/* Check if creating the Avahi server object succeeded */
2172
if(mc.server == NULL){
2173
fprintf(stderr, "Mandos plugin mandos-client: "
2174
"Failed to create Avahi server: %s\n",
2175
avahi_strerror(error));
2176
exitcode = EX_UNAVAILABLE;
2184
/* Create the Avahi service browser */
2185
sb = avahi_s_service_browser_new(mc.server, if_index,
2186
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2187
NULL, 0, browse_callback, NULL);
2189
fprintf(stderr, "Mandos plugin mandos-client: "
2190
"Failed to create service browser: %s\n",
2191
avahi_strerror(avahi_server_errno(mc.server)));
2192
exitcode = EX_UNAVAILABLE;
2200
/* Run the main loop */
2203
fprintf(stderr, "Mandos plugin mandos-client: "
2204
"Starting Avahi loop search\n");
2207
ret = avahi_loop_with_timeout(mc.simple_poll,
2208
(int)(retry_interval * 1000));
2210
fprintf(stderr, "Mandos plugin mandos-client: "
2211
"avahi_loop_with_timeout exited %s\n",
2212
(ret == 0) ? "successfully" : "with error");
2218
fprintf(stderr, "Mandos plugin mandos-client: "
2219
"%s exiting\n", argv[0]);
2222
/* Cleanup things */
2224
avahi_s_service_browser_free(sb);
2226
if(mc.server != NULL)
2227
avahi_server_free(mc.server);
2229
if(mc.simple_poll != NULL)
2230
avahi_simple_poll_free(mc.simple_poll);
2232
if(gnutls_initialized){
2233
gnutls_certificate_free_credentials(mc.cred);
2234
gnutls_global_deinit();
2235
gnutls_dh_params_deinit(mc.dh_params);
2238
if(gpgme_initialized){
2239
gpgme_release(mc.ctx);
2242
/* Cleans up the circular linked list of Mandos servers the client
2244
if(mc.current_server != NULL){
2245
mc.current_server->prev->next = NULL;
2246
while(mc.current_server != NULL){
2247
server *next = mc.current_server->next;
2248
free(mc.current_server);
2249
mc.current_server = next;
2253
/* Re-raise priviliges */
2258
perror_plus("seteuid");
2260
/* Run network hooks */
2261
if(not run_network_hooks("stop", interface, delay)){
2265
/* Take down the network interface */
2266
if(take_down_interface and geteuid() == 0){
2267
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2269
perror_plus("ioctl SIOCGIFFLAGS");
2270
} else if(network.ifr_flags & IFF_UP){
2271
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2272
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2274
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2277
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2279
perror_plus("close");
2283
/* Lower privileges permanently */
2287
perror_plus("setuid");
2290
/* Removes the GPGME temp directory and all files inside */
2291
if(tempdir_created){
2292
struct dirent **direntries = NULL;
2293
struct dirent *direntry = NULL;
2294
int numentries = scandir(tempdir, &direntries, notdotentries,
2296
if (numentries > 0){
2297
for(int i = 0; i < numentries; i++){
2298
direntry = direntries[i];
2299
char *fullname = NULL;
2300
ret = asprintf(&fullname, "%s/%s", tempdir,
2303
perror_plus("asprintf");
2306
ret = remove(fullname);
2308
fprintf(stderr, "Mandos plugin mandos-client: "
2309
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2315
/* need to clean even if 0 because man page doesn't specify */
2317
if (numentries == -1){
2318
perror_plus("scandir");
2320
ret = rmdir(tempdir);
2321
if(ret == -1 and errno != ENOENT){
2322
perror_plus("rmdir");
2327
sigemptyset(&old_sigterm_action.sa_mask);
2328
old_sigterm_action.sa_handler = SIG_DFL;
2329
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2330
&old_sigterm_action,
2333
perror_plus("sigaction");
2336
ret = raise(signal_received);
2337
} while(ret != 0 and errno == EINTR);
2339
perror_plus("raise");
2342
TEMP_FAILURE_RETRY(pause());