1
/* -*- coding: utf-8 -*- */
3
* Mandos-client - get and decrypt data from a Mandos server
5
* This program is partly derived from an example program for an Avahi
6
* service browser, downloaded from
7
* <http://avahi.org/browser/examples/core-browse-services.c>. This
8
* includes the following functions: "resolve_callback",
9
* "browse_callback", and parts of "main".
12
* Copyright © 2008,2009 Teddy Hogeborn
13
* Copyright © 2008,2009 Björn Påhlsson
15
* This program is free software: you can redistribute it and/or
16
* modify it under the terms of the GNU General Public License as
17
* published by the Free Software Foundation, either version 3 of the
18
* License, or (at your option) any later version.
20
* This program is distributed in the hope that it will be useful, but
21
* WITHOUT ANY WARRANTY; without even the implied warranty of
22
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23
* General Public License for more details.
25
* You should have received a copy of the GNU General Public License
26
* along with this program. If not, see
27
* <http://www.gnu.org/licenses/>.
29
* Contact the authors at <mandos@fukt.bsnet.se>.
32
/* Needed by GPGME, specifically gpgme_data_seek() */
33
#ifndef _LARGEFILE_SOURCE
34
#define _LARGEFILE_SOURCE
36
#ifndef _FILE_OFFSET_BITS
37
#define _FILE_OFFSET_BITS 64
40
#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */
42
#include <stdio.h> /* fprintf(), stderr, fwrite(),
43
stdout, ferror(), remove() */
44
#include <stdint.h> /* uint16_t, uint32_t */
45
#include <stddef.h> /* NULL, size_t, ssize_t */
46
#include <stdlib.h> /* free(), EXIT_SUCCESS, EXIT_FAILURE,
47
srand(), strtof(), abort() */
48
#include <stdbool.h> /* bool, false, true */
49
#include <string.h> /* memset(), strcmp(), strlen(),
50
strerror(), asprintf(), strcpy() */
51
#include <sys/ioctl.h> /* ioctl */
52
#include <sys/types.h> /* socket(), inet_pton(), sockaddr,
53
sockaddr_in6, PF_INET6,
54
SOCK_STREAM, uid_t, gid_t, open(),
56
#include <sys/stat.h> /* open() */
57
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
58
inet_pton(), connect() */
59
#include <fcntl.h> /* open() */
60
#include <dirent.h> /* opendir(), struct dirent, readdir()
62
#include <inttypes.h> /* PRIu16, PRIdMAX, intmax_t,
64
#include <assert.h> /* assert() */
65
#include <errno.h> /* perror(), errno */
66
#include <time.h> /* nanosleep(), time() */
67
#include <net/if.h> /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
68
SIOCSIFFLAGS, if_indextoname(),
69
if_nametoindex(), IF_NAMESIZE */
70
#include <netinet/in.h> /* IN6_IS_ADDR_LINKLOCAL,
71
INET_ADDRSTRLEN, INET6_ADDRSTRLEN
73
#include <unistd.h> /* close(), SEEK_SET, off_t, write(),
74
getuid(), getgid(), seteuid(),
76
#include <arpa/inet.h> /* inet_pton(), htons */
77
#include <iso646.h> /* not, or, and */
78
#include <argp.h> /* struct argp_option, error_t, struct
79
argp_state, struct argp,
80
argp_parse(), ARGP_KEY_ARG,
81
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
82
#include <signal.h> /* sigemptyset(), sigaddset(),
83
sigaction(), SIGTERM, sig_atomic_t,
87
#include <sys/klog.h> /* klogctl() */
88
#endif /* __linux__ */
91
/* All Avahi types, constants and functions
94
#include <avahi-core/core.h>
95
#include <avahi-core/lookup.h>
96
#include <avahi-core/log.h>
97
#include <avahi-common/simple-watch.h>
98
#include <avahi-common/malloc.h>
99
#include <avahi-common/error.h>
102
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
105
init_gnutls_session(),
107
#include <gnutls/openpgp.h>
108
/* gnutls_certificate_set_openpgp_key_file(),
109
GNUTLS_OPENPGP_FMT_BASE64 */
112
#include <gpgme.h> /* All GPGME types, constants and
115
GPGME_PROTOCOL_OpenPGP,
118
#define BUFFER_SIZE 256
120
#define PATHDIR "/conf/conf.d/mandos"
121
#define SECKEY "seckey.txt"
122
#define PUBKEY "pubkey.txt"
125
static const char mandos_protocol_version[] = "1";
126
const char *argp_program_version = "mandos-client " VERSION;
127
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
128
static const char sys_class_net[] = "/sys/class/net";
129
char *connect_to = NULL;
131
/* Used for passing in values through the Avahi callback functions */
133
AvahiSimplePoll *simple_poll;
135
gnutls_certificate_credentials_t cred;
136
unsigned int dh_bits;
137
gnutls_dh_params_t dh_params;
138
const char *priority;
142
/* global context so signal handler can reach it*/
143
mandos_context mc = { .simple_poll = NULL, .server = NULL,
144
.dh_bits = 1024, .priority = "SECURE256"
145
":!CTYPE-X.509:+CTYPE-OPENPGP" };
147
sig_atomic_t quit_now = 0;
148
int signal_received = 0;
151
* Make additional room in "buffer" for at least BUFFER_SIZE more
152
* bytes. "buffer_capacity" is how much is currently allocated,
153
* "buffer_length" is how much is already used.
155
size_t incbuffer(char **buffer, size_t buffer_length,
156
size_t buffer_capacity){
157
if(buffer_length + BUFFER_SIZE > buffer_capacity){
158
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
162
buffer_capacity += BUFFER_SIZE;
164
return buffer_capacity;
170
static bool init_gpgme(const char *seckey,
171
const char *pubkey, const char *tempdir){
173
gpgme_engine_info_t engine_info;
177
* Helper function to insert pub and seckey to the engine keyring.
179
bool import_key(const char *filename){
182
gpgme_data_t pgp_data;
184
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
190
rc = gpgme_data_new_from_fd(&pgp_data, fd);
191
if(rc != GPG_ERR_NO_ERROR){
192
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
193
gpgme_strsource(rc), gpgme_strerror(rc));
197
rc = gpgme_op_import(mc.ctx, pgp_data);
198
if(rc != GPG_ERR_NO_ERROR){
199
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
200
gpgme_strsource(rc), gpgme_strerror(rc));
204
ret = (int)TEMP_FAILURE_RETRY(close(fd));
208
gpgme_data_release(pgp_data);
213
fprintf(stderr, "Initializing GPGME\n");
217
gpgme_check_version(NULL);
218
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
219
if(rc != GPG_ERR_NO_ERROR){
220
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
221
gpgme_strsource(rc), gpgme_strerror(rc));
225
/* Set GPGME home directory for the OpenPGP engine only */
226
rc = gpgme_get_engine_info(&engine_info);
227
if(rc != GPG_ERR_NO_ERROR){
228
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
229
gpgme_strsource(rc), gpgme_strerror(rc));
232
while(engine_info != NULL){
233
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
234
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
235
engine_info->file_name, tempdir);
238
engine_info = engine_info->next;
240
if(engine_info == NULL){
241
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
245
/* Create new GPGME "context" */
246
rc = gpgme_new(&(mc.ctx));
247
if(rc != GPG_ERR_NO_ERROR){
248
fprintf(stderr, "bad gpgme_new: %s: %s\n",
249
gpgme_strsource(rc), gpgme_strerror(rc));
253
if(not import_key(pubkey) or not import_key(seckey)){
261
* Decrypt OpenPGP data.
262
* Returns -1 on error
264
static ssize_t pgp_packet_decrypt(const char *cryptotext,
267
gpgme_data_t dh_crypto, dh_plain;
270
size_t plaintext_capacity = 0;
271
ssize_t plaintext_length = 0;
274
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
277
/* Create new GPGME data buffer from memory cryptotext */
278
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
280
if(rc != GPG_ERR_NO_ERROR){
281
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
282
gpgme_strsource(rc), gpgme_strerror(rc));
286
/* Create new empty GPGME data buffer for the plaintext */
287
rc = gpgme_data_new(&dh_plain);
288
if(rc != GPG_ERR_NO_ERROR){
289
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
290
gpgme_strsource(rc), gpgme_strerror(rc));
291
gpgme_data_release(dh_crypto);
295
/* Decrypt data from the cryptotext data buffer to the plaintext
297
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
298
if(rc != GPG_ERR_NO_ERROR){
299
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
300
gpgme_strsource(rc), gpgme_strerror(rc));
301
plaintext_length = -1;
303
gpgme_decrypt_result_t result;
304
result = gpgme_op_decrypt_result(mc.ctx);
306
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
308
fprintf(stderr, "Unsupported algorithm: %s\n",
309
result->unsupported_algorithm);
310
fprintf(stderr, "Wrong key usage: %u\n",
311
result->wrong_key_usage);
312
if(result->file_name != NULL){
313
fprintf(stderr, "File name: %s\n", result->file_name);
315
gpgme_recipient_t recipient;
316
recipient = result->recipients;
317
while(recipient != NULL){
318
fprintf(stderr, "Public key algorithm: %s\n",
319
gpgme_pubkey_algo_name(recipient->pubkey_algo));
320
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
321
fprintf(stderr, "Secret key available: %s\n",
322
recipient->status == GPG_ERR_NO_SECKEY
324
recipient = recipient->next;
332
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
335
/* Seek back to the beginning of the GPGME plaintext data buffer */
336
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
337
perror("gpgme_data_seek");
338
plaintext_length = -1;
344
plaintext_capacity = incbuffer(plaintext,
345
(size_t)plaintext_length,
347
if(plaintext_capacity == 0){
349
plaintext_length = -1;
353
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
355
/* Print the data, if any */
361
perror("gpgme_data_read");
362
plaintext_length = -1;
365
plaintext_length += ret;
369
fprintf(stderr, "Decrypted password is: ");
370
for(ssize_t i = 0; i < plaintext_length; i++){
371
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
373
fprintf(stderr, "\n");
378
/* Delete the GPGME cryptotext data buffer */
379
gpgme_data_release(dh_crypto);
381
/* Delete the GPGME plaintext data buffer */
382
gpgme_data_release(dh_plain);
383
return plaintext_length;
386
static const char * safer_gnutls_strerror(int value){
387
const char *ret = gnutls_strerror(value); /* Spurious warning from
388
-Wunreachable-code */
394
/* GnuTLS log function callback */
395
static void debuggnutls(__attribute__((unused)) int level,
397
fprintf(stderr, "GnuTLS: %s", string);
400
static int init_gnutls_global(const char *pubkeyfilename,
401
const char *seckeyfilename){
405
fprintf(stderr, "Initializing GnuTLS\n");
408
ret = gnutls_global_init();
409
if(ret != GNUTLS_E_SUCCESS){
410
fprintf(stderr, "GnuTLS global_init: %s\n",
411
safer_gnutls_strerror(ret));
416
/* "Use a log level over 10 to enable all debugging options."
419
gnutls_global_set_log_level(11);
420
gnutls_global_set_log_function(debuggnutls);
423
/* OpenPGP credentials */
424
gnutls_certificate_allocate_credentials(&mc.cred);
425
if(ret != GNUTLS_E_SUCCESS){
426
fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning
430
safer_gnutls_strerror(ret));
431
gnutls_global_deinit();
436
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
437
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
441
ret = gnutls_certificate_set_openpgp_key_file
442
(mc.cred, pubkeyfilename, seckeyfilename,
443
GNUTLS_OPENPGP_FMT_BASE64);
444
if(ret != GNUTLS_E_SUCCESS){
446
"Error[%d] while reading the OpenPGP key pair ('%s',"
447
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
448
fprintf(stderr, "The GnuTLS error is: %s\n",
449
safer_gnutls_strerror(ret));
453
/* GnuTLS server initialization */
454
ret = gnutls_dh_params_init(&mc.dh_params);
455
if(ret != GNUTLS_E_SUCCESS){
456
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
457
" %s\n", safer_gnutls_strerror(ret));
460
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
461
if(ret != GNUTLS_E_SUCCESS){
462
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
463
safer_gnutls_strerror(ret));
467
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
473
gnutls_certificate_free_credentials(mc.cred);
474
gnutls_global_deinit();
475
gnutls_dh_params_deinit(mc.dh_params);
479
static int init_gnutls_session(gnutls_session_t *session){
481
/* GnuTLS session creation */
483
ret = gnutls_init(session, GNUTLS_SERVER);
487
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
488
if(ret != GNUTLS_E_SUCCESS){
489
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
490
safer_gnutls_strerror(ret));
496
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
498
gnutls_deinit(*session);
501
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
502
if(ret != GNUTLS_E_SUCCESS){
503
fprintf(stderr, "Syntax error at: %s\n", err);
504
fprintf(stderr, "GnuTLS error: %s\n",
505
safer_gnutls_strerror(ret));
506
gnutls_deinit(*session);
512
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
515
gnutls_deinit(*session);
518
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
519
if(ret != GNUTLS_E_SUCCESS){
520
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
521
safer_gnutls_strerror(ret));
522
gnutls_deinit(*session);
526
/* ignore client certificate if any. */
527
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
529
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
534
/* Avahi log function callback */
535
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
536
__attribute__((unused)) const char *txt){}
538
/* Called when a Mandos server is found */
539
static int start_mandos_communication(const char *ip, uint16_t port,
540
AvahiIfIndex if_index,
542
int ret, tcp_sd = -1;
545
struct sockaddr_in in;
546
struct sockaddr_in6 in6;
549
char *decrypted_buffer = NULL;
550
size_t buffer_length = 0;
551
size_t buffer_capacity = 0;
554
gnutls_session_t session;
555
int pf; /* Protocol family */
569
fprintf(stderr, "Bad address family: %d\n", af);
573
ret = init_gnutls_session(&session);
579
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
583
tcp_sd = socket(pf, SOCK_STREAM, 0);
593
memset(&to, 0, sizeof(to));
595
to.in6.sin6_family = (sa_family_t)af;
596
ret = inet_pton(af, ip, &to.in6.sin6_addr);
598
to.in.sin_family = (sa_family_t)af;
599
ret = inet_pton(af, ip, &to.in.sin_addr);
606
fprintf(stderr, "Bad address: %s\n", ip);
610
to.in6.sin6_port = htons(port); /* Spurious warnings from
612
-Wunreachable-code */
614
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
615
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
617
if(if_index == AVAHI_IF_UNSPEC){
618
fprintf(stderr, "An IPv6 link-local address is incomplete"
619
" without a network interface\n");
622
/* Set the network interface number as scope */
623
to.in6.sin6_scope_id = (uint32_t)if_index;
626
to.in.sin_port = htons(port); /* Spurious warnings from
628
-Wunreachable-code */
636
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
637
char interface[IF_NAMESIZE];
638
if(if_indextoname((unsigned int)if_index, interface) == NULL){
639
perror("if_indextoname");
641
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
642
ip, interface, port);
645
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
648
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
649
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
652
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
655
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
661
if(strcmp(addrstr, ip) != 0){
662
fprintf(stderr, "Canonical address form: %s\n", addrstr);
672
ret = connect(tcp_sd, &to.in6, sizeof(to));
674
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
685
const char *out = mandos_protocol_version;
688
size_t out_size = strlen(out);
689
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
690
out_size - written));
695
written += (size_t)ret;
696
if(written < out_size){
699
if(out == mandos_protocol_version){
713
fprintf(stderr, "Establishing TLS session with %s\n", ip);
720
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
727
ret = gnutls_handshake(session);
731
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
733
if(ret != GNUTLS_E_SUCCESS){
735
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
741
/* Read OpenPGP packet that contains the wanted password */
744
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
754
buffer_capacity = incbuffer(&buffer, buffer_length,
756
if(buffer_capacity == 0){
765
sret = gnutls_record_recv(session, buffer+buffer_length,
772
case GNUTLS_E_INTERRUPTED:
775
case GNUTLS_E_REHANDSHAKE:
777
ret = gnutls_handshake(session);
782
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
784
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
790
fprintf(stderr, "Unknown error while reading data from"
791
" encrypted session with Mandos server\n");
792
gnutls_bye(session, GNUTLS_SHUT_RDWR);
796
buffer_length += (size_t) sret;
801
fprintf(stderr, "Closing TLS session\n");
809
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
813
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
815
if(buffer_length > 0){
816
ssize_t decrypted_buffer_size;
817
decrypted_buffer_size = pgp_packet_decrypt(buffer,
820
if(decrypted_buffer_size >= 0){
823
while(written < (size_t) decrypted_buffer_size){
828
ret = (int)fwrite(decrypted_buffer + written, 1,
829
(size_t)decrypted_buffer_size - written,
831
if(ret == 0 and ferror(stdout)){
833
fprintf(stderr, "Error writing encrypted data: %s\n",
838
written += (size_t)ret;
844
/* Shutdown procedure */
847
free(decrypted_buffer);
850
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
855
gnutls_deinit(session);
862
static void resolve_callback(AvahiSServiceResolver *r,
863
AvahiIfIndex interface,
865
AvahiResolverEvent event,
869
const char *host_name,
870
const AvahiAddress *address,
872
AVAHI_GCC_UNUSED AvahiStringList *txt,
873
AVAHI_GCC_UNUSED AvahiLookupResultFlags
875
AVAHI_GCC_UNUSED void* userdata){
878
/* Called whenever a service has been resolved successfully or
887
case AVAHI_RESOLVER_FAILURE:
888
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
889
" of type '%s' in domain '%s': %s\n", name, type, domain,
890
avahi_strerror(avahi_server_errno(mc.server)));
893
case AVAHI_RESOLVER_FOUND:
895
char ip[AVAHI_ADDRESS_STR_MAX];
896
avahi_address_snprint(ip, sizeof(ip), address);
898
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
899
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
900
ip, (intmax_t)interface, port);
902
int ret = start_mandos_communication(ip, port, interface,
903
avahi_proto_to_af(proto));
905
avahi_simple_poll_quit(mc.simple_poll);
909
avahi_s_service_resolver_free(r);
912
static void browse_callback(AvahiSServiceBrowser *b,
913
AvahiIfIndex interface,
914
AvahiProtocol protocol,
915
AvahiBrowserEvent event,
919
AVAHI_GCC_UNUSED AvahiLookupResultFlags
921
AVAHI_GCC_UNUSED void* userdata){
924
/* Called whenever a new services becomes available on the LAN or
925
is removed from the LAN */
933
case AVAHI_BROWSER_FAILURE:
935
fprintf(stderr, "(Avahi browser) %s\n",
936
avahi_strerror(avahi_server_errno(mc.server)));
937
avahi_simple_poll_quit(mc.simple_poll);
940
case AVAHI_BROWSER_NEW:
941
/* We ignore the returned Avahi resolver object. In the callback
942
function we free it. If the Avahi server is terminated before
943
the callback function is called the Avahi server will free the
946
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
947
name, type, domain, protocol, 0,
948
resolve_callback, NULL) == NULL)
949
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
950
name, avahi_strerror(avahi_server_errno(mc.server)));
953
case AVAHI_BROWSER_REMOVE:
956
case AVAHI_BROWSER_ALL_FOR_NOW:
957
case AVAHI_BROWSER_CACHE_EXHAUSTED:
959
fprintf(stderr, "No Mandos server found, still searching...\n");
965
/* stop main loop after sigterm has been called */
966
static void handle_sigterm(int sig){
971
signal_received = sig;
972
int old_errno = errno;
973
if(mc.simple_poll != NULL){
974
avahi_simple_poll_quit(mc.simple_poll);
980
* This function determines if a directory entry in /sys/class/net
981
* corresponds to an acceptable network device.
982
* (This function is passed to scandir(3) as a filter function.)
984
int good_interface(const struct dirent *if_entry){
986
char *flagname = NULL;
987
int ret = asprintf(&flagname, "%s/%s/flags", sys_class_net,
993
if(if_entry->d_name[0] == '.'){
996
int flags_fd = (int)TEMP_FAILURE_RETRY(open(flagname, O_RDONLY));
1001
typedef short ifreq_flags; /* ifreq.ifr_flags in netdevice(7) */
1002
/* read line from flags_fd */
1003
ssize_t to_read = (sizeof(ifreq_flags)*2)+3; /* "0x1003\n" */
1004
char *flagstring = malloc((size_t)to_read);
1005
if(flagstring == NULL){
1011
ssret = (ssize_t)TEMP_FAILURE_RETRY(read(flags_fd, flagstring,
1028
tmpmax = strtoimax(flagstring, &tmp, 0);
1029
if(errno != 0 or tmp == flagstring or (*tmp != '\0'
1030
and not (isspace(*tmp)))
1031
or tmpmax != (ifreq_flags)tmpmax){
1036
ifreq_flags flags = (ifreq_flags)tmpmax;
1037
/* Reject the loopback device */
1038
if(flags & IFF_LOOPBACK){
1041
/* Accept point-to-point devices only if connect_to is specified */
1042
if(connect_to != NULL and (flags & IFF_POINTOPOINT)){
1045
/* Otherwise, reject non-broadcast-capable devices */
1046
if(not (flags & IFF_BROADCAST)){
1049
/* Accept this device */
1053
int main(int argc, char *argv[]){
1054
AvahiSServiceBrowser *sb = NULL;
1059
int exitcode = EXIT_SUCCESS;
1060
const char *interface = "";
1061
struct ifreq network;
1063
bool take_down_interface = false;
1066
char tempdir[] = "/tmp/mandosXXXXXX";
1067
bool tempdir_created = false;
1068
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1069
const char *seckey = PATHDIR "/" SECKEY;
1070
const char *pubkey = PATHDIR "/" PUBKEY;
1072
bool gnutls_initialized = false;
1073
bool gpgme_initialized = false;
1076
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1077
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1082
/* Lower any group privileges we might have, just to be safe */
1089
/* Lower user privileges (temporarily) */
1101
struct argp_option options[] = {
1102
{ .name = "debug", .key = 128,
1103
.doc = "Debug mode", .group = 3 },
1104
{ .name = "connect", .key = 'c',
1105
.arg = "ADDRESS:PORT",
1106
.doc = "Connect directly to a specific Mandos server",
1108
{ .name = "interface", .key = 'i',
1110
.doc = "Network interface that will be used to search for"
1113
{ .name = "seckey", .key = 's',
1115
.doc = "OpenPGP secret key file base name",
1117
{ .name = "pubkey", .key = 'p',
1119
.doc = "OpenPGP public key file base name",
1121
{ .name = "dh-bits", .key = 129,
1123
.doc = "Bit length of the prime number used in the"
1124
" Diffie-Hellman key exchange",
1126
{ .name = "priority", .key = 130,
1128
.doc = "GnuTLS priority string for the TLS handshake",
1130
{ .name = "delay", .key = 131,
1132
.doc = "Maximum delay to wait for interface startup",
1137
error_t parse_opt(int key, char *arg,
1138
struct argp_state *state){
1140
case 128: /* --debug */
1143
case 'c': /* --connect */
1146
case 'i': /* --interface */
1149
case 's': /* --seckey */
1152
case 'p': /* --pubkey */
1155
case 129: /* --dh-bits */
1157
tmpmax = strtoimax(arg, &tmp, 10);
1158
if(errno != 0 or tmp == arg or *tmp != '\0'
1159
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1160
fprintf(stderr, "Bad number of DH bits\n");
1163
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1165
case 130: /* --priority */
1168
case 131: /* --delay */
1170
delay = strtof(arg, &tmp);
1171
if(errno != 0 or tmp == arg or *tmp != '\0'){
1172
fprintf(stderr, "Bad delay\n");
1181
return ARGP_ERR_UNKNOWN;
1186
struct argp argp = { .options = options, .parser = parse_opt,
1188
.doc = "Mandos client -- Get and decrypt"
1189
" passwords from a Mandos server" };
1190
ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
1191
if(ret == ARGP_ERR_UNKNOWN){
1192
fprintf(stderr, "Unknown error while parsing arguments\n");
1193
exitcode = EXIT_FAILURE;
1199
avahi_set_log_function(empty_log);
1202
if(interface[0] == '\0'){
1203
struct dirent **direntries;
1204
ret = scandir(sys_class_net, &direntries, good_interface,
1207
/* Pick the first good interface */
1208
interface = strdup(direntries[0]->d_name);
1209
if(interface == NULL){
1212
exitcode = EXIT_FAILURE;
1218
fprintf(stderr, "Could not find a network interface\n");
1219
exitcode = EXIT_FAILURE;
1224
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1225
from the signal handler */
1226
/* Initialize the pseudo-RNG for Avahi */
1227
srand((unsigned int) time(NULL));
1228
mc.simple_poll = avahi_simple_poll_new();
1229
if(mc.simple_poll == NULL){
1230
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1231
exitcode = EXIT_FAILURE;
1235
sigemptyset(&sigterm_action.sa_mask);
1236
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1238
perror("sigaddset");
1239
exitcode = EXIT_FAILURE;
1242
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1244
perror("sigaddset");
1245
exitcode = EXIT_FAILURE;
1248
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1250
perror("sigaddset");
1251
exitcode = EXIT_FAILURE;
1254
/* Need to check if the handler is SIG_IGN before handling:
1255
| [[info:libc:Initial Signal Actions]] |
1256
| [[info:libc:Basic Signal Handling]] |
1258
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1260
perror("sigaction");
1261
return EXIT_FAILURE;
1263
if(old_sigterm_action.sa_handler != SIG_IGN){
1264
ret = sigaction(SIGINT, &sigterm_action, NULL);
1266
perror("sigaction");
1267
exitcode = EXIT_FAILURE;
1271
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1273
perror("sigaction");
1274
return EXIT_FAILURE;
1276
if(old_sigterm_action.sa_handler != SIG_IGN){
1277
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1279
perror("sigaction");
1280
exitcode = EXIT_FAILURE;
1284
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1286
perror("sigaction");
1287
return EXIT_FAILURE;
1289
if(old_sigterm_action.sa_handler != SIG_IGN){
1290
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1292
perror("sigaction");
1293
exitcode = EXIT_FAILURE;
1298
/* If the interface is down, bring it up */
1299
if(strcmp(interface, "none") != 0){
1300
if_index = (AvahiIfIndex) if_nametoindex(interface);
1302
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1303
exitcode = EXIT_FAILURE;
1311
/* Re-raise priviliges */
1319
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1320
messages to mess up the prompt */
1321
ret = klogctl(8, NULL, 5);
1322
bool restore_loglevel = true;
1324
restore_loglevel = false;
1327
#endif /* __linux__ */
1329
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1332
exitcode = EXIT_FAILURE;
1334
if(restore_loglevel){
1335
ret = klogctl(7, NULL, 0);
1340
#endif /* __linux__ */
1341
/* Lower privileges */
1349
strcpy(network.ifr_name, interface);
1350
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1352
perror("ioctl SIOCGIFFLAGS");
1354
if(restore_loglevel){
1355
ret = klogctl(7, NULL, 0);
1360
#endif /* __linux__ */
1361
exitcode = EXIT_FAILURE;
1362
/* Lower privileges */
1370
if((network.ifr_flags & IFF_UP) == 0){
1371
network.ifr_flags |= IFF_UP;
1372
take_down_interface = true;
1373
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1375
take_down_interface = false;
1376
perror("ioctl SIOCSIFFLAGS");
1377
exitcode = EXIT_FAILURE;
1379
if(restore_loglevel){
1380
ret = klogctl(7, NULL, 0);
1385
#endif /* __linux__ */
1386
/* Lower privileges */
1395
/* sleep checking until interface is running */
1396
for(int i=0; i < delay * 4; i++){
1397
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1399
perror("ioctl SIOCGIFFLAGS");
1400
} else if(network.ifr_flags & IFF_RUNNING){
1403
struct timespec sleeptime = { .tv_nsec = 250000000 };
1404
ret = nanosleep(&sleeptime, NULL);
1405
if(ret == -1 and errno != EINTR){
1406
perror("nanosleep");
1409
if(not take_down_interface){
1410
/* We won't need the socket anymore */
1411
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1417
if(restore_loglevel){
1418
/* Restores kernel loglevel to default */
1419
ret = klogctl(7, NULL, 0);
1424
#endif /* __linux__ */
1425
/* Lower privileges */
1427
if(take_down_interface){
1428
/* Lower privileges */
1434
/* Lower privileges permanently */
1446
ret = init_gnutls_global(pubkey, seckey);
1448
fprintf(stderr, "init_gnutls_global failed\n");
1449
exitcode = EXIT_FAILURE;
1452
gnutls_initialized = true;
1459
tempdir_created = true;
1460
if(mkdtemp(tempdir) == NULL){
1461
tempdir_created = false;
1470
if(not init_gpgme(pubkey, seckey, tempdir)){
1471
fprintf(stderr, "init_gpgme failed\n");
1472
exitcode = EXIT_FAILURE;
1475
gpgme_initialized = true;
1482
if(connect_to != NULL){
1483
/* Connect directly, do not use Zeroconf */
1484
/* (Mainly meant for debugging) */
1485
char *address = strrchr(connect_to, ':');
1486
if(address == NULL){
1487
fprintf(stderr, "No colon in address\n");
1488
exitcode = EXIT_FAILURE;
1498
tmpmax = strtoimax(address+1, &tmp, 10);
1499
if(errno != 0 or tmp == address+1 or *tmp != '\0'
1500
or tmpmax != (uint16_t)tmpmax){
1501
fprintf(stderr, "Bad port number\n");
1502
exitcode = EXIT_FAILURE;
1510
port = (uint16_t)tmpmax;
1512
address = connect_to;
1513
/* Colon in address indicates IPv6 */
1515
if(strchr(address, ':') != NULL){
1525
ret = start_mandos_communication(address, port, if_index, af);
1527
exitcode = EXIT_FAILURE;
1529
exitcode = EXIT_SUCCESS;
1539
AvahiServerConfig config;
1540
/* Do not publish any local Zeroconf records */
1541
avahi_server_config_init(&config);
1542
config.publish_hinfo = 0;
1543
config.publish_addresses = 0;
1544
config.publish_workstation = 0;
1545
config.publish_domain = 0;
1547
/* Allocate a new server */
1548
mc.server = avahi_server_new(avahi_simple_poll_get
1549
(mc.simple_poll), &config, NULL,
1552
/* Free the Avahi configuration data */
1553
avahi_server_config_free(&config);
1556
/* Check if creating the Avahi server object succeeded */
1557
if(mc.server == NULL){
1558
fprintf(stderr, "Failed to create Avahi server: %s\n",
1559
avahi_strerror(error));
1560
exitcode = EXIT_FAILURE;
1568
/* Create the Avahi service browser */
1569
sb = avahi_s_service_browser_new(mc.server, if_index,
1570
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
1571
NULL, 0, browse_callback, NULL);
1573
fprintf(stderr, "Failed to create service browser: %s\n",
1574
avahi_strerror(avahi_server_errno(mc.server)));
1575
exitcode = EXIT_FAILURE;
1583
/* Run the main loop */
1586
fprintf(stderr, "Starting Avahi loop search\n");
1589
avahi_simple_poll_loop(mc.simple_poll);
1594
fprintf(stderr, "%s exiting\n", argv[0]);
1597
/* Cleanup things */
1599
avahi_s_service_browser_free(sb);
1601
if(mc.server != NULL)
1602
avahi_server_free(mc.server);
1604
if(mc.simple_poll != NULL)
1605
avahi_simple_poll_free(mc.simple_poll);
1607
if(gnutls_initialized){
1608
gnutls_certificate_free_credentials(mc.cred);
1609
gnutls_global_deinit();
1610
gnutls_dh_params_deinit(mc.dh_params);
1613
if(gpgme_initialized){
1614
gpgme_release(mc.ctx);
1617
/* Take down the network interface */
1618
if(take_down_interface){
1619
/* Re-raise priviliges */
1626
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1628
perror("ioctl SIOCGIFFLAGS");
1629
} else if(network.ifr_flags & IFF_UP) {
1630
network.ifr_flags &= ~IFF_UP; /* clear flag */
1631
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1633
perror("ioctl SIOCSIFFLAGS");
1636
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1640
/* Lower privileges permanently */
1649
/* Removes the temp directory used by GPGME */
1650
if(tempdir_created){
1652
struct dirent *direntry;
1653
d = opendir(tempdir);
1655
if(errno != ENOENT){
1660
direntry = readdir(d);
1661
if(direntry == NULL){
1664
/* Skip "." and ".." */
1665
if(direntry->d_name[0] == '.'
1666
and (direntry->d_name[1] == '\0'
1667
or (direntry->d_name[1] == '.'
1668
and direntry->d_name[2] == '\0'))){
1671
char *fullname = NULL;
1672
ret = asprintf(&fullname, "%s/%s", tempdir,
1678
ret = remove(fullname);
1680
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1687
ret = rmdir(tempdir);
1688
if(ret == -1 and errno != ENOENT){
1694
sigemptyset(&old_sigterm_action.sa_mask);
1695
old_sigterm_action.sa_handler = SIG_DFL;
1696
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
1697
&old_sigterm_action,
1700
perror("sigaction");
1703
ret = raise(signal_received);
1704
} while(ret != 0 and errno == EINTR);
1709
TEMP_FAILURE_RETRY(pause());