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-2014 Teddy Hogeborn
13
* Copyright © 2008-2014 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
35
#endif /* not _LARGEFILE_SOURCE */
36
#ifndef _FILE_OFFSET_BITS
37
#define _FILE_OFFSET_BITS 64
38
#endif /* not _FILE_OFFSET_BITS */
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, intptr_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(),
60
#include <fcntl.h> /* open() */
61
#include <dirent.h> /* opendir(), struct dirent, readdir()
63
#include <inttypes.h> /* PRIu16, PRIdMAX, intmax_t,
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(),
76
setgid(), pause(), _exit() */
77
#include <arpa/inet.h> /* inet_pton(), htons() */
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() */
90
#include <grp.h> /* setgroups() */
91
#include <argz.h> /* argz_add_sep(), argz_next(),
92
argz_delete(), argz_append(),
93
argz_stringify(), argz_add(),
95
#include <netdb.h> /* getnameinfo(), NI_NUMERICHOST,
96
EAI_SYSTEM, gai_strerror() */
99
#include <sys/klog.h> /* klogctl() */
100
#endif /* __linux__ */
103
/* All Avahi types, constants and functions
106
#include <avahi-core/core.h>
107
#include <avahi-core/lookup.h>
108
#include <avahi-core/log.h>
109
#include <avahi-common/simple-watch.h>
110
#include <avahi-common/malloc.h>
111
#include <avahi-common/error.h>
114
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
117
init_gnutls_session(),
119
#include <gnutls/openpgp.h>
120
/* gnutls_certificate_set_openpgp_key_file(),
121
GNUTLS_OPENPGP_FMT_BASE64 */
124
#include <gpgme.h> /* All GPGME types, constants and
127
GPGME_PROTOCOL_OpenPGP,
130
#define BUFFER_SIZE 256
132
#define PATHDIR "/conf/conf.d/mandos"
133
#define SECKEY "seckey.txt"
134
#define PUBKEY "pubkey.txt"
135
#define HOOKDIR "/lib/mandos/network-hooks.d"
138
static const char mandos_protocol_version[] = "1";
139
const char *argp_program_version = "mandos-client " VERSION;
140
const char *argp_program_bug_address = "<mandos@recompile.se>";
141
static const char sys_class_net[] = "/sys/class/net";
142
char *connect_to = NULL;
143
const char *hookdir = HOOKDIR;
148
/* Doubly linked list that need to be circularly linked when used */
149
typedef struct server{
152
AvahiIfIndex if_index;
154
struct timespec last_seen;
159
/* Used for passing in values through the Avahi callback functions */
162
gnutls_certificate_credentials_t cred;
163
unsigned int dh_bits;
164
gnutls_dh_params_t dh_params;
165
const char *priority;
167
server *current_server;
169
size_t interfaces_size;
172
/* global so signal handler can reach it*/
173
AvahiSimplePoll *simple_poll;
175
sig_atomic_t quit_now = 0;
176
int signal_received = 0;
178
/* Function to use when printing errors */
179
void perror_plus(const char *print_text){
181
fprintf(stderr, "Mandos plugin %s: ",
182
program_invocation_short_name);
187
__attribute__((format (gnu_printf, 2, 3), nonnull))
188
int fprintf_plus(FILE *stream, const char *format, ...){
190
va_start (ap, format);
192
TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ",
193
program_invocation_short_name));
194
return (int)TEMP_FAILURE_RETRY(vfprintf(stream, format, ap));
198
* Make additional room in "buffer" for at least BUFFER_SIZE more
199
* bytes. "buffer_capacity" is how much is currently allocated,
200
* "buffer_length" is how much is already used.
202
__attribute__((nonnull, warn_unused_result))
203
size_t incbuffer(char **buffer, size_t buffer_length,
204
size_t buffer_capacity){
205
if(buffer_length + BUFFER_SIZE > buffer_capacity){
206
char *new_buf = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
208
int old_errno = errno;
215
buffer_capacity += BUFFER_SIZE;
217
return buffer_capacity;
220
/* Add server to set of servers to retry periodically */
221
__attribute__((nonnull, warn_unused_result))
222
bool add_server(const char *ip, in_port_t port, AvahiIfIndex if_index,
223
int af, server **current_server){
225
server *new_server = malloc(sizeof(server));
226
if(new_server == NULL){
227
perror_plus("malloc");
230
*new_server = (server){ .ip = strdup(ip),
232
.if_index = if_index,
234
if(new_server->ip == NULL){
235
perror_plus("strdup");
238
ret = clock_gettime(CLOCK_MONOTONIC, &(new_server->last_seen));
240
perror_plus("clock_gettime");
243
/* Special case of first server */
244
if(*current_server == NULL){
245
new_server->next = new_server;
246
new_server->prev = new_server;
247
*current_server = new_server;
249
/* Place the new server last in the list */
250
new_server->next = *current_server;
251
new_server->prev = (*current_server)->prev;
252
new_server->prev->next = new_server;
253
(*current_server)->prev = new_server;
261
__attribute__((nonnull, warn_unused_result))
262
static bool init_gpgme(const char * const seckey,
263
const char * const pubkey,
264
const char * const tempdir,
267
gpgme_engine_info_t engine_info;
270
* Helper function to insert pub and seckey to the engine keyring.
272
bool import_key(const char * const filename){
275
gpgme_data_t pgp_data;
277
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
283
rc = gpgme_data_new_from_fd(&pgp_data, fd);
284
if(rc != GPG_ERR_NO_ERROR){
285
fprintf_plus(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
286
gpgme_strsource(rc), gpgme_strerror(rc));
290
rc = gpgme_op_import(mc->ctx, pgp_data);
291
if(rc != GPG_ERR_NO_ERROR){
292
fprintf_plus(stderr, "bad gpgme_op_import: %s: %s\n",
293
gpgme_strsource(rc), gpgme_strerror(rc));
297
ret = (int)TEMP_FAILURE_RETRY(close(fd));
299
perror_plus("close");
301
gpgme_data_release(pgp_data);
306
fprintf_plus(stderr, "Initializing GPGME\n");
310
gpgme_check_version(NULL);
311
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
312
if(rc != GPG_ERR_NO_ERROR){
313
fprintf_plus(stderr, "bad gpgme_engine_check_version: %s: %s\n",
314
gpgme_strsource(rc), gpgme_strerror(rc));
318
/* Set GPGME home directory for the OpenPGP engine only */
319
rc = gpgme_get_engine_info(&engine_info);
320
if(rc != GPG_ERR_NO_ERROR){
321
fprintf_plus(stderr, "bad gpgme_get_engine_info: %s: %s\n",
322
gpgme_strsource(rc), gpgme_strerror(rc));
325
while(engine_info != NULL){
326
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
327
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
328
engine_info->file_name, tempdir);
331
engine_info = engine_info->next;
333
if(engine_info == NULL){
334
fprintf_plus(stderr, "Could not set GPGME home dir to %s\n",
339
/* Create new GPGME "context" */
340
rc = gpgme_new(&(mc->ctx));
341
if(rc != GPG_ERR_NO_ERROR){
342
fprintf_plus(stderr, "Mandos plugin mandos-client: "
343
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
348
if(not import_key(pubkey) or not import_key(seckey)){
356
* Decrypt OpenPGP data.
357
* Returns -1 on error
359
__attribute__((nonnull, warn_unused_result))
360
static ssize_t pgp_packet_decrypt(const char *cryptotext,
364
gpgme_data_t dh_crypto, dh_plain;
367
size_t plaintext_capacity = 0;
368
ssize_t plaintext_length = 0;
371
fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n");
374
/* Create new GPGME data buffer from memory cryptotext */
375
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
377
if(rc != GPG_ERR_NO_ERROR){
378
fprintf_plus(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
379
gpgme_strsource(rc), gpgme_strerror(rc));
383
/* Create new empty GPGME data buffer for the plaintext */
384
rc = gpgme_data_new(&dh_plain);
385
if(rc != GPG_ERR_NO_ERROR){
386
fprintf_plus(stderr, "Mandos plugin mandos-client: "
387
"bad gpgme_data_new: %s: %s\n",
388
gpgme_strsource(rc), gpgme_strerror(rc));
389
gpgme_data_release(dh_crypto);
393
/* Decrypt data from the cryptotext data buffer to the plaintext
395
rc = gpgme_op_decrypt(mc->ctx, dh_crypto, dh_plain);
396
if(rc != GPG_ERR_NO_ERROR){
397
fprintf_plus(stderr, "bad gpgme_op_decrypt: %s: %s\n",
398
gpgme_strsource(rc), gpgme_strerror(rc));
399
plaintext_length = -1;
401
gpgme_decrypt_result_t result;
402
result = gpgme_op_decrypt_result(mc->ctx);
404
fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n");
406
fprintf_plus(stderr, "Unsupported algorithm: %s\n",
407
result->unsupported_algorithm);
408
fprintf_plus(stderr, "Wrong key usage: %u\n",
409
result->wrong_key_usage);
410
if(result->file_name != NULL){
411
fprintf_plus(stderr, "File name: %s\n", result->file_name);
413
gpgme_recipient_t recipient;
414
recipient = result->recipients;
415
while(recipient != NULL){
416
fprintf_plus(stderr, "Public key algorithm: %s\n",
417
gpgme_pubkey_algo_name
418
(recipient->pubkey_algo));
419
fprintf_plus(stderr, "Key ID: %s\n", recipient->keyid);
420
fprintf_plus(stderr, "Secret key available: %s\n",
421
recipient->status == GPG_ERR_NO_SECKEY
423
recipient = recipient->next;
431
fprintf_plus(stderr, "Decryption of OpenPGP data succeeded\n");
434
/* Seek back to the beginning of the GPGME plaintext data buffer */
435
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
436
perror_plus("gpgme_data_seek");
437
plaintext_length = -1;
443
plaintext_capacity = incbuffer(plaintext,
444
(size_t)plaintext_length,
446
if(plaintext_capacity == 0){
447
perror_plus("incbuffer");
448
plaintext_length = -1;
452
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
454
/* Print the data, if any */
460
perror_plus("gpgme_data_read");
461
plaintext_length = -1;
464
plaintext_length += ret;
468
fprintf_plus(stderr, "Decrypted password is: ");
469
for(ssize_t i = 0; i < plaintext_length; i++){
470
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
472
fprintf(stderr, "\n");
477
/* Delete the GPGME cryptotext data buffer */
478
gpgme_data_release(dh_crypto);
480
/* Delete the GPGME plaintext data buffer */
481
gpgme_data_release(dh_plain);
482
return plaintext_length;
485
__attribute__((warn_unused_result))
486
static const char *safer_gnutls_strerror(int value){
487
const char *ret = gnutls_strerror(value);
493
/* GnuTLS log function callback */
494
__attribute__((nonnull))
495
static void debuggnutls(__attribute__((unused)) int level,
497
fprintf_plus(stderr, "GnuTLS: %s", string);
500
__attribute__((nonnull, warn_unused_result))
501
static int init_gnutls_global(const char *pubkeyfilename,
502
const char *seckeyfilename,
507
fprintf_plus(stderr, "Initializing GnuTLS\n");
510
ret = gnutls_global_init();
511
if(ret != GNUTLS_E_SUCCESS){
512
fprintf_plus(stderr, "GnuTLS global_init: %s\n",
513
safer_gnutls_strerror(ret));
518
/* "Use a log level over 10 to enable all debugging options."
521
gnutls_global_set_log_level(11);
522
gnutls_global_set_log_function(debuggnutls);
525
/* OpenPGP credentials */
526
ret = gnutls_certificate_allocate_credentials(&mc->cred);
527
if(ret != GNUTLS_E_SUCCESS){
528
fprintf_plus(stderr, "GnuTLS memory error: %s\n",
529
safer_gnutls_strerror(ret));
530
gnutls_global_deinit();
535
fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and"
536
" secret key %s as GnuTLS credentials\n",
541
ret = gnutls_certificate_set_openpgp_key_file
542
(mc->cred, pubkeyfilename, seckeyfilename,
543
GNUTLS_OPENPGP_FMT_BASE64);
544
if(ret != GNUTLS_E_SUCCESS){
546
"Error[%d] while reading the OpenPGP key pair ('%s',"
547
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
548
fprintf_plus(stderr, "The GnuTLS error is: %s\n",
549
safer_gnutls_strerror(ret));
553
/* GnuTLS server initialization */
554
ret = gnutls_dh_params_init(&mc->dh_params);
555
if(ret != GNUTLS_E_SUCCESS){
556
fprintf_plus(stderr, "Error in GnuTLS DH parameter"
557
" initialization: %s\n",
558
safer_gnutls_strerror(ret));
561
ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits);
562
if(ret != GNUTLS_E_SUCCESS){
563
fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n",
564
safer_gnutls_strerror(ret));
568
gnutls_certificate_set_dh_params(mc->cred, mc->dh_params);
574
gnutls_certificate_free_credentials(mc->cred);
575
gnutls_global_deinit();
576
gnutls_dh_params_deinit(mc->dh_params);
580
__attribute__((nonnull, warn_unused_result))
581
static int init_gnutls_session(gnutls_session_t *session,
584
/* GnuTLS session creation */
586
ret = gnutls_init(session, GNUTLS_SERVER);
590
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
591
if(ret != GNUTLS_E_SUCCESS){
593
"Error in GnuTLS session initialization: %s\n",
594
safer_gnutls_strerror(ret));
600
ret = gnutls_priority_set_direct(*session, mc->priority, &err);
602
gnutls_deinit(*session);
605
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
606
if(ret != GNUTLS_E_SUCCESS){
607
fprintf_plus(stderr, "Syntax error at: %s\n", err);
608
fprintf_plus(stderr, "GnuTLS error: %s\n",
609
safer_gnutls_strerror(ret));
610
gnutls_deinit(*session);
616
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
619
gnutls_deinit(*session);
622
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
623
if(ret != GNUTLS_E_SUCCESS){
624
fprintf_plus(stderr, "Error setting GnuTLS credentials: %s\n",
625
safer_gnutls_strerror(ret));
626
gnutls_deinit(*session);
630
/* ignore client certificate if any. */
631
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
633
gnutls_dh_set_prime_bits(*session, mc->dh_bits);
638
/* Avahi log function callback */
639
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
640
__attribute__((unused)) const char *txt){}
642
/* Called when a Mandos server is found */
643
__attribute__((nonnull, warn_unused_result))
644
static int start_mandos_communication(const char *ip, in_port_t port,
645
AvahiIfIndex if_index,
646
int af, mandos_context *mc){
647
int ret, tcp_sd = -1;
649
struct sockaddr_storage to;
651
char *decrypted_buffer = NULL;
652
size_t buffer_length = 0;
653
size_t buffer_capacity = 0;
656
gnutls_session_t session;
657
int pf; /* Protocol family */
674
fprintf_plus(stderr, "Bad address family: %d\n", af);
679
/* If the interface is specified and we have a list of interfaces */
680
if(if_index != AVAHI_IF_UNSPEC and mc->interfaces != NULL){
681
/* Check if the interface is one of the interfaces we are using */
684
char *interface = NULL;
685
while((interface=argz_next(mc->interfaces, mc->interfaces_size,
687
if(if_nametoindex(interface) == (unsigned int)if_index){
694
/* This interface does not match any in the list, so we don't
695
connect to the server */
697
char interface[IF_NAMESIZE];
698
if(if_indextoname((unsigned int)if_index, interface) == NULL){
699
perror_plus("if_indextoname");
701
fprintf_plus(stderr, "Skipping server on non-used interface"
703
if_indextoname((unsigned int)if_index,
711
ret = init_gnutls_session(&session, mc);
717
fprintf_plus(stderr, "Setting up a TCP connection to %s, port %"
718
PRIuMAX "\n", ip, (uintmax_t)port);
721
tcp_sd = socket(pf, SOCK_STREAM, 0);
724
perror_plus("socket");
734
memset(&to, 0, sizeof(to));
736
((struct sockaddr_in6 *)&to)->sin6_family = (sa_family_t)af;
737
ret = inet_pton(af, ip, &((struct sockaddr_in6 *)&to)->sin6_addr);
739
((struct sockaddr_in *)&to)->sin_family = (sa_family_t)af;
740
ret = inet_pton(af, ip, &((struct sockaddr_in *)&to)->sin_addr);
744
perror_plus("inet_pton");
750
fprintf_plus(stderr, "Bad address: %s\n", ip);
755
((struct sockaddr_in6 *)&to)->sin6_port = htons(port);
756
if(IN6_IS_ADDR_LINKLOCAL
757
(&((struct sockaddr_in6 *)&to)->sin6_addr)){
758
if(if_index == AVAHI_IF_UNSPEC){
759
fprintf_plus(stderr, "An IPv6 link-local address is"
760
" incomplete without a network interface\n");
764
/* Set the network interface number as scope */
765
((struct sockaddr_in6 *)&to)->sin6_scope_id = (uint32_t)if_index;
768
((struct sockaddr_in *)&to)->sin_port = htons(port);
777
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
778
char interface[IF_NAMESIZE];
779
if(if_indextoname((unsigned int)if_index, interface) == NULL){
780
perror_plus("if_indextoname");
782
fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIuMAX
783
"\n", ip, interface, (uintmax_t)port);
786
fprintf_plus(stderr, "Connection to: %s, port %" PRIuMAX "\n",
787
ip, (uintmax_t)port);
789
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
790
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
792
ret = getnameinfo((struct sockaddr *)&to,
793
sizeof(struct sockaddr_in6),
794
addrstr, sizeof(addrstr), NULL, 0,
797
ret = getnameinfo((struct sockaddr *)&to,
798
sizeof(struct sockaddr_in),
799
addrstr, sizeof(addrstr), NULL, 0,
802
if(ret == EAI_SYSTEM){
803
perror_plus("getnameinfo");
804
} else if(ret != 0) {
805
fprintf_plus(stderr, "getnameinfo: %s", gai_strerror(ret));
806
} else if(strcmp(addrstr, ip) != 0){
807
fprintf_plus(stderr, "Canonical address form: %s\n", addrstr);
817
ret = connect(tcp_sd, (struct sockaddr *)&to,
818
sizeof(struct sockaddr_in6));
820
ret = connect(tcp_sd, (struct sockaddr *)&to, /* IPv4 */
821
sizeof(struct sockaddr_in));
824
if((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
826
perror_plus("connect");
837
const char *out = mandos_protocol_version;
840
size_t out_size = strlen(out);
841
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
842
out_size - written));
845
perror_plus("write");
849
written += (size_t)ret;
850
if(written < out_size){
853
if(out == mandos_protocol_version){
868
fprintf_plus(stderr, "Establishing TLS session with %s\n", ip);
876
/* This casting via intptr_t is to eliminate warning about casting
877
an int to a pointer type. This is exactly how the GnuTLS Guile
878
function "set-session-transport-fd!" does it. */
879
gnutls_transport_set_ptr(session,
880
(gnutls_transport_ptr_t)(intptr_t)tcp_sd);
888
ret = gnutls_handshake(session);
893
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
895
if(ret != GNUTLS_E_SUCCESS){
897
fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n");
904
/* Read OpenPGP packet that contains the wanted password */
907
fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from"
918
buffer_capacity = incbuffer(&buffer, buffer_length,
920
if(buffer_capacity == 0){
922
perror_plus("incbuffer");
932
sret = gnutls_record_recv(session, buffer+buffer_length,
939
case GNUTLS_E_INTERRUPTED:
942
case GNUTLS_E_REHANDSHAKE:
944
ret = gnutls_handshake(session);
950
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
952
fprintf_plus(stderr, "*** GnuTLS Re-handshake failed "
960
fprintf_plus(stderr, "Unknown error while reading data from"
961
" encrypted session with Mandos server\n");
962
gnutls_bye(session, GNUTLS_SHUT_RDWR);
967
buffer_length += (size_t) sret;
972
fprintf_plus(stderr, "Closing TLS session\n");
981
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
986
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
988
if(buffer_length > 0){
989
ssize_t decrypted_buffer_size;
990
decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length,
991
&decrypted_buffer, mc);
992
if(decrypted_buffer_size >= 0){
995
while(written < (size_t) decrypted_buffer_size){
1001
ret = (int)fwrite(decrypted_buffer + written, 1,
1002
(size_t)decrypted_buffer_size - written,
1004
if(ret == 0 and ferror(stdout)){
1007
fprintf_plus(stderr, "Error writing encrypted data: %s\n",
1013
written += (size_t)ret;
1019
/* Shutdown procedure */
1024
free(decrypted_buffer);
1027
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
1033
perror_plus("close");
1035
gnutls_deinit(session);
1045
__attribute__((nonnull))
1046
static void resolve_callback(AvahiSServiceResolver *r,
1047
AvahiIfIndex interface,
1048
AvahiProtocol proto,
1049
AvahiResolverEvent event,
1053
const char *host_name,
1054
const AvahiAddress *address,
1056
AVAHI_GCC_UNUSED AvahiStringList *txt,
1057
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1064
/* Called whenever a service has been resolved successfully or
1073
case AVAHI_RESOLVER_FAILURE:
1074
fprintf_plus(stderr, "(Avahi Resolver) Failed to resolve service "
1075
"'%s' of type '%s' in domain '%s': %s\n", name, type,
1077
avahi_strerror(avahi_server_errno
1078
(((mandos_context*)mc)->server)));
1081
case AVAHI_RESOLVER_FOUND:
1083
char ip[AVAHI_ADDRESS_STR_MAX];
1084
avahi_address_snprint(ip, sizeof(ip), address);
1086
fprintf_plus(stderr, "Mandos server \"%s\" found on %s (%s, %"
1087
PRIdMAX ") on port %" PRIu16 "\n", name,
1088
host_name, ip, (intmax_t)interface, port);
1090
int ret = start_mandos_communication(ip, (in_port_t)port,
1092
avahi_proto_to_af(proto),
1095
avahi_simple_poll_quit(simple_poll);
1097
if(not add_server(ip, (in_port_t)port, interface,
1098
avahi_proto_to_af(proto),
1099
&((mandos_context*)mc)->current_server)){
1100
fprintf_plus(stderr, "Failed to add server \"%s\" to server"
1106
avahi_s_service_resolver_free(r);
1109
static void browse_callback(AvahiSServiceBrowser *b,
1110
AvahiIfIndex interface,
1111
AvahiProtocol protocol,
1112
AvahiBrowserEvent event,
1116
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1123
/* Called whenever a new services becomes available on the LAN or
1124
is removed from the LAN */
1132
case AVAHI_BROWSER_FAILURE:
1134
fprintf_plus(stderr, "(Avahi browser) %s\n",
1135
avahi_strerror(avahi_server_errno
1136
(((mandos_context*)mc)->server)));
1137
avahi_simple_poll_quit(simple_poll);
1140
case AVAHI_BROWSER_NEW:
1141
/* We ignore the returned Avahi resolver object. In the callback
1142
function we free it. If the Avahi server is terminated before
1143
the callback function is called the Avahi server will free the
1146
if(avahi_s_service_resolver_new(((mandos_context*)mc)->server,
1147
interface, protocol, name, type,
1148
domain, protocol, 0,
1149
resolve_callback, mc) == NULL)
1150
fprintf_plus(stderr, "Avahi: Failed to resolve service '%s':"
1152
avahi_strerror(avahi_server_errno
1153
(((mandos_context*)mc)->server)));
1156
case AVAHI_BROWSER_REMOVE:
1159
case AVAHI_BROWSER_ALL_FOR_NOW:
1160
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1162
fprintf_plus(stderr, "No Mandos server found, still"
1169
/* Signal handler that stops main loop after SIGTERM */
1170
static void handle_sigterm(int sig){
1175
signal_received = sig;
1176
int old_errno = errno;
1177
/* set main loop to exit */
1178
if(simple_poll != NULL){
1179
avahi_simple_poll_quit(simple_poll);
1184
__attribute__((nonnull, warn_unused_result))
1185
bool get_flags(const char *ifname, struct ifreq *ifr){
1189
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1192
perror_plus("socket");
1196
strcpy(ifr->ifr_name, ifname);
1197
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1201
perror_plus("ioctl SIOCGIFFLAGS");
1209
__attribute__((nonnull, warn_unused_result))
1210
bool good_flags(const char *ifname, const struct ifreq *ifr){
1212
/* Reject the loopback device */
1213
if(ifr->ifr_flags & IFF_LOOPBACK){
1215
fprintf_plus(stderr, "Rejecting loopback interface \"%s\"\n",
1220
/* Accept point-to-point devices only if connect_to is specified */
1221
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1223
fprintf_plus(stderr, "Accepting point-to-point interface"
1224
" \"%s\"\n", ifname);
1228
/* Otherwise, reject non-broadcast-capable devices */
1229
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1231
fprintf_plus(stderr, "Rejecting non-broadcast interface"
1232
" \"%s\"\n", ifname);
1236
/* Reject non-ARP interfaces (including dummy interfaces) */
1237
if(ifr->ifr_flags & IFF_NOARP){
1239
fprintf_plus(stderr, "Rejecting non-ARP interface \"%s\"\n",
1245
/* Accept this device */
1247
fprintf_plus(stderr, "Interface \"%s\" is good\n", ifname);
1253
* This function determines if a directory entry in /sys/class/net
1254
* corresponds to an acceptable network device.
1255
* (This function is passed to scandir(3) as a filter function.)
1257
__attribute__((nonnull, warn_unused_result))
1258
int good_interface(const struct dirent *if_entry){
1259
if(if_entry->d_name[0] == '.'){
1264
if(not get_flags(if_entry->d_name, &ifr)){
1266
fprintf_plus(stderr, "Failed to get flags for interface "
1267
"\"%s\"\n", if_entry->d_name);
1272
if(not good_flags(if_entry->d_name, &ifr)){
1279
* This function determines if a network interface is up.
1281
__attribute__((nonnull, warn_unused_result))
1282
bool interface_is_up(const char *interface){
1284
if(not get_flags(interface, &ifr)){
1286
fprintf_plus(stderr, "Failed to get flags for interface "
1287
"\"%s\"\n", interface);
1292
return (bool)(ifr.ifr_flags & IFF_UP);
1296
* This function determines if a network interface is running
1298
__attribute__((nonnull, warn_unused_result))
1299
bool interface_is_running(const char *interface){
1301
if(not get_flags(interface, &ifr)){
1303
fprintf_plus(stderr, "Failed to get flags for interface "
1304
"\"%s\"\n", interface);
1309
return (bool)(ifr.ifr_flags & IFF_RUNNING);
1312
__attribute__((nonnull, pure, warn_unused_result))
1313
int notdotentries(const struct dirent *direntry){
1314
/* Skip "." and ".." */
1315
if(direntry->d_name[0] == '.'
1316
and (direntry->d_name[1] == '\0'
1317
or (direntry->d_name[1] == '.'
1318
and direntry->d_name[2] == '\0'))){
1324
/* Is this directory entry a runnable program? */
1325
__attribute__((nonnull, warn_unused_result))
1326
int runnable_hook(const struct dirent *direntry){
1331
if((direntry->d_name)[0] == '\0'){
1336
sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1337
"abcdefghijklmnopqrstuvwxyz"
1340
if((direntry->d_name)[sret] != '\0'){
1341
/* Contains non-allowed characters */
1343
fprintf_plus(stderr, "Ignoring hook \"%s\" with bad name\n",
1349
ret = fstatat(hookdir_fd, direntry->d_name, &st, 0);
1352
perror_plus("Could not stat hook");
1356
if(not (S_ISREG(st.st_mode))){
1357
/* Not a regular file */
1359
fprintf_plus(stderr, "Ignoring hook \"%s\" - not a file\n",
1364
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1365
/* Not executable */
1367
fprintf_plus(stderr, "Ignoring hook \"%s\" - not executable\n",
1373
fprintf_plus(stderr, "Hook \"%s\" is acceptable\n",
1379
__attribute__((nonnull, warn_unused_result))
1380
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval,
1381
mandos_context *mc){
1383
struct timespec now;
1384
struct timespec waited_time;
1385
intmax_t block_time;
1388
if(mc->current_server == NULL){
1390
fprintf_plus(stderr, "Wait until first server is found."
1393
ret = avahi_simple_poll_iterate(s, -1);
1396
fprintf_plus(stderr, "Check current_server if we should run"
1399
/* the current time */
1400
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1402
perror_plus("clock_gettime");
1405
/* Calculating in ms how long time between now and server
1406
who we visted longest time ago. Now - last seen. */
1407
waited_time.tv_sec = (now.tv_sec
1408
- mc->current_server->last_seen.tv_sec);
1409
waited_time.tv_nsec = (now.tv_nsec
1410
- mc->current_server->last_seen.tv_nsec);
1411
/* total time is 10s/10,000ms.
1412
Converting to s from ms by dividing by 1,000,
1413
and ns to ms by dividing by 1,000,000. */
1414
block_time = ((retry_interval
1415
- ((intmax_t)waited_time.tv_sec * 1000))
1416
- ((intmax_t)waited_time.tv_nsec / 1000000));
1419
fprintf_plus(stderr, "Blocking for %" PRIdMAX " ms\n",
1423
if(block_time <= 0){
1424
ret = start_mandos_communication(mc->current_server->ip,
1425
mc->current_server->port,
1426
mc->current_server->if_index,
1427
mc->current_server->af, mc);
1429
avahi_simple_poll_quit(s);
1432
ret = clock_gettime(CLOCK_MONOTONIC,
1433
&mc->current_server->last_seen);
1435
perror_plus("clock_gettime");
1438
mc->current_server = mc->current_server->next;
1439
block_time = 0; /* Call avahi to find new Mandos
1440
servers, but don't block */
1443
ret = avahi_simple_poll_iterate(s, (int)block_time);
1446
if(ret > 0 or errno != EINTR){
1447
return (ret != 1) ? ret : 0;
1453
/* Set effective uid to 0, return errno */
1454
__attribute__((warn_unused_result))
1455
error_t raise_privileges(void){
1456
error_t old_errno = errno;
1457
error_t ret_errno = 0;
1458
if(seteuid(0) == -1){
1460
perror_plus("seteuid");
1466
/* Set effective and real user ID to 0. Return errno. */
1467
__attribute__((warn_unused_result))
1468
error_t raise_privileges_permanently(void){
1469
error_t old_errno = errno;
1470
error_t ret_errno = raise_privileges();
1475
if(setuid(0) == -1){
1477
perror_plus("seteuid");
1483
/* Set effective user ID to unprivileged saved user ID */
1484
__attribute__((warn_unused_result))
1485
error_t lower_privileges(void){
1486
error_t old_errno = errno;
1487
error_t ret_errno = 0;
1488
if(seteuid(uid) == -1){
1490
perror_plus("seteuid");
1496
/* Lower privileges permanently */
1497
__attribute__((warn_unused_result))
1498
error_t lower_privileges_permanently(void){
1499
error_t old_errno = errno;
1500
error_t ret_errno = 0;
1501
if(setuid(uid) == -1){
1503
perror_plus("setuid");
1511
* Based on the example in the GNU LibC manual chapter 13.13 "File
1512
* Descriptor Flags".
1513
| [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
1515
__attribute__((warn_unused_result))
1516
static int set_cloexec_flag(int fd){
1517
int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
1518
/* If reading the flags failed, return error indication now. */
1522
/* Store modified flag word in the descriptor. */
1523
return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
1526
#endif /* not O_CLOEXEC */
1528
__attribute__((nonnull))
1529
void run_network_hooks(const char *mode, const char *interface,
1531
struct dirent **direntries;
1532
if(hookdir_fd == -1){
1533
hookdir_fd = open(hookdir, O_RDONLY |
1536
#else /* not O_CLOEXEC */
1538
#endif /* not O_CLOEXEC */
1540
if(hookdir_fd == -1){
1541
if(errno == ENOENT){
1543
fprintf_plus(stderr, "Network hook directory \"%s\" not"
1544
" found\n", hookdir);
1547
perror_plus("open");
1552
if(set_cloexec_flag(hookdir_fd) < 0){
1553
perror_plus("set_cloexec_flag");
1554
if((int)TEMP_FAILURE_RETRY(close(hookdir_fd)) == -1){
1555
perror_plus("close");
1561
#endif /* not O_CLOEXEC */
1564
#if __GLIBC_PREREQ(2, 15)
1565
int numhooks = scandirat(hookdir_fd, ".", &direntries,
1566
runnable_hook, alphasort);
1567
#else /* not __GLIBC_PREREQ(2, 15) */
1568
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1570
#endif /* not __GLIBC_PREREQ(2, 15) */
1571
#else /* not __GLIBC__ */
1572
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1574
#endif /* not __GLIBC__ */
1576
perror_plus("scandir");
1579
struct dirent *direntry;
1581
int devnull = open("/dev/null", O_RDONLY);
1582
for(int i = 0; i < numhooks; i++){
1583
direntry = direntries[i];
1585
fprintf_plus(stderr, "Running network hook \"%s\"\n",
1588
pid_t hook_pid = fork();
1591
/* Raise privileges */
1592
if(raise_privileges_permanently() != 0){
1593
perror_plus("Failed to raise privileges");
1600
perror_plus("setgid");
1603
/* Reset supplementary groups */
1605
ret = setgroups(0, NULL);
1607
perror_plus("setgroups");
1610
ret = dup2(devnull, STDIN_FILENO);
1612
perror_plus("dup2(devnull, STDIN_FILENO)");
1615
ret = close(devnull);
1617
perror_plus("close");
1620
ret = dup2(STDERR_FILENO, STDOUT_FILENO);
1622
perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)");
1625
ret = setenv("MANDOSNETHOOKDIR", hookdir, 1);
1627
perror_plus("setenv");
1630
ret = setenv("DEVICE", interface, 1);
1632
perror_plus("setenv");
1635
ret = setenv("VERBOSITY", debug ? "1" : "0", 1);
1637
perror_plus("setenv");
1640
ret = setenv("MODE", mode, 1);
1642
perror_plus("setenv");
1646
ret = asprintf(&delaystring, "%f", (double)delay);
1648
perror_plus("asprintf");
1651
ret = setenv("DELAY", delaystring, 1);
1654
perror_plus("setenv");
1658
if(connect_to != NULL){
1659
ret = setenv("CONNECT", connect_to, 1);
1661
perror_plus("setenv");
1665
if(fexecve(hookdir_fd, (char *const [])
1666
{ direntry->d_name, NULL }, environ) == -1){
1667
perror_plus("fexecve");
1668
_exit(EXIT_FAILURE);
1672
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1673
perror_plus("waitpid");
1676
if(WIFEXITED(status)){
1677
if(WEXITSTATUS(status) != 0){
1678
fprintf_plus(stderr, "Warning: network hook \"%s\" exited"
1679
" with status %d\n", direntry->d_name,
1680
WEXITSTATUS(status));
1683
} else if(WIFSIGNALED(status)){
1684
fprintf_plus(stderr, "Warning: network hook \"%s\" died by"
1685
" signal %d\n", direntry->d_name,
1689
fprintf_plus(stderr, "Warning: network hook \"%s\""
1690
" crashed\n", direntry->d_name);
1695
fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n",
1699
if((int)TEMP_FAILURE_RETRY(close(hookdir_fd)) == -1){
1700
perror_plus("close");
1707
__attribute__((nonnull, warn_unused_result))
1708
error_t bring_up_interface(const char *const interface,
1710
error_t old_errno = errno;
1712
struct ifreq network;
1713
unsigned int if_index = if_nametoindex(interface);
1715
fprintf_plus(stderr, "No such interface: \"%s\"\n", interface);
1725
if(not interface_is_up(interface)){
1726
error_t ret_errno = 0, ioctl_errno = 0;
1727
if(not get_flags(interface, &network)){
1729
fprintf_plus(stderr, "Failed to get flags for interface "
1730
"\"%s\"\n", interface);
1734
network.ifr_flags |= IFF_UP; /* set flag */
1736
int sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1739
perror_plus("socket");
1745
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1747
perror_plus("close");
1754
fprintf_plus(stderr, "Bringing up interface \"%s\"\n",
1758
/* Raise privileges */
1759
ret_errno = raise_privileges();
1761
perror_plus("Failed to raise privileges");
1766
bool restore_loglevel = false;
1768
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1769
messages about the network interface to mess up the prompt */
1770
ret_linux = klogctl(8, NULL, 5);
1771
if(ret_linux == -1){
1772
perror_plus("klogctl");
1774
restore_loglevel = true;
1777
#endif /* __linux__ */
1778
int ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network);
1779
ioctl_errno = errno;
1781
if(restore_loglevel){
1782
ret_linux = klogctl(7, NULL, 0);
1783
if(ret_linux == -1){
1784
perror_plus("klogctl");
1787
#endif /* __linux__ */
1789
/* If raise_privileges() succeeded above */
1791
/* Lower privileges */
1792
ret_errno = lower_privileges();
1795
perror_plus("Failed to lower privileges");
1799
/* Close the socket */
1800
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1802
perror_plus("close");
1805
if(ret_setflags == -1){
1806
errno = ioctl_errno;
1807
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1812
fprintf_plus(stderr, "Interface \"%s\" is already up; good\n",
1816
/* Sleep checking until interface is running.
1817
Check every 0.25s, up to total time of delay */
1818
for(int i=0; i < delay * 4; i++){
1819
if(interface_is_running(interface)){
1822
struct timespec sleeptime = { .tv_nsec = 250000000 };
1823
ret = nanosleep(&sleeptime, NULL);
1824
if(ret == -1 and errno != EINTR){
1825
perror_plus("nanosleep");
1833
__attribute__((nonnull, warn_unused_result))
1834
error_t take_down_interface(const char *const interface){
1835
error_t old_errno = errno;
1836
struct ifreq network;
1837
unsigned int if_index = if_nametoindex(interface);
1839
fprintf_plus(stderr, "No such interface: \"%s\"\n", interface);
1843
if(interface_is_up(interface)){
1844
error_t ret_errno = 0, ioctl_errno = 0;
1845
if(not get_flags(interface, &network) and debug){
1847
fprintf_plus(stderr, "Failed to get flags for interface "
1848
"\"%s\"\n", interface);
1852
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
1854
int sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1857
perror_plus("socket");
1863
fprintf_plus(stderr, "Taking down interface \"%s\"\n",
1867
/* Raise privileges */
1868
ret_errno = raise_privileges();
1870
perror_plus("Failed to raise privileges");
1873
int ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network);
1874
ioctl_errno = errno;
1876
/* If raise_privileges() succeeded above */
1878
/* Lower privileges */
1879
ret_errno = lower_privileges();
1882
perror_plus("Failed to lower privileges");
1886
/* Close the socket */
1887
int ret = (int)TEMP_FAILURE_RETRY(close(sd));
1889
perror_plus("close");
1892
if(ret_setflags == -1){
1893
errno = ioctl_errno;
1894
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
1899
fprintf_plus(stderr, "Interface \"%s\" is already down; odd\n",
1907
int main(int argc, char *argv[]){
1908
mandos_context mc = { .server = NULL, .dh_bits = 1024,
1909
.priority = "SECURE256:!CTYPE-X.509:"
1910
"+CTYPE-OPENPGP", .current_server = NULL,
1911
.interfaces = NULL, .interfaces_size = 0 };
1912
AvahiSServiceBrowser *sb = NULL;
1917
int exitcode = EXIT_SUCCESS;
1918
char *interfaces_to_take_down = NULL;
1919
size_t interfaces_to_take_down_size = 0;
1920
char run_tempdir[] = "/run/tmp/mandosXXXXXX";
1921
char old_tempdir[] = "/tmp/mandosXXXXXX";
1922
char *tempdir = NULL;
1923
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1924
const char *seckey = PATHDIR "/" SECKEY;
1925
const char *pubkey = PATHDIR "/" PUBKEY;
1926
char *interfaces_hooks = NULL;
1928
bool gnutls_initialized = false;
1929
bool gpgme_initialized = false;
1931
double retry_interval = 10; /* 10s between trying a server and
1932
retrying the same server again */
1934
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1935
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1940
/* Lower any group privileges we might have, just to be safe */
1944
perror_plus("setgid");
1947
/* Lower user privileges (temporarily) */
1951
perror_plus("seteuid");
1959
struct argp_option options[] = {
1960
{ .name = "debug", .key = 128,
1961
.doc = "Debug mode", .group = 3 },
1962
{ .name = "connect", .key = 'c',
1963
.arg = "ADDRESS:PORT",
1964
.doc = "Connect directly to a specific Mandos server",
1966
{ .name = "interface", .key = 'i',
1968
.doc = "Network interface that will be used to search for"
1971
{ .name = "seckey", .key = 's',
1973
.doc = "OpenPGP secret key file base name",
1975
{ .name = "pubkey", .key = 'p',
1977
.doc = "OpenPGP public key file base name",
1979
{ .name = "dh-bits", .key = 129,
1981
.doc = "Bit length of the prime number used in the"
1982
" Diffie-Hellman key exchange",
1984
{ .name = "priority", .key = 130,
1986
.doc = "GnuTLS priority string for the TLS handshake",
1988
{ .name = "delay", .key = 131,
1990
.doc = "Maximum delay to wait for interface startup",
1992
{ .name = "retry", .key = 132,
1994
.doc = "Retry interval used when denied by the Mandos server",
1996
{ .name = "network-hook-dir", .key = 133,
1998
.doc = "Directory where network hooks are located",
2001
* These reproduce what we would get without ARGP_NO_HELP
2003
{ .name = "help", .key = '?',
2004
.doc = "Give this help list", .group = -1 },
2005
{ .name = "usage", .key = -3,
2006
.doc = "Give a short usage message", .group = -1 },
2007
{ .name = "version", .key = 'V',
2008
.doc = "Print program version", .group = -1 },
2012
error_t parse_opt(int key, char *arg,
2013
struct argp_state *state){
2016
case 128: /* --debug */
2019
case 'c': /* --connect */
2022
case 'i': /* --interface */
2023
ret_errno = argz_add_sep(&mc.interfaces, &mc.interfaces_size,
2026
argp_error(state, "%s", strerror(ret_errno));
2029
case 's': /* --seckey */
2032
case 'p': /* --pubkey */
2035
case 129: /* --dh-bits */
2037
tmpmax = strtoimax(arg, &tmp, 10);
2038
if(errno != 0 or tmp == arg or *tmp != '\0'
2039
or tmpmax != (typeof(mc.dh_bits))tmpmax){
2040
argp_error(state, "Bad number of DH bits");
2042
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
2044
case 130: /* --priority */
2047
case 131: /* --delay */
2049
delay = strtof(arg, &tmp);
2050
if(errno != 0 or tmp == arg or *tmp != '\0'){
2051
argp_error(state, "Bad delay");
2053
case 132: /* --retry */
2055
retry_interval = strtod(arg, &tmp);
2056
if(errno != 0 or tmp == arg or *tmp != '\0'
2057
or (retry_interval * 1000) > INT_MAX
2058
or retry_interval < 0){
2059
argp_error(state, "Bad retry interval");
2062
case 133: /* --network-hook-dir */
2066
* These reproduce what we would get without ARGP_NO_HELP
2068
case '?': /* --help */
2069
argp_state_help(state, state->out_stream,
2070
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
2071
& ~(unsigned int)ARGP_HELP_EXIT_OK);
2072
case -3: /* --usage */
2073
argp_state_help(state, state->out_stream,
2074
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
2075
case 'V': /* --version */
2076
fprintf_plus(state->out_stream, "%s\n", argp_program_version);
2077
exit(argp_err_exit_status);
2080
return ARGP_ERR_UNKNOWN;
2085
struct argp argp = { .options = options, .parser = parse_opt,
2087
.doc = "Mandos client -- Get and decrypt"
2088
" passwords from a Mandos server" };
2089
ret = argp_parse(&argp, argc, argv,
2090
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
2097
perror_plus("argp_parse");
2098
exitcode = EX_OSERR;
2101
exitcode = EX_USAGE;
2107
/* Work around Debian bug #633582:
2108
<http://bugs.debian.org/633582> */
2110
/* Re-raise privileges */
2111
ret_errno = raise_privileges();
2114
perror_plus("Failed to raise privileges");
2118
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
2119
int seckey_fd = open(seckey, O_RDONLY);
2120
if(seckey_fd == -1){
2121
perror_plus("open");
2123
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
2125
perror_plus("fstat");
2127
if(S_ISREG(st.st_mode)
2128
and st.st_uid == 0 and st.st_gid == 0){
2129
ret = fchown(seckey_fd, uid, gid);
2131
perror_plus("fchown");
2135
TEMP_FAILURE_RETRY(close(seckey_fd));
2139
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
2140
int pubkey_fd = open(pubkey, O_RDONLY);
2141
if(pubkey_fd == -1){
2142
perror_plus("open");
2144
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
2146
perror_plus("fstat");
2148
if(S_ISREG(st.st_mode)
2149
and st.st_uid == 0 and st.st_gid == 0){
2150
ret = fchown(pubkey_fd, uid, gid);
2152
perror_plus("fchown");
2156
TEMP_FAILURE_RETRY(close(pubkey_fd));
2160
/* Lower privileges */
2161
ret_errno = lower_privileges();
2164
perror_plus("Failed to lower privileges");
2169
/* Remove invalid interface names (except "none") */
2171
char *interface = NULL;
2172
while((interface = argz_next(mc.interfaces, mc.interfaces_size,
2174
if(strcmp(interface, "none") != 0
2175
and if_nametoindex(interface) == 0){
2176
if(interface[0] != '\0'){
2177
fprintf_plus(stderr, "Not using nonexisting interface"
2178
" \"%s\"\n", interface);
2180
argz_delete(&mc.interfaces, &mc.interfaces_size, interface);
2186
/* Run network hooks */
2188
if(mc.interfaces != NULL){
2189
interfaces_hooks = malloc(mc.interfaces_size);
2190
if(interfaces_hooks == NULL){
2191
perror_plus("malloc");
2194
memcpy(interfaces_hooks, mc.interfaces, mc.interfaces_size);
2195
argz_stringify(interfaces_hooks, mc.interfaces_size, (int)',');
2197
run_network_hooks("start", interfaces_hooks != NULL ?
2198
interfaces_hooks : "", delay);
2202
avahi_set_log_function(empty_log);
2205
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
2206
from the signal handler */
2207
/* Initialize the pseudo-RNG for Avahi */
2208
srand((unsigned int) time(NULL));
2209
simple_poll = avahi_simple_poll_new();
2210
if(simple_poll == NULL){
2211
fprintf_plus(stderr,
2212
"Avahi: Failed to create simple poll object.\n");
2213
exitcode = EX_UNAVAILABLE;
2217
sigemptyset(&sigterm_action.sa_mask);
2218
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
2220
perror_plus("sigaddset");
2221
exitcode = EX_OSERR;
2224
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
2226
perror_plus("sigaddset");
2227
exitcode = EX_OSERR;
2230
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
2232
perror_plus("sigaddset");
2233
exitcode = EX_OSERR;
2236
/* Need to check if the handler is SIG_IGN before handling:
2237
| [[info:libc:Initial Signal Actions]] |
2238
| [[info:libc:Basic Signal Handling]] |
2240
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
2242
perror_plus("sigaction");
2245
if(old_sigterm_action.sa_handler != SIG_IGN){
2246
ret = sigaction(SIGINT, &sigterm_action, NULL);
2248
perror_plus("sigaction");
2249
exitcode = EX_OSERR;
2253
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
2255
perror_plus("sigaction");
2258
if(old_sigterm_action.sa_handler != SIG_IGN){
2259
ret = sigaction(SIGHUP, &sigterm_action, NULL);
2261
perror_plus("sigaction");
2262
exitcode = EX_OSERR;
2266
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
2268
perror_plus("sigaction");
2271
if(old_sigterm_action.sa_handler != SIG_IGN){
2272
ret = sigaction(SIGTERM, &sigterm_action, NULL);
2274
perror_plus("sigaction");
2275
exitcode = EX_OSERR;
2280
/* If no interfaces were specified, make a list */
2281
if(mc.interfaces == NULL){
2282
struct dirent **direntries;
2283
/* Look for any good interfaces */
2284
ret = scandir(sys_class_net, &direntries, good_interface,
2287
/* Add all found interfaces to interfaces list */
2288
for(int i = 0; i < ret; ++i){
2289
ret_errno = argz_add(&mc.interfaces, &mc.interfaces_size,
2290
direntries[i]->d_name);
2293
perror_plus("argz_add");
2297
fprintf_plus(stderr, "Will use interface \"%s\"\n",
2298
direntries[i]->d_name);
2304
fprintf_plus(stderr, "Could not find a network interface\n");
2305
exitcode = EXIT_FAILURE;
2310
/* Bring up interfaces which are down, and remove any "none"s */
2312
char *interface = NULL;
2313
while((interface = argz_next(mc.interfaces, mc.interfaces_size,
2315
/* If interface name is "none", stop bringing up interfaces.
2316
Also remove all instances of "none" from the list */
2317
if(strcmp(interface, "none") == 0){
2318
argz_delete(&mc.interfaces, &mc.interfaces_size,
2321
while((interface = argz_next(mc.interfaces,
2322
mc.interfaces_size, interface))){
2323
if(strcmp(interface, "none") == 0){
2324
argz_delete(&mc.interfaces, &mc.interfaces_size,
2331
bool interface_was_up = interface_is_up(interface);
2332
errno = bring_up_interface(interface, delay);
2333
if(not interface_was_up){
2335
perror_plus("Failed to bring up interface");
2337
errno = argz_add(&interfaces_to_take_down,
2338
&interfaces_to_take_down_size,
2341
perror_plus("argz_add");
2346
if(debug and (interfaces_to_take_down == NULL)){
2347
fprintf_plus(stderr, "No interfaces were brought up\n");
2351
/* If we only got one interface, explicitly use only that one */
2352
if(argz_count(mc.interfaces, mc.interfaces_size) == 1){
2354
fprintf_plus(stderr, "Using only interface \"%s\"\n",
2357
if_index = (AvahiIfIndex)if_nametoindex(mc.interfaces);
2364
ret = init_gnutls_global(pubkey, seckey, &mc);
2366
fprintf_plus(stderr, "init_gnutls_global failed\n");
2367
exitcode = EX_UNAVAILABLE;
2370
gnutls_initialized = true;
2377
/* Try /run/tmp before /tmp */
2378
tempdir = mkdtemp(run_tempdir);
2379
if(tempdir == NULL and errno == ENOENT){
2381
fprintf_plus(stderr, "Tempdir %s did not work, trying %s\n",
2382
run_tempdir, old_tempdir);
2384
tempdir = mkdtemp(old_tempdir);
2386
if(tempdir == NULL){
2387
perror_plus("mkdtemp");
2395
if(not init_gpgme(pubkey, seckey, tempdir, &mc)){
2396
fprintf_plus(stderr, "init_gpgme failed\n");
2397
exitcode = EX_UNAVAILABLE;
2400
gpgme_initialized = true;
2407
if(connect_to != NULL){
2408
/* Connect directly, do not use Zeroconf */
2409
/* (Mainly meant for debugging) */
2410
char *address = strrchr(connect_to, ':');
2412
if(address == NULL){
2413
fprintf_plus(stderr, "No colon in address\n");
2414
exitcode = EX_USAGE;
2424
tmpmax = strtoimax(address+1, &tmp, 10);
2425
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2426
or tmpmax != (in_port_t)tmpmax){
2427
fprintf_plus(stderr, "Bad port number\n");
2428
exitcode = EX_USAGE;
2436
port = (in_port_t)tmpmax;
2438
/* Colon in address indicates IPv6 */
2440
if(strchr(connect_to, ':') != NULL){
2442
/* Accept [] around IPv6 address - see RFC 5952 */
2443
if(connect_to[0] == '[' and address[-1] == ']')
2451
address = connect_to;
2457
while(not quit_now){
2458
ret = start_mandos_communication(address, port, if_index, af,
2460
if(quit_now or ret == 0){
2464
fprintf_plus(stderr, "Retrying in %d seconds\n",
2465
(int)retry_interval);
2467
sleep((unsigned int)retry_interval);
2471
exitcode = EXIT_SUCCESS;
2482
AvahiServerConfig config;
2483
/* Do not publish any local Zeroconf records */
2484
avahi_server_config_init(&config);
2485
config.publish_hinfo = 0;
2486
config.publish_addresses = 0;
2487
config.publish_workstation = 0;
2488
config.publish_domain = 0;
2490
/* Allocate a new server */
2491
mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll),
2492
&config, NULL, NULL, &ret_errno);
2494
/* Free the Avahi configuration data */
2495
avahi_server_config_free(&config);
2498
/* Check if creating the Avahi server object succeeded */
2499
if(mc.server == NULL){
2500
fprintf_plus(stderr, "Failed to create Avahi server: %s\n",
2501
avahi_strerror(ret_errno));
2502
exitcode = EX_UNAVAILABLE;
2510
/* Create the Avahi service browser */
2511
sb = avahi_s_service_browser_new(mc.server, if_index,
2512
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2513
NULL, 0, browse_callback,
2516
fprintf_plus(stderr, "Failed to create service browser: %s\n",
2517
avahi_strerror(avahi_server_errno(mc.server)));
2518
exitcode = EX_UNAVAILABLE;
2526
/* Run the main loop */
2529
fprintf_plus(stderr, "Starting Avahi loop search\n");
2532
ret = avahi_loop_with_timeout(simple_poll,
2533
(int)(retry_interval * 1000), &mc);
2535
fprintf_plus(stderr, "avahi_loop_with_timeout exited %s\n",
2536
(ret == 0) ? "successfully" : "with error");
2542
fprintf_plus(stderr, "%s exiting\n", argv[0]);
2545
/* Cleanup things */
2546
free(mc.interfaces);
2549
avahi_s_service_browser_free(sb);
2551
if(mc.server != NULL)
2552
avahi_server_free(mc.server);
2554
if(simple_poll != NULL)
2555
avahi_simple_poll_free(simple_poll);
2557
if(gnutls_initialized){
2558
gnutls_certificate_free_credentials(mc.cred);
2559
gnutls_global_deinit();
2560
gnutls_dh_params_deinit(mc.dh_params);
2563
if(gpgme_initialized){
2564
gpgme_release(mc.ctx);
2567
/* Cleans up the circular linked list of Mandos servers the client
2569
if(mc.current_server != NULL){
2570
mc.current_server->prev->next = NULL;
2571
while(mc.current_server != NULL){
2572
server *next = mc.current_server->next;
2573
free(mc.current_server);
2574
mc.current_server = next;
2578
/* Re-raise privileges */
2580
ret_errno = raise_privileges();
2582
perror_plus("Failed to raise privileges");
2585
/* Run network hooks */
2586
run_network_hooks("stop", interfaces_hooks != NULL ?
2587
interfaces_hooks : "", delay);
2589
/* Take down the network interfaces which were brought up */
2591
char *interface = NULL;
2592
while((interface=argz_next(interfaces_to_take_down,
2593
interfaces_to_take_down_size,
2595
ret_errno = take_down_interface(interface);
2598
perror_plus("Failed to take down interface");
2601
if(debug and (interfaces_to_take_down == NULL)){
2602
fprintf_plus(stderr, "No interfaces needed to be taken"
2608
ret_errno = lower_privileges_permanently();
2610
perror_plus("Failed to lower privileges permanently");
2614
free(interfaces_to_take_down);
2615
free(interfaces_hooks);
2617
/* Removes the GPGME temp directory and all files inside */
2618
if(tempdir != NULL){
2619
struct dirent **direntries = NULL;
2620
struct dirent *direntry = NULL;
2621
int numentries = scandir(tempdir, &direntries, notdotentries,
2624
for(int i = 0; i < numentries; i++){
2625
direntry = direntries[i];
2626
char *fullname = NULL;
2627
ret = asprintf(&fullname, "%s/%s", tempdir,
2630
perror_plus("asprintf");
2633
ret = remove(fullname);
2635
fprintf_plus(stderr, "remove(\"%s\"): %s\n", fullname,
2642
/* need to clean even if 0 because man page doesn't specify */
2644
if(numentries == -1){
2645
perror_plus("scandir");
2647
ret = rmdir(tempdir);
2648
if(ret == -1 and errno != ENOENT){
2649
perror_plus("rmdir");
2654
sigemptyset(&old_sigterm_action.sa_mask);
2655
old_sigterm_action.sa_handler = SIG_DFL;
2656
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2657
&old_sigterm_action,
2660
perror_plus("sigaction");
2663
ret = raise(signal_received);
2664
} while(ret != 0 and errno == EINTR);
2666
perror_plus("raise");
2669
TEMP_FAILURE_RETRY(pause());