45
103
#include <avahi-common/malloc.h>
46
104
#include <avahi-common/error.h>
49
#include <sys/types.h> /* socket(), inet_pton() */
50
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
51
struct in6_addr, inet_pton() */
52
#include <gnutls/gnutls.h> /* All GnuTLS stuff */
53
#include <gnutls/openpgp.h> /* GnuTLS with openpgp stuff */
55
#include <unistd.h> /* close() */
56
#include <netinet/in.h>
57
#include <stdbool.h> /* true */
58
#include <string.h> /* memset */
59
#include <arpa/inet.h> /* inet_pton() */
60
#include <iso646.h> /* not */
63
#include <errno.h> /* perror() */
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,
69
123
#define BUFFER_SIZE 256
72
const char *certdir = "/conf/conf.d/cryptkeyreq/";
73
const char *certfile = "openpgp-client.txt";
74
const char *certkey = "openpgp-client-key.txt";
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"
76
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 */
79
gnutls_session_t session;
151
AvahiSimplePoll *simple_poll;
80
153
gnutls_certificate_credentials_t cred;
154
unsigned int dh_bits;
81
155
gnutls_dh_params_t dh_params;
85
ssize_t pgp_packet_decrypt (char *packet, size_t packet_size,
86
char **new_packet, const char *homedir){
87
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){
91
ssize_t new_packet_capacity = 0;
92
ssize_t new_packet_length = 0;
93
238
gpgme_engine_info_t engine_info;
96
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");
100
285
gpgme_check_version(NULL);
101
286
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
102
if (rc != GPG_ERR_NO_ERROR){
103
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
287
if(rc != GPG_ERR_NO_ERROR){
288
fprintf(stderr, "Mandos plugin mandos-client: "
289
"bad gpgme_engine_check_version: %s: %s\n",
104
290
gpgme_strsource(rc), gpgme_strerror(rc));
108
/* Set GPGME home directory */
109
rc = gpgme_get_engine_info (&engine_info);
110
if (rc != GPG_ERR_NO_ERROR){
111
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",
112
299
gpgme_strsource(rc), gpgme_strerror(rc));
115
302
while(engine_info != NULL){
116
303
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
117
304
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
118
engine_info->file_name, homedir);
305
engine_info->file_name, tempdir);
121
308
engine_info = engine_info->next;
123
310
if(engine_info == NULL){
124
fprintf(stderr, "Could not set home dir to %s\n", homedir);
128
/* Create new GPGME data buffer from packet buffer */
129
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
130
if (rc != GPG_ERR_NO_ERROR){
131
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",
132
356
gpgme_strsource(rc), gpgme_strerror(rc));
136
360
/* Create new empty GPGME data buffer for the plaintext */
137
361
rc = gpgme_data_new(&dh_plain);
138
if (rc != GPG_ERR_NO_ERROR){
139
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
140
gpgme_strsource(rc), gpgme_strerror(rc));
144
/* Create new GPGME "context" */
145
rc = gpgme_new(&ctx);
146
if (rc != GPG_ERR_NO_ERROR){
147
fprintf(stderr, "bad gpgme_new: %s: %s\n",
148
gpgme_strsource(rc), gpgme_strerror(rc));
152
/* Decrypt data from the FILE pointer to the plaintext data
154
rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
155
if (rc != GPG_ERR_NO_ERROR){
156
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
157
gpgme_strsource(rc), gpgme_strerror(rc));
162
fprintf(stderr, "Decryption of OpenPGP packet succeeded\n");
166
gpgme_decrypt_result_t result;
167
result = gpgme_op_decrypt_result(ctx);
169
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
171
fprintf(stderr, "Unsupported algorithm: %s\n",
172
result->unsupported_algorithm);
173
fprintf(stderr, "Wrong key usage: %d\n",
174
result->wrong_key_usage);
175
if(result->file_name != NULL){
176
fprintf(stderr, "File name: %s\n", result->file_name);
178
gpgme_recipient_t recipient;
179
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;
181
397
while(recipient != NULL){
182
fprintf(stderr, "Public key algorithm: %s\n",
398
fprintf(stderr, "Mandos plugin mandos-client: "
399
"Public key algorithm: %s\n",
183
400
gpgme_pubkey_algo_name(recipient->pubkey_algo));
184
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
185
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",
186
405
recipient->status == GPG_ERR_NO_SECKEY
188
407
recipient = recipient->next;
194
/* Delete the GPGME FILE pointer cryptotext data buffer */
195
gpgme_data_release(dh_crypto);
415
fprintf(stderr, "Mandos plugin mandos-client: "
416
"Decryption of OpenPGP data succeeded\n");
197
419
/* Seek back to the beginning of the GPGME plaintext data buffer */
198
if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){
199
perror("pgpme_data_seek");
420
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
421
perror_plus("gpgme_data_seek");
422
plaintext_length = -1;
204
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
205
*new_packet = realloc(*new_packet,
206
(unsigned int)new_packet_capacity
208
if (*new_packet == NULL){
212
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;
215
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length,
437
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
217
439
/* Print the data, if any */
222
perror("gpgme_data_read");
225
new_packet_length += ret;
228
/* FIXME: check characters before printing to screen so to not print
229
terminal control characters */
231
/* fprintf(stderr, "decrypted password is: "); */
232
/* fwrite(*new_packet, 1, new_packet_length, stderr); */
233
/* 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);
236
466
/* Delete the GPGME plaintext data buffer */
237
467
gpgme_data_release(dh_plain);
238
return new_packet_length;
468
return plaintext_length;
241
static const char * safer_gnutls_strerror (int value) {
242
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 */
244
475
ret = "(unknown)";
248
void debuggnutls(__attribute__((unused)) int level,
250
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);
253
int initgnutls(encrypted_session *es){
485
static int init_gnutls_global(const char *pubkeyfilename,
486
const char *seckeyfilename){
258
fprintf(stderr, "Initializing GnuTLS\n");
490
fprintf(stderr, "Mandos plugin mandos-client: "
491
"Initializing GnuTLS\n");
261
if ((ret = gnutls_global_init ())
262
!= GNUTLS_E_SUCCESS) {
263
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."
268
505
gnutls_global_set_log_level(11);
269
506
gnutls_global_set_log_function(debuggnutls);
272
/* openpgp credentials */
273
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
274
!= GNUTLS_E_SUCCESS) {
275
fprintf (stderr, "memory error: %s\n",
276
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();
281
fprintf(stderr, "Attempting to use OpenPGP certificate %s"
282
" 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,
286
525
ret = gnutls_certificate_set_openpgp_key_file
287
(es->cred, certfile, certkey, GNUTLS_OPENPGP_FMT_BASE64);
288
if (ret != GNUTLS_E_SUCCESS) {
290
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s',"
292
ret, certfile, certkey);
293
fprintf(stdout, "The Error is: %s\n",
294
safer_gnutls_strerror(ret));
298
//GnuTLS server initialization
299
if ((ret = gnutls_dh_params_init (&es->dh_params))
300
!= GNUTLS_E_SUCCESS) {
301
fprintf (stderr, "Error in dh parameter initialization: %s\n",
302
safer_gnutls_strerror(ret));
306
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
307
!= GNUTLS_E_SUCCESS) {
308
fprintf (stderr, "Error in prime generation: %s\n",
309
safer_gnutls_strerror(ret));
313
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
315
// GnuTLS session creation
316
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
317
!= GNUTLS_E_SUCCESS){
318
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
319
safer_gnutls_strerror(ret));
322
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
323
!= GNUTLS_E_SUCCESS) {
324
fprintf(stderr, "Syntax error at: %s\n", err);
325
fprintf(stderr, "GnuTLS error: %s\n",
326
safer_gnutls_strerror(ret));
330
if ((ret = gnutls_credentials_set
331
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
332
!= GNUTLS_E_SUCCESS) {
333
fprintf(stderr, "Error setting a credentials set: %s\n",
334
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);
338
616
/* ignore client certificate if any. */
339
gnutls_certificate_server_set_request (es->session,
617
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
342
gnutls_dh_set_prime_bits (es->session, DH_BITS);
619
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
347
void empty_log(__attribute__((unused)) AvahiLogLevel level,
348
__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){}
350
int start_mandos_communication(const char *ip, uint16_t port,
351
AvahiIfIndex if_index){
353
struct sockaddr_in6 to;
354
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;
355
638
char *buffer = NULL;
356
char *decrypted_buffer;
639
char *decrypted_buffer = NULL;
357
640
size_t buffer_length = 0;
358
641
size_t buffer_capacity = 0;
359
ssize_t decrypted_buffer_size;
362
char interface[IF_NAMESIZE];
365
fprintf(stderr, "Setting up a tcp connection to %s, port %d\n",
369
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
375
if(if_indextoname((unsigned int)if_index, interface) == NULL){
377
perror("if_indextoname");
383
fprintf(stderr, "Binding to interface %s\n", interface);
386
memset(&to,0,sizeof(to)); /* Spurious warning */
387
to.sin6_family = AF_INET6;
388
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");
394
fprintf(stderr, "Bad address: %s\n", ip);
397
to.sin6_port = htons(port); /* Spurious warning */
399
to.sin6_scope_id = (uint32_t)if_index;
402
fprintf(stderr, "Connection to: %s, port %d\n", ip, port);
403
/* char addrstr[INET6_ADDRSTRLEN]; */
404
/* if(inet_ntop(to.sin6_family, &(to.sin6_addr), addrstr, */
405
/* sizeof(addrstr)) == NULL){ */
406
/* perror("inet_ntop"); */
408
/* fprintf(stderr, "Really connecting to: %s, port %d\n", */
409
/* addrstr, ntohs(to.sin6_port)); */
413
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
419
ret = initgnutls (&es);
425
gnutls_transport_set_ptr (es.session,
426
(gnutls_transport_ptr_t) tcp_sd);
429
fprintf(stderr, "Establishing TLS session with %s\n", ip);
432
ret = gnutls_handshake (es.session);
434
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){
436
fprintf(stderr, "\n*** Handshake failed ***\n");
858
fprintf(stderr, "Mandos plugin mandos-client: "
859
"*** GnuTLS Handshake failed ***\n");
443
//Retrieve OpenPGP packet that contains the wanted password
866
/* Read OpenPGP packet that contains the wanted password */
446
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);
451
if (buffer_length + BUFFER_SIZE > buffer_capacity){
452
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
457
buffer_capacity += BUFFER_SIZE;
460
ret = gnutls_record_recv
461
(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,
467
901
case GNUTLS_E_INTERRUPTED:
468
902
case GNUTLS_E_AGAIN:
470
904
case GNUTLS_E_REHANDSHAKE:
471
ret = gnutls_handshake (es.session);
473
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");
480
fprintf(stderr, "Unknown error while reading data from"
481
" encrypted session with mandos server\n");
483
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);
487
buffer_length += (size_t) ret;
491
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;
492
954
decrypted_buffer_size = pgp_packet_decrypt(buffer,
496
if (decrypted_buffer_size >= 0){
957
if(decrypted_buffer_size >= 0){
497
960
while(written < (size_t) decrypted_buffer_size){
498
ret = (int)fwrite (decrypted_buffer + written, 1,
499
(size_t)decrypted_buffer_size - written,
966
ret = (int)fwrite(decrypted_buffer + written, 1,
967
(size_t)decrypted_buffer_size - written,
501
969
if(ret == 0 and ferror(stdout)){
503
fprintf(stderr, "Error writing encrypted data: %s\n",
972
fprintf(stderr, "Mandos plugin mandos-client: "
973
"Error writing encrypted data: %s\n",
504
974
strerror(errno));
509
979
written += (size_t)ret;
511
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);
520
fprintf(stderr, "Closing TLS session\n");
524
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
527
gnutls_deinit (es.session);
528
gnutls_certificate_free_credentials (es.cred);
529
gnutls_global_deinit ();
533
static AvahiSimplePoll *simple_poll = NULL;
534
static AvahiServer *server = NULL;
536
static void resolve_callback(
537
AvahiSServiceResolver *r,
538
AvahiIfIndex interface,
539
AVAHI_GCC_UNUSED AvahiProtocol protocol,
540
AvahiResolverEvent event,
544
const char *host_name,
545
const AvahiAddress *address,
547
AVAHI_GCC_UNUSED AvahiStringList *txt,
548
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
549
AVAHI_GCC_UNUSED void* userdata) {
551
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){
553
1027
/* Called whenever a service has been resolved successfully or
558
1036
case AVAHI_RESOLVER_FAILURE:
559
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of"
560
" type '%s' in domain '%s': %s\n", name, type, domain,
561
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)));
564
1043
case AVAHI_RESOLVER_FOUND:
566
1045
char ip[AVAHI_ADDRESS_STR_MAX];
567
1046
avahi_address_snprint(ip, sizeof(ip), address);
569
fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on"
570
" 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);
572
int ret = start_mandos_communication(ip, port, 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));
578
1063
avahi_s_service_resolver_free(r);
581
static void browse_callback(
582
AvahiSServiceBrowser *b,
583
AvahiIfIndex interface,
584
AvahiProtocol protocol,
585
AvahiBrowserEvent event,
589
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
592
AvahiServer *s = userdata;
593
assert(b); /* Spurious warning */
595
/* Called whenever a new services becomes available on the LAN or
596
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);
600
case AVAHI_BROWSER_FAILURE:
602
fprintf(stderr, "(Browser) %s\n",
603
avahi_strerror(avahi_server_errno(server)));
604
avahi_simple_poll_quit(simple_poll);
607
case AVAHI_BROWSER_NEW:
608
/* We ignore the returned resolver object. In the callback
609
function we free it. If the server is terminated before
610
the callback function is called the server will free
611
the resolver for us. */
613
if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
615
AVAHI_PROTO_INET6, 0,
616
resolve_callback, s)))
617
fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
618
avahi_strerror(avahi_server_errno(s)));
621
case AVAHI_BROWSER_REMOVE:
624
case AVAHI_BROWSER_ALL_FOR_NOW:
625
case AVAHI_BROWSER_CACHE_EXHAUSTED:
630
/* combinds file name and path and returns the malloced new string. som sane checks could/should be added */
631
const char *combinepath(const char *first, const char *second){
633
tmp = malloc(strlen(first) + strlen(second) + 2);
639
if (first[0] != '\0' and first[strlen(first) - 1] != '/'){
647
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;
648
2130
AvahiServerConfig config;
649
AvahiSServiceBrowser *sb = NULL;
652
int returncode = EXIT_SUCCESS;
653
const char *interface = NULL;
654
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
655
char *connect_to = NULL;
658
static struct option long_options[] = {
659
{"debug", no_argument, (int *)&debug, 1},
660
{"connect", required_argument, 0, 'C'},
661
{"interface", required_argument, 0, 'i'},
662
{"certdir", required_argument, 0, 'd'},
663
{"certkey", required_argument, 0, 'c'},
664
{"certfile", required_argument, 0, 'k'},
667
int option_index = 0;
668
ret = getopt_long (argc, argv, "i:", long_options,
698
certfile = combinepath(certdir, certfile);
699
if (certfile == NULL){
703
if(interface != NULL){
704
if_index = (AvahiIfIndex) if_nametoindex(interface);
706
fprintf(stderr, "No such interface: \"%s\"\n", interface);
711
if(connect_to != NULL){
712
/* Connect directly, do not use Zeroconf */
713
/* (Mainly meant for debugging) */
714
char *address = strrchr(connect_to, ':');
716
fprintf(stderr, "No colon in address\n");
720
uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
722
perror("Bad port number");
726
address = connect_to;
727
ret = start_mandos_communication(address, port, if_index);
735
certkey = combinepath(certdir, certkey);
736
if (certkey == NULL){
741
avahi_set_log_function(empty_log);
744
/* Initialize the psuedo-RNG */
745
srand((unsigned int) time(NULL));
747
/* Allocate main loop object */
748
if (!(simple_poll = avahi_simple_poll_new())) {
749
fprintf(stderr, "Failed to create simple poll object.\n");
754
/* Do not publish any local records */
2131
/* Do not publish any local Zeroconf records */
755
2132
avahi_server_config_init(&config);
756
2133
config.publish_hinfo = 0;
757
2134
config.publish_addresses = 0;
758
2135
config.publish_workstation = 0;
759
2136
config.publish_domain = 0;
761
2138
/* Allocate a new server */
762
server = avahi_server_new(avahi_simple_poll_get(simple_poll),
763
&config, NULL, NULL, &error);
765
/* 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 */
766
2144
avahi_server_config_free(&config);
768
/* Check if creating the server object succeeded */
770
fprintf(stderr, "Failed to create server: %s\n",
771
avahi_strerror(error));
772
returncode = EXIT_FAILURE;
776
/* Create the service browser */
777
sb = avahi_s_service_browser_new(server, if_index,
779
"_mandos._tcp", NULL, 0,
780
browse_callback, server);
782
fprintf(stderr, "Failed to create service browser: %s\n",
783
avahi_strerror(avahi_server_errno(server)));
784
returncode = EXIT_FAILURE;
788
/* Run the main loop */
791
fprintf(stderr, "Starting avahi loop search\n");
794
avahi_simple_poll_loop(simple_poll);
799
fprintf(stderr, "%s exiting\n", argv[0]);
804
avahi_s_service_browser_free(sb);
807
avahi_server_free(server);
810
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());