47
103
#include <avahi-common/malloc.h>
48
104
#include <avahi-common/error.h>
51
#include <sys/types.h> /* socket(), inet_pton() */
52
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
53
struct in6_addr, inet_pton() */
54
#include <gnutls/gnutls.h> /* All GnuTLS stuff */
55
#include <gnutls/openpgp.h> /* GnuTLS with openpgp stuff */
57
#include <unistd.h> /* close() */
58
#include <netinet/in.h>
59
#include <stdbool.h> /* true */
60
#include <string.h> /* memset */
61
#include <arpa/inet.h> /* inet_pton() */
62
#include <iso646.h> /* not */
65
#include <errno.h> /* perror() */
72
#define CERT_ROOT "/conf/conf.d/cryptkeyreq/"
74
#define CERTFILE CERT_ROOT "openpgp-client.txt"
75
#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,
76
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"
79
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 */
82
gnutls_session_t session;
151
AvahiSimplePoll *simple_poll;
83
153
gnutls_certificate_credentials_t cred;
154
unsigned int dh_bits;
84
155
gnutls_dh_params_t dh_params;
88
ssize_t pgp_packet_decrypt (char *packet, size_t packet_size,
89
char **new_packet, const char *homedir){
90
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,
196
AvahiIfIndex if_index,
199
server *new_server = malloc(sizeof(server));
200
if(new_server == NULL){
201
perror_plus("malloc");
204
*new_server = (server){ .ip = strdup(ip),
206
.if_index = if_index,
208
if(new_server->ip == NULL){
209
perror_plus("strdup");
212
/* Special case of first server */
213
if (mc.current_server == NULL){
214
new_server->next = new_server;
215
new_server->prev = new_server;
216
mc.current_server = new_server;
217
/* Place the new server last in the list */
219
new_server->next = mc.current_server;
220
new_server->prev = mc.current_server->prev;
221
new_server->prev->next = new_server;
222
mc.current_server->prev = new_server;
224
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
226
perror_plus("clock_gettime");
235
static bool init_gpgme(const char *seckey,
236
const char *pubkey, const char *tempdir){
94
ssize_t new_packet_capacity = 0;
95
ssize_t new_packet_length = 0;
96
238
gpgme_engine_info_t engine_info;
99
fprintf(stderr, "Trying to decrypt OpenPGP packet\n");
242
* Helper function to insert pub and seckey to the engine keyring.
244
bool import_key(const char *filename){
247
gpgme_data_t pgp_data;
249
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
255
rc = gpgme_data_new_from_fd(&pgp_data, fd);
256
if(rc != GPG_ERR_NO_ERROR){
257
fprintf(stderr, "Mandos plugin mandos-client: "
258
"bad gpgme_data_new_from_fd: %s: %s\n",
259
gpgme_strsource(rc), gpgme_strerror(rc));
263
rc = gpgme_op_import(mc.ctx, pgp_data);
264
if(rc != GPG_ERR_NO_ERROR){
265
fprintf(stderr, "Mandos plugin mandos-client: "
266
"bad gpgme_op_import: %s: %s\n",
267
gpgme_strsource(rc), gpgme_strerror(rc));
271
ret = (int)TEMP_FAILURE_RETRY(close(fd));
273
perror_plus("close");
275
gpgme_data_release(pgp_data);
280
fprintf(stderr, "Mandos plugin mandos-client: "
281
"Initializing GPGME\n");
103
285
gpgme_check_version(NULL);
104
gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
286
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
287
if(rc != GPG_ERR_NO_ERROR){
288
fprintf(stderr, "Mandos plugin mandos-client: "
289
"bad gpgme_engine_check_version: %s: %s\n",
290
gpgme_strsource(rc), gpgme_strerror(rc));
106
/* Set GPGME home directory */
107
rc = gpgme_get_engine_info (&engine_info);
108
if (rc != GPG_ERR_NO_ERROR){
109
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
294
/* Set GPGME home directory for the OpenPGP engine only */
295
rc = gpgme_get_engine_info(&engine_info);
296
if(rc != GPG_ERR_NO_ERROR){
297
fprintf(stderr, "Mandos plugin mandos-client: "
298
"bad gpgme_get_engine_info: %s: %s\n",
110
299
gpgme_strsource(rc), gpgme_strerror(rc));
113
302
while(engine_info != NULL){
114
303
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
115
304
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
116
engine_info->file_name, homedir);
305
engine_info->file_name, tempdir);
119
308
engine_info = engine_info->next;
121
310
if(engine_info == NULL){
122
fprintf(stderr, "Could not set home dir to %s\n", homedir);
126
/* Create new GPGME data buffer from packet buffer */
127
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
128
if (rc != GPG_ERR_NO_ERROR){
129
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
311
fprintf(stderr, "Mandos plugin mandos-client: "
312
"Could not set GPGME home dir to %s\n", tempdir);
316
/* Create new GPGME "context" */
317
rc = gpgme_new(&(mc.ctx));
318
if(rc != GPG_ERR_NO_ERROR){
319
fprintf(stderr, "Mandos plugin mandos-client: "
320
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
325
if(not import_key(pubkey) or not import_key(seckey)){
333
* Decrypt OpenPGP data.
334
* Returns -1 on error
336
static ssize_t pgp_packet_decrypt(const char *cryptotext,
339
gpgme_data_t dh_crypto, dh_plain;
342
size_t plaintext_capacity = 0;
343
ssize_t plaintext_length = 0;
346
fprintf(stderr, "Mandos plugin mandos-client: "
347
"Trying to decrypt OpenPGP data\n");
350
/* Create new GPGME data buffer from memory cryptotext */
351
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
353
if(rc != GPG_ERR_NO_ERROR){
354
fprintf(stderr, "Mandos plugin mandos-client: "
355
"bad gpgme_data_new_from_mem: %s: %s\n",
130
356
gpgme_strsource(rc), gpgme_strerror(rc));
134
360
/* Create new empty GPGME data buffer for the plaintext */
135
361
rc = gpgme_data_new(&dh_plain);
136
if (rc != GPG_ERR_NO_ERROR){
137
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
138
gpgme_strsource(rc), gpgme_strerror(rc));
142
/* Create new GPGME "context" */
143
rc = gpgme_new(&ctx);
144
if (rc != GPG_ERR_NO_ERROR){
145
fprintf(stderr, "bad gpgme_new: %s: %s\n",
146
gpgme_strsource(rc), gpgme_strerror(rc));
150
/* Decrypt data from the FILE pointer to the plaintext data
152
rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
153
if (rc != GPG_ERR_NO_ERROR){
154
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
155
gpgme_strsource(rc), gpgme_strerror(rc));
160
fprintf(stderr, "Decryption of OpenPGP packet succeeded\n");
164
gpgme_decrypt_result_t result;
165
result = gpgme_op_decrypt_result(ctx);
167
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
169
fprintf(stderr, "Unsupported algorithm: %s\n",
170
result->unsupported_algorithm);
171
fprintf(stderr, "Wrong key usage: %d\n",
172
result->wrong_key_usage);
173
if(result->file_name != NULL){
174
fprintf(stderr, "File name: %s\n", result->file_name);
176
gpgme_recipient_t recipient;
177
recipient = result->recipients;
362
if(rc != GPG_ERR_NO_ERROR){
363
fprintf(stderr, "Mandos plugin mandos-client: "
364
"bad gpgme_data_new: %s: %s\n",
365
gpgme_strsource(rc), gpgme_strerror(rc));
366
gpgme_data_release(dh_crypto);
370
/* Decrypt data from the cryptotext data buffer to the plaintext
372
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
373
if(rc != GPG_ERR_NO_ERROR){
374
fprintf(stderr, "Mandos plugin mandos-client: "
375
"bad gpgme_op_decrypt: %s: %s\n",
376
gpgme_strsource(rc), gpgme_strerror(rc));
377
plaintext_length = -1;
379
gpgme_decrypt_result_t result;
380
result = gpgme_op_decrypt_result(mc.ctx);
382
fprintf(stderr, "Mandos plugin mandos-client: "
383
"gpgme_op_decrypt_result failed\n");
385
fprintf(stderr, "Mandos plugin mandos-client: "
386
"Unsupported algorithm: %s\n",
387
result->unsupported_algorithm);
388
fprintf(stderr, "Mandos plugin mandos-client: "
389
"Wrong key usage: %u\n",
390
result->wrong_key_usage);
391
if(result->file_name != NULL){
392
fprintf(stderr, "Mandos plugin mandos-client: "
393
"File name: %s\n", result->file_name);
395
gpgme_recipient_t recipient;
396
recipient = result->recipients;
179
397
while(recipient != NULL){
180
fprintf(stderr, "Public key algorithm: %s\n",
398
fprintf(stderr, "Mandos plugin mandos-client: "
399
"Public key algorithm: %s\n",
181
400
gpgme_pubkey_algo_name(recipient->pubkey_algo));
182
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
183
fprintf(stderr, "Secret key available: %s\n",
401
fprintf(stderr, "Mandos plugin mandos-client: "
402
"Key ID: %s\n", recipient->keyid);
403
fprintf(stderr, "Mandos plugin mandos-client: "
404
"Secret key available: %s\n",
184
405
recipient->status == GPG_ERR_NO_SECKEY
186
407
recipient = recipient->next;
192
/* Delete the GPGME FILE pointer cryptotext data buffer */
193
gpgme_data_release(dh_crypto);
415
fprintf(stderr, "Mandos plugin mandos-client: "
416
"Decryption of OpenPGP data succeeded\n");
195
419
/* Seek back to the beginning of the GPGME plaintext data buffer */
196
gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET);
420
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
421
perror_plus("gpgme_data_seek");
422
plaintext_length = -1;
200
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
201
*new_packet = realloc(*new_packet,
202
(unsigned int)new_packet_capacity
204
if (*new_packet == NULL){
208
new_packet_capacity += BUFFER_SIZE;
428
plaintext_capacity = incbuffer(plaintext,
429
(size_t)plaintext_length,
431
if(plaintext_capacity == 0){
432
perror_plus("incbuffer");
433
plaintext_length = -1;
211
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length,
437
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
213
439
/* Print the data, if any */
218
perror("gpgme_data_read");
221
new_packet_length += ret;
224
/* FIXME: check characters before printing to screen so to not print
225
terminal control characters */
227
/* fprintf(stderr, "decrypted password is: "); */
228
/* fwrite(*new_packet, 1, new_packet_length, stderr); */
229
/* fprintf(stderr, "\n"); */
445
perror_plus("gpgme_data_read");
446
plaintext_length = -1;
449
plaintext_length += ret;
453
fprintf(stderr, "Mandos plugin mandos-client: "
454
"Decrypted password is: ");
455
for(ssize_t i = 0; i < plaintext_length; i++){
456
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
458
fprintf(stderr, "\n");
463
/* Delete the GPGME cryptotext data buffer */
464
gpgme_data_release(dh_crypto);
232
466
/* Delete the GPGME plaintext data buffer */
233
467
gpgme_data_release(dh_plain);
234
return new_packet_length;
468
return plaintext_length;
237
static const char * safer_gnutls_strerror (int value) {
238
const char *ret = gnutls_strerror (value);
471
static const char * safer_gnutls_strerror(int value){
472
const char *ret = gnutls_strerror(value); /* Spurious warning from
473
-Wunreachable-code */
240
475
ret = "(unknown)";
244
void debuggnutls(__attribute__((unused)) int level,
246
fprintf(stderr, "%s", string);
479
/* GnuTLS log function callback */
480
static void debuggnutls(__attribute__((unused)) int level,
482
fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
249
int initgnutls(encrypted_session *es){
485
static int init_gnutls_global(const char *pubkeyfilename,
486
const char *seckeyfilename){
254
fprintf(stderr, "Initializing GnuTLS\n");
490
fprintf(stderr, "Mandos plugin mandos-client: "
491
"Initializing GnuTLS\n");
257
if ((ret = gnutls_global_init ())
258
!= GNUTLS_E_SUCCESS) {
259
fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
494
ret = gnutls_global_init();
495
if(ret != GNUTLS_E_SUCCESS){
496
fprintf(stderr, "Mandos plugin mandos-client: "
497
"GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
502
/* "Use a log level over 10 to enable all debugging options."
264
505
gnutls_global_set_log_level(11);
265
506
gnutls_global_set_log_function(debuggnutls);
268
/* openpgp credentials */
269
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
270
!= GNUTLS_E_SUCCESS) {
271
fprintf (stderr, "memory error: %s\n",
272
safer_gnutls_strerror(ret));
509
/* OpenPGP credentials */
510
ret = gnutls_certificate_allocate_credentials(&mc.cred);
511
if(ret != GNUTLS_E_SUCCESS){
512
fprintf(stderr, "Mandos plugin mandos-client: "
513
"GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
514
gnutls_global_deinit();
277
fprintf(stderr, "Attempting to use OpenPGP certificate %s"
278
" and keyfile %s as GnuTLS credentials\n", CERTFILE,
519
fprintf(stderr, "Mandos plugin mandos-client: "
520
"Attempting to use OpenPGP public key %s and"
521
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
282
525
ret = gnutls_certificate_set_openpgp_key_file
283
(es->cred, CERTFILE, KEYFILE, GNUTLS_OPENPGP_FMT_BASE64);
284
if (ret != GNUTLS_E_SUCCESS) {
286
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s',"
288
ret, CERTFILE, KEYFILE);
289
fprintf(stdout, "The Error is: %s\n",
290
safer_gnutls_strerror(ret));
294
//GnuTLS server initialization
295
if ((ret = gnutls_dh_params_init (&es->dh_params))
296
!= GNUTLS_E_SUCCESS) {
297
fprintf (stderr, "Error in dh parameter initialization: %s\n",
298
safer_gnutls_strerror(ret));
302
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
303
!= GNUTLS_E_SUCCESS) {
304
fprintf (stderr, "Error in prime generation: %s\n",
305
safer_gnutls_strerror(ret));
309
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
311
// GnuTLS session creation
312
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
313
!= GNUTLS_E_SUCCESS){
314
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
315
safer_gnutls_strerror(ret));
318
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
319
!= GNUTLS_E_SUCCESS) {
320
fprintf(stderr, "Syntax error at: %s\n", err);
321
fprintf(stderr, "GnuTLS error: %s\n",
322
safer_gnutls_strerror(ret));
326
if ((ret = gnutls_credentials_set
327
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
328
!= GNUTLS_E_SUCCESS) {
329
fprintf(stderr, "Error setting a credentials set: %s\n",
330
safer_gnutls_strerror(ret));
526
(mc.cred, pubkeyfilename, seckeyfilename,
527
GNUTLS_OPENPGP_FMT_BASE64);
528
if(ret != GNUTLS_E_SUCCESS){
530
"Mandos plugin mandos-client: "
531
"Error[%d] while reading the OpenPGP key pair ('%s',"
532
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
533
fprintf(stderr, "Mandos plugin mandos-client: "
534
"The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
538
/* GnuTLS server initialization */
539
ret = gnutls_dh_params_init(&mc.dh_params);
540
if(ret != GNUTLS_E_SUCCESS){
541
fprintf(stderr, "Mandos plugin mandos-client: "
542
"Error in GnuTLS DH parameter initialization:"
543
" %s\n", safer_gnutls_strerror(ret));
546
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
547
if(ret != GNUTLS_E_SUCCESS){
548
fprintf(stderr, "Mandos plugin mandos-client: "
549
"Error in GnuTLS prime generation: %s\n",
550
safer_gnutls_strerror(ret));
554
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
560
gnutls_certificate_free_credentials(mc.cred);
561
gnutls_global_deinit();
562
gnutls_dh_params_deinit(mc.dh_params);
566
static int init_gnutls_session(gnutls_session_t *session){
568
/* GnuTLS session creation */
570
ret = gnutls_init(session, GNUTLS_SERVER);
574
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
575
if(ret != GNUTLS_E_SUCCESS){
576
fprintf(stderr, "Mandos plugin mandos-client: "
577
"Error in GnuTLS session initialization: %s\n",
578
safer_gnutls_strerror(ret));
584
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
586
gnutls_deinit(*session);
589
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
590
if(ret != GNUTLS_E_SUCCESS){
591
fprintf(stderr, "Mandos plugin mandos-client: "
592
"Syntax error at: %s\n", err);
593
fprintf(stderr, "Mandos plugin mandos-client: "
594
"GnuTLS error: %s\n", safer_gnutls_strerror(ret));
595
gnutls_deinit(*session);
601
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
604
gnutls_deinit(*session);
607
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
608
if(ret != GNUTLS_E_SUCCESS){
609
fprintf(stderr, "Mandos plugin mandos-client: "
610
"Error setting GnuTLS credentials: %s\n",
611
safer_gnutls_strerror(ret));
612
gnutls_deinit(*session);
334
616
/* ignore client certificate if any. */
335
gnutls_certificate_server_set_request (es->session,
617
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
338
gnutls_dh_set_prime_bits (es->session, DH_BITS);
619
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
343
void empty_log(__attribute__((unused)) AvahiLogLevel level,
344
__attribute__((unused)) const char *txt){}
624
/* Avahi log function callback */
625
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
626
__attribute__((unused)) const char *txt){}
346
int start_mandos_communication(const char *ip, uint16_t port,
347
unsigned int if_index){
349
struct sockaddr_in6 to;
350
encrypted_session es;
628
/* Called when a Mandos server is found */
629
static int start_mandos_communication(const char *ip, uint16_t port,
630
AvahiIfIndex if_index,
632
int ret, tcp_sd = -1;
635
struct sockaddr_in in;
636
struct sockaddr_in6 in6;
351
638
char *buffer = NULL;
352
char *decrypted_buffer;
639
char *decrypted_buffer = NULL;
353
640
size_t buffer_length = 0;
354
641
size_t buffer_capacity = 0;
355
ssize_t decrypted_buffer_size;
358
char interface[IF_NAMESIZE];
361
fprintf(stderr, "Setting up a tcp connection to %s\n", ip);
364
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
370
if(if_indextoname(if_index, interface) == NULL){
372
perror("if_indextoname");
378
fprintf(stderr, "Binding to interface %s\n", interface);
381
memset(&to,0,sizeof(to)); /* Spurious warning */
382
to.sin6_family = AF_INET6;
383
ret = inet_pton(AF_INET6, ip, &to.sin6_addr);
644
gnutls_session_t session;
645
int pf; /* Protocol family */
662
fprintf(stderr, "Mandos plugin mandos-client: "
663
"Bad address family: %d\n", af);
668
ret = init_gnutls_session(&session);
674
fprintf(stderr, "Mandos plugin mandos-client: "
675
"Setting up a TCP connection to %s, port %" PRIu16
679
tcp_sd = socket(pf, SOCK_STREAM, 0);
682
perror_plus("socket");
692
memset(&to, 0, sizeof(to));
694
to.in6.sin6_family = (sa_family_t)af;
695
ret = inet_pton(af, ip, &to.in6.sin6_addr);
697
to.in.sin_family = (sa_family_t)af;
698
ret = inet_pton(af, ip, &to.in.sin_addr);
702
perror_plus("inet_pton");
389
fprintf(stderr, "Bad address: %s\n", ip);
392
to.sin6_port = htons(port); /* Spurious warning */
394
to.sin6_scope_id = (uint32_t)if_index;
397
fprintf(stderr, "Connection to: %s\n", ip);
400
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
406
ret = initgnutls (&es);
412
gnutls_transport_set_ptr (es.session,
413
(gnutls_transport_ptr_t) tcp_sd);
416
fprintf(stderr, "Establishing TLS session with %s\n", ip);
419
ret = gnutls_handshake (es.session);
421
if (ret != GNUTLS_E_SUCCESS){
708
fprintf(stderr, "Mandos plugin mandos-client: "
709
"Bad address: %s\n", ip);
714
to.in6.sin6_port = htons(port); /* Spurious warnings from
716
-Wunreachable-code */
718
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
719
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
721
if(if_index == AVAHI_IF_UNSPEC){
722
fprintf(stderr, "Mandos plugin mandos-client: "
723
"An IPv6 link-local address is incomplete"
724
" without a network interface\n");
728
/* Set the network interface number as scope */
729
to.in6.sin6_scope_id = (uint32_t)if_index;
732
to.in.sin_port = htons(port); /* Spurious warnings from
734
-Wunreachable-code */
743
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
744
char interface[IF_NAMESIZE];
745
if(if_indextoname((unsigned int)if_index, interface) == NULL){
746
perror_plus("if_indextoname");
748
fprintf(stderr, "Mandos plugin mandos-client: "
749
"Connection to: %s%%%s, port %" PRIu16 "\n",
750
ip, interface, port);
753
fprintf(stderr, "Mandos plugin mandos-client: "
754
"Connection to: %s, port %" PRIu16 "\n", ip, port);
756
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
757
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
760
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
763
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
767
perror_plus("inet_ntop");
769
if(strcmp(addrstr, ip) != 0){
770
fprintf(stderr, "Mandos plugin mandos-client: "
771
"Canonical address form: %s\n", addrstr);
782
ret = connect(tcp_sd, &to.in6, sizeof(to));
784
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
787
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
789
perror_plus("connect");
800
const char *out = mandos_protocol_version;
803
size_t out_size = strlen(out);
804
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
805
out_size - written));
808
perror_plus("write");
812
written += (size_t)ret;
813
if(written < out_size){
816
if(out == mandos_protocol_version){
831
fprintf(stderr, "Mandos plugin mandos-client: "
832
"Establishing TLS session with %s\n", ip);
840
/* Spurious warning from -Wint-to-pointer-cast */
841
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
849
ret = gnutls_handshake(session);
854
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
856
if(ret != GNUTLS_E_SUCCESS){
423
fprintf(stderr, "\n*** Handshake failed ***\n");
858
fprintf(stderr, "Mandos plugin mandos-client: "
859
"*** GnuTLS Handshake failed ***\n");
430
//Retrieve OpenPGP packet that contains the wanted password
866
/* Read OpenPGP packet that contains the wanted password */
433
fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
869
fprintf(stderr, "Mandos plugin mandos-client: "
870
"Retrieving OpenPGP encrypted password from %s\n", ip);
438
if (buffer_length + BUFFER_SIZE > buffer_capacity){
439
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
444
buffer_capacity += BUFFER_SIZE;
447
ret = gnutls_record_recv
448
(es.session, buffer+buffer_length, BUFFER_SIZE);
880
buffer_capacity = incbuffer(&buffer, buffer_length,
882
if(buffer_capacity == 0){
884
perror_plus("incbuffer");
894
sret = gnutls_record_recv(session, buffer+buffer_length,
454
901
case GNUTLS_E_INTERRUPTED:
455
902
case GNUTLS_E_AGAIN:
457
904
case GNUTLS_E_REHANDSHAKE:
458
ret = gnutls_handshake (es.session);
460
fprintf(stderr, "\n*** Handshake failed ***\n");
906
ret = gnutls_handshake(session);
912
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
914
fprintf(stderr, "Mandos plugin mandos-client: "
915
"*** GnuTLS Re-handshake failed ***\n");
467
fprintf(stderr, "Unknown error while reading data from"
468
" encrypted session with mandos server\n");
470
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
922
fprintf(stderr, "Mandos plugin mandos-client: "
923
"Unknown error while reading data from"
924
" encrypted session with Mandos server\n");
925
gnutls_bye(session, GNUTLS_SHUT_RDWR);
474
buffer_length += (size_t) ret;
478
if (buffer_length > 0){
930
buffer_length += (size_t) sret;
935
fprintf(stderr, "Mandos plugin mandos-client: "
936
"Closing TLS session\n");
945
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
950
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
952
if(buffer_length > 0){
953
ssize_t decrypted_buffer_size;
479
954
decrypted_buffer_size = pgp_packet_decrypt(buffer,
483
if (decrypted_buffer_size >= 0){
484
while(written < decrypted_buffer_size){
485
ret = (int)fwrite (decrypted_buffer + written, 1,
486
(size_t)decrypted_buffer_size - written,
957
if(decrypted_buffer_size >= 0){
960
while(written < (size_t) decrypted_buffer_size){
966
ret = (int)fwrite(decrypted_buffer + written, 1,
967
(size_t)decrypted_buffer_size - written,
488
969
if(ret == 0 and ferror(stdout)){
490
fprintf(stderr, "Error writing encrypted data: %s\n",
972
fprintf(stderr, "Mandos plugin mandos-client: "
973
"Error writing encrypted data: %s\n",
491
974
strerror(errno));
496
979
written += (size_t)ret;
498
free(decrypted_buffer);
985
/* Shutdown procedure */
990
free(decrypted_buffer);
993
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
999
perror_plus("close");
1001
gnutls_deinit(session);
507
fprintf(stderr, "Closing TLS session\n");
511
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
514
gnutls_deinit (es.session);
515
gnutls_certificate_free_credentials (es.cred);
516
gnutls_global_deinit ();
520
static AvahiSimplePoll *simple_poll = NULL;
521
static AvahiServer *server = NULL;
523
static void resolve_callback(
524
AvahiSServiceResolver *r,
525
AvahiIfIndex interface,
526
AVAHI_GCC_UNUSED AvahiProtocol protocol,
527
AvahiResolverEvent event,
531
const char *host_name,
532
const AvahiAddress *address,
534
AVAHI_GCC_UNUSED AvahiStringList *txt,
535
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
536
AVAHI_GCC_UNUSED void* userdata) {
538
assert(r); /* Spurious warning */
1011
static void resolve_callback(AvahiSServiceResolver *r,
1012
AvahiIfIndex interface,
1013
AvahiProtocol proto,
1014
AvahiResolverEvent event,
1018
const char *host_name,
1019
const AvahiAddress *address,
1021
AVAHI_GCC_UNUSED AvahiStringList *txt,
1022
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1024
AVAHI_GCC_UNUSED void* userdata){
540
1027
/* Called whenever a service has been resolved successfully or
545
1036
case AVAHI_RESOLVER_FAILURE:
546
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of"
547
" type '%s' in domain '%s': %s\n", name, type, domain,
548
avahi_strerror(avahi_server_errno(server)));
1037
fprintf(stderr, "Mandos plugin mandos-client: "
1038
"(Avahi Resolver) Failed to resolve service '%s'"
1039
" of type '%s' in domain '%s': %s\n", name, type, domain,
1040
avahi_strerror(avahi_server_errno(mc.server)));
551
1043
case AVAHI_RESOLVER_FOUND:
553
1045
char ip[AVAHI_ADDRESS_STR_MAX];
554
1046
avahi_address_snprint(ip, sizeof(ip), address);
556
fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on"
557
" port %d\n", name, host_name, ip, port);
1048
fprintf(stderr, "Mandos plugin mandos-client: "
1049
"Mandos server \"%s\" found on %s (%s, %"
1050
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1051
ip, (intmax_t)interface, port);
559
int ret = start_mandos_communication(ip, port,
560
(unsigned int) interface);
1053
int ret = start_mandos_communication(ip, port, interface,
1054
avahi_proto_to_af(proto));
1056
avahi_simple_poll_quit(mc.simple_poll);
1058
ret = add_server(ip, port, interface,
1059
avahi_proto_to_af(proto));
566
1063
avahi_s_service_resolver_free(r);
569
static void browse_callback(
570
AvahiSServiceBrowser *b,
571
AvahiIfIndex interface,
572
AvahiProtocol protocol,
573
AvahiBrowserEvent event,
577
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
580
AvahiServer *s = userdata;
581
assert(b); /* Spurious warning */
583
/* Called whenever a new services becomes available on the LAN or
584
is removed from the LAN */
1066
static void browse_callback(AvahiSServiceBrowser *b,
1067
AvahiIfIndex interface,
1068
AvahiProtocol protocol,
1069
AvahiBrowserEvent event,
1073
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1075
AVAHI_GCC_UNUSED void* userdata){
1078
/* Called whenever a new services becomes available on the LAN or
1079
is removed from the LAN */
1087
case AVAHI_BROWSER_FAILURE:
1089
fprintf(stderr, "Mandos plugin mandos-client: "
1090
"(Avahi browser) %s\n",
1091
avahi_strerror(avahi_server_errno(mc.server)));
1092
avahi_simple_poll_quit(mc.simple_poll);
1095
case AVAHI_BROWSER_NEW:
1096
/* We ignore the returned Avahi resolver object. In the callback
1097
function we free it. If the Avahi server is terminated before
1098
the callback function is called the Avahi server will free the
1101
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1102
name, type, domain, protocol, 0,
1103
resolve_callback, NULL) == NULL)
1104
fprintf(stderr, "Mandos plugin mandos-client: "
1105
"Avahi: Failed to resolve service '%s': %s\n",
1106
name, avahi_strerror(avahi_server_errno(mc.server)));
1109
case AVAHI_BROWSER_REMOVE:
1112
case AVAHI_BROWSER_ALL_FOR_NOW:
1113
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1115
fprintf(stderr, "Mandos plugin mandos-client: "
1116
"No Mandos server found, still searching...\n");
1122
/* Signal handler that stops main loop after SIGTERM */
1123
static void handle_sigterm(int sig){
1128
signal_received = sig;
1129
int old_errno = errno;
1130
/* set main loop to exit */
1131
if(mc.simple_poll != NULL){
1132
avahi_simple_poll_quit(mc.simple_poll);
1137
bool get_flags(const char *ifname, struct ifreq *ifr){
1140
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1142
perror_plus("socket");
1145
strcpy(ifr->ifr_name, ifname);
1146
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1149
perror_plus("ioctl SIOCGIFFLAGS");
1156
bool good_flags(const char *ifname, const struct ifreq *ifr){
1158
/* Reject the loopback device */
1159
if(ifr->ifr_flags & IFF_LOOPBACK){
1161
fprintf(stderr, "Mandos plugin mandos-client: "
1162
"Rejecting loopback interface \"%s\"\n", ifname);
1166
/* Accept point-to-point devices only if connect_to is specified */
1167
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1169
fprintf(stderr, "Mandos plugin mandos-client: "
1170
"Accepting point-to-point interface \"%s\"\n", ifname);
1174
/* Otherwise, reject non-broadcast-capable devices */
1175
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1177
fprintf(stderr, "Mandos plugin mandos-client: "
1178
"Rejecting non-broadcast interface \"%s\"\n", ifname);
1182
/* Reject non-ARP interfaces (including dummy interfaces) */
1183
if(ifr->ifr_flags & IFF_NOARP){
1185
fprintf(stderr, "Mandos plugin mandos-client: "
1186
"Rejecting non-ARP interface \"%s\"\n", ifname);
1191
/* Accept this device */
1193
fprintf(stderr, "Mandos plugin mandos-client: "
1194
"Interface \"%s\" is good\n", ifname);
1200
* This function determines if a directory entry in /sys/class/net
1201
* corresponds to an acceptable network device.
1202
* (This function is passed to scandir(3) as a filter function.)
1204
int good_interface(const struct dirent *if_entry){
1205
if(if_entry->d_name[0] == '.'){
1210
if(not get_flags(if_entry->d_name, &ifr)){
1212
fprintf(stderr, "Mandos plugin mandos-client: "
1213
"Failed to get flags for interface \"%s\"\n",
1219
if(not good_flags(if_entry->d_name, &ifr)){
1226
* This function determines if a directory entry in /sys/class/net
1227
* corresponds to an acceptable network device which is up.
1228
* (This function is passed to scandir(3) as a filter function.)
1230
int up_interface(const struct dirent *if_entry){
1231
if(if_entry->d_name[0] == '.'){
1236
if(not get_flags(if_entry->d_name, &ifr)){
1238
fprintf(stderr, "Mandos plugin mandos-client: "
1239
"Failed to get flags for interface \"%s\"\n",
1245
/* Reject down interfaces */
1246
if(not (ifr.ifr_flags & IFF_UP)){
1248
fprintf(stderr, "Mandos plugin mandos-client: "
1249
"Rejecting down interface \"%s\"\n",
1255
/* Reject non-running interfaces */
1256
if(not (ifr.ifr_flags & IFF_RUNNING)){
1258
fprintf(stderr, "Mandos plugin mandos-client: "
1259
"Rejecting non-running interface \"%s\"\n",
1265
if(not good_flags(if_entry->d_name, &ifr)){
1271
int notdotentries(const struct dirent *direntry){
1272
/* Skip "." and ".." */
1273
if(direntry->d_name[0] == '.'
1274
and (direntry->d_name[1] == '\0'
1275
or (direntry->d_name[1] == '.'
1276
and direntry->d_name[2] == '\0'))){
1282
/* Is this directory entry a runnable program? */
1283
int runnable_hook(const struct dirent *direntry){
1287
if((direntry->d_name)[0] == '\0'){
1292
/* Save pointer to last character */
1293
char *end = strchr(direntry->d_name, '\0')-1;
1300
if(((direntry->d_name)[0] == '#')
1302
/* Temporary #name# */
1306
/* XXX more rules here */
1308
char *fullname = NULL;
1309
ret = asprintf(&fullname, "%s/%s", hookdir,
1312
perror_plus("asprintf");
1316
ret = stat(fullname, &st);
1319
perror_plus("Could not stat plugin");
1323
if(not (S_ISREG(st.st_mode))){
1324
/* Not a regular file */
1327
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1328
/* Not executable */
1334
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1336
struct timespec now;
1337
struct timespec waited_time;
1338
intmax_t block_time;
1341
if(mc.current_server == NULL){
1343
fprintf(stderr, "Mandos plugin mandos-client: "
1344
"Wait until first server is found. No timeout!\n");
1346
ret = avahi_simple_poll_iterate(s, -1);
1349
fprintf(stderr, "Mandos plugin mandos-client: "
1350
"Check current_server if we should run it,"
1353
/* the current time */
1354
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1356
perror_plus("clock_gettime");
1359
/* Calculating in ms how long time between now and server
1360
who we visted longest time ago. Now - last seen. */
1361
waited_time.tv_sec = (now.tv_sec
1362
- mc.current_server->last_seen.tv_sec);
1363
waited_time.tv_nsec = (now.tv_nsec
1364
- mc.current_server->last_seen.tv_nsec);
1365
/* total time is 10s/10,000ms.
1366
Converting to s from ms by dividing by 1,000,
1367
and ns to ms by dividing by 1,000,000. */
1368
block_time = ((retry_interval
1369
- ((intmax_t)waited_time.tv_sec * 1000))
1370
- ((intmax_t)waited_time.tv_nsec / 1000000));
1373
fprintf(stderr, "Mandos plugin mandos-client: "
1374
"Blocking for %" PRIdMAX " ms\n", block_time);
1377
if(block_time <= 0){
1378
ret = start_mandos_communication(mc.current_server->ip,
1379
mc.current_server->port,
1380
mc.current_server->if_index,
1381
mc.current_server->af);
1383
avahi_simple_poll_quit(mc.simple_poll);
1386
ret = clock_gettime(CLOCK_MONOTONIC,
1387
&mc.current_server->last_seen);
1389
perror_plus("clock_gettime");
1392
mc.current_server = mc.current_server->next;
1393
block_time = 0; /* Call avahi to find new Mandos
1394
servers, but don't block */
1397
ret = avahi_simple_poll_iterate(s, (int)block_time);
1400
if (ret > 0 or errno != EINTR){
1401
return (ret != 1) ? ret : 0;
1407
int main(int argc, char *argv[]){
1408
AvahiSServiceBrowser *sb = NULL;
1413
int exitcode = EXIT_SUCCESS;
1414
const char *interface = "";
1415
struct ifreq network;
1417
bool take_down_interface = false;
1420
char tempdir[] = "/tmp/mandosXXXXXX";
1421
bool tempdir_created = false;
1422
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1423
const char *seckey = PATHDIR "/" SECKEY;
1424
const char *pubkey = PATHDIR "/" PUBKEY;
1426
bool gnutls_initialized = false;
1427
bool gpgme_initialized = false;
1429
double retry_interval = 10; /* 10s between trying a server and
1430
retrying the same server again */
1432
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1433
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1438
/* Lower any group privileges we might have, just to be safe */
1442
perror_plus("setgid");
1445
/* Lower user privileges (temporarily) */
1449
perror_plus("seteuid");
1457
struct argp_option options[] = {
1458
{ .name = "debug", .key = 128,
1459
.doc = "Debug mode", .group = 3 },
1460
{ .name = "connect", .key = 'c',
1461
.arg = "ADDRESS:PORT",
1462
.doc = "Connect directly to a specific Mandos server",
1464
{ .name = "interface", .key = 'i',
1466
.doc = "Network interface that will be used to search for"
1469
{ .name = "seckey", .key = 's',
1471
.doc = "OpenPGP secret key file base name",
1473
{ .name = "pubkey", .key = 'p',
1475
.doc = "OpenPGP public key file base name",
1477
{ .name = "dh-bits", .key = 129,
1479
.doc = "Bit length of the prime number used in the"
1480
" Diffie-Hellman key exchange",
1482
{ .name = "priority", .key = 130,
1484
.doc = "GnuTLS priority string for the TLS handshake",
1486
{ .name = "delay", .key = 131,
1488
.doc = "Maximum delay to wait for interface startup",
1490
{ .name = "retry", .key = 132,
1492
.doc = "Retry interval used when denied by the mandos server",
1494
{ .name = "network-hook-dir", .key = 133,
1496
.doc = "Directory where network hooks are located",
1499
* These reproduce what we would get without ARGP_NO_HELP
1501
{ .name = "help", .key = '?',
1502
.doc = "Give this help list", .group = -1 },
1503
{ .name = "usage", .key = -3,
1504
.doc = "Give a short usage message", .group = -1 },
1505
{ .name = "version", .key = 'V',
1506
.doc = "Print program version", .group = -1 },
1510
error_t parse_opt(int key, char *arg,
1511
struct argp_state *state){
1514
case 128: /* --debug */
1517
case 'c': /* --connect */
1520
case 'i': /* --interface */
1523
case 's': /* --seckey */
1526
case 'p': /* --pubkey */
1529
case 129: /* --dh-bits */
1531
tmpmax = strtoimax(arg, &tmp, 10);
1532
if(errno != 0 or tmp == arg or *tmp != '\0'
1533
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1534
argp_error(state, "Bad number of DH bits");
1536
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1538
case 130: /* --priority */
1541
case 131: /* --delay */
1543
delay = strtof(arg, &tmp);
1544
if(errno != 0 or tmp == arg or *tmp != '\0'){
1545
argp_error(state, "Bad delay");
1547
case 132: /* --retry */
1549
retry_interval = strtod(arg, &tmp);
1550
if(errno != 0 or tmp == arg or *tmp != '\0'
1551
or (retry_interval * 1000) > INT_MAX
1552
or retry_interval < 0){
1553
argp_error(state, "Bad retry interval");
1556
case 133: /* --network-hook-dir */
1560
* These reproduce what we would get without ARGP_NO_HELP
1562
case '?': /* --help */
1563
argp_state_help(state, state->out_stream,
1564
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1565
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1566
case -3: /* --usage */
1567
argp_state_help(state, state->out_stream,
1568
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1569
case 'V': /* --version */
1570
fprintf(state->out_stream, "Mandos plugin mandos-client: ");
1571
fprintf(state->out_stream, "%s\n", argp_program_version);
1572
exit(argp_err_exit_status);
1575
return ARGP_ERR_UNKNOWN;
1580
struct argp argp = { .options = options, .parser = parse_opt,
1582
.doc = "Mandos client -- Get and decrypt"
1583
" passwords from a Mandos server" };
1584
ret = argp_parse(&argp, argc, argv,
1585
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
588
case AVAHI_BROWSER_FAILURE:
590
fprintf(stderr, "(Browser) %s\n",
591
avahi_strerror(avahi_server_errno(server)));
592
avahi_simple_poll_quit(simple_poll);
595
case AVAHI_BROWSER_NEW:
596
/* We ignore the returned resolver object. In the callback
597
function we free it. If the server is terminated before
598
the callback function is called the server will free
599
the resolver for us. */
601
if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
603
AVAHI_PROTO_INET6, 0,
604
resolve_callback, s)))
605
fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
606
avahi_strerror(avahi_server_errno(s)));
609
case AVAHI_BROWSER_REMOVE:
612
case AVAHI_BROWSER_ALL_FOR_NOW:
613
case AVAHI_BROWSER_CACHE_EXHAUSTED:
618
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
1592
perror_plus("argp_parse");
1593
exitcode = EX_OSERR;
1596
exitcode = EX_USAGE;
1602
/* Work around Debian bug #633582:
1603
<http://bugs.debian.org/633582> */
1606
/* Re-raise priviliges */
1610
perror_plus("seteuid");
1613
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
1614
int seckey_fd = open(seckey, O_RDONLY);
1615
if(seckey_fd == -1){
1616
perror_plus("open");
1618
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
1620
perror_plus("fstat");
1622
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1623
ret = fchown(seckey_fd, uid, gid);
1625
perror_plus("fchown");
1629
TEMP_FAILURE_RETRY(close(seckey_fd));
1633
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
1634
int pubkey_fd = open(pubkey, O_RDONLY);
1635
if(pubkey_fd == -1){
1636
perror_plus("open");
1638
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
1640
perror_plus("fstat");
1642
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1643
ret = fchown(pubkey_fd, uid, gid);
1645
perror_plus("fchown");
1649
TEMP_FAILURE_RETRY(close(pubkey_fd));
1653
/* Lower privileges */
1657
perror_plus("seteuid");
1661
/* Find network hooks and run them */
1663
struct dirent **direntries;
1664
struct dirent *direntry;
1665
int numhooks = scandir(hookdir, &direntries, runnable_hook,
1668
perror_plus("scandir");
1670
int devnull = open("/dev/null", O_RDONLY);
1671
for(int i = 0; i < numhooks; i++){
1672
direntry = direntries[0];
1673
char *fullname = NULL;
1674
ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name);
1676
perror_plus("asprintf");
1679
pid_t hook_pid = fork();
1682
dup2(devnull, STDIN_FILENO);
1684
dup2(STDERR_FILENO, STDOUT_FILENO);
1685
ret = setenv("DEVICE", interface, 1);
1687
perror_plus("setenv");
1690
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1692
perror_plus("setenv");
1695
ret = setenv("MODE", "start", 1);
1697
perror_plus("setenv");
1701
ret = asprintf(&delaystring, "%f", delay);
1703
perror_plus("asprintf");
1706
ret = setenv("DELAY", delaystring, 1);
1709
perror_plus("setenv");
1713
ret = execl(fullname, direntry->d_name, "start", NULL);
1714
perror_plus("execl");
1717
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1718
perror_plus("waitpid");
1722
if(WIFEXITED(status)){
1723
if(WEXITSTATUS(status) != 0){
1724
fprintf(stderr, "Mandos plugin mandos-client: "
1725
"Warning: network hook \"%s\" exited"
1726
" with status %d\n", direntry->d_name,
1727
WEXITSTATUS(status));
1731
} else if(WIFSIGNALED(status)){
1732
fprintf(stderr, "Mandos plugin mandos-client: "
1733
"Warning: network hook \"%s\" died by"
1734
" signal %d\n", direntry->d_name,
1739
fprintf(stderr, "Mandos plugin mandos-client: "
1740
"Warning: network hook \"%s\" crashed\n",
1756
avahi_set_log_function(empty_log);
1759
if(interface[0] == '\0'){
1760
struct dirent **direntries;
1761
/* First look for interfaces that are up */
1762
ret = scandir(sys_class_net, &direntries, up_interface,
1765
/* No up interfaces, look for any good interfaces */
1767
ret = scandir(sys_class_net, &direntries, good_interface,
1771
/* Pick the first interface returned */
1772
interface = strdup(direntries[0]->d_name);
1774
fprintf(stderr, "Mandos plugin mandos-client: "
1775
"Using interface \"%s\"\n", interface);
1777
if(interface == NULL){
1778
perror_plus("malloc");
1780
exitcode = EXIT_FAILURE;
1786
fprintf(stderr, "Mandos plugin mandos-client: "
1787
"Could not find a network interface\n");
1788
exitcode = EXIT_FAILURE;
1793
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1794
from the signal handler */
1795
/* Initialize the pseudo-RNG for Avahi */
1796
srand((unsigned int) time(NULL));
1797
mc.simple_poll = avahi_simple_poll_new();
1798
if(mc.simple_poll == NULL){
1799
fprintf(stderr, "Mandos plugin mandos-client: "
1800
"Avahi: Failed to create simple poll object.\n");
1801
exitcode = EX_UNAVAILABLE;
1805
sigemptyset(&sigterm_action.sa_mask);
1806
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1808
perror_plus("sigaddset");
1809
exitcode = EX_OSERR;
1812
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1814
perror_plus("sigaddset");
1815
exitcode = EX_OSERR;
1818
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1820
perror_plus("sigaddset");
1821
exitcode = EX_OSERR;
1824
/* Need to check if the handler is SIG_IGN before handling:
1825
| [[info:libc:Initial Signal Actions]] |
1826
| [[info:libc:Basic Signal Handling]] |
1828
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1830
perror_plus("sigaction");
1833
if(old_sigterm_action.sa_handler != SIG_IGN){
1834
ret = sigaction(SIGINT, &sigterm_action, NULL);
1836
perror_plus("sigaction");
1837
exitcode = EX_OSERR;
1841
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1843
perror_plus("sigaction");
1846
if(old_sigterm_action.sa_handler != SIG_IGN){
1847
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1849
perror_plus("sigaction");
1850
exitcode = EX_OSERR;
1854
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1856
perror_plus("sigaction");
1859
if(old_sigterm_action.sa_handler != SIG_IGN){
1860
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1862
perror_plus("sigaction");
1863
exitcode = EX_OSERR;
1868
/* If the interface is down, bring it up */
1869
if(strcmp(interface, "none") != 0){
1870
if_index = (AvahiIfIndex) if_nametoindex(interface);
1872
fprintf(stderr, "Mandos plugin mandos-client: "
1873
"No such interface: \"%s\"\n", interface);
1874
exitcode = EX_UNAVAILABLE;
1882
/* Re-raise priviliges */
1886
perror_plus("seteuid");
1890
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1891
messages about the network interface to mess up the prompt */
1892
ret = klogctl(8, NULL, 5);
1893
bool restore_loglevel = true;
1895
restore_loglevel = false;
1896
perror_plus("klogctl");
1898
#endif /* __linux__ */
1900
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1902
perror_plus("socket");
1903
exitcode = EX_OSERR;
1905
if(restore_loglevel){
1906
ret = klogctl(7, NULL, 0);
1908
perror_plus("klogctl");
1911
#endif /* __linux__ */
1912
/* Lower privileges */
1916
perror_plus("seteuid");
1920
strcpy(network.ifr_name, interface);
1921
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1923
perror_plus("ioctl SIOCGIFFLAGS");
1925
if(restore_loglevel){
1926
ret = klogctl(7, NULL, 0);
1928
perror_plus("klogctl");
1931
#endif /* __linux__ */
1932
exitcode = EX_OSERR;
1933
/* Lower privileges */
1937
perror_plus("seteuid");
1941
if((network.ifr_flags & IFF_UP) == 0){
1942
network.ifr_flags |= IFF_UP;
1943
take_down_interface = true;
1944
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1946
take_down_interface = false;
1947
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1948
exitcode = EX_OSERR;
1950
if(restore_loglevel){
1951
ret = klogctl(7, NULL, 0);
1953
perror_plus("klogctl");
1956
#endif /* __linux__ */
1957
/* Lower privileges */
1961
perror_plus("seteuid");
1966
/* Sleep checking until interface is running.
1967
Check every 0.25s, up to total time of delay */
1968
for(int i=0; i < delay * 4; i++){
1969
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1971
perror_plus("ioctl SIOCGIFFLAGS");
1972
} else if(network.ifr_flags & IFF_RUNNING){
1975
struct timespec sleeptime = { .tv_nsec = 250000000 };
1976
ret = nanosleep(&sleeptime, NULL);
1977
if(ret == -1 and errno != EINTR){
1978
perror_plus("nanosleep");
1981
if(not take_down_interface){
1982
/* We won't need the socket anymore */
1983
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1985
perror_plus("close");
1989
if(restore_loglevel){
1990
/* Restores kernel loglevel to default */
1991
ret = klogctl(7, NULL, 0);
1993
perror_plus("klogctl");
1996
#endif /* __linux__ */
1997
/* Lower privileges */
1999
if(take_down_interface){
2000
/* Lower privileges */
2003
perror_plus("seteuid");
2006
/* Lower privileges permanently */
2009
perror_plus("setuid");
2018
ret = init_gnutls_global(pubkey, seckey);
2020
fprintf(stderr, "Mandos plugin mandos-client: "
2021
"init_gnutls_global failed\n");
2022
exitcode = EX_UNAVAILABLE;
2025
gnutls_initialized = true;
2032
if(mkdtemp(tempdir) == NULL){
2033
perror_plus("mkdtemp");
2036
tempdir_created = true;
2042
if(not init_gpgme(pubkey, seckey, tempdir)){
2043
fprintf(stderr, "Mandos plugin mandos-client: "
2044
"init_gpgme failed\n");
2045
exitcode = EX_UNAVAILABLE;
2048
gpgme_initialized = true;
2055
if(connect_to != NULL){
2056
/* Connect directly, do not use Zeroconf */
2057
/* (Mainly meant for debugging) */
2058
char *address = strrchr(connect_to, ':');
2059
if(address == NULL){
2060
fprintf(stderr, "Mandos plugin mandos-client: "
2061
"No colon in address\n");
2062
exitcode = EX_USAGE;
2072
tmpmax = strtoimax(address+1, &tmp, 10);
2073
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2074
or tmpmax != (uint16_t)tmpmax){
2075
fprintf(stderr, "Mandos plugin mandos-client: "
2076
"Bad port number\n");
2077
exitcode = EX_USAGE;
2085
port = (uint16_t)tmpmax;
2087
/* Colon in address indicates IPv6 */
2089
if(strchr(connect_to, ':') != NULL){
2091
/* Accept [] around IPv6 address - see RFC 5952 */
2092
if(connect_to[0] == '[' and address[-1] == ']')
2100
address = connect_to;
2106
while(not quit_now){
2107
ret = start_mandos_communication(address, port, if_index, af);
2108
if(quit_now or ret == 0){
2112
fprintf(stderr, "Mandos plugin mandos-client: "
2113
"Retrying in %d seconds\n", (int)retry_interval);
2115
sleep((int)retry_interval);
2119
exitcode = EXIT_SUCCESS;
619
2130
AvahiServerConfig config;
620
AvahiSServiceBrowser *sb = NULL;
623
int returncode = EXIT_SUCCESS;
624
const char *interface = "eth0";
627
static struct option long_options[] = {
628
{"debug", no_argument, (int *)&debug, 1},
629
{"interface", required_argument, 0, 'i'},
632
int option_index = 0;
633
ret = getopt_long (argc, argv, "i:", long_options,
652
avahi_set_log_function(empty_log);
655
/* Initialize the psuedo-RNG */
656
srand((unsigned int) time(NULL));
658
/* Allocate main loop object */
659
if (!(simple_poll = avahi_simple_poll_new())) {
660
fprintf(stderr, "Failed to create simple poll object.\n");
665
/* Do not publish any local records */
2131
/* Do not publish any local Zeroconf records */
666
2132
avahi_server_config_init(&config);
667
2133
config.publish_hinfo = 0;
668
2134
config.publish_addresses = 0;
669
2135
config.publish_workstation = 0;
670
2136
config.publish_domain = 0;
672
2138
/* Allocate a new server */
673
server = avahi_server_new(avahi_simple_poll_get(simple_poll),
674
&config, NULL, NULL, &error);
676
/* Free the configuration data */
2139
mc.server = avahi_server_new(avahi_simple_poll_get
2140
(mc.simple_poll), &config, NULL,
2143
/* Free the Avahi configuration data */
677
2144
avahi_server_config_free(&config);
679
/* Check if creating the server object succeeded */
681
fprintf(stderr, "Failed to create server: %s\n",
682
avahi_strerror(error));
683
returncode = EXIT_FAILURE;
687
/* Create the service browser */
688
sb = avahi_s_service_browser_new(server,
690
if_nametoindex(interface),
692
"_mandos._tcp", NULL, 0,
693
browse_callback, server);
695
fprintf(stderr, "Failed to create service browser: %s\n",
696
avahi_strerror(avahi_server_errno(server)));
697
returncode = EXIT_FAILURE;
701
/* Run the main loop */
704
fprintf(stderr, "Starting avahi loop search\n");
707
avahi_simple_poll_loop(simple_poll);
712
fprintf(stderr, "%s exiting\n", argv[0]);
717
avahi_s_service_browser_free(sb);
720
avahi_server_free(server);
723
avahi_simple_poll_free(simple_poll);
2147
/* Check if creating the Avahi server object succeeded */
2148
if(mc.server == NULL){
2149
fprintf(stderr, "Mandos plugin mandos-client: "
2150
"Failed to create Avahi server: %s\n",
2151
avahi_strerror(error));
2152
exitcode = EX_UNAVAILABLE;
2160
/* Create the Avahi service browser */
2161
sb = avahi_s_service_browser_new(mc.server, if_index,
2162
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2163
NULL, 0, browse_callback, NULL);
2165
fprintf(stderr, "Mandos plugin mandos-client: "
2166
"Failed to create service browser: %s\n",
2167
avahi_strerror(avahi_server_errno(mc.server)));
2168
exitcode = EX_UNAVAILABLE;
2176
/* Run the main loop */
2179
fprintf(stderr, "Mandos plugin mandos-client: "
2180
"Starting Avahi loop search\n");
2183
ret = avahi_loop_with_timeout(mc.simple_poll,
2184
(int)(retry_interval * 1000));
2186
fprintf(stderr, "Mandos plugin mandos-client: "
2187
"avahi_loop_with_timeout exited %s\n",
2188
(ret == 0) ? "successfully" : "with error");
2194
fprintf(stderr, "Mandos plugin mandos-client: "
2195
"%s exiting\n", argv[0]);
2198
/* Cleanup things */
2200
avahi_s_service_browser_free(sb);
2202
if(mc.server != NULL)
2203
avahi_server_free(mc.server);
2205
if(mc.simple_poll != NULL)
2206
avahi_simple_poll_free(mc.simple_poll);
2208
if(gnutls_initialized){
2209
gnutls_certificate_free_credentials(mc.cred);
2210
gnutls_global_deinit();
2211
gnutls_dh_params_deinit(mc.dh_params);
2214
if(gpgme_initialized){
2215
gpgme_release(mc.ctx);
2218
/* Cleans up the circular linked list of Mandos servers the client
2220
if(mc.current_server != NULL){
2221
mc.current_server->prev->next = NULL;
2222
while(mc.current_server != NULL){
2223
server *next = mc.current_server->next;
2224
free(mc.current_server);
2225
mc.current_server = next;
2229
/* XXX run network hooks "stop" here */
2231
/* Take down the network interface */
2232
if(take_down_interface){
2233
/* Re-raise priviliges */
2237
perror_plus("seteuid");
2240
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2242
perror_plus("ioctl SIOCGIFFLAGS");
2243
} else if(network.ifr_flags & IFF_UP){
2244
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2245
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2247
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2250
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2252
perror_plus("close");
2254
/* Lower privileges permanently */
2258
perror_plus("setuid");
2263
/* Removes the GPGME temp directory and all files inside */
2264
if(tempdir_created){
2265
struct dirent **direntries = NULL;
2266
struct dirent *direntry = NULL;
2267
int numentries = scandir(tempdir, &direntries, notdotentries,
2269
if (numentries > 0){
2270
for(int i = 0; i < numentries; i++){
2271
direntry = direntries[i];
2272
char *fullname = NULL;
2273
ret = asprintf(&fullname, "%s/%s", tempdir,
2276
perror_plus("asprintf");
2279
ret = remove(fullname);
2281
fprintf(stderr, "Mandos plugin mandos-client: "
2282
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2288
/* need to clean even if 0 because man page doesn't specify */
2290
if (numentries == -1){
2291
perror_plus("scandir");
2293
ret = rmdir(tempdir);
2294
if(ret == -1 and errno != ENOENT){
2295
perror_plus("rmdir");
2300
sigemptyset(&old_sigterm_action.sa_mask);
2301
old_sigterm_action.sa_handler = SIG_DFL;
2302
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2303
&old_sigterm_action,
2306
perror_plus("sigaction");
2309
ret = raise(signal_received);
2310
} while(ret != 0 and errno == EINTR);
2312
perror_plus("raise");
2315
TEMP_FAILURE_RETRY(pause());