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
#define _LARGEFILE_SOURCE
34
#define _FILE_OFFSET_BITS 64
36
#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */
38
#include <stdio.h> /* fprintf(), stderr, fwrite(),
39
stdout, ferror(), sscanf(),
41
#include <stdint.h> /* uint16_t, uint32_t */
42
#include <stddef.h> /* NULL, size_t, ssize_t */
43
#include <stdlib.h> /* free(), EXIT_SUCCESS, EXIT_FAILURE,
45
#include <stdbool.h> /* bool, false, true */
46
#include <string.h> /* memset(), strcmp(), strlen(),
47
strerror(), asprintf(), strcpy() */
48
#include <sys/ioctl.h> /* ioctl */
49
#include <sys/types.h> /* socket(), inet_pton(), sockaddr,
50
sockaddr_in6, PF_INET6,
51
SOCK_STREAM, uid_t, gid_t, open(),
53
#include <sys/stat.h> /* open() */
54
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
55
inet_pton(), connect() */
56
#include <fcntl.h> /* open() */
57
#include <dirent.h> /* opendir(), struct dirent, readdir()
59
#include <inttypes.h> /* PRIu16, intmax_t, SCNdMAX */
60
#include <assert.h> /* assert() */
61
#include <errno.h> /* perror(), errno */
62
#include <time.h> /* nanosleep(), time() */
63
#include <net/if.h> /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
64
SIOCSIFFLAGS, if_indextoname(),
65
if_nametoindex(), IF_NAMESIZE */
66
#include <netinet/in.h> /* IN6_IS_ADDR_LINKLOCAL,
67
INET_ADDRSTRLEN, INET6_ADDRSTRLEN
69
#include <unistd.h> /* close(), SEEK_SET, off_t, write(),
70
getuid(), getgid(), setuid(),
72
#include <arpa/inet.h> /* inet_pton(), htons */
73
#include <iso646.h> /* not, or, and */
74
#include <argp.h> /* struct argp_option, error_t, struct
75
argp_state, struct argp,
76
argp_parse(), ARGP_KEY_ARG,
77
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
78
#include <signal.h> /* sigemptyset(), sigaddset(), sigaction(), SIGTERM, sigaction */
80
#include <sys/klog.h> /* klogctl() */
84
/* All Avahi types, constants and functions
87
#include <avahi-core/core.h>
88
#include <avahi-core/lookup.h>
89
#include <avahi-core/log.h>
90
#include <avahi-common/simple-watch.h>
91
#include <avahi-common/malloc.h>
92
#include <avahi-common/error.h>
95
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
98
init_gnutls_session(),
100
#include <gnutls/openpgp.h>
101
/* gnutls_certificate_set_openpgp_key_file(),
102
GNUTLS_OPENPGP_FMT_BASE64 */
105
#include <gpgme.h> /* All GPGME types, constants and
108
GPGME_PROTOCOL_OpenPGP,
111
#define BUFFER_SIZE 256
113
#define PATHDIR "/conf/conf.d/mandos"
114
#define SECKEY "seckey.txt"
115
#define PUBKEY "pubkey.txt"
118
static const char mandos_protocol_version[] = "1";
119
const char *argp_program_version = "mandos-client " VERSION;
120
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
122
/* Used for passing in values through the Avahi callback functions */
124
AvahiSimplePoll *simple_poll;
126
gnutls_certificate_credentials_t cred;
127
unsigned int dh_bits;
128
gnutls_dh_params_t dh_params;
129
const char *priority;
133
/* global context so signal handler can reach it*/
134
mandos_context mc = { .simple_poll = NULL, .server = NULL,
135
.dh_bits = 1024, .priority = "SECURE256"
136
":!CTYPE-X.509:+CTYPE-OPENPGP" };
139
* Make additional room in "buffer" for at least BUFFER_SIZE
140
* additional bytes. "buffer_capacity" is how much is currently
141
* allocated, "buffer_length" is how much is already used.
143
size_t incbuffer(char **buffer, size_t buffer_length,
144
size_t buffer_capacity){
145
if(buffer_length + BUFFER_SIZE > buffer_capacity){
146
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
150
buffer_capacity += BUFFER_SIZE;
152
return buffer_capacity;
158
static bool init_gpgme(const char *seckey,
159
const char *pubkey, const char *tempdir){
162
gpgme_engine_info_t engine_info;
166
* Helper function to insert pub and seckey to the engine keyring.
168
bool import_key(const char *filename){
170
gpgme_data_t pgp_data;
172
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
178
rc = gpgme_data_new_from_fd(&pgp_data, fd);
179
if(rc != GPG_ERR_NO_ERROR){
180
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
181
gpgme_strsource(rc), gpgme_strerror(rc));
185
rc = gpgme_op_import(mc.ctx, pgp_data);
186
if(rc != GPG_ERR_NO_ERROR){
187
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
188
gpgme_strsource(rc), gpgme_strerror(rc));
192
ret = (int)TEMP_FAILURE_RETRY(close(fd));
196
gpgme_data_release(pgp_data);
201
fprintf(stderr, "Initialize gpgme\n");
205
gpgme_check_version(NULL);
206
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
207
if(rc != GPG_ERR_NO_ERROR){
208
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
209
gpgme_strsource(rc), gpgme_strerror(rc));
213
/* Set GPGME home directory for the OpenPGP engine only */
214
rc = gpgme_get_engine_info(&engine_info);
215
if(rc != GPG_ERR_NO_ERROR){
216
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
217
gpgme_strsource(rc), gpgme_strerror(rc));
220
while(engine_info != NULL){
221
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
222
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
223
engine_info->file_name, tempdir);
226
engine_info = engine_info->next;
228
if(engine_info == NULL){
229
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
233
/* Create new GPGME "context" */
234
rc = gpgme_new(&(mc.ctx));
235
if(rc != GPG_ERR_NO_ERROR){
236
fprintf(stderr, "bad gpgme_new: %s: %s\n",
237
gpgme_strsource(rc), gpgme_strerror(rc));
241
if(not import_key(pubkey) or not import_key(seckey)){
249
* Decrypt OpenPGP data.
250
* Returns -1 on error
252
static ssize_t pgp_packet_decrypt(const char *cryptotext,
255
gpgme_data_t dh_crypto, dh_plain;
258
size_t plaintext_capacity = 0;
259
ssize_t plaintext_length = 0;
262
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
265
/* Create new GPGME data buffer from memory cryptotext */
266
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
268
if(rc != GPG_ERR_NO_ERROR){
269
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
270
gpgme_strsource(rc), gpgme_strerror(rc));
274
/* Create new empty GPGME data buffer for the plaintext */
275
rc = gpgme_data_new(&dh_plain);
276
if(rc != GPG_ERR_NO_ERROR){
277
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
278
gpgme_strsource(rc), gpgme_strerror(rc));
279
gpgme_data_release(dh_crypto);
283
/* Decrypt data from the cryptotext data buffer to the plaintext
285
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
286
if(rc != GPG_ERR_NO_ERROR){
287
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
288
gpgme_strsource(rc), gpgme_strerror(rc));
289
plaintext_length = -1;
291
gpgme_decrypt_result_t result;
292
result = gpgme_op_decrypt_result(mc.ctx);
294
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
296
fprintf(stderr, "Unsupported algorithm: %s\n",
297
result->unsupported_algorithm);
298
fprintf(stderr, "Wrong key usage: %u\n",
299
result->wrong_key_usage);
300
if(result->file_name != NULL){
301
fprintf(stderr, "File name: %s\n", result->file_name);
303
gpgme_recipient_t recipient;
304
recipient = result->recipients;
306
while(recipient != NULL){
307
fprintf(stderr, "Public key algorithm: %s\n",
308
gpgme_pubkey_algo_name(recipient->pubkey_algo));
309
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
310
fprintf(stderr, "Secret key available: %s\n",
311
recipient->status == GPG_ERR_NO_SECKEY
313
recipient = recipient->next;
322
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
325
/* Seek back to the beginning of the GPGME plaintext data buffer */
326
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
327
perror("gpgme_data_seek");
328
plaintext_length = -1;
334
plaintext_capacity = incbuffer(plaintext,
335
(size_t)plaintext_length,
337
if(plaintext_capacity == 0){
339
plaintext_length = -1;
343
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
345
/* Print the data, if any */
351
perror("gpgme_data_read");
352
plaintext_length = -1;
355
plaintext_length += ret;
359
fprintf(stderr, "Decrypted password is: ");
360
for(ssize_t i = 0; i < plaintext_length; i++){
361
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
363
fprintf(stderr, "\n");
368
/* Delete the GPGME cryptotext data buffer */
369
gpgme_data_release(dh_crypto);
371
/* Delete the GPGME plaintext data buffer */
372
gpgme_data_release(dh_plain);
373
return plaintext_length;
376
static const char * safer_gnutls_strerror(int value){
377
const char *ret = gnutls_strerror(value); /* Spurious warning from
378
-Wunreachable-code */
384
/* GnuTLS log function callback */
385
static void debuggnutls(__attribute__((unused)) int level,
387
fprintf(stderr, "GnuTLS: %s", string);
390
static int init_gnutls_global(const char *pubkeyfilename,
391
const char *seckeyfilename){
395
fprintf(stderr, "Initializing GnuTLS\n");
398
ret = gnutls_global_init();
399
if(ret != GNUTLS_E_SUCCESS){
400
fprintf(stderr, "GnuTLS global_init: %s\n",
401
safer_gnutls_strerror(ret));
406
/* "Use a log level over 10 to enable all debugging options."
409
gnutls_global_set_log_level(11);
410
gnutls_global_set_log_function(debuggnutls);
413
/* OpenPGP credentials */
414
gnutls_certificate_allocate_credentials(&mc.cred);
415
if(ret != GNUTLS_E_SUCCESS){
416
fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning
420
safer_gnutls_strerror(ret));
421
gnutls_global_deinit();
426
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
427
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
431
ret = gnutls_certificate_set_openpgp_key_file
432
(mc.cred, pubkeyfilename, seckeyfilename,
433
GNUTLS_OPENPGP_FMT_BASE64);
434
if(ret != GNUTLS_E_SUCCESS){
436
"Error[%d] while reading the OpenPGP key pair ('%s',"
437
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
438
fprintf(stderr, "The GnuTLS error is: %s\n",
439
safer_gnutls_strerror(ret));
443
/* GnuTLS server initialization */
444
ret = gnutls_dh_params_init(&mc.dh_params);
445
if(ret != GNUTLS_E_SUCCESS){
446
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
447
" %s\n", safer_gnutls_strerror(ret));
450
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
451
if(ret != GNUTLS_E_SUCCESS){
452
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
453
safer_gnutls_strerror(ret));
457
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
463
gnutls_certificate_free_credentials(mc.cred);
464
gnutls_global_deinit();
465
gnutls_dh_params_deinit(mc.dh_params);
469
static int init_gnutls_session(gnutls_session_t *session){
471
/* GnuTLS session creation */
472
ret = gnutls_init(session, GNUTLS_SERVER);
473
if(ret != GNUTLS_E_SUCCESS){
474
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
475
safer_gnutls_strerror(ret));
480
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
481
if(ret != GNUTLS_E_SUCCESS){
482
fprintf(stderr, "Syntax error at: %s\n", err);
483
fprintf(stderr, "GnuTLS error: %s\n",
484
safer_gnutls_strerror(ret));
485
gnutls_deinit(*session);
490
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
492
if(ret != GNUTLS_E_SUCCESS){
493
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
494
safer_gnutls_strerror(ret));
495
gnutls_deinit(*session);
499
/* ignore client certificate if any. */
500
gnutls_certificate_server_set_request(*session,
503
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
508
/* Avahi log function callback */
509
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
510
__attribute__((unused)) const char *txt){}
512
/* Called when a Mandos server is found */
513
static int start_mandos_communication(const char *ip, uint16_t port,
514
AvahiIfIndex if_index,
519
struct sockaddr_in in;
520
struct sockaddr_in6 in6;
523
char *decrypted_buffer;
524
size_t buffer_length = 0;
525
size_t buffer_capacity = 0;
526
ssize_t decrypted_buffer_size;
529
gnutls_session_t session;
530
int pf; /* Protocol family */
540
fprintf(stderr, "Bad address family: %d\n", af);
544
ret = init_gnutls_session(&session);
550
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
554
tcp_sd = socket(pf, SOCK_STREAM, 0);
560
memset(&to, 0, sizeof(to));
562
to.in6.sin6_family = (uint16_t)af;
563
ret = inet_pton(af, ip, &to.in6.sin6_addr);
565
to.in.sin_family = (sa_family_t)af;
566
ret = inet_pton(af, ip, &to.in.sin_addr);
573
fprintf(stderr, "Bad address: %s\n", ip);
577
to.in6.sin6_port = htons(port); /* Spurious warnings from
579
-Wunreachable-code */
581
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
582
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
584
if(if_index == AVAHI_IF_UNSPEC){
585
fprintf(stderr, "An IPv6 link-local address is incomplete"
586
" without a network interface\n");
589
/* Set the network interface number as scope */
590
to.in6.sin6_scope_id = (uint32_t)if_index;
593
to.in.sin_port = htons(port); /* Spurious warnings from
595
-Wunreachable-code */
599
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
600
char interface[IF_NAMESIZE];
601
if(if_indextoname((unsigned int)if_index, interface) == NULL){
602
perror("if_indextoname");
604
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
605
ip, interface, port);
608
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
611
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
612
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
615
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
618
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
624
if(strcmp(addrstr, ip) != 0){
625
fprintf(stderr, "Canonical address form: %s\n", addrstr);
631
ret = connect(tcp_sd, &to.in6, sizeof(to));
633
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
640
const char *out = mandos_protocol_version;
643
size_t out_size = strlen(out);
644
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
645
out_size - written));
651
written += (size_t)ret;
652
if(written < out_size){
655
if(out == mandos_protocol_version){
665
fprintf(stderr, "Establishing TLS session with %s\n", ip);
668
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
671
ret = gnutls_handshake(session);
672
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
674
if(ret != GNUTLS_E_SUCCESS){
676
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
683
/* Read OpenPGP packet that contains the wanted password */
686
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
691
buffer_capacity = incbuffer(&buffer, buffer_length,
693
if(buffer_capacity == 0){
699
sret = gnutls_record_recv(session, buffer+buffer_length,
706
case GNUTLS_E_INTERRUPTED:
709
case GNUTLS_E_REHANDSHAKE:
711
ret = gnutls_handshake(session);
712
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
714
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
721
fprintf(stderr, "Unknown error while reading data from"
722
" encrypted session with Mandos server\n");
724
gnutls_bye(session, GNUTLS_SHUT_RDWR);
728
buffer_length += (size_t) sret;
733
fprintf(stderr, "Closing TLS session\n");
736
gnutls_bye(session, GNUTLS_SHUT_RDWR);
738
if(buffer_length > 0){
739
decrypted_buffer_size = pgp_packet_decrypt(buffer,
742
if(decrypted_buffer_size >= 0){
744
while(written < (size_t) decrypted_buffer_size){
745
ret = (int)fwrite(decrypted_buffer + written, 1,
746
(size_t)decrypted_buffer_size - written,
748
if(ret == 0 and ferror(stdout)){
750
fprintf(stderr, "Error writing encrypted data: %s\n",
756
written += (size_t)ret;
758
free(decrypted_buffer);
766
/* Shutdown procedure */
770
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
774
gnutls_deinit(session);
778
static void resolve_callback(AvahiSServiceResolver *r,
779
AvahiIfIndex interface,
781
AvahiResolverEvent event,
785
const char *host_name,
786
const AvahiAddress *address,
788
AVAHI_GCC_UNUSED AvahiStringList *txt,
789
AVAHI_GCC_UNUSED AvahiLookupResultFlags
791
__attribute__((unused)) void* userdata){
794
/* Called whenever a service has been resolved successfully or
799
case AVAHI_RESOLVER_FAILURE:
800
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
801
" of type '%s' in domain '%s': %s\n", name, type, domain,
802
avahi_strerror(avahi_server_errno(mc.server)));
805
case AVAHI_RESOLVER_FOUND:
807
char ip[AVAHI_ADDRESS_STR_MAX];
808
avahi_address_snprint(ip, sizeof(ip), address);
810
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
811
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
812
ip, (intmax_t)interface, port);
814
int ret = start_mandos_communication(ip, port, interface,
815
avahi_proto_to_af(proto));
817
avahi_simple_poll_quit(mc.simple_poll);
821
avahi_s_service_resolver_free(r);
824
static void browse_callback(AvahiSServiceBrowser *b,
825
AvahiIfIndex interface,
826
AvahiProtocol protocol,
827
AvahiBrowserEvent event,
831
AVAHI_GCC_UNUSED AvahiLookupResultFlags
833
__attribute__((unused)) void* userdata){
836
/* Called whenever a new services becomes available on the LAN or
837
is removed from the LAN */
841
case AVAHI_BROWSER_FAILURE:
843
fprintf(stderr, "(Avahi browser) %s\n",
844
avahi_strerror(avahi_server_errno(mc.server)));
845
avahi_simple_poll_quit(mc.simple_poll);
848
case AVAHI_BROWSER_NEW:
849
/* We ignore the returned Avahi resolver object. In the callback
850
function we free it. If the Avahi server is terminated before
851
the callback function is called the Avahi server will free the
854
if(!(avahi_s_service_resolver_new(mc.server, interface,
855
protocol, name, type, domain,
856
AVAHI_PROTO_INET6, 0,
857
resolve_callback, NULL)))
858
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
859
name, avahi_strerror(avahi_server_errno(mc.server)));
862
case AVAHI_BROWSER_REMOVE:
865
case AVAHI_BROWSER_ALL_FOR_NOW:
866
case AVAHI_BROWSER_CACHE_EXHAUSTED:
868
fprintf(stderr, "No Mandos server found, still searching...\n");
874
/* stop main loop after sigterm has been called */
875
static void handle_sigterm(__attribute__((unused)) int sig){
876
int old_errno = errno;
877
avahi_simple_poll_quit(mc.simple_poll);
881
int main(int argc, char *argv[]){
882
AvahiSServiceBrowser *sb = NULL;
887
int exitcode = EXIT_SUCCESS;
888
const char *interface = "eth0";
889
struct ifreq network;
893
char *connect_to = NULL;
894
char tempdir[] = "/tmp/mandosXXXXXX";
895
bool tempdir_created = false;
896
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
897
const char *seckey = PATHDIR "/" SECKEY;
898
const char *pubkey = PATHDIR "/" PUBKEY;
900
bool gnutls_initialized = false;
901
bool gpgme_initialized = false;
904
struct sigaction old_sigterm_action;
905
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
908
struct argp_option options[] = {
909
{ .name = "debug", .key = 128,
910
.doc = "Debug mode", .group = 3 },
911
{ .name = "connect", .key = 'c',
912
.arg = "ADDRESS:PORT",
913
.doc = "Connect directly to a specific Mandos server",
915
{ .name = "interface", .key = 'i',
917
.doc = "Network interface that will be used to search for"
920
{ .name = "seckey", .key = 's',
922
.doc = "OpenPGP secret key file base name",
924
{ .name = "pubkey", .key = 'p',
926
.doc = "OpenPGP public key file base name",
928
{ .name = "dh-bits", .key = 129,
930
.doc = "Bit length of the prime number used in the"
931
" Diffie-Hellman key exchange",
933
{ .name = "priority", .key = 130,
935
.doc = "GnuTLS priority string for the TLS handshake",
937
{ .name = "delay", .key = 131,
939
.doc = "Maximum delay to wait for interface startup",
944
error_t parse_opt(int key, char *arg,
945
struct argp_state *state){
947
case 128: /* --debug */
950
case 'c': /* --connect */
953
case 'i': /* --interface */
956
case 's': /* --seckey */
959
case 'p': /* --pubkey */
962
case 129: /* --dh-bits */
963
ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars);
964
if(ret < 1 or tmpmax != (typeof(mc.dh_bits))tmpmax
965
or arg[numchars] != '\0'){
966
fprintf(stderr, "Bad number of DH bits\n");
969
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
971
case 130: /* --priority */
974
case 131: /* --delay */
975
ret = sscanf(arg, "%lf%n", &delay, &numchars);
976
if(ret < 1 or arg[numchars] != '\0'){
977
fprintf(stderr, "Bad delay\n");
986
return ARGP_ERR_UNKNOWN;
991
struct argp argp = { .options = options, .parser = parse_opt,
993
.doc = "Mandos client -- Get and decrypt"
994
" passwords from a Mandos server" };
995
ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
996
if(ret == ARGP_ERR_UNKNOWN){
997
fprintf(stderr, "Unknown error while parsing arguments\n");
998
exitcode = EXIT_FAILURE;
1004
avahi_set_log_function(empty_log);
1007
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1008
from the signal handler */
1009
/* Initialize the pseudo-RNG for Avahi */
1010
srand((unsigned int) time(NULL));
1011
mc.simple_poll = avahi_simple_poll_new();
1012
if(mc.simple_poll == NULL){
1013
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1014
exitcode = EXIT_FAILURE;
1018
sigemptyset(&sigterm_action.sa_mask);
1019
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1021
perror("sigaddset");
1022
exitcode = EXIT_FAILURE;
1025
ret = sigaction(SIGTERM, &sigterm_action, &old_sigterm_action);
1027
perror("sigaction");
1028
exitcode = EXIT_FAILURE;
1033
/* If the interface is down, bring it up */
1034
if(interface[0] != '\0'){
1036
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1037
messages to mess up the prompt */
1038
ret = klogctl(8, NULL, 5);
1039
bool restore_loglevel = true;
1041
restore_loglevel = false;
1046
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1049
exitcode = EXIT_FAILURE;
1051
if(restore_loglevel){
1052
ret = klogctl(7, NULL, 0);
1060
strcpy(network.ifr_name, interface);
1061
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1063
perror("ioctl SIOCGIFFLAGS");
1065
if(restore_loglevel){
1066
ret = klogctl(7, NULL, 0);
1072
exitcode = EXIT_FAILURE;
1075
if((network.ifr_flags & IFF_UP) == 0){
1076
network.ifr_flags |= IFF_UP;
1077
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1079
perror("ioctl SIOCSIFFLAGS");
1080
exitcode = EXIT_FAILURE;
1082
if(restore_loglevel){
1083
ret = klogctl(7, NULL, 0);
1092
/* sleep checking until interface is running */
1093
for(int i=0; i < delay * 4; i++){
1094
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1096
perror("ioctl SIOCGIFFLAGS");
1097
} else if(network.ifr_flags & IFF_RUNNING){
1100
struct timespec sleeptime = { .tv_nsec = 250000000 };
1101
ret = nanosleep(&sleeptime, NULL);
1102
if(ret == -1 and errno != EINTR){
1103
perror("nanosleep");
1106
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1111
if(restore_loglevel){
1112
/* Restores kernel loglevel to default */
1113
ret = klogctl(7, NULL, 0);
1135
ret = init_gnutls_global(pubkey, seckey);
1137
fprintf(stderr, "init_gnutls_global failed\n");
1138
exitcode = EXIT_FAILURE;
1141
gnutls_initialized = true;
1144
if(mkdtemp(tempdir) == NULL){
1148
tempdir_created = true;
1150
if(not init_gpgme(pubkey, seckey, tempdir)){
1151
fprintf(stderr, "init_gpgme failed\n");
1152
exitcode = EXIT_FAILURE;
1155
gpgme_initialized = true;
1158
if(interface[0] != '\0'){
1159
if_index = (AvahiIfIndex) if_nametoindex(interface);
1161
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1162
exitcode = EXIT_FAILURE;
1167
if(connect_to != NULL){
1168
/* Connect directly, do not use Zeroconf */
1169
/* (Mainly meant for debugging) */
1170
char *address = strrchr(connect_to, ':');
1171
if(address == NULL){
1172
fprintf(stderr, "No colon in address\n");
1173
exitcode = EXIT_FAILURE;
1177
ret = sscanf(address+1, "%" SCNdMAX "%n", &tmpmax, &numchars);
1178
if(ret < 1 or tmpmax != (uint16_t)tmpmax
1179
or address[numchars+1] != '\0'){
1180
fprintf(stderr, "Bad port number\n");
1181
exitcode = EXIT_FAILURE;
1184
port = (uint16_t)tmpmax;
1186
address = connect_to;
1187
/* Colon in address indicates IPv6 */
1189
if(strchr(address, ':') != NULL){
1194
ret = start_mandos_communication(address, port, if_index,
1197
exitcode = EXIT_FAILURE;
1199
exitcode = EXIT_SUCCESS;
1205
AvahiServerConfig config;
1206
/* Do not publish any local Zeroconf records */
1207
avahi_server_config_init(&config);
1208
config.publish_hinfo = 0;
1209
config.publish_addresses = 0;
1210
config.publish_workstation = 0;
1211
config.publish_domain = 0;
1213
/* Allocate a new server */
1214
mc.server = avahi_server_new(avahi_simple_poll_get
1215
(mc.simple_poll), &config, NULL,
1218
/* Free the Avahi configuration data */
1219
avahi_server_config_free(&config);
1222
/* Check if creating the Avahi server object succeeded */
1223
if(mc.server == NULL){
1224
fprintf(stderr, "Failed to create Avahi server: %s\n",
1225
avahi_strerror(error));
1226
exitcode = EXIT_FAILURE;
1230
/* Create the Avahi service browser */
1231
sb = avahi_s_service_browser_new(mc.server, if_index,
1232
AVAHI_PROTO_INET6, "_mandos._tcp",
1233
NULL, 0, browse_callback, NULL);
1235
fprintf(stderr, "Failed to create service browser: %s\n",
1236
avahi_strerror(avahi_server_errno(mc.server)));
1237
exitcode = EXIT_FAILURE;
1241
/* Run the main loop */
1244
fprintf(stderr, "Starting Avahi loop search\n");
1247
avahi_simple_poll_loop(mc.simple_poll);
1252
fprintf(stderr, "%s exiting\n", argv[0]);
1255
/* Cleanup things */
1257
avahi_s_service_browser_free(sb);
1259
if(mc.server != NULL)
1260
avahi_server_free(mc.server);
1262
if(mc.simple_poll != NULL)
1263
avahi_simple_poll_free(mc.simple_poll);
1265
if(gnutls_initialized){
1266
gnutls_certificate_free_credentials(mc.cred);
1267
gnutls_global_deinit();
1268
gnutls_dh_params_deinit(mc.dh_params);
1271
if(gpgme_initialized){
1272
gpgme_release(mc.ctx);
1275
/* Removes the temp directory used by GPGME */
1276
if(tempdir_created){
1278
struct dirent *direntry;
1279
d = opendir(tempdir);
1281
if(errno != ENOENT){
1286
direntry = readdir(d);
1287
if(direntry == NULL){
1290
/* Skip "." and ".." */
1291
if(direntry->d_name[0] == '.'
1292
and (direntry->d_name[1] == '\0'
1293
or (direntry->d_name[1] == '.'
1294
and direntry->d_name[2] == '\0'))){
1297
char *fullname = NULL;
1298
ret = asprintf(&fullname, "%s/%s", tempdir,
1304
ret = remove(fullname);
1306
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1313
ret = rmdir(tempdir);
1314
if(ret == -1 and errno != ENOENT){