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@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, 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() */
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 */
90
#include <sys/klog.h> /* klogctl() */
91
#endif /* __linux__ */
94
/* All Avahi types, constants and functions
97
#include <avahi-core/core.h>
98
#include <avahi-core/lookup.h>
99
#include <avahi-core/log.h>
100
#include <avahi-common/simple-watch.h>
101
#include <avahi-common/malloc.h>
102
#include <avahi-common/error.h>
105
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
108
init_gnutls_session(),
110
#include <gnutls/openpgp.h>
111
/* gnutls_certificate_set_openpgp_key_file(),
112
GNUTLS_OPENPGP_FMT_BASE64 */
115
#include <gpgme.h> /* All GPGME types, constants and
118
GPGME_PROTOCOL_OpenPGP,
121
#define BUFFER_SIZE 256
123
#define PATHDIR "/conf/conf.d/mandos"
124
#define SECKEY "seckey.txt"
125
#define PUBKEY "pubkey.txt"
128
static const char mandos_protocol_version[] = "1";
129
const char *argp_program_version = "mandos-client " VERSION;
130
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
131
static const char sys_class_net[] = "/sys/class/net";
132
char *connect_to = NULL;
134
/* Doubly linked list that need to be circularly linked when used */
135
typedef struct server{
138
AvahiIfIndex if_index;
140
struct timespec last_seen;
145
/* Used for passing in values through the Avahi callback functions */
147
AvahiSimplePoll *simple_poll;
149
gnutls_certificate_credentials_t cred;
150
unsigned int dh_bits;
151
gnutls_dh_params_t dh_params;
152
const char *priority;
154
server *current_server;
157
/* global context so signal handler can reach it*/
158
mandos_context mc = { .simple_poll = NULL, .server = NULL,
159
.dh_bits = 1024, .priority = "SECURE256"
160
":!CTYPE-X.509:+CTYPE-OPENPGP",
161
.current_server = NULL };
163
sig_atomic_t quit_now = 0;
164
int signal_received = 0;
166
/* Function to use when printing errors */
167
void perror_plus(const char *print_text){
168
fprintf(stderr, "Mandos plugin %s: ",
169
program_invocation_short_name);
174
* Make additional room in "buffer" for at least BUFFER_SIZE more
175
* bytes. "buffer_capacity" is how much is currently allocated,
176
* "buffer_length" is how much is already used.
178
size_t incbuffer(char **buffer, size_t buffer_length,
179
size_t buffer_capacity){
180
if(buffer_length + BUFFER_SIZE > buffer_capacity){
181
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
185
buffer_capacity += BUFFER_SIZE;
187
return buffer_capacity;
190
int add_server(const char *ip, uint16_t port,
191
AvahiIfIndex if_index,
194
server *new_server = malloc(sizeof(server));
195
if(new_server == NULL){
196
perror_plus("malloc");
199
*new_server = (server){ .ip = strdup(ip),
201
.if_index = if_index,
203
if(new_server->ip == NULL){
204
perror_plus("strdup");
207
/* unique case of first server */
208
if (mc.current_server == NULL){
209
new_server->next = new_server;
210
new_server->prev = new_server;
211
mc.current_server = new_server;
212
/* Placing the new server last in the list */
214
new_server->next = mc.current_server;
215
new_server->prev = mc.current_server->prev;
216
new_server->prev->next = new_server;
217
mc.current_server->prev = new_server;
219
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
221
perror_plus("clock_gettime");
230
static bool init_gpgme(const char *seckey,
231
const char *pubkey, const char *tempdir){
233
gpgme_engine_info_t engine_info;
237
* Helper function to insert pub and seckey to the engine keyring.
239
bool import_key(const char *filename){
242
gpgme_data_t pgp_data;
244
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
250
rc = gpgme_data_new_from_fd(&pgp_data, fd);
251
if(rc != GPG_ERR_NO_ERROR){
252
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
253
gpgme_strsource(rc), gpgme_strerror(rc));
257
rc = gpgme_op_import(mc.ctx, pgp_data);
258
if(rc != GPG_ERR_NO_ERROR){
259
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
260
gpgme_strsource(rc), gpgme_strerror(rc));
264
ret = (int)TEMP_FAILURE_RETRY(close(fd));
266
perror_plus("close");
268
gpgme_data_release(pgp_data);
273
fprintf(stderr, "Initializing GPGME\n");
277
gpgme_check_version(NULL);
278
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
279
if(rc != GPG_ERR_NO_ERROR){
280
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
281
gpgme_strsource(rc), gpgme_strerror(rc));
285
/* Set GPGME home directory for the OpenPGP engine only */
286
rc = gpgme_get_engine_info(&engine_info);
287
if(rc != GPG_ERR_NO_ERROR){
288
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
289
gpgme_strsource(rc), gpgme_strerror(rc));
292
while(engine_info != NULL){
293
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
294
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
295
engine_info->file_name, tempdir);
298
engine_info = engine_info->next;
300
if(engine_info == NULL){
301
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
305
/* Create new GPGME "context" */
306
rc = gpgme_new(&(mc.ctx));
307
if(rc != GPG_ERR_NO_ERROR){
308
fprintf(stderr, "bad gpgme_new: %s: %s\n",
309
gpgme_strsource(rc), gpgme_strerror(rc));
313
if(not import_key(pubkey) or not import_key(seckey)){
321
* Decrypt OpenPGP data.
322
* Returns -1 on error
324
static ssize_t pgp_packet_decrypt(const char *cryptotext,
327
gpgme_data_t dh_crypto, dh_plain;
330
size_t plaintext_capacity = 0;
331
ssize_t plaintext_length = 0;
334
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
337
/* Create new GPGME data buffer from memory cryptotext */
338
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
340
if(rc != GPG_ERR_NO_ERROR){
341
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
342
gpgme_strsource(rc), gpgme_strerror(rc));
346
/* Create new empty GPGME data buffer for the plaintext */
347
rc = gpgme_data_new(&dh_plain);
348
if(rc != GPG_ERR_NO_ERROR){
349
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
350
gpgme_strsource(rc), gpgme_strerror(rc));
351
gpgme_data_release(dh_crypto);
355
/* Decrypt data from the cryptotext data buffer to the plaintext
357
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
358
if(rc != GPG_ERR_NO_ERROR){
359
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
360
gpgme_strsource(rc), gpgme_strerror(rc));
361
plaintext_length = -1;
363
gpgme_decrypt_result_t result;
364
result = gpgme_op_decrypt_result(mc.ctx);
366
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
368
fprintf(stderr, "Unsupported algorithm: %s\n",
369
result->unsupported_algorithm);
370
fprintf(stderr, "Wrong key usage: %u\n",
371
result->wrong_key_usage);
372
if(result->file_name != NULL){
373
fprintf(stderr, "File name: %s\n", result->file_name);
375
gpgme_recipient_t recipient;
376
recipient = result->recipients;
377
while(recipient != NULL){
378
fprintf(stderr, "Public key algorithm: %s\n",
379
gpgme_pubkey_algo_name(recipient->pubkey_algo));
380
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
381
fprintf(stderr, "Secret key available: %s\n",
382
recipient->status == GPG_ERR_NO_SECKEY
384
recipient = recipient->next;
392
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
395
/* Seek back to the beginning of the GPGME plaintext data buffer */
396
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
397
perror_plus("gpgme_data_seek");
398
plaintext_length = -1;
404
plaintext_capacity = incbuffer(plaintext,
405
(size_t)plaintext_length,
407
if(plaintext_capacity == 0){
408
perror_plus("incbuffer");
409
plaintext_length = -1;
413
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
415
/* Print the data, if any */
421
perror_plus("gpgme_data_read");
422
plaintext_length = -1;
425
plaintext_length += ret;
429
fprintf(stderr, "Decrypted password is: ");
430
for(ssize_t i = 0; i < plaintext_length; i++){
431
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
433
fprintf(stderr, "\n");
438
/* Delete the GPGME cryptotext data buffer */
439
gpgme_data_release(dh_crypto);
441
/* Delete the GPGME plaintext data buffer */
442
gpgme_data_release(dh_plain);
443
return plaintext_length;
446
static const char * safer_gnutls_strerror(int value){
447
const char *ret = gnutls_strerror(value); /* Spurious warning from
448
-Wunreachable-code */
454
/* GnuTLS log function callback */
455
static void debuggnutls(__attribute__((unused)) int level,
457
fprintf(stderr, "GnuTLS: %s", string);
460
static int init_gnutls_global(const char *pubkeyfilename,
461
const char *seckeyfilename){
465
fprintf(stderr, "Initializing GnuTLS\n");
468
ret = gnutls_global_init();
469
if(ret != GNUTLS_E_SUCCESS){
470
fprintf(stderr, "GnuTLS global_init: %s\n",
471
safer_gnutls_strerror(ret));
476
/* "Use a log level over 10 to enable all debugging options."
479
gnutls_global_set_log_level(11);
480
gnutls_global_set_log_function(debuggnutls);
483
/* OpenPGP credentials */
484
ret = gnutls_certificate_allocate_credentials(&mc.cred);
485
if(ret != GNUTLS_E_SUCCESS){
486
fprintf(stderr, "GnuTLS memory error: %s\n",
487
safer_gnutls_strerror(ret));
488
gnutls_global_deinit();
493
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
494
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
498
ret = gnutls_certificate_set_openpgp_key_file
499
(mc.cred, pubkeyfilename, seckeyfilename,
500
GNUTLS_OPENPGP_FMT_BASE64);
501
if(ret != GNUTLS_E_SUCCESS){
503
"Error[%d] while reading the OpenPGP key pair ('%s',"
504
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
505
fprintf(stderr, "The GnuTLS error is: %s\n",
506
safer_gnutls_strerror(ret));
510
/* GnuTLS server initialization */
511
ret = gnutls_dh_params_init(&mc.dh_params);
512
if(ret != GNUTLS_E_SUCCESS){
513
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
514
" %s\n", safer_gnutls_strerror(ret));
517
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
518
if(ret != GNUTLS_E_SUCCESS){
519
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
520
safer_gnutls_strerror(ret));
524
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
530
gnutls_certificate_free_credentials(mc.cred);
531
gnutls_global_deinit();
532
gnutls_dh_params_deinit(mc.dh_params);
536
static int init_gnutls_session(gnutls_session_t *session){
538
/* GnuTLS session creation */
540
ret = gnutls_init(session, GNUTLS_SERVER);
544
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
545
if(ret != GNUTLS_E_SUCCESS){
546
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
547
safer_gnutls_strerror(ret));
553
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
555
gnutls_deinit(*session);
558
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
559
if(ret != GNUTLS_E_SUCCESS){
560
fprintf(stderr, "Syntax error at: %s\n", err);
561
fprintf(stderr, "GnuTLS error: %s\n",
562
safer_gnutls_strerror(ret));
563
gnutls_deinit(*session);
569
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
572
gnutls_deinit(*session);
575
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
576
if(ret != GNUTLS_E_SUCCESS){
577
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
578
safer_gnutls_strerror(ret));
579
gnutls_deinit(*session);
583
/* ignore client certificate if any. */
584
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
586
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
591
/* Avahi log function callback */
592
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
593
__attribute__((unused)) const char *txt){}
595
/* Called when a Mandos server is found */
596
static int start_mandos_communication(const char *ip, uint16_t port,
597
AvahiIfIndex if_index,
599
int ret, tcp_sd = -1;
602
struct sockaddr_in in;
603
struct sockaddr_in6 in6;
606
char *decrypted_buffer = NULL;
607
size_t buffer_length = 0;
608
size_t buffer_capacity = 0;
611
gnutls_session_t session;
612
int pf; /* Protocol family */
629
fprintf(stderr, "Bad address family: %d\n", af);
634
ret = init_gnutls_session(&session);
640
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
644
tcp_sd = socket(pf, SOCK_STREAM, 0);
647
perror_plus("socket");
657
memset(&to, 0, sizeof(to));
659
to.in6.sin6_family = (sa_family_t)af;
660
ret = inet_pton(af, ip, &to.in6.sin6_addr);
662
to.in.sin_family = (sa_family_t)af;
663
ret = inet_pton(af, ip, &to.in.sin_addr);
667
perror_plus("inet_pton");
673
fprintf(stderr, "Bad address: %s\n", ip);
678
to.in6.sin6_port = htons(port); /* Spurious warnings from
680
-Wunreachable-code */
682
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
683
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
685
if(if_index == AVAHI_IF_UNSPEC){
686
fprintf(stderr, "An IPv6 link-local address is incomplete"
687
" without a network interface\n");
691
/* Set the network interface number as scope */
692
to.in6.sin6_scope_id = (uint32_t)if_index;
695
to.in.sin_port = htons(port); /* Spurious warnings from
697
-Wunreachable-code */
706
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
707
char interface[IF_NAMESIZE];
708
if(if_indextoname((unsigned int)if_index, interface) == NULL){
709
perror_plus("if_indextoname");
711
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
712
ip, interface, port);
715
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
718
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
719
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
722
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
725
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
729
perror_plus("inet_ntop");
731
if(strcmp(addrstr, ip) != 0){
732
fprintf(stderr, "Canonical address form: %s\n", addrstr);
743
ret = connect(tcp_sd, &to.in6, sizeof(to));
745
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
748
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
750
perror_plus("connect");
761
const char *out = mandos_protocol_version;
764
size_t out_size = strlen(out);
765
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
766
out_size - written));
769
perror_plus("write");
773
written += (size_t)ret;
774
if(written < out_size){
777
if(out == mandos_protocol_version){
792
fprintf(stderr, "Establishing TLS session with %s\n", ip);
800
/* Spurious warning from -Wint-to-pointer-cast */
801
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
809
ret = gnutls_handshake(session);
814
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
816
if(ret != GNUTLS_E_SUCCESS){
818
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
825
/* Read OpenPGP packet that contains the wanted password */
828
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
839
buffer_capacity = incbuffer(&buffer, buffer_length,
841
if(buffer_capacity == 0){
843
perror_plus("incbuffer");
853
sret = gnutls_record_recv(session, buffer+buffer_length,
860
case GNUTLS_E_INTERRUPTED:
863
case GNUTLS_E_REHANDSHAKE:
865
ret = gnutls_handshake(session);
871
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
873
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
880
fprintf(stderr, "Unknown error while reading data from"
881
" encrypted session with Mandos server\n");
882
gnutls_bye(session, GNUTLS_SHUT_RDWR);
887
buffer_length += (size_t) sret;
892
fprintf(stderr, "Closing TLS session\n");
901
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
906
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
908
if(buffer_length > 0){
909
ssize_t decrypted_buffer_size;
910
decrypted_buffer_size = pgp_packet_decrypt(buffer,
913
if(decrypted_buffer_size >= 0){
916
while(written < (size_t) decrypted_buffer_size){
922
ret = (int)fwrite(decrypted_buffer + written, 1,
923
(size_t)decrypted_buffer_size - written,
925
if(ret == 0 and ferror(stdout)){
928
fprintf(stderr, "Error writing encrypted data: %s\n",
934
written += (size_t)ret;
940
/* Shutdown procedure */
945
free(decrypted_buffer);
948
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
954
perror_plus("close");
956
gnutls_deinit(session);
966
static void resolve_callback(AvahiSServiceResolver *r,
967
AvahiIfIndex interface,
969
AvahiResolverEvent event,
973
const char *host_name,
974
const AvahiAddress *address,
976
AVAHI_GCC_UNUSED AvahiStringList *txt,
977
AVAHI_GCC_UNUSED AvahiLookupResultFlags
979
AVAHI_GCC_UNUSED void* userdata){
982
/* Called whenever a service has been resolved successfully or
991
case AVAHI_RESOLVER_FAILURE:
992
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
993
" of type '%s' in domain '%s': %s\n", name, type, domain,
994
avahi_strerror(avahi_server_errno(mc.server)));
997
case AVAHI_RESOLVER_FOUND:
999
char ip[AVAHI_ADDRESS_STR_MAX];
1000
avahi_address_snprint(ip, sizeof(ip), address);
1002
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
1003
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1004
ip, (intmax_t)interface, port);
1006
int ret = start_mandos_communication(ip, port, interface,
1007
avahi_proto_to_af(proto));
1009
avahi_simple_poll_quit(mc.simple_poll);
1011
ret = add_server(ip, port, interface,
1012
avahi_proto_to_af(proto));
1016
avahi_s_service_resolver_free(r);
1019
static void browse_callback(AvahiSServiceBrowser *b,
1020
AvahiIfIndex interface,
1021
AvahiProtocol protocol,
1022
AvahiBrowserEvent event,
1026
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1028
AVAHI_GCC_UNUSED void* userdata){
1031
/* Called whenever a new services becomes available on the LAN or
1032
is removed from the LAN */
1040
case AVAHI_BROWSER_FAILURE:
1042
fprintf(stderr, "(Avahi browser) %s\n",
1043
avahi_strerror(avahi_server_errno(mc.server)));
1044
avahi_simple_poll_quit(mc.simple_poll);
1047
case AVAHI_BROWSER_NEW:
1048
/* We ignore the returned Avahi resolver object. In the callback
1049
function we free it. If the Avahi server is terminated before
1050
the callback function is called the Avahi server will free the
1053
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1054
name, type, domain, protocol, 0,
1055
resolve_callback, NULL) == NULL)
1056
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
1057
name, avahi_strerror(avahi_server_errno(mc.server)));
1060
case AVAHI_BROWSER_REMOVE:
1063
case AVAHI_BROWSER_ALL_FOR_NOW:
1064
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1066
fprintf(stderr, "No Mandos server found, still searching...\n");
1072
/* Signal handler that stops main loop after SIGTERM */
1073
static void handle_sigterm(int sig){
1078
signal_received = sig;
1079
int old_errno = errno;
1080
/* set main loop to exit */
1081
if(mc.simple_poll != NULL){
1082
avahi_simple_poll_quit(mc.simple_poll);
1088
* This function determines if a directory entry in /sys/class/net
1089
* corresponds to an acceptable network device.
1090
* (This function is passed to scandir(3) as a filter function.)
1092
int good_interface(const struct dirent *if_entry){
1094
char *flagname = NULL;
1095
if(if_entry->d_name[0] == '.'){
1098
int ret = asprintf(&flagname, "%s/%s/flags", sys_class_net,
1101
perror_plus("asprintf");
1104
int flags_fd = (int)TEMP_FAILURE_RETRY(open(flagname, O_RDONLY));
1106
perror_plus("open");
1111
typedef short ifreq_flags; /* ifreq.ifr_flags in netdevice(7) */
1112
/* read line from flags_fd */
1113
ssize_t to_read = 2+(sizeof(ifreq_flags)*2)+1; /* "0x1003\n" */
1114
char *flagstring = malloc((size_t)to_read+1); /* +1 for final \0 */
1115
flagstring[(size_t)to_read] = '\0';
1116
if(flagstring == NULL){
1117
perror_plus("malloc");
1122
ssret = (ssize_t)TEMP_FAILURE_RETRY(read(flags_fd, flagstring,
1125
perror_plus("read");
1139
tmpmax = strtoimax(flagstring, &tmp, 0);
1140
if(errno != 0 or tmp == flagstring or (*tmp != '\0'
1141
and not (isspace(*tmp)))
1142
or tmpmax != (ifreq_flags)tmpmax){
1144
fprintf(stderr, "Invalid flags \"%s\" for interface \"%s\"\n",
1145
flagstring, if_entry->d_name);
1151
ifreq_flags flags = (ifreq_flags)tmpmax;
1152
/* Reject the loopback device */
1153
if(flags & IFF_LOOPBACK){
1155
fprintf(stderr, "Rejecting loopback interface \"%s\"\n",
1160
/* Accept point-to-point devices only if connect_to is specified */
1161
if(connect_to != NULL and (flags & IFF_POINTOPOINT)){
1163
fprintf(stderr, "Accepting point-to-point interface \"%s\"\n",
1168
/* Otherwise, reject non-broadcast-capable devices */
1169
if(not (flags & IFF_BROADCAST)){
1171
fprintf(stderr, "Rejecting non-broadcast interface \"%s\"\n",
1176
/* Reject non-ARP interfaces (including dummy interfaces) */
1177
if(flags & IFF_NOARP){
1179
fprintf(stderr, "Rejecting non-ARP interface \"%s\"\n",
1184
/* Accept this device */
1186
fprintf(stderr, "Interface \"%s\" is acceptable\n",
1192
int notdotentries(const struct dirent *direntry){
1193
/* Skip "." and ".." */
1194
if(direntry->d_name[0] == '.'
1195
and (direntry->d_name[1] == '\0'
1196
or (direntry->d_name[1] == '.'
1197
and direntry->d_name[2] == '\0'))){
1203
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1205
struct timespec now;
1206
struct timespec waited_time;
1207
intmax_t block_time;
1210
if(mc.current_server == NULL){
1213
"Wait until first server is found. No timeout!\n");
1215
ret = avahi_simple_poll_iterate(s, -1);
1218
fprintf(stderr, "Check current_server if we should run it,"
1221
/* the current time */
1222
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1224
perror_plus("clock_gettime");
1227
/* Calculating in ms how long time between now and server
1228
who we visted longest time ago. Now - last seen. */
1229
waited_time.tv_sec = (now.tv_sec
1230
- mc.current_server->last_seen.tv_sec);
1231
waited_time.tv_nsec = (now.tv_nsec
1232
- mc.current_server->last_seen.tv_nsec);
1233
/* total time is 10s/10,000ms.
1234
Converting to s from ms by dividing by 1,000,
1235
and ns to ms by dividing by 1,000,000. */
1236
block_time = ((retry_interval
1237
- ((intmax_t)waited_time.tv_sec * 1000))
1238
- ((intmax_t)waited_time.tv_nsec / 1000000));
1241
fprintf(stderr, "Blocking for %ld ms\n", block_time);
1244
if(block_time <= 0){
1245
ret = start_mandos_communication(mc.current_server->ip,
1246
mc.current_server->port,
1247
mc.current_server->if_index,
1248
mc.current_server->af);
1250
avahi_simple_poll_quit(mc.simple_poll);
1253
ret = clock_gettime(CLOCK_MONOTONIC,
1254
&mc.current_server->last_seen);
1256
perror_plus("clock_gettime");
1259
mc.current_server = mc.current_server->next;
1260
block_time = 0; /* Call avahi to find new Mandos
1261
servers, but don't block */
1264
ret = avahi_simple_poll_iterate(s, (int)block_time);
1267
if (ret > 0 or errno != EINTR) {
1268
return (ret != 1) ? ret : 0;
1274
int main(int argc, char *argv[]){
1275
AvahiSServiceBrowser *sb = NULL;
1280
int exitcode = EXIT_SUCCESS;
1281
const char *interface = "";
1282
struct ifreq network;
1284
bool take_down_interface = false;
1287
char tempdir[] = "/tmp/mandosXXXXXX";
1288
bool tempdir_created = false;
1289
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1290
const char *seckey = PATHDIR "/" SECKEY;
1291
const char *pubkey = PATHDIR "/" PUBKEY;
1293
bool gnutls_initialized = false;
1294
bool gpgme_initialized = false;
1296
double retry_interval = 10; /* 10s between trying a server and
1297
retrying the same server again */
1299
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1300
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1305
/* Lower any group privileges we might have, just to be safe */
1309
perror_plus("setgid");
1312
/* Lower user privileges (temporarily) */
1316
perror_plus("seteuid");
1324
struct argp_option options[] = {
1325
{ .name = "debug", .key = 128,
1326
.doc = "Debug mode", .group = 3 },
1327
{ .name = "connect", .key = 'c',
1328
.arg = "ADDRESS:PORT",
1329
.doc = "Connect directly to a specific Mandos server",
1331
{ .name = "interface", .key = 'i',
1333
.doc = "Network interface that will be used to search for"
1336
{ .name = "seckey", .key = 's',
1338
.doc = "OpenPGP secret key file base name",
1340
{ .name = "pubkey", .key = 'p',
1342
.doc = "OpenPGP public key file base name",
1344
{ .name = "dh-bits", .key = 129,
1346
.doc = "Bit length of the prime number used in the"
1347
" Diffie-Hellman key exchange",
1349
{ .name = "priority", .key = 130,
1351
.doc = "GnuTLS priority string for the TLS handshake",
1353
{ .name = "delay", .key = 131,
1355
.doc = "Maximum delay to wait for interface startup",
1357
{ .name = "retry", .key = 132,
1359
.doc = "Retry interval used when denied by the mandos server",
1362
* These reproduce what we would get without ARGP_NO_HELP
1364
{ .name = "help", .key = '?',
1365
.doc = "Give this help list", .group = -1 },
1366
{ .name = "usage", .key = -3,
1367
.doc = "Give a short usage message", .group = -1 },
1368
{ .name = "version", .key = 'V',
1369
.doc = "Print program version", .group = -1 },
1373
error_t parse_opt(int key, char *arg,
1374
struct argp_state *state){
1377
case 128: /* --debug */
1380
case 'c': /* --connect */
1383
case 'i': /* --interface */
1386
case 's': /* --seckey */
1389
case 'p': /* --pubkey */
1392
case 129: /* --dh-bits */
1394
tmpmax = strtoimax(arg, &tmp, 10);
1395
if(errno != 0 or tmp == arg or *tmp != '\0'
1396
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1397
argp_error(state, "Bad number of DH bits");
1399
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1401
case 130: /* --priority */
1404
case 131: /* --delay */
1406
delay = strtof(arg, &tmp);
1407
if(errno != 0 or tmp == arg or *tmp != '\0'){
1408
argp_error(state, "Bad delay");
1410
case 132: /* --retry */
1412
retry_interval = strtod(arg, &tmp);
1413
if(errno != 0 or tmp == arg or *tmp != '\0'
1414
or (retry_interval * 1000) > INT_MAX){
1415
argp_error(state, "Bad retry interval");
1419
* These reproduce what we would get without ARGP_NO_HELP
1421
case '?': /* --help */
1422
argp_state_help(state, state->out_stream,
1423
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1424
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1425
case -3: /* --usage */
1426
argp_state_help(state, state->out_stream,
1427
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1428
case 'V': /* --version */
1429
fprintf(state->out_stream, "%s\n", argp_program_version);
1430
exit(argp_err_exit_status);
1433
return ARGP_ERR_UNKNOWN;
1438
struct argp argp = { .options = options, .parser = parse_opt,
1440
.doc = "Mandos client -- Get and decrypt"
1441
" passwords from a Mandos server" };
1442
ret = argp_parse(&argp, argc, argv,
1443
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1450
perror_plus("argp_parse");
1451
exitcode = EX_OSERR;
1454
exitcode = EX_USAGE;
1460
avahi_set_log_function(empty_log);
1463
if(interface[0] == '\0'){
1464
struct dirent **direntries;
1465
ret = scandir(sys_class_net, &direntries, good_interface,
1468
/* Pick the first good interface */
1469
interface = strdup(direntries[0]->d_name);
1471
fprintf(stderr, "Using interface \"%s\"\n", interface);
1473
if(interface == NULL){
1474
perror_plus("malloc");
1476
exitcode = EXIT_FAILURE;
1482
fprintf(stderr, "Could not find a network interface\n");
1483
exitcode = EXIT_FAILURE;
1488
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1489
from the signal handler */
1490
/* Initialize the pseudo-RNG for Avahi */
1491
srand((unsigned int) time(NULL));
1492
mc.simple_poll = avahi_simple_poll_new();
1493
if(mc.simple_poll == NULL){
1494
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1495
exitcode = EX_UNAVAILABLE;
1499
sigemptyset(&sigterm_action.sa_mask);
1500
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1502
perror_plus("sigaddset");
1503
exitcode = EX_OSERR;
1506
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1508
perror_plus("sigaddset");
1509
exitcode = EX_OSERR;
1512
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1514
perror_plus("sigaddset");
1515
exitcode = EX_OSERR;
1518
/* Need to check if the handler is SIG_IGN before handling:
1519
| [[info:libc:Initial Signal Actions]] |
1520
| [[info:libc:Basic Signal Handling]] |
1522
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1524
perror_plus("sigaction");
1527
if(old_sigterm_action.sa_handler != SIG_IGN){
1528
ret = sigaction(SIGINT, &sigterm_action, NULL);
1530
perror_plus("sigaction");
1531
exitcode = EX_OSERR;
1535
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1537
perror_plus("sigaction");
1540
if(old_sigterm_action.sa_handler != SIG_IGN){
1541
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1543
perror_plus("sigaction");
1544
exitcode = EX_OSERR;
1548
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1550
perror_plus("sigaction");
1553
if(old_sigterm_action.sa_handler != SIG_IGN){
1554
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1556
perror_plus("sigaction");
1557
exitcode = EX_OSERR;
1562
/* If the interface is down, bring it up */
1563
if(strcmp(interface, "none") != 0){
1564
if_index = (AvahiIfIndex) if_nametoindex(interface);
1566
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1567
exitcode = EX_UNAVAILABLE;
1575
/* Re-raise priviliges */
1579
perror_plus("seteuid");
1583
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1584
messages about the network interface to mess up the prompt */
1585
ret = klogctl(8, NULL, 5);
1586
bool restore_loglevel = true;
1588
restore_loglevel = false;
1589
perror_plus("klogctl");
1591
#endif /* __linux__ */
1593
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1595
perror_plus("socket");
1596
exitcode = EX_OSERR;
1598
if(restore_loglevel){
1599
ret = klogctl(7, NULL, 0);
1601
perror_plus("klogctl");
1604
#endif /* __linux__ */
1605
/* Lower privileges */
1609
perror_plus("seteuid");
1613
strcpy(network.ifr_name, interface);
1614
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1616
perror_plus("ioctl SIOCGIFFLAGS");
1618
if(restore_loglevel){
1619
ret = klogctl(7, NULL, 0);
1621
perror_plus("klogctl");
1624
#endif /* __linux__ */
1625
exitcode = EX_OSERR;
1626
/* Lower privileges */
1630
perror_plus("seteuid");
1634
if((network.ifr_flags & IFF_UP) == 0){
1635
network.ifr_flags |= IFF_UP;
1636
take_down_interface = true;
1637
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1639
take_down_interface = false;
1640
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1641
exitcode = EX_OSERR;
1643
if(restore_loglevel){
1644
ret = klogctl(7, NULL, 0);
1646
perror_plus("klogctl");
1649
#endif /* __linux__ */
1650
/* Lower privileges */
1654
perror_plus("seteuid");
1659
/* Sleep checking until interface is running.
1660
Check every 0.25s, up to total time of delay */
1661
for(int i=0; i < delay * 4; i++){
1662
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1664
perror_plus("ioctl SIOCGIFFLAGS");
1665
} else if(network.ifr_flags & IFF_RUNNING){
1668
struct timespec sleeptime = { .tv_nsec = 250000000 };
1669
ret = nanosleep(&sleeptime, NULL);
1670
if(ret == -1 and errno != EINTR){
1671
perror_plus("nanosleep");
1674
if(not take_down_interface){
1675
/* We won't need the socket anymore */
1676
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1678
perror_plus("close");
1682
if(restore_loglevel){
1683
/* Restores kernel loglevel to default */
1684
ret = klogctl(7, NULL, 0);
1686
perror_plus("klogctl");
1689
#endif /* __linux__ */
1690
/* Lower privileges */
1692
if(take_down_interface){
1693
/* Lower privileges */
1696
perror_plus("seteuid");
1699
/* Lower privileges permanently */
1702
perror_plus("setuid");
1711
ret = init_gnutls_global(pubkey, seckey);
1713
fprintf(stderr, "init_gnutls_global failed\n");
1714
exitcode = EX_UNAVAILABLE;
1717
gnutls_initialized = true;
1724
if(mkdtemp(tempdir) == NULL){
1725
perror_plus("mkdtemp");
1728
tempdir_created = true;
1734
if(not init_gpgme(pubkey, seckey, tempdir)){
1735
fprintf(stderr, "init_gpgme failed\n");
1736
exitcode = EX_UNAVAILABLE;
1739
gpgme_initialized = true;
1746
if(connect_to != NULL){
1747
/* Connect directly, do not use Zeroconf */
1748
/* (Mainly meant for debugging) */
1749
char *address = strrchr(connect_to, ':');
1750
if(address == NULL){
1751
fprintf(stderr, "No colon in address\n");
1752
exitcode = EX_USAGE;
1762
tmpmax = strtoimax(address+1, &tmp, 10);
1763
if(errno != 0 or tmp == address+1 or *tmp != '\0'
1764
or tmpmax != (uint16_t)tmpmax){
1765
fprintf(stderr, "Bad port number\n");
1766
exitcode = EX_USAGE;
1774
port = (uint16_t)tmpmax;
1776
address = connect_to;
1777
/* Colon in address indicates IPv6 */
1779
if(strchr(address, ':') != NULL){
1789
while(not quit_now){
1790
ret = start_mandos_communication(address, port, if_index, af);
1791
if(quit_now or ret == 0){
1794
sleep((int)retry_interval or 1);
1798
exitcode = EXIT_SUCCESS;
1809
AvahiServerConfig config;
1810
/* Do not publish any local Zeroconf records */
1811
avahi_server_config_init(&config);
1812
config.publish_hinfo = 0;
1813
config.publish_addresses = 0;
1814
config.publish_workstation = 0;
1815
config.publish_domain = 0;
1817
/* Allocate a new server */
1818
mc.server = avahi_server_new(avahi_simple_poll_get
1819
(mc.simple_poll), &config, NULL,
1822
/* Free the Avahi configuration data */
1823
avahi_server_config_free(&config);
1826
/* Check if creating the Avahi server object succeeded */
1827
if(mc.server == NULL){
1828
fprintf(stderr, "Failed to create Avahi server: %s\n",
1829
avahi_strerror(error));
1830
exitcode = EX_UNAVAILABLE;
1838
/* Create the Avahi service browser */
1839
sb = avahi_s_service_browser_new(mc.server, if_index,
1840
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
1841
NULL, 0, browse_callback, NULL);
1843
fprintf(stderr, "Failed to create service browser: %s\n",
1844
avahi_strerror(avahi_server_errno(mc.server)));
1845
exitcode = EX_UNAVAILABLE;
1853
/* Run the main loop */
1856
fprintf(stderr, "Starting Avahi loop search\n");
1859
ret = avahi_loop_with_timeout(mc.simple_poll,
1860
(int)(retry_interval * 1000));
1862
fprintf(stderr, "avahi_loop_with_timeout exited %s\n",
1863
(ret == 0) ? "successfully" : "with error");
1869
fprintf(stderr, "%s exiting\n", argv[0]);
1872
/* Cleanup things */
1874
avahi_s_service_browser_free(sb);
1876
if(mc.server != NULL)
1877
avahi_server_free(mc.server);
1879
if(mc.simple_poll != NULL)
1880
avahi_simple_poll_free(mc.simple_poll);
1882
if(gnutls_initialized){
1883
gnutls_certificate_free_credentials(mc.cred);
1884
gnutls_global_deinit();
1885
gnutls_dh_params_deinit(mc.dh_params);
1888
if(gpgme_initialized){
1889
gpgme_release(mc.ctx);
1892
/* Cleans up the circular linked list of Mandos servers the client
1894
if(mc.current_server != NULL){
1895
mc.current_server->prev->next = NULL;
1896
while(mc.current_server != NULL){
1897
server *next = mc.current_server->next;
1898
free(mc.current_server);
1899
mc.current_server = next;
1903
/* Take down the network interface */
1904
if(take_down_interface){
1905
/* Re-raise priviliges */
1909
perror_plus("seteuid");
1912
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1914
perror_plus("ioctl SIOCGIFFLAGS");
1915
} else if(network.ifr_flags & IFF_UP) {
1916
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
1917
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1919
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
1922
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1924
perror_plus("close");
1926
/* Lower privileges permanently */
1930
perror_plus("setuid");
1935
/* Removes the GPGME temp directory and all files inside */
1936
if(tempdir_created){
1937
struct dirent **direntries = NULL;
1938
struct dirent *direntry = NULL;
1939
ret = scandir(tempdir, &direntries, notdotentries, alphasort);
1941
for(int i = 0; i < ret; i++){
1942
direntry = direntries[i];
1943
char *fullname = NULL;
1944
ret = asprintf(&fullname, "%s/%s", tempdir,
1947
perror_plus("asprintf");
1950
ret = remove(fullname);
1952
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1959
/* need to be cleaned even if ret == 0 because man page doesn't
1963
perror_plus("scandir");
1965
ret = rmdir(tempdir);
1966
if(ret == -1 and errno != ENOENT){
1967
perror_plus("rmdir");
1972
sigemptyset(&old_sigterm_action.sa_mask);
1973
old_sigterm_action.sa_handler = SIG_DFL;
1974
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
1975
&old_sigterm_action,
1978
perror_plus("sigaction");
1981
ret = raise(signal_received);
1982
} while(ret != 0 and errno == EINTR);
1984
perror_plus("raise");
1987
TEMP_FAILURE_RETRY(pause());