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);
177
int fprintf_plus(FILE *stream, const char *format, ...){
179
va_start (ap, format);
181
TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ", program_invocation_short_name));
182
return TEMP_FAILURE_RETRY(vfprintf(stream, format, ap));
186
* Make additional room in "buffer" for at least BUFFER_SIZE more
187
* bytes. "buffer_capacity" is how much is currently allocated,
188
* "buffer_length" is how much is already used.
190
size_t incbuffer(char **buffer, size_t buffer_length,
191
size_t buffer_capacity){
192
if(buffer_length + BUFFER_SIZE > buffer_capacity){
193
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
197
buffer_capacity += BUFFER_SIZE;
199
return buffer_capacity;
202
/* Add server to set of servers to retry periodically */
203
int add_server(const char *ip, uint16_t port, AvahiIfIndex if_index,
206
server *new_server = malloc(sizeof(server));
207
if(new_server == NULL){
208
perror_plus("malloc");
211
*new_server = (server){ .ip = strdup(ip),
213
.if_index = if_index,
215
if(new_server->ip == NULL){
216
perror_plus("strdup");
219
/* Special case of first server */
220
if (mc.current_server == NULL){
221
new_server->next = new_server;
222
new_server->prev = new_server;
223
mc.current_server = new_server;
224
/* Place the new server last in the list */
226
new_server->next = mc.current_server;
227
new_server->prev = mc.current_server->prev;
228
new_server->prev->next = new_server;
229
mc.current_server->prev = new_server;
231
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
233
perror_plus("clock_gettime");
242
static bool init_gpgme(const char *seckey, const char *pubkey,
243
const char *tempdir){
245
gpgme_engine_info_t engine_info;
249
* Helper function to insert pub and seckey to the engine keyring.
251
bool import_key(const char *filename){
254
gpgme_data_t pgp_data;
256
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
262
rc = gpgme_data_new_from_fd(&pgp_data, fd);
263
if(rc != GPG_ERR_NO_ERROR){
264
fprintf(stderr, "Mandos plugin mandos-client: "
265
"bad gpgme_data_new_from_fd: %s: %s\n",
266
gpgme_strsource(rc), gpgme_strerror(rc));
270
rc = gpgme_op_import(mc.ctx, pgp_data);
271
if(rc != GPG_ERR_NO_ERROR){
272
fprintf(stderr, "Mandos plugin mandos-client: "
273
"bad gpgme_op_import: %s: %s\n",
274
gpgme_strsource(rc), gpgme_strerror(rc));
278
ret = (int)TEMP_FAILURE_RETRY(close(fd));
280
perror_plus("close");
282
gpgme_data_release(pgp_data);
287
fprintf(stderr, "Mandos plugin mandos-client: "
288
"Initializing GPGME\n");
292
gpgme_check_version(NULL);
293
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
294
if(rc != GPG_ERR_NO_ERROR){
295
fprintf(stderr, "Mandos plugin mandos-client: "
296
"bad gpgme_engine_check_version: %s: %s\n",
297
gpgme_strsource(rc), gpgme_strerror(rc));
301
/* Set GPGME home directory for the OpenPGP engine only */
302
rc = gpgme_get_engine_info(&engine_info);
303
if(rc != GPG_ERR_NO_ERROR){
304
fprintf(stderr, "Mandos plugin mandos-client: "
305
"bad gpgme_get_engine_info: %s: %s\n",
306
gpgme_strsource(rc), gpgme_strerror(rc));
309
while(engine_info != NULL){
310
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
311
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
312
engine_info->file_name, tempdir);
315
engine_info = engine_info->next;
317
if(engine_info == NULL){
318
fprintf(stderr, "Mandos plugin mandos-client: "
319
"Could not set GPGME home dir to %s\n", tempdir);
323
/* Create new GPGME "context" */
324
rc = gpgme_new(&(mc.ctx));
325
if(rc != GPG_ERR_NO_ERROR){
326
fprintf(stderr, "Mandos plugin mandos-client: "
327
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
332
if(not import_key(pubkey) or not import_key(seckey)){
340
* Decrypt OpenPGP data.
341
* Returns -1 on error
343
static ssize_t pgp_packet_decrypt(const char *cryptotext,
346
gpgme_data_t dh_crypto, dh_plain;
349
size_t plaintext_capacity = 0;
350
ssize_t plaintext_length = 0;
353
fprintf(stderr, "Mandos plugin mandos-client: "
354
"Trying to decrypt OpenPGP data\n");
357
/* Create new GPGME data buffer from memory cryptotext */
358
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
360
if(rc != GPG_ERR_NO_ERROR){
361
fprintf(stderr, "Mandos plugin mandos-client: "
362
"bad gpgme_data_new_from_mem: %s: %s\n",
363
gpgme_strsource(rc), gpgme_strerror(rc));
367
/* Create new empty GPGME data buffer for the plaintext */
368
rc = gpgme_data_new(&dh_plain);
369
if(rc != GPG_ERR_NO_ERROR){
370
fprintf(stderr, "Mandos plugin mandos-client: "
371
"bad gpgme_data_new: %s: %s\n",
372
gpgme_strsource(rc), gpgme_strerror(rc));
373
gpgme_data_release(dh_crypto);
377
/* Decrypt data from the cryptotext data buffer to the plaintext
379
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
380
if(rc != GPG_ERR_NO_ERROR){
381
fprintf(stderr, "Mandos plugin mandos-client: "
382
"bad gpgme_op_decrypt: %s: %s\n",
383
gpgme_strsource(rc), gpgme_strerror(rc));
384
plaintext_length = -1;
386
gpgme_decrypt_result_t result;
387
result = gpgme_op_decrypt_result(mc.ctx);
389
fprintf(stderr, "Mandos plugin mandos-client: "
390
"gpgme_op_decrypt_result failed\n");
392
fprintf(stderr, "Mandos plugin mandos-client: "
393
"Unsupported algorithm: %s\n",
394
result->unsupported_algorithm);
395
fprintf(stderr, "Mandos plugin mandos-client: "
396
"Wrong key usage: %u\n",
397
result->wrong_key_usage);
398
if(result->file_name != NULL){
399
fprintf(stderr, "Mandos plugin mandos-client: "
400
"File name: %s\n", result->file_name);
402
gpgme_recipient_t recipient;
403
recipient = result->recipients;
404
while(recipient != NULL){
405
fprintf(stderr, "Mandos plugin mandos-client: "
406
"Public key algorithm: %s\n",
407
gpgme_pubkey_algo_name(recipient->pubkey_algo));
408
fprintf(stderr, "Mandos plugin mandos-client: "
409
"Key ID: %s\n", recipient->keyid);
410
fprintf(stderr, "Mandos plugin mandos-client: "
411
"Secret key available: %s\n",
412
recipient->status == GPG_ERR_NO_SECKEY
414
recipient = recipient->next;
422
fprintf(stderr, "Mandos plugin mandos-client: "
423
"Decryption of OpenPGP data succeeded\n");
426
/* Seek back to the beginning of the GPGME plaintext data buffer */
427
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
428
perror_plus("gpgme_data_seek");
429
plaintext_length = -1;
435
plaintext_capacity = incbuffer(plaintext,
436
(size_t)plaintext_length,
438
if(plaintext_capacity == 0){
439
perror_plus("incbuffer");
440
plaintext_length = -1;
444
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
446
/* Print the data, if any */
452
perror_plus("gpgme_data_read");
453
plaintext_length = -1;
456
plaintext_length += ret;
460
fprintf(stderr, "Mandos plugin mandos-client: "
461
"Decrypted password is: ");
462
for(ssize_t i = 0; i < plaintext_length; i++){
463
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
465
fprintf(stderr, "\n");
470
/* Delete the GPGME cryptotext data buffer */
471
gpgme_data_release(dh_crypto);
473
/* Delete the GPGME plaintext data buffer */
474
gpgme_data_release(dh_plain);
475
return plaintext_length;
478
static const char * safer_gnutls_strerror(int value){
479
const char *ret = gnutls_strerror(value); /* Spurious warning from
480
-Wunreachable-code */
486
/* GnuTLS log function callback */
487
static void debuggnutls(__attribute__((unused)) int level,
489
fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
492
static int init_gnutls_global(const char *pubkeyfilename,
493
const char *seckeyfilename){
497
fprintf(stderr, "Mandos plugin mandos-client: "
498
"Initializing GnuTLS\n");
501
ret = gnutls_global_init();
502
if(ret != GNUTLS_E_SUCCESS){
503
fprintf(stderr, "Mandos plugin mandos-client: "
504
"GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
509
/* "Use a log level over 10 to enable all debugging options."
512
gnutls_global_set_log_level(11);
513
gnutls_global_set_log_function(debuggnutls);
516
/* OpenPGP credentials */
517
ret = gnutls_certificate_allocate_credentials(&mc.cred);
518
if(ret != GNUTLS_E_SUCCESS){
519
fprintf(stderr, "Mandos plugin mandos-client: "
520
"GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
521
gnutls_global_deinit();
526
fprintf(stderr, "Mandos plugin mandos-client: "
527
"Attempting to use OpenPGP public key %s and"
528
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
532
ret = gnutls_certificate_set_openpgp_key_file
533
(mc.cred, pubkeyfilename, seckeyfilename,
534
GNUTLS_OPENPGP_FMT_BASE64);
535
if(ret != GNUTLS_E_SUCCESS){
537
"Mandos plugin mandos-client: "
538
"Error[%d] while reading the OpenPGP key pair ('%s',"
539
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
540
fprintf(stderr, "Mandos plugin mandos-client: "
541
"The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
545
/* GnuTLS server initialization */
546
ret = gnutls_dh_params_init(&mc.dh_params);
547
if(ret != GNUTLS_E_SUCCESS){
548
fprintf(stderr, "Mandos plugin mandos-client: "
549
"Error in GnuTLS DH parameter initialization:"
550
" %s\n", safer_gnutls_strerror(ret));
553
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
554
if(ret != GNUTLS_E_SUCCESS){
555
fprintf(stderr, "Mandos plugin mandos-client: "
556
"Error in GnuTLS prime generation: %s\n",
557
safer_gnutls_strerror(ret));
561
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
567
gnutls_certificate_free_credentials(mc.cred);
568
gnutls_global_deinit();
569
gnutls_dh_params_deinit(mc.dh_params);
573
static int init_gnutls_session(gnutls_session_t *session){
575
/* GnuTLS session creation */
577
ret = gnutls_init(session, GNUTLS_SERVER);
581
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
582
if(ret != GNUTLS_E_SUCCESS){
583
fprintf(stderr, "Mandos plugin mandos-client: "
584
"Error in GnuTLS session initialization: %s\n",
585
safer_gnutls_strerror(ret));
591
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
593
gnutls_deinit(*session);
596
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
597
if(ret != GNUTLS_E_SUCCESS){
598
fprintf(stderr, "Mandos plugin mandos-client: "
599
"Syntax error at: %s\n", err);
600
fprintf(stderr, "Mandos plugin mandos-client: "
601
"GnuTLS error: %s\n", safer_gnutls_strerror(ret));
602
gnutls_deinit(*session);
608
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
611
gnutls_deinit(*session);
614
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
615
if(ret != GNUTLS_E_SUCCESS){
616
fprintf(stderr, "Mandos plugin mandos-client: "
617
"Error setting GnuTLS credentials: %s\n",
618
safer_gnutls_strerror(ret));
619
gnutls_deinit(*session);
623
/* ignore client certificate if any. */
624
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
626
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
631
/* Avahi log function callback */
632
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
633
__attribute__((unused)) const char *txt){}
635
/* Called when a Mandos server is found */
636
static int start_mandos_communication(const char *ip, uint16_t port,
637
AvahiIfIndex if_index,
639
int ret, tcp_sd = -1;
642
struct sockaddr_in in;
643
struct sockaddr_in6 in6;
646
char *decrypted_buffer = NULL;
647
size_t buffer_length = 0;
648
size_t buffer_capacity = 0;
651
gnutls_session_t session;
652
int pf; /* Protocol family */
669
fprintf(stderr, "Mandos plugin mandos-client: "
670
"Bad address family: %d\n", af);
675
ret = init_gnutls_session(&session);
681
fprintf(stderr, "Mandos plugin mandos-client: "
682
"Setting up a TCP connection to %s, port %" PRIu16
686
tcp_sd = socket(pf, SOCK_STREAM, 0);
689
perror_plus("socket");
699
memset(&to, 0, sizeof(to));
701
to.in6.sin6_family = (sa_family_t)af;
702
ret = inet_pton(af, ip, &to.in6.sin6_addr);
704
to.in.sin_family = (sa_family_t)af;
705
ret = inet_pton(af, ip, &to.in.sin_addr);
709
perror_plus("inet_pton");
715
fprintf(stderr, "Mandos plugin mandos-client: "
716
"Bad address: %s\n", ip);
721
to.in6.sin6_port = htons(port); /* Spurious warnings from
723
-Wunreachable-code */
725
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
726
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
728
if(if_index == AVAHI_IF_UNSPEC){
729
fprintf(stderr, "Mandos plugin mandos-client: "
730
"An IPv6 link-local address is incomplete"
731
" without a network interface\n");
735
/* Set the network interface number as scope */
736
to.in6.sin6_scope_id = (uint32_t)if_index;
739
to.in.sin_port = htons(port); /* Spurious warnings from
741
-Wunreachable-code */
750
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
751
char interface[IF_NAMESIZE];
752
if(if_indextoname((unsigned int)if_index, interface) == NULL){
753
perror_plus("if_indextoname");
755
fprintf(stderr, "Mandos plugin mandos-client: "
756
"Connection to: %s%%%s, port %" PRIu16 "\n",
757
ip, interface, port);
760
fprintf(stderr, "Mandos plugin mandos-client: "
761
"Connection to: %s, port %" PRIu16 "\n", ip, port);
763
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
764
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
767
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
770
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
774
perror_plus("inet_ntop");
776
if(strcmp(addrstr, ip) != 0){
777
fprintf(stderr, "Mandos plugin mandos-client: "
778
"Canonical address form: %s\n", addrstr);
789
ret = connect(tcp_sd, &to.in6, sizeof(to));
791
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
794
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
796
perror_plus("connect");
807
const char *out = mandos_protocol_version;
810
size_t out_size = strlen(out);
811
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
812
out_size - written));
815
perror_plus("write");
819
written += (size_t)ret;
820
if(written < out_size){
823
if(out == mandos_protocol_version){
838
fprintf(stderr, "Mandos plugin mandos-client: "
839
"Establishing TLS session with %s\n", ip);
847
/* Spurious warning from -Wint-to-pointer-cast */
848
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
856
ret = gnutls_handshake(session);
861
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
863
if(ret != GNUTLS_E_SUCCESS){
865
fprintf(stderr, "Mandos plugin mandos-client: "
866
"*** GnuTLS Handshake failed ***\n");
873
/* Read OpenPGP packet that contains the wanted password */
876
fprintf(stderr, "Mandos plugin mandos-client: "
877
"Retrieving OpenPGP encrypted password from %s\n", ip);
887
buffer_capacity = incbuffer(&buffer, buffer_length,
889
if(buffer_capacity == 0){
891
perror_plus("incbuffer");
901
sret = gnutls_record_recv(session, buffer+buffer_length,
908
case GNUTLS_E_INTERRUPTED:
911
case GNUTLS_E_REHANDSHAKE:
913
ret = gnutls_handshake(session);
919
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
921
fprintf(stderr, "Mandos plugin mandos-client: "
922
"*** GnuTLS Re-handshake failed ***\n");
929
fprintf(stderr, "Mandos plugin mandos-client: "
930
"Unknown error while reading data from"
931
" encrypted session with Mandos server\n");
932
gnutls_bye(session, GNUTLS_SHUT_RDWR);
937
buffer_length += (size_t) sret;
942
fprintf(stderr, "Mandos plugin mandos-client: "
943
"Closing TLS session\n");
952
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
957
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
959
if(buffer_length > 0){
960
ssize_t decrypted_buffer_size;
961
decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length,
963
if(decrypted_buffer_size >= 0){
966
while(written < (size_t) decrypted_buffer_size){
972
ret = (int)fwrite(decrypted_buffer + written, 1,
973
(size_t)decrypted_buffer_size - written,
975
if(ret == 0 and ferror(stdout)){
978
fprintf(stderr, "Mandos plugin mandos-client: "
979
"Error writing encrypted data: %s\n",
985
written += (size_t)ret;
991
/* Shutdown procedure */
996
free(decrypted_buffer);
999
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
1005
perror_plus("close");
1007
gnutls_deinit(session);
1017
static void resolve_callback(AvahiSServiceResolver *r,
1018
AvahiIfIndex interface,
1019
AvahiProtocol proto,
1020
AvahiResolverEvent event,
1024
const char *host_name,
1025
const AvahiAddress *address,
1027
AVAHI_GCC_UNUSED AvahiStringList *txt,
1028
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1030
AVAHI_GCC_UNUSED void* userdata){
1033
/* Called whenever a service has been resolved successfully or
1042
case AVAHI_RESOLVER_FAILURE:
1043
fprintf(stderr, "Mandos plugin mandos-client: "
1044
"(Avahi Resolver) Failed to resolve service '%s'"
1045
" of type '%s' in domain '%s': %s\n", name, type, domain,
1046
avahi_strerror(avahi_server_errno(mc.server)));
1049
case AVAHI_RESOLVER_FOUND:
1051
char ip[AVAHI_ADDRESS_STR_MAX];
1052
avahi_address_snprint(ip, sizeof(ip), address);
1054
fprintf(stderr, "Mandos plugin mandos-client: "
1055
"Mandos server \"%s\" found on %s (%s, %"
1056
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1057
ip, (intmax_t)interface, port);
1059
int ret = start_mandos_communication(ip, port, interface,
1060
avahi_proto_to_af(proto));
1062
avahi_simple_poll_quit(mc.simple_poll);
1064
ret = add_server(ip, port, interface,
1065
avahi_proto_to_af(proto));
1069
avahi_s_service_resolver_free(r);
1072
static void browse_callback(AvahiSServiceBrowser *b,
1073
AvahiIfIndex interface,
1074
AvahiProtocol protocol,
1075
AvahiBrowserEvent event,
1079
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1081
AVAHI_GCC_UNUSED void* userdata){
1084
/* Called whenever a new services becomes available on the LAN or
1085
is removed from the LAN */
1093
case AVAHI_BROWSER_FAILURE:
1095
fprintf(stderr, "Mandos plugin mandos-client: "
1096
"(Avahi browser) %s\n",
1097
avahi_strerror(avahi_server_errno(mc.server)));
1098
avahi_simple_poll_quit(mc.simple_poll);
1101
case AVAHI_BROWSER_NEW:
1102
/* We ignore the returned Avahi resolver object. In the callback
1103
function we free it. If the Avahi server is terminated before
1104
the callback function is called the Avahi server will free the
1107
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1108
name, type, domain, protocol, 0,
1109
resolve_callback, NULL) == NULL)
1110
fprintf(stderr, "Mandos plugin mandos-client: "
1111
"Avahi: Failed to resolve service '%s': %s\n",
1112
name, avahi_strerror(avahi_server_errno(mc.server)));
1115
case AVAHI_BROWSER_REMOVE:
1118
case AVAHI_BROWSER_ALL_FOR_NOW:
1119
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1121
fprintf(stderr, "Mandos plugin mandos-client: "
1122
"No Mandos server found, still searching...\n");
1128
/* Signal handler that stops main loop after SIGTERM */
1129
static void handle_sigterm(int sig){
1134
signal_received = sig;
1135
int old_errno = errno;
1136
/* set main loop to exit */
1137
if(mc.simple_poll != NULL){
1138
avahi_simple_poll_quit(mc.simple_poll);
1143
bool get_flags(const char *ifname, struct ifreq *ifr){
1146
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1148
perror_plus("socket");
1151
strcpy(ifr->ifr_name, ifname);
1152
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1155
perror_plus("ioctl SIOCGIFFLAGS");
1162
bool good_flags(const char *ifname, const struct ifreq *ifr){
1164
/* Reject the loopback device */
1165
if(ifr->ifr_flags & IFF_LOOPBACK){
1167
fprintf(stderr, "Mandos plugin mandos-client: "
1168
"Rejecting loopback interface \"%s\"\n", ifname);
1172
/* Accept point-to-point devices only if connect_to is specified */
1173
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1175
fprintf(stderr, "Mandos plugin mandos-client: "
1176
"Accepting point-to-point interface \"%s\"\n", ifname);
1180
/* Otherwise, reject non-broadcast-capable devices */
1181
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1183
fprintf(stderr, "Mandos plugin mandos-client: "
1184
"Rejecting non-broadcast interface \"%s\"\n", ifname);
1188
/* Reject non-ARP interfaces (including dummy interfaces) */
1189
if(ifr->ifr_flags & IFF_NOARP){
1191
fprintf(stderr, "Mandos plugin mandos-client: "
1192
"Rejecting non-ARP interface \"%s\"\n", ifname);
1197
/* Accept this device */
1199
fprintf(stderr, "Mandos plugin mandos-client: "
1200
"Interface \"%s\" is good\n", ifname);
1206
* This function determines if a directory entry in /sys/class/net
1207
* corresponds to an acceptable network device.
1208
* (This function is passed to scandir(3) as a filter function.)
1210
int good_interface(const struct dirent *if_entry){
1211
if(if_entry->d_name[0] == '.'){
1216
if(not get_flags(if_entry->d_name, &ifr)){
1218
fprintf(stderr, "Mandos plugin mandos-client: "
1219
"Failed to get flags for interface \"%s\"\n",
1225
if(not good_flags(if_entry->d_name, &ifr)){
1232
* This function determines if a directory entry in /sys/class/net
1233
* corresponds to an acceptable network device which is up.
1234
* (This function is passed to scandir(3) as a filter function.)
1236
int up_interface(const struct dirent *if_entry){
1237
if(if_entry->d_name[0] == '.'){
1242
if(not get_flags(if_entry->d_name, &ifr)){
1244
fprintf(stderr, "Mandos plugin mandos-client: "
1245
"Failed to get flags for interface \"%s\"\n",
1251
/* Reject down interfaces */
1252
if(not (ifr.ifr_flags & IFF_UP)){
1254
fprintf(stderr, "Mandos plugin mandos-client: "
1255
"Rejecting down interface \"%s\"\n",
1261
/* Reject non-running interfaces */
1262
if(not (ifr.ifr_flags & IFF_RUNNING)){
1264
fprintf(stderr, "Mandos plugin mandos-client: "
1265
"Rejecting non-running interface \"%s\"\n",
1271
if(not good_flags(if_entry->d_name, &ifr)){
1277
int notdotentries(const struct dirent *direntry){
1278
/* Skip "." and ".." */
1279
if(direntry->d_name[0] == '.'
1280
and (direntry->d_name[1] == '\0'
1281
or (direntry->d_name[1] == '.'
1282
and direntry->d_name[2] == '\0'))){
1288
/* Is this directory entry a runnable program? */
1289
int runnable_hook(const struct dirent *direntry){
1294
if((direntry->d_name)[0] == '\0'){
1299
sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1300
"abcdefghijklmnopqrstuvwxyz"
1303
if((direntry->d_name)[sret] != '\0'){
1304
/* Contains non-allowed characters */
1306
fprintf(stderr, "Mandos plugin mandos-client: "
1307
"Ignoring hook \"%s\" with bad name\n",
1313
char *fullname = NULL;
1314
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1316
perror_plus("asprintf");
1320
ret = stat(fullname, &st);
1323
perror_plus("Could not stat hook");
1327
if(not (S_ISREG(st.st_mode))){
1328
/* Not a regular file */
1330
fprintf(stderr, "Mandos plugin mandos-client: "
1331
"Ignoring hook \"%s\" - not a file\n",
1336
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1337
/* Not executable */
1339
fprintf(stderr, "Mandos plugin mandos-client: "
1340
"Ignoring hook \"%s\" - not executable\n",
1348
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1350
struct timespec now;
1351
struct timespec waited_time;
1352
intmax_t block_time;
1355
if(mc.current_server == NULL){
1357
fprintf(stderr, "Mandos plugin mandos-client: "
1358
"Wait until first server is found. No timeout!\n");
1360
ret = avahi_simple_poll_iterate(s, -1);
1363
fprintf(stderr, "Mandos plugin mandos-client: "
1364
"Check current_server if we should run it,"
1367
/* the current time */
1368
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1370
perror_plus("clock_gettime");
1373
/* Calculating in ms how long time between now and server
1374
who we visted longest time ago. Now - last seen. */
1375
waited_time.tv_sec = (now.tv_sec
1376
- mc.current_server->last_seen.tv_sec);
1377
waited_time.tv_nsec = (now.tv_nsec
1378
- mc.current_server->last_seen.tv_nsec);
1379
/* total time is 10s/10,000ms.
1380
Converting to s from ms by dividing by 1,000,
1381
and ns to ms by dividing by 1,000,000. */
1382
block_time = ((retry_interval
1383
- ((intmax_t)waited_time.tv_sec * 1000))
1384
- ((intmax_t)waited_time.tv_nsec / 1000000));
1387
fprintf(stderr, "Mandos plugin mandos-client: "
1388
"Blocking for %" PRIdMAX " ms\n", block_time);
1391
if(block_time <= 0){
1392
ret = start_mandos_communication(mc.current_server->ip,
1393
mc.current_server->port,
1394
mc.current_server->if_index,
1395
mc.current_server->af);
1397
avahi_simple_poll_quit(mc.simple_poll);
1400
ret = clock_gettime(CLOCK_MONOTONIC,
1401
&mc.current_server->last_seen);
1403
perror_plus("clock_gettime");
1406
mc.current_server = mc.current_server->next;
1407
block_time = 0; /* Call avahi to find new Mandos
1408
servers, but don't block */
1411
ret = avahi_simple_poll_iterate(s, (int)block_time);
1414
if (ret > 0 or errno != EINTR){
1415
return (ret != 1) ? ret : 0;
1421
int main(int argc, char *argv[]){
1422
AvahiSServiceBrowser *sb = NULL;
1427
int exitcode = EXIT_SUCCESS;
1428
const char *interface = "";
1429
struct ifreq network;
1431
bool take_down_interface = false;
1434
char tempdir[] = "/tmp/mandosXXXXXX";
1435
bool tempdir_created = false;
1436
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1437
const char *seckey = PATHDIR "/" SECKEY;
1438
const char *pubkey = PATHDIR "/" PUBKEY;
1440
bool gnutls_initialized = false;
1441
bool gpgme_initialized = false;
1443
double retry_interval = 10; /* 10s between trying a server and
1444
retrying the same server again */
1446
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1447
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1452
/* Lower any group privileges we might have, just to be safe */
1456
perror_plus("setgid");
1459
/* Lower user privileges (temporarily) */
1463
perror_plus("seteuid");
1471
struct argp_option options[] = {
1472
{ .name = "debug", .key = 128,
1473
.doc = "Debug mode", .group = 3 },
1474
{ .name = "connect", .key = 'c',
1475
.arg = "ADDRESS:PORT",
1476
.doc = "Connect directly to a specific Mandos server",
1478
{ .name = "interface", .key = 'i',
1480
.doc = "Network interface that will be used to search for"
1483
{ .name = "seckey", .key = 's',
1485
.doc = "OpenPGP secret key file base name",
1487
{ .name = "pubkey", .key = 'p',
1489
.doc = "OpenPGP public key file base name",
1491
{ .name = "dh-bits", .key = 129,
1493
.doc = "Bit length of the prime number used in the"
1494
" Diffie-Hellman key exchange",
1496
{ .name = "priority", .key = 130,
1498
.doc = "GnuTLS priority string for the TLS handshake",
1500
{ .name = "delay", .key = 131,
1502
.doc = "Maximum delay to wait for interface startup",
1504
{ .name = "retry", .key = 132,
1506
.doc = "Retry interval used when denied by the mandos server",
1508
{ .name = "network-hook-dir", .key = 133,
1510
.doc = "Directory where network hooks are located",
1513
* These reproduce what we would get without ARGP_NO_HELP
1515
{ .name = "help", .key = '?',
1516
.doc = "Give this help list", .group = -1 },
1517
{ .name = "usage", .key = -3,
1518
.doc = "Give a short usage message", .group = -1 },
1519
{ .name = "version", .key = 'V',
1520
.doc = "Print program version", .group = -1 },
1524
error_t parse_opt(int key, char *arg,
1525
struct argp_state *state){
1528
case 128: /* --debug */
1531
case 'c': /* --connect */
1534
case 'i': /* --interface */
1537
case 's': /* --seckey */
1540
case 'p': /* --pubkey */
1543
case 129: /* --dh-bits */
1545
tmpmax = strtoimax(arg, &tmp, 10);
1546
if(errno != 0 or tmp == arg or *tmp != '\0'
1547
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1548
argp_error(state, "Bad number of DH bits");
1550
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1552
case 130: /* --priority */
1555
case 131: /* --delay */
1557
delay = strtof(arg, &tmp);
1558
if(errno != 0 or tmp == arg or *tmp != '\0'){
1559
argp_error(state, "Bad delay");
1561
case 132: /* --retry */
1563
retry_interval = strtod(arg, &tmp);
1564
if(errno != 0 or tmp == arg or *tmp != '\0'
1565
or (retry_interval * 1000) > INT_MAX
1566
or retry_interval < 0){
1567
argp_error(state, "Bad retry interval");
1570
case 133: /* --network-hook-dir */
1574
* These reproduce what we would get without ARGP_NO_HELP
1576
case '?': /* --help */
1577
argp_state_help(state, state->out_stream,
1578
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1579
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1580
case -3: /* --usage */
1581
argp_state_help(state, state->out_stream,
1582
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1583
case 'V': /* --version */
1584
fprintf(state->out_stream, "Mandos plugin mandos-client: ");
1585
fprintf(state->out_stream, "%s\n", argp_program_version);
1586
exit(argp_err_exit_status);
1589
return ARGP_ERR_UNKNOWN;
1594
struct argp argp = { .options = options, .parser = parse_opt,
1596
.doc = "Mandos client -- Get and decrypt"
1597
" passwords from a Mandos server" };
1598
ret = argp_parse(&argp, argc, argv,
1599
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1606
perror_plus("argp_parse");
1607
exitcode = EX_OSERR;
1610
exitcode = EX_USAGE;
1616
/* Work around Debian bug #633582:
1617
<http://bugs.debian.org/633582> */
1620
/* Re-raise priviliges */
1624
perror_plus("seteuid");
1627
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
1628
int seckey_fd = open(seckey, O_RDONLY);
1629
if(seckey_fd == -1){
1630
perror_plus("open");
1632
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
1634
perror_plus("fstat");
1636
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1637
ret = fchown(seckey_fd, uid, gid);
1639
perror_plus("fchown");
1643
TEMP_FAILURE_RETRY(close(seckey_fd));
1647
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
1648
int pubkey_fd = open(pubkey, O_RDONLY);
1649
if(pubkey_fd == -1){
1650
perror_plus("open");
1652
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
1654
perror_plus("fstat");
1656
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1657
ret = fchown(pubkey_fd, uid, gid);
1659
perror_plus("fchown");
1663
TEMP_FAILURE_RETRY(close(pubkey_fd));
1667
/* Lower privileges */
1671
perror_plus("seteuid");
1675
/* Find network hooks and run them */
1677
struct dirent **direntries;
1678
struct dirent *direntry;
1679
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1682
perror_plus("scandir");
1684
int devnull = open("/dev/null", O_RDONLY);
1685
for(int i = 0; i < numhooks; i++){
1686
direntry = direntries[0];
1687
char *fullname = NULL;
1688
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1690
perror_plus("asprintf");
1693
pid_t hook_pid = fork();
1696
dup2(devnull, STDIN_FILENO);
1698
dup2(STDERR_FILENO, STDOUT_FILENO);
1699
ret = setenv("MANDOSNETHOOKDIR", hookdir, 1);
1701
perror_plus("setenv");
1704
ret = setenv("DEVICE", interface, 1);
1706
perror_plus("setenv");
1709
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1711
perror_plus("setenv");
1714
ret = setenv("MODE", "start", 1);
1716
perror_plus("setenv");
1720
ret = asprintf(&delaystring, "%f", delay);
1722
perror_plus("asprintf");
1725
ret = setenv("DELAY", delaystring, 1);
1728
perror_plus("setenv");
1732
ret = execl(fullname, direntry->d_name, "start", NULL);
1733
perror_plus("execl");
1736
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1737
perror_plus("waitpid");
1741
if(WIFEXITED(status)){
1742
if(WEXITSTATUS(status) != 0){
1743
fprintf(stderr, "Mandos plugin mandos-client: "
1744
"Warning: network hook \"%s\" exited"
1745
" with status %d\n", direntry->d_name,
1746
WEXITSTATUS(status));
1750
} else if(WIFSIGNALED(status)){
1751
fprintf(stderr, "Mandos plugin mandos-client: "
1752
"Warning: network hook \"%s\" died by"
1753
" signal %d\n", direntry->d_name,
1758
fprintf(stderr, "Mandos plugin mandos-client: "
1759
"Warning: network hook \"%s\" crashed\n",
1775
avahi_set_log_function(empty_log);
1778
if(interface[0] == '\0'){
1779
struct dirent **direntries;
1780
/* First look for interfaces that are up */
1781
ret = scandir(sys_class_net, &direntries, up_interface,
1784
/* No up interfaces, look for any good interfaces */
1786
ret = scandir(sys_class_net, &direntries, good_interface,
1790
/* Pick the first interface returned */
1791
interface = strdup(direntries[0]->d_name);
1793
fprintf(stderr, "Mandos plugin mandos-client: "
1794
"Using interface \"%s\"\n", interface);
1796
if(interface == NULL){
1797
perror_plus("malloc");
1799
exitcode = EXIT_FAILURE;
1805
fprintf(stderr, "Mandos plugin mandos-client: "
1806
"Could not find a network interface\n");
1807
exitcode = EXIT_FAILURE;
1812
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1813
from the signal handler */
1814
/* Initialize the pseudo-RNG for Avahi */
1815
srand((unsigned int) time(NULL));
1816
mc.simple_poll = avahi_simple_poll_new();
1817
if(mc.simple_poll == NULL){
1818
fprintf(stderr, "Mandos plugin mandos-client: "
1819
"Avahi: Failed to create simple poll object.\n");
1820
exitcode = EX_UNAVAILABLE;
1824
sigemptyset(&sigterm_action.sa_mask);
1825
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1827
perror_plus("sigaddset");
1828
exitcode = EX_OSERR;
1831
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1833
perror_plus("sigaddset");
1834
exitcode = EX_OSERR;
1837
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1839
perror_plus("sigaddset");
1840
exitcode = EX_OSERR;
1843
/* Need to check if the handler is SIG_IGN before handling:
1844
| [[info:libc:Initial Signal Actions]] |
1845
| [[info:libc:Basic Signal Handling]] |
1847
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1849
perror_plus("sigaction");
1852
if(old_sigterm_action.sa_handler != SIG_IGN){
1853
ret = sigaction(SIGINT, &sigterm_action, NULL);
1855
perror_plus("sigaction");
1856
exitcode = EX_OSERR;
1860
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1862
perror_plus("sigaction");
1865
if(old_sigterm_action.sa_handler != SIG_IGN){
1866
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1868
perror_plus("sigaction");
1869
exitcode = EX_OSERR;
1873
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1875
perror_plus("sigaction");
1878
if(old_sigterm_action.sa_handler != SIG_IGN){
1879
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1881
perror_plus("sigaction");
1882
exitcode = EX_OSERR;
1887
/* If the interface is down, bring it up */
1888
if(strcmp(interface, "none") != 0){
1889
if_index = (AvahiIfIndex) if_nametoindex(interface);
1891
fprintf(stderr, "Mandos plugin mandos-client: "
1892
"No such interface: \"%s\"\n", interface);
1893
exitcode = EX_UNAVAILABLE;
1901
/* Re-raise priviliges */
1905
perror_plus("seteuid");
1909
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1910
messages about the network interface to mess up the prompt */
1911
ret = klogctl(8, NULL, 5);
1912
bool restore_loglevel = true;
1914
restore_loglevel = false;
1915
perror_plus("klogctl");
1917
#endif /* __linux__ */
1919
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1921
perror_plus("socket");
1922
exitcode = EX_OSERR;
1924
if(restore_loglevel){
1925
ret = klogctl(7, NULL, 0);
1927
perror_plus("klogctl");
1930
#endif /* __linux__ */
1931
/* Lower privileges */
1935
perror_plus("seteuid");
1939
strcpy(network.ifr_name, interface);
1940
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1942
perror_plus("ioctl SIOCGIFFLAGS");
1944
if(restore_loglevel){
1945
ret = klogctl(7, NULL, 0);
1947
perror_plus("klogctl");
1950
#endif /* __linux__ */
1951
exitcode = EX_OSERR;
1952
/* Lower privileges */
1956
perror_plus("seteuid");
1960
if((network.ifr_flags & IFF_UP) == 0){
1961
network.ifr_flags |= IFF_UP;
1962
take_down_interface = true;
1963
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1965
take_down_interface = false;
1966
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1967
exitcode = EX_OSERR;
1969
if(restore_loglevel){
1970
ret = klogctl(7, NULL, 0);
1972
perror_plus("klogctl");
1975
#endif /* __linux__ */
1976
/* Lower privileges */
1980
perror_plus("seteuid");
1985
/* Sleep checking until interface is running.
1986
Check every 0.25s, up to total time of delay */
1987
for(int i=0; i < delay * 4; i++){
1988
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1990
perror_plus("ioctl SIOCGIFFLAGS");
1991
} else if(network.ifr_flags & IFF_RUNNING){
1994
struct timespec sleeptime = { .tv_nsec = 250000000 };
1995
ret = nanosleep(&sleeptime, NULL);
1996
if(ret == -1 and errno != EINTR){
1997
perror_plus("nanosleep");
2000
if(not take_down_interface){
2001
/* We won't need the socket anymore */
2002
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2004
perror_plus("close");
2008
if(restore_loglevel){
2009
/* Restores kernel loglevel to default */
2010
ret = klogctl(7, NULL, 0);
2012
perror_plus("klogctl");
2015
#endif /* __linux__ */
2016
/* Lower privileges */
2018
if(take_down_interface){
2019
/* Lower privileges */
2022
perror_plus("seteuid");
2025
/* Lower privileges permanently */
2028
perror_plus("setuid");
2037
ret = init_gnutls_global(pubkey, seckey);
2039
fprintf(stderr, "Mandos plugin mandos-client: "
2040
"init_gnutls_global failed\n");
2041
exitcode = EX_UNAVAILABLE;
2044
gnutls_initialized = true;
2051
if(mkdtemp(tempdir) == NULL){
2052
perror_plus("mkdtemp");
2055
tempdir_created = true;
2061
if(not init_gpgme(pubkey, seckey, tempdir)){
2062
fprintf(stderr, "Mandos plugin mandos-client: "
2063
"init_gpgme failed\n");
2064
exitcode = EX_UNAVAILABLE;
2067
gpgme_initialized = true;
2074
if(connect_to != NULL){
2075
/* Connect directly, do not use Zeroconf */
2076
/* (Mainly meant for debugging) */
2077
char *address = strrchr(connect_to, ':');
2078
if(address == NULL){
2079
fprintf(stderr, "Mandos plugin mandos-client: "
2080
"No colon in address\n");
2081
exitcode = EX_USAGE;
2091
tmpmax = strtoimax(address+1, &tmp, 10);
2092
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2093
or tmpmax != (uint16_t)tmpmax){
2094
fprintf(stderr, "Mandos plugin mandos-client: "
2095
"Bad port number\n");
2096
exitcode = EX_USAGE;
2104
port = (uint16_t)tmpmax;
2106
/* Colon in address indicates IPv6 */
2108
if(strchr(connect_to, ':') != NULL){
2110
/* Accept [] around IPv6 address - see RFC 5952 */
2111
if(connect_to[0] == '[' and address[-1] == ']')
2119
address = connect_to;
2125
while(not quit_now){
2126
ret = start_mandos_communication(address, port, if_index, af);
2127
if(quit_now or ret == 0){
2131
fprintf(stderr, "Mandos plugin mandos-client: "
2132
"Retrying in %d seconds\n", (int)retry_interval);
2134
sleep((int)retry_interval);
2138
exitcode = EXIT_SUCCESS;
2149
AvahiServerConfig config;
2150
/* Do not publish any local Zeroconf records */
2151
avahi_server_config_init(&config);
2152
config.publish_hinfo = 0;
2153
config.publish_addresses = 0;
2154
config.publish_workstation = 0;
2155
config.publish_domain = 0;
2157
/* Allocate a new server */
2158
mc.server = avahi_server_new(avahi_simple_poll_get
2159
(mc.simple_poll), &config, NULL,
2162
/* Free the Avahi configuration data */
2163
avahi_server_config_free(&config);
2166
/* Check if creating the Avahi server object succeeded */
2167
if(mc.server == NULL){
2168
fprintf(stderr, "Mandos plugin mandos-client: "
2169
"Failed to create Avahi server: %s\n",
2170
avahi_strerror(error));
2171
exitcode = EX_UNAVAILABLE;
2179
/* Create the Avahi service browser */
2180
sb = avahi_s_service_browser_new(mc.server, if_index,
2181
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2182
NULL, 0, browse_callback, NULL);
2184
fprintf(stderr, "Mandos plugin mandos-client: "
2185
"Failed to create service browser: %s\n",
2186
avahi_strerror(avahi_server_errno(mc.server)));
2187
exitcode = EX_UNAVAILABLE;
2195
/* Run the main loop */
2198
fprintf(stderr, "Mandos plugin mandos-client: "
2199
"Starting Avahi loop search\n");
2202
ret = avahi_loop_with_timeout(mc.simple_poll,
2203
(int)(retry_interval * 1000));
2205
fprintf(stderr, "Mandos plugin mandos-client: "
2206
"avahi_loop_with_timeout exited %s\n",
2207
(ret == 0) ? "successfully" : "with error");
2213
fprintf(stderr, "Mandos plugin mandos-client: "
2214
"%s exiting\n", argv[0]);
2217
/* Cleanup things */
2219
avahi_s_service_browser_free(sb);
2221
if(mc.server != NULL)
2222
avahi_server_free(mc.server);
2224
if(mc.simple_poll != NULL)
2225
avahi_simple_poll_free(mc.simple_poll);
2227
if(gnutls_initialized){
2228
gnutls_certificate_free_credentials(mc.cred);
2229
gnutls_global_deinit();
2230
gnutls_dh_params_deinit(mc.dh_params);
2233
if(gpgme_initialized){
2234
gpgme_release(mc.ctx);
2237
/* Cleans up the circular linked list of Mandos servers the client
2239
if(mc.current_server != NULL){
2240
mc.current_server->prev->next = NULL;
2241
while(mc.current_server != NULL){
2242
server *next = mc.current_server->next;
2243
free(mc.current_server);
2244
mc.current_server = next;
2248
/* XXX run network hooks "stop" here */
2250
/* Take down the network interface */
2251
if(take_down_interface){
2252
/* Re-raise priviliges */
2256
perror_plus("seteuid");
2259
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2261
perror_plus("ioctl SIOCGIFFLAGS");
2262
} else if(network.ifr_flags & IFF_UP){
2263
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2264
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2266
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2269
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2271
perror_plus("close");
2273
/* Lower privileges permanently */
2277
perror_plus("setuid");
2282
/* Removes the GPGME temp directory and all files inside */
2283
if(tempdir_created){
2284
struct dirent **direntries = NULL;
2285
struct dirent *direntry = NULL;
2286
int numentries = scandir(tempdir, &direntries, notdotentries,
2288
if (numentries > 0){
2289
for(int i = 0; i < numentries; i++){
2290
direntry = direntries[i];
2291
char *fullname = NULL;
2292
ret = asprintf(&fullname, "%s/%s", tempdir,
2295
perror_plus("asprintf");
2298
ret = remove(fullname);
2300
fprintf(stderr, "Mandos plugin mandos-client: "
2301
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2307
/* need to clean even if 0 because man page doesn't specify */
2309
if (numentries == -1){
2310
perror_plus("scandir");
2312
ret = rmdir(tempdir);
2313
if(ret == -1 and errno != ENOENT){
2314
perror_plus("rmdir");
2319
sigemptyset(&old_sigterm_action.sa_mask);
2320
old_sigterm_action.sa_handler = SIG_DFL;
2321
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2322
&old_sigterm_action,
2325
perror_plus("sigaction");
2328
ret = raise(signal_received);
2329
} while(ret != 0 and errno == EINTR);
2331
perror_plus("raise");
2334
TEMP_FAILURE_RETRY(pause());