33
103
#include <avahi-common/malloc.h>
34
104
#include <avahi-common/error.h>
37
#include <sys/types.h> /* socket(), setsockopt(), inet_pton() */
38
#include <sys/socket.h> /* socket(), setsockopt(), struct sockaddr_in6, struct in6_addr, inet_pton() */
39
#include <gnutls/gnutls.h> /* ALL GNUTLS STUFF */
40
#include <gnutls/openpgp.h> /* gnutls with openpgp stuff */
42
#include <unistd.h> /* close() */
43
#include <netinet/in.h>
44
#include <stdbool.h> /* true */
45
#include <string.h> /* memset */
46
#include <arpa/inet.h> /* inet_pton() */
47
#include <iso646.h> /* not */
50
#include <errno.h> /* perror() */
55
#define CERT_ROOT "/conf/conf.d/cryptkeyreq/"
57
#define CERTFILE CERT_ROOT "openpgp-client.txt"
58
#define KEYFILE CERT_ROOT "openpgp-client-key.txt"
107
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
110
init_gnutls_session(),
112
#include <gnutls/openpgp.h>
113
/* gnutls_certificate_set_openpgp_key_file(),
114
GNUTLS_OPENPGP_FMT_BASE64 */
117
#include <gpgme.h> /* All GPGME types, constants and
120
GPGME_PROTOCOL_OpenPGP,
59
123
#define BUFFER_SIZE 256
125
#define PATHDIR "/conf/conf.d/mandos"
126
#define SECKEY "seckey.txt"
127
#define PUBKEY "pubkey.txt"
128
#define HOOKDIR "/lib/mandos/network-hooks.d"
62
130
bool debug = false;
131
static const char mandos_protocol_version[] = "1";
132
const char *argp_program_version = "mandos-client " VERSION;
133
const char *argp_program_bug_address = "<mandos@recompile.se>";
134
static const char sys_class_net[] = "/sys/class/net";
135
char *connect_to = NULL;
136
const char *hookdir = HOOKDIR;
138
/* Doubly linked list that need to be circularly linked when used */
139
typedef struct server{
142
AvahiIfIndex if_index;
144
struct timespec last_seen;
149
/* Used for passing in values through the Avahi callback functions */
65
gnutls_session_t session;
151
AvahiSimplePoll *simple_poll;
66
153
gnutls_certificate_credentials_t cred;
154
unsigned int dh_bits;
67
155
gnutls_dh_params_t dh_params;
71
ssize_t gpg_packet_decrypt (char *packet, size_t packet_size, char **new_packet, char *homedir){
72
gpgme_data_t dh_crypto, dh_plain;
156
const char *priority;
158
server *current_server;
161
/* global context so signal handler can reach it*/
162
mandos_context mc = { .simple_poll = NULL, .server = NULL,
163
.dh_bits = 1024, .priority = "SECURE256"
164
":!CTYPE-X.509:+CTYPE-OPENPGP",
165
.current_server = NULL };
167
sig_atomic_t quit_now = 0;
168
int signal_received = 0;
170
/* Function to use when printing errors */
171
void perror_plus(const char *print_text){
172
fprintf(stderr, "Mandos plugin %s: ",
173
program_invocation_short_name);
178
* Make additional room in "buffer" for at least BUFFER_SIZE more
179
* bytes. "buffer_capacity" is how much is currently allocated,
180
* "buffer_length" is how much is already used.
182
size_t incbuffer(char **buffer, size_t buffer_length,
183
size_t buffer_capacity){
184
if(buffer_length + BUFFER_SIZE > buffer_capacity){
185
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
189
buffer_capacity += BUFFER_SIZE;
191
return buffer_capacity;
194
/* Add server to set of servers to retry periodically */
195
int add_server(const char *ip, uint16_t port, AvahiIfIndex if_index,
198
server *new_server = malloc(sizeof(server));
199
if(new_server == NULL){
200
perror_plus("malloc");
203
*new_server = (server){ .ip = strdup(ip),
205
.if_index = if_index,
207
if(new_server->ip == NULL){
208
perror_plus("strdup");
211
/* Special case of first server */
212
if (mc.current_server == NULL){
213
new_server->next = new_server;
214
new_server->prev = new_server;
215
mc.current_server = new_server;
216
/* Place the new server last in the list */
218
new_server->next = mc.current_server;
219
new_server->prev = mc.current_server->prev;
220
new_server->prev->next = new_server;
221
mc.current_server->prev = new_server;
223
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
225
perror_plus("clock_gettime");
234
static bool init_gpgme(const char *seckey, const char *pubkey,
235
const char *tempdir){
76
size_t new_packet_capacity = 0;
77
size_t new_packet_length = 0;
78
237
gpgme_engine_info_t engine_info;
81
fprintf(stderr, "Attempting to decrypt password from gpg packet\n");
241
* Helper function to insert pub and seckey to the engine keyring.
243
bool import_key(const char *filename){
246
gpgme_data_t pgp_data;
248
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
254
rc = gpgme_data_new_from_fd(&pgp_data, fd);
255
if(rc != GPG_ERR_NO_ERROR){
256
fprintf(stderr, "Mandos plugin mandos-client: "
257
"bad gpgme_data_new_from_fd: %s: %s\n",
258
gpgme_strsource(rc), gpgme_strerror(rc));
262
rc = gpgme_op_import(mc.ctx, pgp_data);
263
if(rc != GPG_ERR_NO_ERROR){
264
fprintf(stderr, "Mandos plugin mandos-client: "
265
"bad gpgme_op_import: %s: %s\n",
266
gpgme_strsource(rc), gpgme_strerror(rc));
270
ret = (int)TEMP_FAILURE_RETRY(close(fd));
272
perror_plus("close");
274
gpgme_data_release(pgp_data);
279
fprintf(stderr, "Mandos plugin mandos-client: "
280
"Initializing GPGME\n");
85
284
gpgme_check_version(NULL);
86
gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
285
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
286
if(rc != GPG_ERR_NO_ERROR){
287
fprintf(stderr, "Mandos plugin mandos-client: "
288
"bad gpgme_engine_check_version: %s: %s\n",
289
gpgme_strsource(rc), gpgme_strerror(rc));
88
/* Set GPGME home directory */
89
rc = gpgme_get_engine_info (&engine_info);
90
if (rc != GPG_ERR_NO_ERROR){
91
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
293
/* Set GPGME home directory for the OpenPGP engine only */
294
rc = gpgme_get_engine_info(&engine_info);
295
if(rc != GPG_ERR_NO_ERROR){
296
fprintf(stderr, "Mandos plugin mandos-client: "
297
"bad gpgme_get_engine_info: %s: %s\n",
92
298
gpgme_strsource(rc), gpgme_strerror(rc));
95
301
while(engine_info != NULL){
96
302
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
97
303
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
98
engine_info->file_name, homedir);
304
engine_info->file_name, tempdir);
101
307
engine_info = engine_info->next;
103
309
if(engine_info == NULL){
104
fprintf(stderr, "Could not set home dir to %s\n", homedir);
108
/* Create new GPGME data buffer from packet buffer */
109
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
110
if (rc != GPG_ERR_NO_ERROR){
111
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
310
fprintf(stderr, "Mandos plugin mandos-client: "
311
"Could not set GPGME home dir to %s\n", tempdir);
315
/* Create new GPGME "context" */
316
rc = gpgme_new(&(mc.ctx));
317
if(rc != GPG_ERR_NO_ERROR){
318
fprintf(stderr, "Mandos plugin mandos-client: "
319
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
324
if(not import_key(pubkey) or not import_key(seckey)){
332
* Decrypt OpenPGP data.
333
* Returns -1 on error
335
static ssize_t pgp_packet_decrypt(const char *cryptotext,
338
gpgme_data_t dh_crypto, dh_plain;
341
size_t plaintext_capacity = 0;
342
ssize_t plaintext_length = 0;
345
fprintf(stderr, "Mandos plugin mandos-client: "
346
"Trying to decrypt OpenPGP data\n");
349
/* Create new GPGME data buffer from memory cryptotext */
350
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
352
if(rc != GPG_ERR_NO_ERROR){
353
fprintf(stderr, "Mandos plugin mandos-client: "
354
"bad gpgme_data_new_from_mem: %s: %s\n",
112
355
gpgme_strsource(rc), gpgme_strerror(rc));
116
359
/* Create new empty GPGME data buffer for the plaintext */
117
360
rc = gpgme_data_new(&dh_plain);
118
if (rc != GPG_ERR_NO_ERROR){
119
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
120
gpgme_strsource(rc), gpgme_strerror(rc));
124
/* Create new GPGME "context" */
125
rc = gpgme_new(&ctx);
126
if (rc != GPG_ERR_NO_ERROR){
127
fprintf(stderr, "bad gpgme_new: %s: %s\n",
128
gpgme_strsource(rc), gpgme_strerror(rc));
132
/* Decrypt data from the FILE pointer to the plaintext data buffer */
133
rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
134
if (rc != GPG_ERR_NO_ERROR){
135
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
136
gpgme_strsource(rc), gpgme_strerror(rc));
141
fprintf(stderr, "decryption of gpg packet succeeded\n");
145
gpgme_decrypt_result_t result;
146
result = gpgme_op_decrypt_result(ctx);
148
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
150
fprintf(stderr, "Unsupported algorithm: %s\n", result->unsupported_algorithm);
151
fprintf(stderr, "Wrong key usage: %d\n", result->wrong_key_usage);
152
if(result->file_name != NULL){
153
fprintf(stderr, "File name: %s\n", result->file_name);
155
gpgme_recipient_t recipient;
156
recipient = result->recipients;
361
if(rc != GPG_ERR_NO_ERROR){
362
fprintf(stderr, "Mandos plugin mandos-client: "
363
"bad gpgme_data_new: %s: %s\n",
364
gpgme_strsource(rc), gpgme_strerror(rc));
365
gpgme_data_release(dh_crypto);
369
/* Decrypt data from the cryptotext data buffer to the plaintext
371
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
372
if(rc != GPG_ERR_NO_ERROR){
373
fprintf(stderr, "Mandos plugin mandos-client: "
374
"bad gpgme_op_decrypt: %s: %s\n",
375
gpgme_strsource(rc), gpgme_strerror(rc));
376
plaintext_length = -1;
378
gpgme_decrypt_result_t result;
379
result = gpgme_op_decrypt_result(mc.ctx);
381
fprintf(stderr, "Mandos plugin mandos-client: "
382
"gpgme_op_decrypt_result failed\n");
384
fprintf(stderr, "Mandos plugin mandos-client: "
385
"Unsupported algorithm: %s\n",
386
result->unsupported_algorithm);
387
fprintf(stderr, "Mandos plugin mandos-client: "
388
"Wrong key usage: %u\n",
389
result->wrong_key_usage);
390
if(result->file_name != NULL){
391
fprintf(stderr, "Mandos plugin mandos-client: "
392
"File name: %s\n", result->file_name);
394
gpgme_recipient_t recipient;
395
recipient = result->recipients;
158
396
while(recipient != NULL){
159
fprintf(stderr, "Public key algorithm: %s\n",
397
fprintf(stderr, "Mandos plugin mandos-client: "
398
"Public key algorithm: %s\n",
160
399
gpgme_pubkey_algo_name(recipient->pubkey_algo));
161
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
162
fprintf(stderr, "Secret key available: %s\n",
163
recipient->status == GPG_ERR_NO_SECKEY ? "No" : "Yes");
400
fprintf(stderr, "Mandos plugin mandos-client: "
401
"Key ID: %s\n", recipient->keyid);
402
fprintf(stderr, "Mandos plugin mandos-client: "
403
"Secret key available: %s\n",
404
recipient->status == GPG_ERR_NO_SECKEY
164
406
recipient = recipient->next;
170
/* Delete the GPGME FILE pointer cryptotext data buffer */
171
gpgme_data_release(dh_crypto);
414
fprintf(stderr, "Mandos plugin mandos-client: "
415
"Decryption of OpenPGP data succeeded\n");
173
418
/* Seek back to the beginning of the GPGME plaintext data buffer */
174
gpgme_data_seek(dh_plain, 0, SEEK_SET);
419
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
420
perror_plus("gpgme_data_seek");
421
plaintext_length = -1;
178
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
179
*new_packet = realloc(*new_packet, new_packet_capacity + BUFFER_SIZE);
180
if (*new_packet == NULL){
184
new_packet_capacity += BUFFER_SIZE;
427
plaintext_capacity = incbuffer(plaintext,
428
(size_t)plaintext_length,
430
if(plaintext_capacity == 0){
431
perror_plus("incbuffer");
432
plaintext_length = -1;
187
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length, BUFFER_SIZE);
436
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
188
438
/* Print the data, if any */
190
/* If password is empty, then a incorrect error will be printed */
194
perror("gpgme_data_read");
444
perror_plus("gpgme_data_read");
445
plaintext_length = -1;
197
new_packet_length += ret;
448
plaintext_length += ret;
201
fprintf(stderr, "decrypted password is: %s\n", *new_packet);
452
fprintf(stderr, "Mandos plugin mandos-client: "
453
"Decrypted password is: ");
454
for(ssize_t i = 0; i < plaintext_length; i++){
455
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
457
fprintf(stderr, "\n");
204
/* Delete the GPGME plaintext data buffer */
462
/* Delete the GPGME cryptotext data buffer */
463
gpgme_data_release(dh_crypto);
465
/* Delete the GPGME plaintext data buffer */
205
466
gpgme_data_release(dh_plain);
206
return new_packet_length;
467
return plaintext_length;
209
static const char * safer_gnutls_strerror (int value) {
210
const char *ret = gnutls_strerror (value);
470
static const char * safer_gnutls_strerror(int value){
471
const char *ret = gnutls_strerror(value); /* Spurious warning from
472
-Wunreachable-code */
212
474
ret = "(unknown)";
216
void debuggnutls(int level, const char* string){
217
fprintf(stderr, "%s", string);
478
/* GnuTLS log function callback */
479
static void debuggnutls(__attribute__((unused)) int level,
481
fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
220
int initgnutls(encrypted_session *es){
484
static int init_gnutls_global(const char *pubkeyfilename,
485
const char *seckeyfilename){
225
fprintf(stderr, "Initializing gnutls\n");
489
fprintf(stderr, "Mandos plugin mandos-client: "
490
"Initializing GnuTLS\n");
229
if ((ret = gnutls_global_init ())
230
!= GNUTLS_E_SUCCESS) {
231
fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
493
ret = gnutls_global_init();
494
if(ret != GNUTLS_E_SUCCESS){
495
fprintf(stderr, "Mandos plugin mandos-client: "
496
"GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
501
/* "Use a log level over 10 to enable all debugging options."
236
504
gnutls_global_set_log_level(11);
237
505
gnutls_global_set_log_function(debuggnutls);
241
/* openpgp credentials */
242
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
243
!= GNUTLS_E_SUCCESS) {
244
fprintf (stderr, "memory error: %s\n", safer_gnutls_strerror(ret));
508
/* OpenPGP credentials */
509
ret = gnutls_certificate_allocate_credentials(&mc.cred);
510
if(ret != GNUTLS_E_SUCCESS){
511
fprintf(stderr, "Mandos plugin mandos-client: "
512
"GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
513
gnutls_global_deinit();
249
fprintf(stderr, "Attempting to use openpgp certificate %s"
250
" and keyfile %s as gnutls credentials\n", CERTFILE, KEYFILE);
518
fprintf(stderr, "Mandos plugin mandos-client: "
519
"Attempting to use OpenPGP public key %s and"
520
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
253
524
ret = gnutls_certificate_set_openpgp_key_file
254
(es->cred, CERTFILE, KEYFILE, GNUTLS_OPENPGP_FMT_BASE64);
255
if (ret != GNUTLS_E_SUCCESS) {
257
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s', '%s')\n",
258
ret, CERTFILE, KEYFILE);
259
fprintf(stdout, "The Error is: %s\n",
260
safer_gnutls_strerror(ret));
264
//Gnutls server initialization
265
if ((ret = gnutls_dh_params_init (&es->dh_params))
266
!= GNUTLS_E_SUCCESS) {
267
fprintf (stderr, "Error in dh parameter initialization: %s\n",
268
safer_gnutls_strerror(ret));
272
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
273
!= GNUTLS_E_SUCCESS) {
274
fprintf (stderr, "Error in prime generation: %s\n",
275
safer_gnutls_strerror(ret));
279
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
281
// Gnutls session creation
282
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
283
!= GNUTLS_E_SUCCESS){
284
fprintf(stderr, "Error in gnutls session initialization: %s\n",
285
safer_gnutls_strerror(ret));
288
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
289
!= GNUTLS_E_SUCCESS) {
290
fprintf(stderr, "Syntax error at: %s\n", err);
291
fprintf(stderr, "Gnutls error: %s\n",
292
safer_gnutls_strerror(ret));
296
if ((ret = gnutls_credentials_set
297
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
298
!= GNUTLS_E_SUCCESS) {
299
fprintf(stderr, "Error setting a credentials set: %s\n",
300
safer_gnutls_strerror(ret));
525
(mc.cred, pubkeyfilename, seckeyfilename,
526
GNUTLS_OPENPGP_FMT_BASE64);
527
if(ret != GNUTLS_E_SUCCESS){
529
"Mandos plugin mandos-client: "
530
"Error[%d] while reading the OpenPGP key pair ('%s',"
531
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
532
fprintf(stderr, "Mandos plugin mandos-client: "
533
"The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
537
/* GnuTLS server initialization */
538
ret = gnutls_dh_params_init(&mc.dh_params);
539
if(ret != GNUTLS_E_SUCCESS){
540
fprintf(stderr, "Mandos plugin mandos-client: "
541
"Error in GnuTLS DH parameter initialization:"
542
" %s\n", safer_gnutls_strerror(ret));
545
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
546
if(ret != GNUTLS_E_SUCCESS){
547
fprintf(stderr, "Mandos plugin mandos-client: "
548
"Error in GnuTLS prime generation: %s\n",
549
safer_gnutls_strerror(ret));
553
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
559
gnutls_certificate_free_credentials(mc.cred);
560
gnutls_global_deinit();
561
gnutls_dh_params_deinit(mc.dh_params);
565
static int init_gnutls_session(gnutls_session_t *session){
567
/* GnuTLS session creation */
569
ret = gnutls_init(session, GNUTLS_SERVER);
573
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
574
if(ret != GNUTLS_E_SUCCESS){
575
fprintf(stderr, "Mandos plugin mandos-client: "
576
"Error in GnuTLS session initialization: %s\n",
577
safer_gnutls_strerror(ret));
583
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
585
gnutls_deinit(*session);
588
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
589
if(ret != GNUTLS_E_SUCCESS){
590
fprintf(stderr, "Mandos plugin mandos-client: "
591
"Syntax error at: %s\n", err);
592
fprintf(stderr, "Mandos plugin mandos-client: "
593
"GnuTLS error: %s\n", safer_gnutls_strerror(ret));
594
gnutls_deinit(*session);
600
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
603
gnutls_deinit(*session);
606
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
607
if(ret != GNUTLS_E_SUCCESS){
608
fprintf(stderr, "Mandos plugin mandos-client: "
609
"Error setting GnuTLS credentials: %s\n",
610
safer_gnutls_strerror(ret));
611
gnutls_deinit(*session);
304
615
/* ignore client certificate if any. */
305
gnutls_certificate_server_set_request (es->session, GNUTLS_CERT_IGNORE);
616
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
307
gnutls_dh_set_prime_bits (es->session, DH_BITS);
618
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
312
void empty_log(AvahiLogLevel level, const char *txt){}
623
/* Avahi log function callback */
624
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
625
__attribute__((unused)) const char *txt){}
314
int start_mandos_communcation(char *ip, uint16_t port){
316
struct sockaddr_in6 to;
317
struct in6_addr ip_addr;
318
encrypted_session es;
627
/* Called when a Mandos server is found */
628
static int start_mandos_communication(const char *ip, uint16_t port,
629
AvahiIfIndex if_index,
631
int ret, tcp_sd = -1;
634
struct sockaddr_in in;
635
struct sockaddr_in6 in6;
319
637
char *buffer = NULL;
320
char *decrypted_buffer;
638
char *decrypted_buffer = NULL;
321
639
size_t buffer_length = 0;
322
640
size_t buffer_capacity = 0;
323
ssize_t decrypted_buffer_size;
325
const char interface[] = "eth0";
328
fprintf(stderr, "Setting up a tcp connection to %s\n", ip);
331
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
338
fprintf(stderr, "Binding to interface %s\n", interface);
341
ret = setsockopt(tcp_sd, SOL_SOCKET, SO_BINDTODEVICE, interface, 5);
343
perror("setsockopt bindtodevice");
347
memset(&to,0,sizeof(to));
348
to.sin6_family = AF_INET6;
349
ret = inet_pton(AF_INET6, ip, &ip_addr);
643
gnutls_session_t session;
644
int pf; /* Protocol family */
661
fprintf(stderr, "Mandos plugin mandos-client: "
662
"Bad address family: %d\n", af);
667
ret = init_gnutls_session(&session);
673
fprintf(stderr, "Mandos plugin mandos-client: "
674
"Setting up a TCP connection to %s, port %" PRIu16
678
tcp_sd = socket(pf, SOCK_STREAM, 0);
681
perror_plus("socket");
691
memset(&to, 0, sizeof(to));
693
to.in6.sin6_family = (sa_family_t)af;
694
ret = inet_pton(af, ip, &to.in6.sin6_addr);
696
to.in.sin_family = (sa_family_t)af;
697
ret = inet_pton(af, ip, &to.in.sin_addr);
701
perror_plus("inet_pton");
355
fprintf(stderr, "Bad address: %s\n", ip);
358
to.sin6_port = htons(port);
359
to.sin6_scope_id = if_nametoindex(interface);
362
fprintf(stderr, "Connection to: %s\n", ip);
365
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
371
ret = initgnutls (&es);
378
gnutls_transport_set_ptr (es.session, (gnutls_transport_ptr_t) tcp_sd);
381
fprintf(stderr, "Establishing tls session with %s\n", ip);
385
ret = gnutls_handshake (es.session);
387
if (ret != GNUTLS_E_SUCCESS){
388
fprintf(stderr, "\n*** Handshake failed ***\n");
394
//Retrieve gpg packet that contains the wanted password
397
fprintf(stderr, "Retrieving pgp encrypted password from %s\n", ip);
401
if (buffer_length + BUFFER_SIZE > buffer_capacity){
402
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
407
buffer_capacity += BUFFER_SIZE;
410
ret = gnutls_record_recv
411
(es.session, buffer+buffer_length, BUFFER_SIZE);
707
fprintf(stderr, "Mandos plugin mandos-client: "
708
"Bad address: %s\n", ip);
713
to.in6.sin6_port = htons(port); /* Spurious warnings from
715
-Wunreachable-code */
717
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
718
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
720
if(if_index == AVAHI_IF_UNSPEC){
721
fprintf(stderr, "Mandos plugin mandos-client: "
722
"An IPv6 link-local address is incomplete"
723
" without a network interface\n");
727
/* Set the network interface number as scope */
728
to.in6.sin6_scope_id = (uint32_t)if_index;
731
to.in.sin_port = htons(port); /* Spurious warnings from
733
-Wunreachable-code */
742
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
743
char interface[IF_NAMESIZE];
744
if(if_indextoname((unsigned int)if_index, interface) == NULL){
745
perror_plus("if_indextoname");
747
fprintf(stderr, "Mandos plugin mandos-client: "
748
"Connection to: %s%%%s, port %" PRIu16 "\n",
749
ip, interface, port);
752
fprintf(stderr, "Mandos plugin mandos-client: "
753
"Connection to: %s, port %" PRIu16 "\n", ip, port);
755
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
756
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
759
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
762
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
766
perror_plus("inet_ntop");
768
if(strcmp(addrstr, ip) != 0){
769
fprintf(stderr, "Mandos plugin mandos-client: "
770
"Canonical address form: %s\n", addrstr);
781
ret = connect(tcp_sd, &to.in6, sizeof(to));
783
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
786
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
788
perror_plus("connect");
799
const char *out = mandos_protocol_version;
802
size_t out_size = strlen(out);
803
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
804
out_size - written));
807
perror_plus("write");
811
written += (size_t)ret;
812
if(written < out_size){
815
if(out == mandos_protocol_version){
830
fprintf(stderr, "Mandos plugin mandos-client: "
831
"Establishing TLS session with %s\n", ip);
839
/* Spurious warning from -Wint-to-pointer-cast */
840
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
848
ret = gnutls_handshake(session);
853
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
855
if(ret != GNUTLS_E_SUCCESS){
857
fprintf(stderr, "Mandos plugin mandos-client: "
858
"*** GnuTLS Handshake failed ***\n");
865
/* Read OpenPGP packet that contains the wanted password */
868
fprintf(stderr, "Mandos plugin mandos-client: "
869
"Retrieving OpenPGP encrypted password from %s\n", ip);
879
buffer_capacity = incbuffer(&buffer, buffer_length,
881
if(buffer_capacity == 0){
883
perror_plus("incbuffer");
893
sret = gnutls_record_recv(session, buffer+buffer_length,
417
900
case GNUTLS_E_INTERRUPTED:
418
901
case GNUTLS_E_AGAIN:
420
903
case GNUTLS_E_REHANDSHAKE:
421
ret = gnutls_handshake (es.session);
423
fprintf(stderr, "\n*** Handshake failed ***\n");
905
ret = gnutls_handshake(session);
911
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
913
fprintf(stderr, "Mandos plugin mandos-client: "
914
"*** GnuTLS Re-handshake failed ***\n");
430
fprintf(stderr, "Unknown error while reading data from encrypted session with mandos server\n");
432
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
436
buffer_length += ret;
440
if (buffer_length > 0){
441
if ((decrypted_buffer_size = gpg_packet_decrypt(buffer, buffer_length, &decrypted_buffer, CERT_ROOT)) >= 0){
442
fwrite (decrypted_buffer, 1, decrypted_buffer_size, stdout);
443
free(decrypted_buffer);
921
fprintf(stderr, "Mandos plugin mandos-client: "
922
"Unknown error while reading data from"
923
" encrypted session with Mandos server\n");
924
gnutls_bye(session, GNUTLS_SHUT_RDWR);
929
buffer_length += (size_t) sret;
934
fprintf(stderr, "Mandos plugin mandos-client: "
935
"Closing TLS session\n");
944
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
949
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
951
if(buffer_length > 0){
952
ssize_t decrypted_buffer_size;
953
decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length,
955
if(decrypted_buffer_size >= 0){
958
while(written < (size_t) decrypted_buffer_size){
964
ret = (int)fwrite(decrypted_buffer + written, 1,
965
(size_t)decrypted_buffer_size - written,
967
if(ret == 0 and ferror(stdout)){
970
fprintf(stderr, "Mandos plugin mandos-client: "
971
"Error writing encrypted data: %s\n",
977
written += (size_t)ret;
983
/* Shutdown procedure */
988
free(decrypted_buffer);
991
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
997
perror_plus("close");
999
gnutls_deinit(session);
452
fprintf(stderr, "Closing tls session\n");
456
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
459
gnutls_deinit (es.session);
460
gnutls_certificate_free_credentials (es.cred);
461
gnutls_global_deinit ();
465
static AvahiSimplePoll *simple_poll = NULL;
466
static AvahiServer *server = NULL;
468
static void resolve_callback(
469
AvahiSServiceResolver *r,
470
AVAHI_GCC_UNUSED AvahiIfIndex interface,
471
AVAHI_GCC_UNUSED AvahiProtocol protocol,
472
AvahiResolverEvent event,
476
const char *host_name,
477
const AvahiAddress *address,
479
AvahiStringList *txt,
480
AvahiLookupResultFlags flags,
481
AVAHI_GCC_UNUSED void* userdata) {
485
/* Called whenever a service has been resolved successfully or timed out */
488
case AVAHI_RESOLVER_FAILURE:
489
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_server_errno(server)));
492
case AVAHI_RESOLVER_FOUND: {
493
char ip[AVAHI_ADDRESS_STR_MAX];
494
avahi_address_snprint(ip, sizeof(ip), address);
496
fprintf(stderr, "Mandos server found at %s on port %d\n", ip, port);
498
int ret = start_mandos_communcation(ip, port);
506
avahi_s_service_resolver_free(r);
509
static void browse_callback(
510
AvahiSServiceBrowser *b,
511
AvahiIfIndex interface,
512
AvahiProtocol protocol,
513
AvahiBrowserEvent event,
517
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
520
AvahiServer *s = userdata;
523
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
527
case AVAHI_BROWSER_FAILURE:
529
fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(server)));
530
avahi_simple_poll_quit(simple_poll);
533
case AVAHI_BROWSER_NEW:
534
/* We ignore the returned resolver object. In the callback
535
function we free it. If the server is terminated before
536
the callback function is called the server will free
537
the resolver for us. */
539
if (!(avahi_s_service_resolver_new(s, interface, protocol, name, type, domain, AVAHI_PROTO_INET6, 0, resolve_callback, s)))
540
fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(s)));
544
case AVAHI_BROWSER_REMOVE:
547
case AVAHI_BROWSER_ALL_FOR_NOW:
548
case AVAHI_BROWSER_CACHE_EXHAUSTED:
553
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
1009
static void resolve_callback(AvahiSServiceResolver *r,
1010
AvahiIfIndex interface,
1011
AvahiProtocol proto,
1012
AvahiResolverEvent event,
1016
const char *host_name,
1017
const AvahiAddress *address,
1019
AVAHI_GCC_UNUSED AvahiStringList *txt,
1020
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1022
AVAHI_GCC_UNUSED void* userdata){
1025
/* Called whenever a service has been resolved successfully or
1034
case AVAHI_RESOLVER_FAILURE:
1035
fprintf(stderr, "Mandos plugin mandos-client: "
1036
"(Avahi Resolver) Failed to resolve service '%s'"
1037
" of type '%s' in domain '%s': %s\n", name, type, domain,
1038
avahi_strerror(avahi_server_errno(mc.server)));
1041
case AVAHI_RESOLVER_FOUND:
1043
char ip[AVAHI_ADDRESS_STR_MAX];
1044
avahi_address_snprint(ip, sizeof(ip), address);
1046
fprintf(stderr, "Mandos plugin mandos-client: "
1047
"Mandos server \"%s\" found on %s (%s, %"
1048
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1049
ip, (intmax_t)interface, port);
1051
int ret = start_mandos_communication(ip, port, interface,
1052
avahi_proto_to_af(proto));
1054
avahi_simple_poll_quit(mc.simple_poll);
1056
ret = add_server(ip, port, interface,
1057
avahi_proto_to_af(proto));
1061
avahi_s_service_resolver_free(r);
1064
static void browse_callback(AvahiSServiceBrowser *b,
1065
AvahiIfIndex interface,
1066
AvahiProtocol protocol,
1067
AvahiBrowserEvent event,
1071
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1073
AVAHI_GCC_UNUSED void* userdata){
1076
/* Called whenever a new services becomes available on the LAN or
1077
is removed from the LAN */
1085
case AVAHI_BROWSER_FAILURE:
1087
fprintf(stderr, "Mandos plugin mandos-client: "
1088
"(Avahi browser) %s\n",
1089
avahi_strerror(avahi_server_errno(mc.server)));
1090
avahi_simple_poll_quit(mc.simple_poll);
1093
case AVAHI_BROWSER_NEW:
1094
/* We ignore the returned Avahi resolver object. In the callback
1095
function we free it. If the Avahi server is terminated before
1096
the callback function is called the Avahi server will free the
1099
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1100
name, type, domain, protocol, 0,
1101
resolve_callback, NULL) == NULL)
1102
fprintf(stderr, "Mandos plugin mandos-client: "
1103
"Avahi: Failed to resolve service '%s': %s\n",
1104
name, avahi_strerror(avahi_server_errno(mc.server)));
1107
case AVAHI_BROWSER_REMOVE:
1110
case AVAHI_BROWSER_ALL_FOR_NOW:
1111
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1113
fprintf(stderr, "Mandos plugin mandos-client: "
1114
"No Mandos server found, still searching...\n");
1120
/* Signal handler that stops main loop after SIGTERM */
1121
static void handle_sigterm(int sig){
1126
signal_received = sig;
1127
int old_errno = errno;
1128
/* set main loop to exit */
1129
if(mc.simple_poll != NULL){
1130
avahi_simple_poll_quit(mc.simple_poll);
1135
bool get_flags(const char *ifname, struct ifreq *ifr){
1138
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1140
perror_plus("socket");
1143
strcpy(ifr->ifr_name, ifname);
1144
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1147
perror_plus("ioctl SIOCGIFFLAGS");
1154
bool good_flags(const char *ifname, const struct ifreq *ifr){
1156
/* Reject the loopback device */
1157
if(ifr->ifr_flags & IFF_LOOPBACK){
1159
fprintf(stderr, "Mandos plugin mandos-client: "
1160
"Rejecting loopback interface \"%s\"\n", ifname);
1164
/* Accept point-to-point devices only if connect_to is specified */
1165
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1167
fprintf(stderr, "Mandos plugin mandos-client: "
1168
"Accepting point-to-point interface \"%s\"\n", ifname);
1172
/* Otherwise, reject non-broadcast-capable devices */
1173
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1175
fprintf(stderr, "Mandos plugin mandos-client: "
1176
"Rejecting non-broadcast interface \"%s\"\n", ifname);
1180
/* Reject non-ARP interfaces (including dummy interfaces) */
1181
if(ifr->ifr_flags & IFF_NOARP){
1183
fprintf(stderr, "Mandos plugin mandos-client: "
1184
"Rejecting non-ARP interface \"%s\"\n", ifname);
1189
/* Accept this device */
1191
fprintf(stderr, "Mandos plugin mandos-client: "
1192
"Interface \"%s\" is good\n", ifname);
1198
* This function determines if a directory entry in /sys/class/net
1199
* corresponds to an acceptable network device.
1200
* (This function is passed to scandir(3) as a filter function.)
1202
int good_interface(const struct dirent *if_entry){
1203
if(if_entry->d_name[0] == '.'){
1208
if(not get_flags(if_entry->d_name, &ifr)){
1210
fprintf(stderr, "Mandos plugin mandos-client: "
1211
"Failed to get flags for interface \"%s\"\n",
1217
if(not good_flags(if_entry->d_name, &ifr)){
1224
* This function determines if a directory entry in /sys/class/net
1225
* corresponds to an acceptable network device which is up.
1226
* (This function is passed to scandir(3) as a filter function.)
1228
int up_interface(const struct dirent *if_entry){
1229
if(if_entry->d_name[0] == '.'){
1234
if(not get_flags(if_entry->d_name, &ifr)){
1236
fprintf(stderr, "Mandos plugin mandos-client: "
1237
"Failed to get flags for interface \"%s\"\n",
1243
/* Reject down interfaces */
1244
if(not (ifr.ifr_flags & IFF_UP)){
1246
fprintf(stderr, "Mandos plugin mandos-client: "
1247
"Rejecting down interface \"%s\"\n",
1253
/* Reject non-running interfaces */
1254
if(not (ifr.ifr_flags & IFF_RUNNING)){
1256
fprintf(stderr, "Mandos plugin mandos-client: "
1257
"Rejecting non-running interface \"%s\"\n",
1263
if(not good_flags(if_entry->d_name, &ifr)){
1269
int notdotentries(const struct dirent *direntry){
1270
/* Skip "." and ".." */
1271
if(direntry->d_name[0] == '.'
1272
and (direntry->d_name[1] == '\0'
1273
or (direntry->d_name[1] == '.'
1274
and direntry->d_name[2] == '\0'))){
1280
/* Is this directory entry a runnable program? */
1281
int runnable_hook(const struct dirent *direntry){
1286
if((direntry->d_name)[0] == '\0'){
1291
sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1292
"abcdefghijklmnopqrstuvwxyz"
1295
if((direntry->d_name)[sret] != '\0'){
1296
/* Contains non-allowed characters */
1298
fprintf(stderr, "Mandos plugin mandos-client: "
1299
"Ignoring hook \"%s\" with bad name\n",
1305
char *fullname = NULL;
1306
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1308
perror_plus("asprintf");
1312
ret = stat(fullname, &st);
1315
perror_plus("Could not stat hook");
1319
if(not (S_ISREG(st.st_mode))){
1320
/* Not a regular file */
1322
fprintf(stderr, "Mandos plugin mandos-client: "
1323
"Ignoring hook \"%s\" - not a file\n",
1328
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1329
/* Not executable */
1331
fprintf(stderr, "Mandos plugin mandos-client: "
1332
"Ignoring hook \"%s\" - not executable\n",
1340
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1342
struct timespec now;
1343
struct timespec waited_time;
1344
intmax_t block_time;
1347
if(mc.current_server == NULL){
1349
fprintf(stderr, "Mandos plugin mandos-client: "
1350
"Wait until first server is found. No timeout!\n");
1352
ret = avahi_simple_poll_iterate(s, -1);
1355
fprintf(stderr, "Mandos plugin mandos-client: "
1356
"Check current_server if we should run it,"
1359
/* the current time */
1360
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1362
perror_plus("clock_gettime");
1365
/* Calculating in ms how long time between now and server
1366
who we visted longest time ago. Now - last seen. */
1367
waited_time.tv_sec = (now.tv_sec
1368
- mc.current_server->last_seen.tv_sec);
1369
waited_time.tv_nsec = (now.tv_nsec
1370
- mc.current_server->last_seen.tv_nsec);
1371
/* total time is 10s/10,000ms.
1372
Converting to s from ms by dividing by 1,000,
1373
and ns to ms by dividing by 1,000,000. */
1374
block_time = ((retry_interval
1375
- ((intmax_t)waited_time.tv_sec * 1000))
1376
- ((intmax_t)waited_time.tv_nsec / 1000000));
1379
fprintf(stderr, "Mandos plugin mandos-client: "
1380
"Blocking for %" PRIdMAX " ms\n", block_time);
1383
if(block_time <= 0){
1384
ret = start_mandos_communication(mc.current_server->ip,
1385
mc.current_server->port,
1386
mc.current_server->if_index,
1387
mc.current_server->af);
1389
avahi_simple_poll_quit(mc.simple_poll);
1392
ret = clock_gettime(CLOCK_MONOTONIC,
1393
&mc.current_server->last_seen);
1395
perror_plus("clock_gettime");
1398
mc.current_server = mc.current_server->next;
1399
block_time = 0; /* Call avahi to find new Mandos
1400
servers, but don't block */
1403
ret = avahi_simple_poll_iterate(s, (int)block_time);
1406
if (ret > 0 or errno != EINTR){
1407
return (ret != 1) ? ret : 0;
1413
int main(int argc, char *argv[]){
1414
AvahiSServiceBrowser *sb = NULL;
1419
int exitcode = EXIT_SUCCESS;
1420
const char *interface = "";
1421
struct ifreq network;
1423
bool take_down_interface = false;
1426
char tempdir[] = "/tmp/mandosXXXXXX";
1427
bool tempdir_created = false;
1428
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1429
const char *seckey = PATHDIR "/" SECKEY;
1430
const char *pubkey = PATHDIR "/" PUBKEY;
1432
bool gnutls_initialized = false;
1433
bool gpgme_initialized = false;
1435
double retry_interval = 10; /* 10s between trying a server and
1436
retrying the same server again */
1438
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1439
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1444
/* Lower any group privileges we might have, just to be safe */
1448
perror_plus("setgid");
1451
/* Lower user privileges (temporarily) */
1455
perror_plus("seteuid");
1463
struct argp_option options[] = {
1464
{ .name = "debug", .key = 128,
1465
.doc = "Debug mode", .group = 3 },
1466
{ .name = "connect", .key = 'c',
1467
.arg = "ADDRESS:PORT",
1468
.doc = "Connect directly to a specific Mandos server",
1470
{ .name = "interface", .key = 'i',
1472
.doc = "Network interface that will be used to search for"
1475
{ .name = "seckey", .key = 's',
1477
.doc = "OpenPGP secret key file base name",
1479
{ .name = "pubkey", .key = 'p',
1481
.doc = "OpenPGP public key file base name",
1483
{ .name = "dh-bits", .key = 129,
1485
.doc = "Bit length of the prime number used in the"
1486
" Diffie-Hellman key exchange",
1488
{ .name = "priority", .key = 130,
1490
.doc = "GnuTLS priority string for the TLS handshake",
1492
{ .name = "delay", .key = 131,
1494
.doc = "Maximum delay to wait for interface startup",
1496
{ .name = "retry", .key = 132,
1498
.doc = "Retry interval used when denied by the mandos server",
1500
{ .name = "network-hook-dir", .key = 133,
1502
.doc = "Directory where network hooks are located",
1505
* These reproduce what we would get without ARGP_NO_HELP
1507
{ .name = "help", .key = '?',
1508
.doc = "Give this help list", .group = -1 },
1509
{ .name = "usage", .key = -3,
1510
.doc = "Give a short usage message", .group = -1 },
1511
{ .name = "version", .key = 'V',
1512
.doc = "Print program version", .group = -1 },
1516
error_t parse_opt(int key, char *arg,
1517
struct argp_state *state){
1520
case 128: /* --debug */
1523
case 'c': /* --connect */
1526
case 'i': /* --interface */
1529
case 's': /* --seckey */
1532
case 'p': /* --pubkey */
1535
case 129: /* --dh-bits */
1537
tmpmax = strtoimax(arg, &tmp, 10);
1538
if(errno != 0 or tmp == arg or *tmp != '\0'
1539
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1540
argp_error(state, "Bad number of DH bits");
1542
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1544
case 130: /* --priority */
1547
case 131: /* --delay */
1549
delay = strtof(arg, &tmp);
1550
if(errno != 0 or tmp == arg or *tmp != '\0'){
1551
argp_error(state, "Bad delay");
1553
case 132: /* --retry */
1555
retry_interval = strtod(arg, &tmp);
1556
if(errno != 0 or tmp == arg or *tmp != '\0'
1557
or (retry_interval * 1000) > INT_MAX
1558
or retry_interval < 0){
1559
argp_error(state, "Bad retry interval");
1562
case 133: /* --network-hook-dir */
1566
* These reproduce what we would get without ARGP_NO_HELP
1568
case '?': /* --help */
1569
argp_state_help(state, state->out_stream,
1570
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1571
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1572
case -3: /* --usage */
1573
argp_state_help(state, state->out_stream,
1574
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1575
case 'V': /* --version */
1576
fprintf(state->out_stream, "Mandos plugin mandos-client: ");
1577
fprintf(state->out_stream, "%s\n", argp_program_version);
1578
exit(argp_err_exit_status);
1581
return ARGP_ERR_UNKNOWN;
1586
struct argp argp = { .options = options, .parser = parse_opt,
1588
.doc = "Mandos client -- Get and decrypt"
1589
" passwords from a Mandos server" };
1590
ret = argp_parse(&argp, argc, argv,
1591
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1598
perror_plus("argp_parse");
1599
exitcode = EX_OSERR;
1602
exitcode = EX_USAGE;
1608
/* Work around Debian bug #633582:
1609
<http://bugs.debian.org/633582> */
1612
/* Re-raise priviliges */
1616
perror_plus("seteuid");
1619
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
1620
int seckey_fd = open(seckey, O_RDONLY);
1621
if(seckey_fd == -1){
1622
perror_plus("open");
1624
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
1626
perror_plus("fstat");
1628
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1629
ret = fchown(seckey_fd, uid, gid);
1631
perror_plus("fchown");
1635
TEMP_FAILURE_RETRY(close(seckey_fd));
1639
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
1640
int pubkey_fd = open(pubkey, O_RDONLY);
1641
if(pubkey_fd == -1){
1642
perror_plus("open");
1644
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
1646
perror_plus("fstat");
1648
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1649
ret = fchown(pubkey_fd, uid, gid);
1651
perror_plus("fchown");
1655
TEMP_FAILURE_RETRY(close(pubkey_fd));
1659
/* Lower privileges */
1663
perror_plus("seteuid");
1667
/* Find network hooks and run them */
1669
struct dirent **direntries;
1670
struct dirent *direntry;
1671
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1674
perror_plus("scandir");
1676
int devnull = open("/dev/null", O_RDONLY);
1677
for(int i = 0; i < numhooks; i++){
1678
direntry = direntries[0];
1679
char *fullname = NULL;
1680
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1682
perror_plus("asprintf");
1685
pid_t hook_pid = fork();
1688
dup2(devnull, STDIN_FILENO);
1690
dup2(STDERR_FILENO, STDOUT_FILENO);
1691
ret = setenv("MANDOSNETHOOKDIR", hookdir, 1);
1693
perror_plus("setenv");
1696
ret = setenv("DEVICE", interface, 1);
1698
perror_plus("setenv");
1701
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1703
perror_plus("setenv");
1706
ret = setenv("MODE", "start", 1);
1708
perror_plus("setenv");
1712
ret = asprintf(&delaystring, "%f", delay);
1714
perror_plus("asprintf");
1717
ret = setenv("DELAY", delaystring, 1);
1720
perror_plus("setenv");
1724
ret = execl(fullname, direntry->d_name, "start", NULL);
1725
perror_plus("execl");
1728
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1729
perror_plus("waitpid");
1733
if(WIFEXITED(status)){
1734
if(WEXITSTATUS(status) != 0){
1735
fprintf(stderr, "Mandos plugin mandos-client: "
1736
"Warning: network hook \"%s\" exited"
1737
" with status %d\n", direntry->d_name,
1738
WEXITSTATUS(status));
1742
} else if(WIFSIGNALED(status)){
1743
fprintf(stderr, "Mandos plugin mandos-client: "
1744
"Warning: network hook \"%s\" died by"
1745
" signal %d\n", direntry->d_name,
1750
fprintf(stderr, "Mandos plugin mandos-client: "
1751
"Warning: network hook \"%s\" crashed\n",
1767
avahi_set_log_function(empty_log);
1770
if(interface[0] == '\0'){
1771
struct dirent **direntries;
1772
/* First look for interfaces that are up */
1773
ret = scandir(sys_class_net, &direntries, up_interface,
1776
/* No up interfaces, look for any good interfaces */
1778
ret = scandir(sys_class_net, &direntries, good_interface,
1782
/* Pick the first interface returned */
1783
interface = strdup(direntries[0]->d_name);
1785
fprintf(stderr, "Mandos plugin mandos-client: "
1786
"Using interface \"%s\"\n", interface);
1788
if(interface == NULL){
1789
perror_plus("malloc");
1791
exitcode = EXIT_FAILURE;
1797
fprintf(stderr, "Mandos plugin mandos-client: "
1798
"Could not find a network interface\n");
1799
exitcode = EXIT_FAILURE;
1804
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1805
from the signal handler */
1806
/* Initialize the pseudo-RNG for Avahi */
1807
srand((unsigned int) time(NULL));
1808
mc.simple_poll = avahi_simple_poll_new();
1809
if(mc.simple_poll == NULL){
1810
fprintf(stderr, "Mandos plugin mandos-client: "
1811
"Avahi: Failed to create simple poll object.\n");
1812
exitcode = EX_UNAVAILABLE;
1816
sigemptyset(&sigterm_action.sa_mask);
1817
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1819
perror_plus("sigaddset");
1820
exitcode = EX_OSERR;
1823
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1825
perror_plus("sigaddset");
1826
exitcode = EX_OSERR;
1829
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1831
perror_plus("sigaddset");
1832
exitcode = EX_OSERR;
1835
/* Need to check if the handler is SIG_IGN before handling:
1836
| [[info:libc:Initial Signal Actions]] |
1837
| [[info:libc:Basic Signal Handling]] |
1839
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1841
perror_plus("sigaction");
1844
if(old_sigterm_action.sa_handler != SIG_IGN){
1845
ret = sigaction(SIGINT, &sigterm_action, NULL);
1847
perror_plus("sigaction");
1848
exitcode = EX_OSERR;
1852
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1854
perror_plus("sigaction");
1857
if(old_sigterm_action.sa_handler != SIG_IGN){
1858
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1860
perror_plus("sigaction");
1861
exitcode = EX_OSERR;
1865
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1867
perror_plus("sigaction");
1870
if(old_sigterm_action.sa_handler != SIG_IGN){
1871
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1873
perror_plus("sigaction");
1874
exitcode = EX_OSERR;
1879
/* If the interface is down, bring it up */
1880
if(strcmp(interface, "none") != 0){
1881
if_index = (AvahiIfIndex) if_nametoindex(interface);
1883
fprintf(stderr, "Mandos plugin mandos-client: "
1884
"No such interface: \"%s\"\n", interface);
1885
exitcode = EX_UNAVAILABLE;
1893
/* Re-raise priviliges */
1897
perror_plus("seteuid");
1901
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1902
messages about the network interface to mess up the prompt */
1903
ret = klogctl(8, NULL, 5);
1904
bool restore_loglevel = true;
1906
restore_loglevel = false;
1907
perror_plus("klogctl");
1909
#endif /* __linux__ */
1911
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1913
perror_plus("socket");
1914
exitcode = EX_OSERR;
1916
if(restore_loglevel){
1917
ret = klogctl(7, NULL, 0);
1919
perror_plus("klogctl");
1922
#endif /* __linux__ */
1923
/* Lower privileges */
1927
perror_plus("seteuid");
1931
strcpy(network.ifr_name, interface);
1932
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1934
perror_plus("ioctl SIOCGIFFLAGS");
1936
if(restore_loglevel){
1937
ret = klogctl(7, NULL, 0);
1939
perror_plus("klogctl");
1942
#endif /* __linux__ */
1943
exitcode = EX_OSERR;
1944
/* Lower privileges */
1948
perror_plus("seteuid");
1952
if((network.ifr_flags & IFF_UP) == 0){
1953
network.ifr_flags |= IFF_UP;
1954
take_down_interface = true;
1955
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1957
take_down_interface = false;
1958
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1959
exitcode = EX_OSERR;
1961
if(restore_loglevel){
1962
ret = klogctl(7, NULL, 0);
1964
perror_plus("klogctl");
1967
#endif /* __linux__ */
1968
/* Lower privileges */
1972
perror_plus("seteuid");
1977
/* Sleep checking until interface is running.
1978
Check every 0.25s, up to total time of delay */
1979
for(int i=0; i < delay * 4; i++){
1980
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1982
perror_plus("ioctl SIOCGIFFLAGS");
1983
} else if(network.ifr_flags & IFF_RUNNING){
1986
struct timespec sleeptime = { .tv_nsec = 250000000 };
1987
ret = nanosleep(&sleeptime, NULL);
1988
if(ret == -1 and errno != EINTR){
1989
perror_plus("nanosleep");
1992
if(not take_down_interface){
1993
/* We won't need the socket anymore */
1994
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1996
perror_plus("close");
2000
if(restore_loglevel){
2001
/* Restores kernel loglevel to default */
2002
ret = klogctl(7, NULL, 0);
2004
perror_plus("klogctl");
2007
#endif /* __linux__ */
2008
/* Lower privileges */
2010
if(take_down_interface){
2011
/* Lower privileges */
2014
perror_plus("seteuid");
2017
/* Lower privileges permanently */
2020
perror_plus("setuid");
2029
ret = init_gnutls_global(pubkey, seckey);
2031
fprintf(stderr, "Mandos plugin mandos-client: "
2032
"init_gnutls_global failed\n");
2033
exitcode = EX_UNAVAILABLE;
2036
gnutls_initialized = true;
2043
if(mkdtemp(tempdir) == NULL){
2044
perror_plus("mkdtemp");
2047
tempdir_created = true;
2053
if(not init_gpgme(pubkey, seckey, tempdir)){
2054
fprintf(stderr, "Mandos plugin mandos-client: "
2055
"init_gpgme failed\n");
2056
exitcode = EX_UNAVAILABLE;
2059
gpgme_initialized = true;
2066
if(connect_to != NULL){
2067
/* Connect directly, do not use Zeroconf */
2068
/* (Mainly meant for debugging) */
2069
char *address = strrchr(connect_to, ':');
2070
if(address == NULL){
2071
fprintf(stderr, "Mandos plugin mandos-client: "
2072
"No colon in address\n");
2073
exitcode = EX_USAGE;
2083
tmpmax = strtoimax(address+1, &tmp, 10);
2084
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2085
or tmpmax != (uint16_t)tmpmax){
2086
fprintf(stderr, "Mandos plugin mandos-client: "
2087
"Bad port number\n");
2088
exitcode = EX_USAGE;
2096
port = (uint16_t)tmpmax;
2098
/* Colon in address indicates IPv6 */
2100
if(strchr(connect_to, ':') != NULL){
2102
/* Accept [] around IPv6 address - see RFC 5952 */
2103
if(connect_to[0] == '[' and address[-1] == ']')
2111
address = connect_to;
2117
while(not quit_now){
2118
ret = start_mandos_communication(address, port, if_index, af);
2119
if(quit_now or ret == 0){
2123
fprintf(stderr, "Mandos plugin mandos-client: "
2124
"Retrying in %d seconds\n", (int)retry_interval);
2126
sleep((int)retry_interval);
2130
exitcode = EXIT_SUCCESS;
554
2141
AvahiServerConfig config;
555
AvahiSServiceBrowser *sb = NULL;
556
const char db[] = "--debug";
559
int returncode = EXIT_SUCCESS;
560
char *basename = rindex(argv[0], '/');
561
if(basename == NULL){
567
char *program_name = malloc(strlen(basename) + sizeof(db));
569
if (program_name == NULL){
574
program_name[0] = '\0';
576
for (int i = 1; i < argc; i++){
577
if (not strncmp(argv[i], db, 5)){
578
strcat(strcat(strcat(program_name, db ), "="), basename);
579
if(not strcmp(argv[i], db) or not strcmp(argv[i], program_name)){
587
avahi_set_log_function(empty_log);
590
/* Initialize the psuedo-RNG */
593
/* Allocate main loop object */
594
if (!(simple_poll = avahi_simple_poll_new())) {
595
fprintf(stderr, "Failed to create simple poll object.\n");
600
/* Do not publish any local records */
2142
/* Do not publish any local Zeroconf records */
601
2143
avahi_server_config_init(&config);
602
2144
config.publish_hinfo = 0;
603
2145
config.publish_addresses = 0;
604
2146
config.publish_workstation = 0;
605
2147
config.publish_domain = 0;
607
2149
/* Allocate a new server */
608
server = avahi_server_new(avahi_simple_poll_get(simple_poll), &config, NULL, NULL, &error);
610
/* Free the configuration data */
2150
mc.server = avahi_server_new(avahi_simple_poll_get
2151
(mc.simple_poll), &config, NULL,
2154
/* Free the Avahi configuration data */
611
2155
avahi_server_config_free(&config);
613
/* Check if creating the server object succeeded */
615
fprintf(stderr, "Failed to create server: %s\n", avahi_strerror(error));
616
returncode = EXIT_FAILURE;
620
/* Create the service browser */
621
if (!(sb = avahi_s_service_browser_new(server, if_nametoindex("eth0"), AVAHI_PROTO_INET6, "_mandos._tcp", NULL, 0, browse_callback, server))) {
622
fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_server_errno(server)));
623
returncode = EXIT_FAILURE;
627
/* Run the main loop */
630
fprintf(stderr, "Starting avahi loop search\n");
633
avahi_simple_poll_loop(simple_poll);
638
fprintf(stderr, "%s exiting\n", argv[0]);
643
avahi_s_service_browser_free(sb);
646
avahi_server_free(server);
649
avahi_simple_poll_free(simple_poll);
2158
/* Check if creating the Avahi server object succeeded */
2159
if(mc.server == NULL){
2160
fprintf(stderr, "Mandos plugin mandos-client: "
2161
"Failed to create Avahi server: %s\n",
2162
avahi_strerror(error));
2163
exitcode = EX_UNAVAILABLE;
2171
/* Create the Avahi service browser */
2172
sb = avahi_s_service_browser_new(mc.server, if_index,
2173
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2174
NULL, 0, browse_callback, NULL);
2176
fprintf(stderr, "Mandos plugin mandos-client: "
2177
"Failed to create service browser: %s\n",
2178
avahi_strerror(avahi_server_errno(mc.server)));
2179
exitcode = EX_UNAVAILABLE;
2187
/* Run the main loop */
2190
fprintf(stderr, "Mandos plugin mandos-client: "
2191
"Starting Avahi loop search\n");
2194
ret = avahi_loop_with_timeout(mc.simple_poll,
2195
(int)(retry_interval * 1000));
2197
fprintf(stderr, "Mandos plugin mandos-client: "
2198
"avahi_loop_with_timeout exited %s\n",
2199
(ret == 0) ? "successfully" : "with error");
2205
fprintf(stderr, "Mandos plugin mandos-client: "
2206
"%s exiting\n", argv[0]);
2209
/* Cleanup things */
2211
avahi_s_service_browser_free(sb);
2213
if(mc.server != NULL)
2214
avahi_server_free(mc.server);
2216
if(mc.simple_poll != NULL)
2217
avahi_simple_poll_free(mc.simple_poll);
2219
if(gnutls_initialized){
2220
gnutls_certificate_free_credentials(mc.cred);
2221
gnutls_global_deinit();
2222
gnutls_dh_params_deinit(mc.dh_params);
2225
if(gpgme_initialized){
2226
gpgme_release(mc.ctx);
2229
/* Cleans up the circular linked list of Mandos servers the client
2231
if(mc.current_server != NULL){
2232
mc.current_server->prev->next = NULL;
2233
while(mc.current_server != NULL){
2234
server *next = mc.current_server->next;
2235
free(mc.current_server);
2236
mc.current_server = next;
2240
/* XXX run network hooks "stop" here */
2242
/* Take down the network interface */
2243
if(take_down_interface){
2244
/* Re-raise priviliges */
2248
perror_plus("seteuid");
2251
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2253
perror_plus("ioctl SIOCGIFFLAGS");
2254
} else if(network.ifr_flags & IFF_UP){
2255
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2256
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2258
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2261
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2263
perror_plus("close");
2265
/* Lower privileges permanently */
2269
perror_plus("setuid");
2274
/* Removes the GPGME temp directory and all files inside */
2275
if(tempdir_created){
2276
struct dirent **direntries = NULL;
2277
struct dirent *direntry = NULL;
2278
int numentries = scandir(tempdir, &direntries, notdotentries,
2280
if (numentries > 0){
2281
for(int i = 0; i < numentries; i++){
2282
direntry = direntries[i];
2283
char *fullname = NULL;
2284
ret = asprintf(&fullname, "%s/%s", tempdir,
2287
perror_plus("asprintf");
2290
ret = remove(fullname);
2292
fprintf(stderr, "Mandos plugin mandos-client: "
2293
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2299
/* need to clean even if 0 because man page doesn't specify */
2301
if (numentries == -1){
2302
perror_plus("scandir");
2304
ret = rmdir(tempdir);
2305
if(ret == -1 and errno != ENOENT){
2306
perror_plus("rmdir");
2311
sigemptyset(&old_sigterm_action.sa_mask);
2312
old_sigterm_action.sa_handler = SIG_DFL;
2313
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2314
&old_sigterm_action,
2317
perror_plus("sigaction");
2320
ret = raise(signal_received);
2321
} while(ret != 0 and errno == EINTR);
2323
perror_plus("raise");
2326
TEMP_FAILURE_RETRY(pause());