103
46
#include <avahi-common/malloc.h>
104
47
#include <avahi-common/error.h>
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,
50
#include <sys/types.h> /* socket(), setsockopt(), inet_pton() */
51
#include <sys/socket.h> /* socket(), setsockopt(), struct sockaddr_in6, 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() */
68
#define CERT_ROOT "/conf/conf.d/cryptkeyreq/"
70
#define CERTFILE CERT_ROOT "openpgp-client.txt"
71
#define KEYFILE CERT_ROOT "openpgp-client-key.txt"
123
72
#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"
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;
137
/* Doubly linked list that need to be circularly linked when used */
138
typedef struct server{
141
AvahiIfIndex if_index;
143
struct timespec last_seen;
148
/* Used for passing in values through the Avahi callback functions */
150
AvahiSimplePoll *simple_poll;
76
gnutls_session_t session;
152
77
gnutls_certificate_credentials_t cred;
153
unsigned int dh_bits;
154
78
gnutls_dh_params_t dh_params;
155
const char *priority;
82
ssize_t gpg_packet_decrypt (char *packet, size_t packet_size, char **new_packet, char *homedir){
83
gpgme_data_t dh_crypto, dh_plain;
157
server *current_server;
160
/* global context so signal handler can reach it*/
161
mandos_context mc = { .simple_poll = NULL, .server = NULL,
162
.dh_bits = 1024, .priority = "SECURE256"
163
":!CTYPE-X.509:+CTYPE-OPENPGP",
164
.current_server = NULL };
166
sig_atomic_t quit_now = 0;
167
int signal_received = 0;
169
/* Function to use when printing errors */
170
void perror_plus(const char *print_text){
171
fprintf(stderr, "Mandos plugin %s: ",
172
program_invocation_short_name);
177
* Make additional room in "buffer" for at least BUFFER_SIZE more
178
* bytes. "buffer_capacity" is how much is currently allocated,
179
* "buffer_length" is how much is already used.
181
size_t incbuffer(char **buffer, size_t buffer_length,
182
size_t buffer_capacity){
183
if(buffer_length + BUFFER_SIZE > buffer_capacity){
184
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
188
buffer_capacity += BUFFER_SIZE;
190
return buffer_capacity;
193
/* Add server to set of servers to retry periodically */
194
int add_server(const char *ip, uint16_t port,
195
AvahiIfIndex if_index,
198
server *new_server = malloc(sizeof(server));
199
if(new_server == NULL){
200
perror_plus("malloc");
203
*new_server = (server){ .ip = strdup(ip),
205
.if_index = if_index,
207
if(new_server->ip == NULL){
208
perror_plus("strdup");
211
/* Special case of first server */
212
if (mc.current_server == NULL){
213
new_server->next = new_server;
214
new_server->prev = new_server;
215
mc.current_server = new_server;
216
/* Place the new server last in the list */
218
new_server->next = mc.current_server;
219
new_server->prev = mc.current_server->prev;
220
new_server->prev->next = new_server;
221
mc.current_server->prev = new_server;
223
ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen);
225
perror_plus("clock_gettime");
234
static bool init_gpgme(const char *seckey,
235
const char *pubkey, const char *tempdir){
87
size_t new_packet_capacity = 0;
88
size_t new_packet_length = 0;
237
89
gpgme_engine_info_t engine_info;
241
* Helper function to insert pub and seckey to the engine keyring.
243
bool import_key(const char *filename){
246
gpgme_data_t pgp_data;
248
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
254
rc = gpgme_data_new_from_fd(&pgp_data, fd);
255
if(rc != GPG_ERR_NO_ERROR){
256
fprintf(stderr, "Mandos plugin mandos-client: "
257
"bad gpgme_data_new_from_fd: %s: %s\n",
258
gpgme_strsource(rc), gpgme_strerror(rc));
262
rc = gpgme_op_import(mc.ctx, pgp_data);
263
if(rc != GPG_ERR_NO_ERROR){
264
fprintf(stderr, "Mandos plugin mandos-client: "
265
"bad gpgme_op_import: %s: %s\n",
266
gpgme_strsource(rc), gpgme_strerror(rc));
270
ret = (int)TEMP_FAILURE_RETRY(close(fd));
272
perror_plus("close");
274
gpgme_data_release(pgp_data);
279
fprintf(stderr, "Mandos plugin mandos-client: "
280
"Initializing GPGME\n");
284
92
gpgme_check_version(NULL);
285
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
286
if(rc != GPG_ERR_NO_ERROR){
287
fprintf(stderr, "Mandos plugin mandos-client: "
288
"bad gpgme_engine_check_version: %s: %s\n",
289
gpgme_strsource(rc), gpgme_strerror(rc));
93
gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
293
/* Set GPGME home directory for the OpenPGP engine only */
294
rc = gpgme_get_engine_info(&engine_info);
295
if(rc != GPG_ERR_NO_ERROR){
296
fprintf(stderr, "Mandos plugin mandos-client: "
297
"bad gpgme_get_engine_info: %s: %s\n",
95
/* Set GPGME home directory */
96
rc = gpgme_get_engine_info (&engine_info);
97
if (rc != GPG_ERR_NO_ERROR){
98
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
298
99
gpgme_strsource(rc), gpgme_strerror(rc));
301
102
while(engine_info != NULL){
302
103
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
303
104
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
304
engine_info->file_name, tempdir);
105
engine_info->file_name, homedir);
307
108
engine_info = engine_info->next;
309
110
if(engine_info == NULL){
310
fprintf(stderr, "Mandos plugin mandos-client: "
311
"Could not set GPGME home dir to %s\n", tempdir);
315
/* Create new GPGME "context" */
316
rc = gpgme_new(&(mc.ctx));
317
if(rc != GPG_ERR_NO_ERROR){
318
fprintf(stderr, "Mandos plugin mandos-client: "
319
"bad gpgme_new: %s: %s\n", gpgme_strsource(rc),
324
if(not import_key(pubkey) or not import_key(seckey)){
332
* Decrypt OpenPGP data.
333
* Returns -1 on error
335
static ssize_t pgp_packet_decrypt(const char *cryptotext,
338
gpgme_data_t dh_crypto, dh_plain;
341
size_t plaintext_capacity = 0;
342
ssize_t plaintext_length = 0;
345
fprintf(stderr, "Mandos plugin mandos-client: "
346
"Trying to decrypt OpenPGP data\n");
349
/* Create new GPGME data buffer from memory cryptotext */
350
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
352
if(rc != GPG_ERR_NO_ERROR){
353
fprintf(stderr, "Mandos plugin mandos-client: "
354
"bad gpgme_data_new_from_mem: %s: %s\n",
111
fprintf(stderr, "Could not set home dir to %s\n", homedir);
115
/* Create new GPGME data buffer from packet buffer */
116
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
117
if (rc != GPG_ERR_NO_ERROR){
118
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
355
119
gpgme_strsource(rc), gpgme_strerror(rc));
359
123
/* Create new empty GPGME data buffer for the plaintext */
360
124
rc = gpgme_data_new(&dh_plain);
361
if(rc != GPG_ERR_NO_ERROR){
362
fprintf(stderr, "Mandos plugin mandos-client: "
363
"bad gpgme_data_new: %s: %s\n",
364
gpgme_strsource(rc), gpgme_strerror(rc));
365
gpgme_data_release(dh_crypto);
369
/* Decrypt data from the cryptotext data buffer to the plaintext
371
rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain);
372
if(rc != GPG_ERR_NO_ERROR){
373
fprintf(stderr, "Mandos plugin mandos-client: "
374
"bad gpgme_op_decrypt: %s: %s\n",
375
gpgme_strsource(rc), gpgme_strerror(rc));
376
plaintext_length = -1;
378
gpgme_decrypt_result_t result;
379
result = gpgme_op_decrypt_result(mc.ctx);
381
fprintf(stderr, "Mandos plugin mandos-client: "
382
"gpgme_op_decrypt_result failed\n");
384
fprintf(stderr, "Mandos plugin mandos-client: "
385
"Unsupported algorithm: %s\n",
386
result->unsupported_algorithm);
387
fprintf(stderr, "Mandos plugin mandos-client: "
388
"Wrong key usage: %u\n",
389
result->wrong_key_usage);
390
if(result->file_name != NULL){
391
fprintf(stderr, "Mandos plugin mandos-client: "
392
"File name: %s\n", result->file_name);
394
gpgme_recipient_t recipient;
395
recipient = result->recipients;
396
while(recipient != NULL){
397
fprintf(stderr, "Mandos plugin mandos-client: "
398
"Public key algorithm: %s\n",
399
gpgme_pubkey_algo_name(recipient->pubkey_algo));
400
fprintf(stderr, "Mandos plugin mandos-client: "
401
"Key ID: %s\n", recipient->keyid);
402
fprintf(stderr, "Mandos plugin mandos-client: "
403
"Secret key available: %s\n",
404
recipient->status == GPG_ERR_NO_SECKEY
406
recipient = recipient->next;
414
fprintf(stderr, "Mandos plugin mandos-client: "
415
"Decryption of OpenPGP data succeeded\n");
125
if (rc != GPG_ERR_NO_ERROR){
126
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
127
gpgme_strsource(rc), gpgme_strerror(rc));
131
/* Create new GPGME "context" */
132
rc = gpgme_new(&ctx);
133
if (rc != GPG_ERR_NO_ERROR){
134
fprintf(stderr, "bad gpgme_new: %s: %s\n",
135
gpgme_strsource(rc), gpgme_strerror(rc));
139
/* Decrypt data from the FILE pointer to the plaintext data buffer */
140
rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
141
if (rc != GPG_ERR_NO_ERROR){
142
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
143
gpgme_strsource(rc), gpgme_strerror(rc));
147
/* gpgme_decrypt_result_t result; */
148
/* result = gpgme_op_decrypt_result(ctx); */
149
/* fprintf(stderr, "Unsupported algorithm: %s\n", result->unsupported_algorithm); */
150
/* fprintf(stderr, "Wrong key usage: %d\n", result->wrong_key_usage); */
151
/* if(result->file_name != NULL){ */
152
/* fprintf(stderr, "File name: %s\n", result->file_name); */
154
/* gpgme_recipient_t recipient; */
155
/* recipient = result->recipients; */
157
/* while(recipient != NULL){ */
158
/* fprintf(stderr, "Public key algorithm: %s\n", */
159
/* gpgme_pubkey_algo_name(recipient->pubkey_algo)); */
160
/* fprintf(stderr, "Key ID: %s\n", recipient->keyid); */
161
/* fprintf(stderr, "Secret key available: %s\n", */
162
/* recipient->status == GPG_ERR_NO_SECKEY ? "No" : "Yes"); */
163
/* recipient = recipient->next; */
167
/* Delete the GPGME FILE pointer cryptotext data buffer */
168
gpgme_data_release(dh_crypto);
418
170
/* Seek back to the beginning of the GPGME plaintext data buffer */
419
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
420
perror_plus("gpgme_data_seek");
421
plaintext_length = -1;
171
gpgme_data_seek(dh_plain, 0, SEEK_SET);
427
plaintext_capacity = incbuffer(plaintext,
428
(size_t)plaintext_length,
430
if(plaintext_capacity == 0){
431
perror_plus("incbuffer");
432
plaintext_length = -1;
175
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
176
*new_packet = realloc(*new_packet, new_packet_capacity + BUFFER_SIZE);
177
if (*new_packet == NULL){
181
new_packet_capacity += BUFFER_SIZE;
436
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
184
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length, BUFFER_SIZE);
438
185
/* Print the data, if any */
187
/* If password is empty, then a incorrect error will be printed */
444
perror_plus("gpgme_data_read");
445
plaintext_length = -1;
448
plaintext_length += ret;
452
fprintf(stderr, "Mandos plugin mandos-client: "
453
"Decrypted password is: ");
454
for(ssize_t i = 0; i < plaintext_length; i++){
455
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
457
fprintf(stderr, "\n");
462
/* Delete the GPGME cryptotext data buffer */
463
gpgme_data_release(dh_crypto);
465
/* Delete the GPGME plaintext data buffer */
191
perror("gpgme_data_read");
194
new_packet_length += ret;
197
/* Delete the GPGME plaintext data buffer */
466
198
gpgme_data_release(dh_plain);
467
return plaintext_length;
199
return new_packet_length;
470
static const char * safer_gnutls_strerror(int value){
471
const char *ret = gnutls_strerror(value); /* Spurious warning from
472
-Wunreachable-code */
202
static const char * safer_gnutls_strerror (int value) {
203
const char *ret = gnutls_strerror (value);
474
205
ret = "(unknown)";
478
/* GnuTLS log function callback */
479
static void debuggnutls(__attribute__((unused)) int level,
481
fprintf(stderr, "Mandos plugin mandos-client: GnuTLS: %s", string);
209
void debuggnutls(int level, const char* string){
210
fprintf(stderr, "%s", string);
484
static int init_gnutls_global(const char *pubkeyfilename,
485
const char *seckeyfilename){
213
int initgnutls(encrypted_session *es){
489
fprintf(stderr, "Mandos plugin mandos-client: "
490
"Initializing GnuTLS\n");
493
ret = gnutls_global_init();
494
if(ret != GNUTLS_E_SUCCESS){
495
fprintf(stderr, "Mandos plugin mandos-client: "
496
"GnuTLS global_init: %s\n", safer_gnutls_strerror(ret));
501
/* "Use a log level over 10 to enable all debugging options."
504
gnutls_global_set_log_level(11);
505
gnutls_global_set_log_function(debuggnutls);
508
/* OpenPGP credentials */
509
ret = gnutls_certificate_allocate_credentials(&mc.cred);
510
if(ret != GNUTLS_E_SUCCESS){
511
fprintf(stderr, "Mandos plugin mandos-client: "
512
"GnuTLS memory error: %s\n", safer_gnutls_strerror(ret));
513
gnutls_global_deinit();
518
fprintf(stderr, "Mandos plugin mandos-client: "
519
"Attempting to use OpenPGP public key %s and"
520
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
217
if ((ret = gnutls_global_init ())
218
!= GNUTLS_E_SUCCESS) {
219
fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
223
/* Uncomment to enable full debuggin on the gnutls library */
224
/* gnutls_global_set_log_level(11); */
225
/* gnutls_global_set_log_function(debuggnutls); */
228
/* openpgp credentials */
229
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
230
!= GNUTLS_E_SUCCESS) {
231
fprintf (stderr, "memory error: %s\n", safer_gnutls_strerror(ret));
524
235
ret = gnutls_certificate_set_openpgp_key_file
525
(mc.cred, pubkeyfilename, seckeyfilename,
526
GNUTLS_OPENPGP_FMT_BASE64);
527
if(ret != GNUTLS_E_SUCCESS){
529
"Mandos plugin mandos-client: "
530
"Error[%d] while reading the OpenPGP key pair ('%s',"
531
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
532
fprintf(stderr, "Mandos plugin mandos-client: "
533
"The GnuTLS error is: %s\n", safer_gnutls_strerror(ret));
537
/* GnuTLS server initialization */
538
ret = gnutls_dh_params_init(&mc.dh_params);
539
if(ret != GNUTLS_E_SUCCESS){
540
fprintf(stderr, "Mandos plugin mandos-client: "
541
"Error in GnuTLS DH parameter initialization:"
542
" %s\n", safer_gnutls_strerror(ret));
545
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
546
if(ret != GNUTLS_E_SUCCESS){
547
fprintf(stderr, "Mandos plugin mandos-client: "
548
"Error in GnuTLS prime generation: %s\n",
549
safer_gnutls_strerror(ret));
553
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
559
gnutls_certificate_free_credentials(mc.cred);
560
gnutls_global_deinit();
561
gnutls_dh_params_deinit(mc.dh_params);
565
static int init_gnutls_session(gnutls_session_t *session){
567
/* GnuTLS session creation */
569
ret = gnutls_init(session, GNUTLS_SERVER);
573
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
574
if(ret != GNUTLS_E_SUCCESS){
575
fprintf(stderr, "Mandos plugin mandos-client: "
576
"Error in GnuTLS session initialization: %s\n",
577
safer_gnutls_strerror(ret));
583
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
585
gnutls_deinit(*session);
588
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
589
if(ret != GNUTLS_E_SUCCESS){
590
fprintf(stderr, "Mandos plugin mandos-client: "
591
"Syntax error at: %s\n", err);
592
fprintf(stderr, "Mandos plugin mandos-client: "
593
"GnuTLS error: %s\n", safer_gnutls_strerror(ret));
594
gnutls_deinit(*session);
600
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
603
gnutls_deinit(*session);
606
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
607
if(ret != GNUTLS_E_SUCCESS){
608
fprintf(stderr, "Mandos plugin mandos-client: "
609
"Error setting GnuTLS credentials: %s\n",
610
safer_gnutls_strerror(ret));
611
gnutls_deinit(*session);
236
(es->cred, CERTFILE, KEYFILE, GNUTLS_OPENPGP_FMT_BASE64);
237
if (ret != GNUTLS_E_SUCCESS) {
239
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s', '%s')\n",
240
ret, CERTFILE, KEYFILE);
241
fprintf(stdout, "The Error is: %s\n",
242
safer_gnutls_strerror(ret));
246
//Gnutls server initialization
247
if ((ret = gnutls_dh_params_init (&es->dh_params))
248
!= GNUTLS_E_SUCCESS) {
249
fprintf (stderr, "Error in dh parameter initialization: %s\n",
250
safer_gnutls_strerror(ret));
254
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
255
!= GNUTLS_E_SUCCESS) {
256
fprintf (stderr, "Error in prime generation: %s\n",
257
safer_gnutls_strerror(ret));
261
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
263
// Gnutls session creation
264
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
265
!= GNUTLS_E_SUCCESS){
266
fprintf(stderr, "Error in gnutls session initialization: %s\n",
267
safer_gnutls_strerror(ret));
270
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
271
!= GNUTLS_E_SUCCESS) {
272
fprintf(stderr, "Syntax error at: %s\n", err);
273
fprintf(stderr, "Gnutls error: %s\n",
274
safer_gnutls_strerror(ret));
278
if ((ret = gnutls_credentials_set
279
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
280
!= GNUTLS_E_SUCCESS) {
281
fprintf(stderr, "Error setting a credentials set: %s\n",
282
safer_gnutls_strerror(ret));
615
286
/* ignore client certificate if any. */
616
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
287
gnutls_certificate_server_set_request (es->session, GNUTLS_CERT_IGNORE);
618
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
289
gnutls_dh_set_prime_bits (es->session, DH_BITS);
623
/* Avahi log function callback */
624
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
625
__attribute__((unused)) const char *txt){}
294
void empty_log(AvahiLogLevel level, const char *txt){}
627
/* Called when a Mandos server is found */
628
static int start_mandos_communication(const char *ip, uint16_t port,
629
AvahiIfIndex if_index,
631
int ret, tcp_sd = -1;
634
struct sockaddr_in in;
635
struct sockaddr_in6 in6;
296
int start_mandos_communcation(char *ip, uint16_t port){
298
struct sockaddr_in6 to;
299
struct in6_addr ip_addr;
300
encrypted_session es;
637
301
char *buffer = NULL;
638
char *decrypted_buffer = NULL;
302
char *decrypted_buffer;
639
303
size_t buffer_length = 0;
640
304
size_t buffer_capacity = 0;
643
gnutls_session_t session;
644
int pf; /* Protocol family */
661
fprintf(stderr, "Mandos plugin mandos-client: "
662
"Bad address family: %d\n", af);
667
ret = init_gnutls_session(&session);
673
fprintf(stderr, "Mandos plugin mandos-client: "
674
"Setting up a TCP connection to %s, port %" PRIu16
678
tcp_sd = socket(pf, SOCK_STREAM, 0);
681
perror_plus("socket");
691
memset(&to, 0, sizeof(to));
693
to.in6.sin6_family = (sa_family_t)af;
694
ret = inet_pton(af, ip, &to.in6.sin6_addr);
696
to.in.sin_family = (sa_family_t)af;
697
ret = inet_pton(af, ip, &to.in.sin_addr);
701
perror_plus("inet_pton");
305
ssize_t decrypted_buffer_size;
309
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
315
ret = setsockopt(tcp_sd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 5);
317
perror("setsockopt bindtodevice");
321
memset(&to,0,sizeof(to));
322
to.sin6_family = AF_INET6;
323
ret = inet_pton(AF_INET6, ip, &ip_addr);
707
fprintf(stderr, "Mandos plugin mandos-client: "
708
"Bad address: %s\n", ip);
713
to.in6.sin6_port = htons(port); /* Spurious warnings from
715
-Wunreachable-code */
717
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
718
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
720
if(if_index == AVAHI_IF_UNSPEC){
721
fprintf(stderr, "Mandos plugin mandos-client: "
722
"An IPv6 link-local address is incomplete"
723
" without a network interface\n");
727
/* Set the network interface number as scope */
728
to.in6.sin6_scope_id = (uint32_t)if_index;
731
to.in.sin_port = htons(port); /* Spurious warnings from
733
-Wunreachable-code */
742
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
743
char interface[IF_NAMESIZE];
744
if(if_indextoname((unsigned int)if_index, interface) == NULL){
745
perror_plus("if_indextoname");
747
fprintf(stderr, "Mandos plugin mandos-client: "
748
"Connection to: %s%%%s, port %" PRIu16 "\n",
749
ip, interface, port);
752
fprintf(stderr, "Mandos plugin mandos-client: "
753
"Connection to: %s, port %" PRIu16 "\n", ip, port);
755
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
756
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
759
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
762
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
766
perror_plus("inet_ntop");
768
if(strcmp(addrstr, ip) != 0){
769
fprintf(stderr, "Mandos plugin mandos-client: "
770
"Canonical address form: %s\n", addrstr);
781
ret = connect(tcp_sd, &to.in6, sizeof(to));
783
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
786
if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){
788
perror_plus("connect");
799
const char *out = mandos_protocol_version;
802
size_t out_size = strlen(out);
803
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
804
out_size - written));
807
perror_plus("write");
811
written += (size_t)ret;
812
if(written < out_size){
815
if(out == mandos_protocol_version){
830
fprintf(stderr, "Mandos plugin mandos-client: "
831
"Establishing TLS session with %s\n", ip);
839
/* Spurious warning from -Wint-to-pointer-cast */
840
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
848
ret = gnutls_handshake(session);
853
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
855
if(ret != GNUTLS_E_SUCCESS){
857
fprintf(stderr, "Mandos plugin mandos-client: "
858
"*** GnuTLS Handshake failed ***\n");
865
/* Read OpenPGP packet that contains the wanted password */
868
fprintf(stderr, "Mandos plugin mandos-client: "
869
"Retrieving OpenPGP encrypted password from %s\n", ip);
879
buffer_capacity = incbuffer(&buffer, buffer_length,
881
if(buffer_capacity == 0){
883
perror_plus("incbuffer");
893
sret = gnutls_record_recv(session, buffer+buffer_length,
329
fprintf(stderr, "Bad address: %s\n", ip);
332
to.sin6_port = htons(port);
333
to.sin6_scope_id = if_nametoindex("eth0");
335
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
341
ret = initgnutls (&es);
348
gnutls_transport_set_ptr (es.session, (gnutls_transport_ptr_t) tcp_sd);
350
ret = gnutls_handshake (es.session);
352
if (ret != GNUTLS_E_SUCCESS){
353
fprintf(stderr, "\n*** Handshake failed ***\n");
361
if (buffer_length + BUFFER_SIZE > buffer_capacity){
362
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
367
buffer_capacity += BUFFER_SIZE;
370
ret = gnutls_record_recv
371
(es.session, buffer+buffer_length, BUFFER_SIZE);
900
377
case GNUTLS_E_INTERRUPTED:
901
378
case GNUTLS_E_AGAIN:
903
380
case GNUTLS_E_REHANDSHAKE:
905
ret = gnutls_handshake(session);
911
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
913
fprintf(stderr, "Mandos plugin mandos-client: "
914
"*** GnuTLS Re-handshake failed ***\n");
381
ret = gnutls_handshake (es.session);
383
fprintf(stderr, "\n*** Handshake failed ***\n");
921
fprintf(stderr, "Mandos plugin mandos-client: "
922
"Unknown error while reading data from"
923
" encrypted session with Mandos server\n");
924
gnutls_bye(session, GNUTLS_SHUT_RDWR);
390
fprintf(stderr, "Unknown error while reading data from encrypted session with mandos server\n");
392
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
929
buffer_length += (size_t) sret;
934
fprintf(stderr, "Mandos plugin mandos-client: "
935
"Closing TLS session\n");
944
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
949
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
951
if(buffer_length > 0){
952
ssize_t decrypted_buffer_size;
953
decrypted_buffer_size = pgp_packet_decrypt(buffer,
956
if(decrypted_buffer_size >= 0){
959
while(written < (size_t) decrypted_buffer_size){
965
ret = (int)fwrite(decrypted_buffer + written, 1,
966
(size_t)decrypted_buffer_size - written,
968
if(ret == 0 and ferror(stdout)){
971
fprintf(stderr, "Mandos plugin mandos-client: "
972
"Error writing encrypted data: %s\n",
978
written += (size_t)ret;
984
/* Shutdown procedure */
989
free(decrypted_buffer);
992
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
998
perror_plus("close");
1000
gnutls_deinit(session);
396
buffer_length += ret;
400
if (buffer_length > 0){
401
if ((decrypted_buffer_size = gpg_packet_decrypt(buffer, buffer_length, &decrypted_buffer, CERT_ROOT)) == 0){
404
fwrite (decrypted_buffer, 1, decrypted_buffer_size, stdout);
405
free(decrypted_buffer);
412
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
415
gnutls_deinit (es.session);
416
gnutls_certificate_free_credentials (es.cred);
417
gnutls_global_deinit ();
1010
static void resolve_callback(AvahiSServiceResolver *r,
1011
AvahiIfIndex interface,
1012
AvahiProtocol proto,
1013
AvahiResolverEvent event,
1017
const char *host_name,
1018
const AvahiAddress *address,
1020
AVAHI_GCC_UNUSED AvahiStringList *txt,
1021
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1023
AVAHI_GCC_UNUSED void* userdata){
1026
/* Called whenever a service has been resolved successfully or
1035
case AVAHI_RESOLVER_FAILURE:
1036
fprintf(stderr, "Mandos plugin mandos-client: "
1037
"(Avahi Resolver) Failed to resolve service '%s'"
1038
" of type '%s' in domain '%s': %s\n", name, type, domain,
1039
avahi_strerror(avahi_server_errno(mc.server)));
1042
case AVAHI_RESOLVER_FOUND:
1044
char ip[AVAHI_ADDRESS_STR_MAX];
1045
avahi_address_snprint(ip, sizeof(ip), address);
1047
fprintf(stderr, "Mandos plugin mandos-client: "
1048
"Mandos server \"%s\" found on %s (%s, %"
1049
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
1050
ip, (intmax_t)interface, port);
1052
int ret = start_mandos_communication(ip, port, interface,
1053
avahi_proto_to_af(proto));
1055
avahi_simple_poll_quit(mc.simple_poll);
1057
ret = add_server(ip, port, interface,
1058
avahi_proto_to_af(proto));
1062
avahi_s_service_resolver_free(r);
1065
static void browse_callback(AvahiSServiceBrowser *b,
1066
AvahiIfIndex interface,
1067
AvahiProtocol protocol,
1068
AvahiBrowserEvent event,
1072
AVAHI_GCC_UNUSED AvahiLookupResultFlags
1074
AVAHI_GCC_UNUSED void* userdata){
1077
/* Called whenever a new services becomes available on the LAN or
1078
is removed from the LAN */
1086
case AVAHI_BROWSER_FAILURE:
1088
fprintf(stderr, "Mandos plugin mandos-client: "
1089
"(Avahi browser) %s\n",
1090
avahi_strerror(avahi_server_errno(mc.server)));
1091
avahi_simple_poll_quit(mc.simple_poll);
1094
case AVAHI_BROWSER_NEW:
1095
/* We ignore the returned Avahi resolver object. In the callback
1096
function we free it. If the Avahi server is terminated before
1097
the callback function is called the Avahi server will free the
1100
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
1101
name, type, domain, protocol, 0,
1102
resolve_callback, NULL) == NULL)
1103
fprintf(stderr, "Mandos plugin mandos-client: "
1104
"Avahi: Failed to resolve service '%s': %s\n",
1105
name, avahi_strerror(avahi_server_errno(mc.server)));
1108
case AVAHI_BROWSER_REMOVE:
1111
case AVAHI_BROWSER_ALL_FOR_NOW:
1112
case AVAHI_BROWSER_CACHE_EXHAUSTED:
1114
fprintf(stderr, "Mandos plugin mandos-client: "
1115
"No Mandos server found, still searching...\n");
1121
/* Signal handler that stops main loop after SIGTERM */
1122
static void handle_sigterm(int sig){
1127
signal_received = sig;
1128
int old_errno = errno;
1129
/* set main loop to exit */
1130
if(mc.simple_poll != NULL){
1131
avahi_simple_poll_quit(mc.simple_poll);
1136
bool get_flags(const char *ifname, struct ifreq *ifr){
1139
int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1141
perror_plus("socket");
1144
strcpy(ifr->ifr_name, ifname);
1145
ret = ioctl(s, SIOCGIFFLAGS, ifr);
1148
perror_plus("ioctl SIOCGIFFLAGS");
1155
bool good_flags(const char *ifname, const struct ifreq *ifr){
1157
/* Reject the loopback device */
1158
if(ifr->ifr_flags & IFF_LOOPBACK){
1160
fprintf(stderr, "Mandos plugin mandos-client: "
1161
"Rejecting loopback interface \"%s\"\n", ifname);
1165
/* Accept point-to-point devices only if connect_to is specified */
1166
if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){
1168
fprintf(stderr, "Mandos plugin mandos-client: "
1169
"Accepting point-to-point interface \"%s\"\n", ifname);
1173
/* Otherwise, reject non-broadcast-capable devices */
1174
if(not (ifr->ifr_flags & IFF_BROADCAST)){
1176
fprintf(stderr, "Mandos plugin mandos-client: "
1177
"Rejecting non-broadcast interface \"%s\"\n", ifname);
1181
/* Reject non-ARP interfaces (including dummy interfaces) */
1182
if(ifr->ifr_flags & IFF_NOARP){
1184
fprintf(stderr, "Mandos plugin mandos-client: "
1185
"Rejecting non-ARP interface \"%s\"\n", ifname);
1190
/* Accept this device */
1192
fprintf(stderr, "Mandos plugin mandos-client: "
1193
"Interface \"%s\" is good\n", ifname);
1199
* This function determines if a directory entry in /sys/class/net
1200
* corresponds to an acceptable network device.
1201
* (This function is passed to scandir(3) as a filter function.)
1203
int good_interface(const struct dirent *if_entry){
1204
if(if_entry->d_name[0] == '.'){
1209
if(not get_flags(if_entry->d_name, &ifr)){
1211
fprintf(stderr, "Mandos plugin mandos-client: "
1212
"Failed to get flags for interface \"%s\"\n",
1218
if(not good_flags(if_entry->d_name, &ifr)){
1225
* This function determines if a directory entry in /sys/class/net
1226
* corresponds to an acceptable network device which is up.
1227
* (This function is passed to scandir(3) as a filter function.)
1229
int up_interface(const struct dirent *if_entry){
1230
if(if_entry->d_name[0] == '.'){
1235
if(not get_flags(if_entry->d_name, &ifr)){
1237
fprintf(stderr, "Mandos plugin mandos-client: "
1238
"Failed to get flags for interface \"%s\"\n",
1244
/* Reject down interfaces */
1245
if(not (ifr.ifr_flags & IFF_UP)){
1247
fprintf(stderr, "Mandos plugin mandos-client: "
1248
"Rejecting down interface \"%s\"\n",
1254
/* Reject non-running interfaces */
1255
if(not (ifr.ifr_flags & IFF_RUNNING)){
1257
fprintf(stderr, "Mandos plugin mandos-client: "
1258
"Rejecting non-running interface \"%s\"\n",
1264
if(not good_flags(if_entry->d_name, &ifr)){
1270
int notdotentries(const struct dirent *direntry){
1271
/* Skip "." and ".." */
1272
if(direntry->d_name[0] == '.'
1273
and (direntry->d_name[1] == '\0'
1274
or (direntry->d_name[1] == '.'
1275
and direntry->d_name[2] == '\0'))){
1281
/* Is this directory entry a runnable program? */
1282
int runnable_hook(const struct dirent *direntry){
1286
if((direntry->d_name)[0] == '\0'){
1291
/* Save pointer to last character */
1292
char *end = strchr(direntry->d_name, '\0')-1;
1299
if(((direntry->d_name)[0] == '#')
1301
/* Temporary #name# */
1305
/* XXX more rules here */
1307
ret = stat(direntry->d_name, &st);
1310
perror_plus("Could not stat plugin");
1314
if(not (S_ISREG(st.st_mode))){
1315
/* Not a regular file */
1318
if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){
1319
/* Not executable */
1325
int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){
1327
struct timespec now;
1328
struct timespec waited_time;
1329
intmax_t block_time;
1332
if(mc.current_server == NULL){
1334
fprintf(stderr, "Mandos plugin mandos-client: "
1335
"Wait until first server is found. No timeout!\n");
1337
ret = avahi_simple_poll_iterate(s, -1);
1340
fprintf(stderr, "Mandos plugin mandos-client: "
1341
"Check current_server if we should run it,"
1344
/* the current time */
1345
ret = clock_gettime(CLOCK_MONOTONIC, &now);
1347
perror_plus("clock_gettime");
1350
/* Calculating in ms how long time between now and server
1351
who we visted longest time ago. Now - last seen. */
1352
waited_time.tv_sec = (now.tv_sec
1353
- mc.current_server->last_seen.tv_sec);
1354
waited_time.tv_nsec = (now.tv_nsec
1355
- mc.current_server->last_seen.tv_nsec);
1356
/* total time is 10s/10,000ms.
1357
Converting to s from ms by dividing by 1,000,
1358
and ns to ms by dividing by 1,000,000. */
1359
block_time = ((retry_interval
1360
- ((intmax_t)waited_time.tv_sec * 1000))
1361
- ((intmax_t)waited_time.tv_nsec / 1000000));
1364
fprintf(stderr, "Mandos plugin mandos-client: "
1365
"Blocking for %" PRIdMAX " ms\n", block_time);
1368
if(block_time <= 0){
1369
ret = start_mandos_communication(mc.current_server->ip,
1370
mc.current_server->port,
1371
mc.current_server->if_index,
1372
mc.current_server->af);
1374
avahi_simple_poll_quit(mc.simple_poll);
1377
ret = clock_gettime(CLOCK_MONOTONIC,
1378
&mc.current_server->last_seen);
1380
perror_plus("clock_gettime");
1383
mc.current_server = mc.current_server->next;
1384
block_time = 0; /* Call avahi to find new Mandos
1385
servers, but don't block */
1388
ret = avahi_simple_poll_iterate(s, (int)block_time);
1391
if (ret > 0 or errno != EINTR){
1392
return (ret != 1) ? ret : 0;
1398
int main(int argc, char *argv[]){
1399
AvahiSServiceBrowser *sb = NULL;
1404
int exitcode = EXIT_SUCCESS;
1405
const char *interface = "";
1406
struct ifreq network;
1408
bool take_down_interface = false;
1411
char tempdir[] = "/tmp/mandosXXXXXX";
1412
bool tempdir_created = false;
1413
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
1414
const char *seckey = PATHDIR "/" SECKEY;
1415
const char *pubkey = PATHDIR "/" PUBKEY;
1417
bool gnutls_initialized = false;
1418
bool gpgme_initialized = false;
1420
double retry_interval = 10; /* 10s between trying a server and
1421
retrying the same server again */
1423
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1424
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1429
/* Lower any group privileges we might have, just to be safe */
1433
perror_plus("setgid");
1436
/* Lower user privileges (temporarily) */
1440
perror_plus("seteuid");
1448
struct argp_option options[] = {
1449
{ .name = "debug", .key = 128,
1450
.doc = "Debug mode", .group = 3 },
1451
{ .name = "connect", .key = 'c',
1452
.arg = "ADDRESS:PORT",
1453
.doc = "Connect directly to a specific Mandos server",
1455
{ .name = "interface", .key = 'i',
1457
.doc = "Network interface that will be used to search for"
1460
{ .name = "seckey", .key = 's',
1462
.doc = "OpenPGP secret key file base name",
1464
{ .name = "pubkey", .key = 'p',
1466
.doc = "OpenPGP public key file base name",
1468
{ .name = "dh-bits", .key = 129,
1470
.doc = "Bit length of the prime number used in the"
1471
" Diffie-Hellman key exchange",
1473
{ .name = "priority", .key = 130,
1475
.doc = "GnuTLS priority string for the TLS handshake",
1477
{ .name = "delay", .key = 131,
1479
.doc = "Maximum delay to wait for interface startup",
1481
{ .name = "retry", .key = 132,
1483
.doc = "Retry interval used when denied by the mandos server",
1486
* These reproduce what we would get without ARGP_NO_HELP
1488
{ .name = "help", .key = '?',
1489
.doc = "Give this help list", .group = -1 },
1490
{ .name = "usage", .key = -3,
1491
.doc = "Give a short usage message", .group = -1 },
1492
{ .name = "version", .key = 'V',
1493
.doc = "Print program version", .group = -1 },
1497
error_t parse_opt(int key, char *arg,
1498
struct argp_state *state){
1501
case 128: /* --debug */
1504
case 'c': /* --connect */
1507
case 'i': /* --interface */
1510
case 's': /* --seckey */
1513
case 'p': /* --pubkey */
1516
case 129: /* --dh-bits */
1518
tmpmax = strtoimax(arg, &tmp, 10);
1519
if(errno != 0 or tmp == arg or *tmp != '\0'
1520
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1521
argp_error(state, "Bad number of DH bits");
1523
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1525
case 130: /* --priority */
1528
case 131: /* --delay */
1530
delay = strtof(arg, &tmp);
1531
if(errno != 0 or tmp == arg or *tmp != '\0'){
1532
argp_error(state, "Bad delay");
1534
case 132: /* --retry */
1536
retry_interval = strtod(arg, &tmp);
1537
if(errno != 0 or tmp == arg or *tmp != '\0'
1538
or (retry_interval * 1000) > INT_MAX
1539
or retry_interval < 0){
1540
argp_error(state, "Bad retry interval");
1544
* These reproduce what we would get without ARGP_NO_HELP
1546
case '?': /* --help */
1547
argp_state_help(state, state->out_stream,
1548
(ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR)
1549
& ~(unsigned int)ARGP_HELP_EXIT_OK);
1550
case -3: /* --usage */
1551
argp_state_help(state, state->out_stream,
1552
ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR);
1553
case 'V': /* --version */
1554
fprintf(state->out_stream, "Mandos plugin mandos-client: ");
1555
fprintf(state->out_stream, "%s\n", argp_program_version);
1556
exit(argp_err_exit_status);
1559
return ARGP_ERR_UNKNOWN;
1564
struct argp argp = { .options = options, .parser = parse_opt,
1566
.doc = "Mandos client -- Get and decrypt"
1567
" passwords from a Mandos server" };
1568
ret = argp_parse(&argp, argc, argv,
1569
ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL);
1576
perror_plus("argp_parse");
1577
exitcode = EX_OSERR;
1580
exitcode = EX_USAGE;
1586
/* Work around Debian bug #633582:
1587
<http://bugs.debian.org/633582> */
1590
/* Re-raise priviliges */
1594
perror_plus("seteuid");
1597
if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){
1598
int seckey_fd = open(seckey, O_RDONLY);
1599
if(seckey_fd == -1){
1600
perror_plus("open");
1602
ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st));
1604
perror_plus("fstat");
1606
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1607
ret = fchown(seckey_fd, uid, gid);
1609
perror_plus("fchown");
1613
TEMP_FAILURE_RETRY(close(seckey_fd));
1617
if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){
1618
int pubkey_fd = open(pubkey, O_RDONLY);
1619
if(pubkey_fd == -1){
1620
perror_plus("open");
1622
ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st));
1624
perror_plus("fstat");
1626
if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
1627
ret = fchown(pubkey_fd, uid, gid);
1629
perror_plus("fchown");
1633
TEMP_FAILURE_RETRY(close(pubkey_fd));
1637
/* Lower privileges */
1641
perror_plus("seteuid");
1645
/* Find network hooks and run them */
1647
struct dirent **direntries;
1648
struct dirent *direntry;
1649
int numhooks = scandir(HOOKDIR, &direntries, runnable_hook,
1652
perror_plus("scandir");
1654
int devnull = open("/dev/null", O_RDONLY);
1655
for(int i = 0; i < numhooks; i++){
1656
direntry = direntries[0];
1657
char *fullname = NULL;
1658
ret = asprintf(&fullname, "%s/%s", tempdir,
1661
perror_plus("asprintf");
1664
pid_t hook_pid = fork();
1667
dup2(devnull, STDIN_FILENO);
1669
dup2(STDERR_FILENO, STDOUT_FILENO);
1670
ret = setenv("DEVICE", interface, 1);
1672
perror_plus("setenv");
1675
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1677
perror_plus("setenv");
1680
ret = setenv("MODE", "start", 1);
1682
perror_plus("setenv");
1686
ret = asprintf(&delaystring, "%f", delay);
1688
perror_plus("asprintf");
1691
ret = setenv("DELAY", delaystring, 1);
1694
perror_plus("setenv");
1698
ret = execl(fullname, direntry->d_name, "start", NULL);
1699
perror_plus("execl");
1702
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1703
perror_plus("waitpid");
1707
if(WIFEXITED(status)){
1708
if(WEXITSTATUS(status) != 0){
1709
fprintf(stderr, "Mandos plugin mandos-client: "
1710
"Warning: network hook \"%s\" exited"
1711
" with status %d\n", direntry->d_name,
1712
WEXITSTATUS(status));
1716
} else if(WIFSIGNALED(status)){
1717
fprintf(stderr, "Mandos plugin mandos-client: "
1718
"Warning: network hook \"%s\" died by"
1719
" signal %d\n", direntry->d_name,
1724
fprintf(stderr, "Mandos plugin mandos-client: "
1725
"Warning: network hook \"%s\" crashed\n",
421
static AvahiSimplePoll *simple_poll = NULL;
422
static AvahiServer *server = NULL;
424
static void resolve_callback(
425
AvahiSServiceResolver *r,
426
AVAHI_GCC_UNUSED AvahiIfIndex interface,
427
AVAHI_GCC_UNUSED AvahiProtocol protocol,
428
AvahiResolverEvent event,
432
const char *host_name,
433
const AvahiAddress *address,
435
AvahiStringList *txt,
436
AvahiLookupResultFlags flags,
437
AVAHI_GCC_UNUSED void* userdata) {
441
/* Called whenever a service has been resolved successfully or timed out */
444
case AVAHI_RESOLVER_FAILURE:
445
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_server_errno(server)));
448
case AVAHI_RESOLVER_FOUND: {
449
char ip[AVAHI_ADDRESS_STR_MAX];
450
avahi_address_snprint(ip, sizeof(ip), address);
451
int ret = start_mandos_communcation(ip, port);
459
avahi_s_service_resolver_free(r);
462
static void browse_callback(
463
AvahiSServiceBrowser *b,
464
AvahiIfIndex interface,
465
AvahiProtocol protocol,
466
AvahiBrowserEvent event,
470
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
473
AvahiServer *s = userdata;
476
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
480
case AVAHI_BROWSER_FAILURE:
482
fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(server)));
483
avahi_simple_poll_quit(simple_poll);
486
case AVAHI_BROWSER_NEW:
487
/* We ignore the returned resolver object. In the callback
488
function we free it. If the server is terminated before
489
the callback function is called the server will free
490
the resolver for us. */
492
if (!(avahi_s_service_resolver_new(s, interface, protocol, name, type, domain, AVAHI_PROTO_INET6, 0, resolve_callback, s)))
493
fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(s)));
497
case AVAHI_BROWSER_REMOVE:
500
case AVAHI_BROWSER_ALL_FOR_NOW:
501
case AVAHI_BROWSER_CACHE_EXHAUSTED:
506
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
507
AvahiServerConfig config;
508
AvahiSServiceBrowser *sb = NULL;
1741
512
avahi_set_log_function(empty_log);
1744
if(interface[0] == '\0'){
1745
struct dirent **direntries;
1746
/* First look for interfaces that are up */
1747
ret = scandir(sys_class_net, &direntries, up_interface,
1750
/* No up interfaces, look for any good interfaces */
1752
ret = scandir(sys_class_net, &direntries, good_interface,
1756
/* Pick the first interface returned */
1757
interface = strdup(direntries[0]->d_name);
1759
fprintf(stderr, "Mandos plugin mandos-client: "
1760
"Using interface \"%s\"\n", interface);
1762
if(interface == NULL){
1763
perror_plus("malloc");
1765
exitcode = EXIT_FAILURE;
1771
fprintf(stderr, "Mandos plugin mandos-client: "
1772
"Could not find a network interface\n");
1773
exitcode = EXIT_FAILURE;
1778
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1779
from the signal handler */
1780
/* Initialize the pseudo-RNG for Avahi */
1781
srand((unsigned int) time(NULL));
1782
mc.simple_poll = avahi_simple_poll_new();
1783
if(mc.simple_poll == NULL){
1784
fprintf(stderr, "Mandos plugin mandos-client: "
1785
"Avahi: Failed to create simple poll object.\n");
1786
exitcode = EX_UNAVAILABLE;
1790
sigemptyset(&sigterm_action.sa_mask);
1791
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1793
perror_plus("sigaddset");
1794
exitcode = EX_OSERR;
1797
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1799
perror_plus("sigaddset");
1800
exitcode = EX_OSERR;
1803
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1805
perror_plus("sigaddset");
1806
exitcode = EX_OSERR;
1809
/* Need to check if the handler is SIG_IGN before handling:
1810
| [[info:libc:Initial Signal Actions]] |
1811
| [[info:libc:Basic Signal Handling]] |
1813
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1815
perror_plus("sigaction");
1818
if(old_sigterm_action.sa_handler != SIG_IGN){
1819
ret = sigaction(SIGINT, &sigterm_action, NULL);
1821
perror_plus("sigaction");
1822
exitcode = EX_OSERR;
1826
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1828
perror_plus("sigaction");
1831
if(old_sigterm_action.sa_handler != SIG_IGN){
1832
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1834
perror_plus("sigaction");
1835
exitcode = EX_OSERR;
1839
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1841
perror_plus("sigaction");
1844
if(old_sigterm_action.sa_handler != SIG_IGN){
1845
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1847
perror_plus("sigaction");
1848
exitcode = EX_OSERR;
1853
/* If the interface is down, bring it up */
1854
if(strcmp(interface, "none") != 0){
1855
if_index = (AvahiIfIndex) if_nametoindex(interface);
1857
fprintf(stderr, "Mandos plugin mandos-client: "
1858
"No such interface: \"%s\"\n", interface);
1859
exitcode = EX_UNAVAILABLE;
1867
/* Re-raise priviliges */
1871
perror_plus("seteuid");
1875
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1876
messages about the network interface to mess up the prompt */
1877
ret = klogctl(8, NULL, 5);
1878
bool restore_loglevel = true;
1880
restore_loglevel = false;
1881
perror_plus("klogctl");
1883
#endif /* __linux__ */
1885
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1887
perror_plus("socket");
1888
exitcode = EX_OSERR;
1890
if(restore_loglevel){
1891
ret = klogctl(7, NULL, 0);
1893
perror_plus("klogctl");
1896
#endif /* __linux__ */
1897
/* Lower privileges */
1901
perror_plus("seteuid");
1905
strcpy(network.ifr_name, interface);
1906
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1908
perror_plus("ioctl SIOCGIFFLAGS");
1910
if(restore_loglevel){
1911
ret = klogctl(7, NULL, 0);
1913
perror_plus("klogctl");
1916
#endif /* __linux__ */
1917
exitcode = EX_OSERR;
1918
/* Lower privileges */
1922
perror_plus("seteuid");
1926
if((network.ifr_flags & IFF_UP) == 0){
1927
network.ifr_flags |= IFF_UP;
1928
take_down_interface = true;
1929
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1931
take_down_interface = false;
1932
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1933
exitcode = EX_OSERR;
1935
if(restore_loglevel){
1936
ret = klogctl(7, NULL, 0);
1938
perror_plus("klogctl");
1941
#endif /* __linux__ */
1942
/* Lower privileges */
1946
perror_plus("seteuid");
1951
/* Sleep checking until interface is running.
1952
Check every 0.25s, up to total time of delay */
1953
for(int i=0; i < delay * 4; i++){
1954
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1956
perror_plus("ioctl SIOCGIFFLAGS");
1957
} else if(network.ifr_flags & IFF_RUNNING){
1960
struct timespec sleeptime = { .tv_nsec = 250000000 };
1961
ret = nanosleep(&sleeptime, NULL);
1962
if(ret == -1 and errno != EINTR){
1963
perror_plus("nanosleep");
1966
if(not take_down_interface){
1967
/* We won't need the socket anymore */
1968
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1970
perror_plus("close");
1974
if(restore_loglevel){
1975
/* Restores kernel loglevel to default */
1976
ret = klogctl(7, NULL, 0);
1978
perror_plus("klogctl");
1981
#endif /* __linux__ */
1982
/* Lower privileges */
1984
if(take_down_interface){
1985
/* Lower privileges */
1988
perror_plus("seteuid");
1991
/* Lower privileges permanently */
1994
perror_plus("setuid");
2003
ret = init_gnutls_global(pubkey, seckey);
2005
fprintf(stderr, "Mandos plugin mandos-client: "
2006
"init_gnutls_global failed\n");
2007
exitcode = EX_UNAVAILABLE;
2010
gnutls_initialized = true;
2017
if(mkdtemp(tempdir) == NULL){
2018
perror_plus("mkdtemp");
2021
tempdir_created = true;
2027
if(not init_gpgme(pubkey, seckey, tempdir)){
2028
fprintf(stderr, "Mandos plugin mandos-client: "
2029
"init_gpgme failed\n");
2030
exitcode = EX_UNAVAILABLE;
2033
gpgme_initialized = true;
2040
if(connect_to != NULL){
2041
/* Connect directly, do not use Zeroconf */
2042
/* (Mainly meant for debugging) */
2043
char *address = strrchr(connect_to, ':');
2044
if(address == NULL){
2045
fprintf(stderr, "Mandos plugin mandos-client: "
2046
"No colon in address\n");
2047
exitcode = EX_USAGE;
2057
tmpmax = strtoimax(address+1, &tmp, 10);
2058
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2059
or tmpmax != (uint16_t)tmpmax){
2060
fprintf(stderr, "Mandos plugin mandos-client: "
2061
"Bad port number\n");
2062
exitcode = EX_USAGE;
2070
port = (uint16_t)tmpmax;
2072
/* Colon in address indicates IPv6 */
2074
if(strchr(connect_to, ':') != NULL){
2076
/* Accept [] around IPv6 address - see RFC 5952 */
2077
if(connect_to[0] == '[' and address[-1] == ']')
2085
address = connect_to;
2091
while(not quit_now){
2092
ret = start_mandos_communication(address, port, if_index, af);
2093
if(quit_now or ret == 0){
2097
fprintf(stderr, "Mandos plugin mandos-client: "
2098
"Retrying in %d seconds\n", (int)retry_interval);
2100
sleep((int)retry_interval);
2104
exitcode = EXIT_SUCCESS;
2115
AvahiServerConfig config;
2116
/* Do not publish any local Zeroconf records */
514
/* Initialize the psuedo-RNG */
517
/* Allocate main loop object */
518
if (!(simple_poll = avahi_simple_poll_new())) {
519
fprintf(stderr, "Failed to create simple poll object.\n");
523
/* Do not publish any local records */
2117
524
avahi_server_config_init(&config);
2118
525
config.publish_hinfo = 0;
2119
526
config.publish_addresses = 0;
2120
527
config.publish_workstation = 0;
2121
528
config.publish_domain = 0;
530
/* /\* Set a unicast DNS server for wide area DNS-SD *\/ */
531
/* avahi_address_parse("193.11.177.11", AVAHI_PROTO_UNSPEC, &config.wide_area_servers[0]); */
532
/* config.n_wide_area_servers = 1; */
533
/* config.enable_wide_area = 1; */
2123
535
/* Allocate a new server */
2124
mc.server = avahi_server_new(avahi_simple_poll_get
2125
(mc.simple_poll), &config, NULL,
2128
/* Free the Avahi configuration data */
536
server = avahi_server_new(avahi_simple_poll_get(simple_poll), &config, NULL, NULL, &error);
538
/* Free the configuration data */
2129
539
avahi_server_config_free(&config);
2132
/* Check if creating the Avahi server object succeeded */
2133
if(mc.server == NULL){
2134
fprintf(stderr, "Mandos plugin mandos-client: "
2135
"Failed to create Avahi server: %s\n",
2136
avahi_strerror(error));
2137
exitcode = EX_UNAVAILABLE;
2145
/* Create the Avahi service browser */
2146
sb = avahi_s_service_browser_new(mc.server, if_index,
2147
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2148
NULL, 0, browse_callback, NULL);
2150
fprintf(stderr, "Mandos plugin mandos-client: "
2151
"Failed to create service browser: %s\n",
2152
avahi_strerror(avahi_server_errno(mc.server)));
2153
exitcode = EX_UNAVAILABLE;
2161
/* Run the main loop */
2164
fprintf(stderr, "Mandos plugin mandos-client: "
2165
"Starting Avahi loop search\n");
2168
ret = avahi_loop_with_timeout(mc.simple_poll,
2169
(int)(retry_interval * 1000));
2171
fprintf(stderr, "Mandos plugin mandos-client: "
2172
"avahi_loop_with_timeout exited %s\n",
2173
(ret == 0) ? "successfully" : "with error");
2179
fprintf(stderr, "Mandos plugin mandos-client: "
2180
"%s exiting\n", argv[0]);
2183
/* Cleanup things */
2185
avahi_s_service_browser_free(sb);
2187
if(mc.server != NULL)
2188
avahi_server_free(mc.server);
2190
if(mc.simple_poll != NULL)
2191
avahi_simple_poll_free(mc.simple_poll);
2193
if(gnutls_initialized){
2194
gnutls_certificate_free_credentials(mc.cred);
2195
gnutls_global_deinit();
2196
gnutls_dh_params_deinit(mc.dh_params);
2199
if(gpgme_initialized){
2200
gpgme_release(mc.ctx);
2203
/* Cleans up the circular linked list of Mandos servers the client
2205
if(mc.current_server != NULL){
2206
mc.current_server->prev->next = NULL;
2207
while(mc.current_server != NULL){
2208
server *next = mc.current_server->next;
2209
free(mc.current_server);
2210
mc.current_server = next;
2214
/* XXX run network hooks "stop" here */
2216
/* Take down the network interface */
2217
if(take_down_interface){
2218
/* Re-raise priviliges */
2222
perror_plus("seteuid");
2225
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2227
perror_plus("ioctl SIOCGIFFLAGS");
2228
} else if(network.ifr_flags & IFF_UP){
2229
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2230
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2232
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2235
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2237
perror_plus("close");
2239
/* Lower privileges permanently */
2243
perror_plus("setuid");
2248
/* Removes the GPGME temp directory and all files inside */
2249
if(tempdir_created){
2250
struct dirent **direntries = NULL;
2251
struct dirent *direntry = NULL;
2252
int numentries = scandir(tempdir, &direntries, notdotentries,
2254
if (numentries > 0){
2255
for(int i = 0; i < numentries; i++){
2256
direntry = direntries[i];
2257
char *fullname = NULL;
2258
ret = asprintf(&fullname, "%s/%s", tempdir,
2261
perror_plus("asprintf");
2264
ret = remove(fullname);
2266
fprintf(stderr, "Mandos plugin mandos-client: "
2267
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2273
/* need to clean even if 0 because man page doesn't specify */
2275
if (numentries == -1){
2276
perror_plus("scandir");
2278
ret = rmdir(tempdir);
2279
if(ret == -1 and errno != ENOENT){
2280
perror_plus("rmdir");
2285
sigemptyset(&old_sigterm_action.sa_mask);
2286
old_sigterm_action.sa_handler = SIG_DFL;
2287
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2288
&old_sigterm_action,
2291
perror_plus("sigaction");
2294
ret = raise(signal_received);
2295
} while(ret != 0 and errno == EINTR);
2297
perror_plus("raise");
2300
TEMP_FAILURE_RETRY(pause());
541
/* Check wether creating the server object succeeded */
543
fprintf(stderr, "Failed to create server: %s\n", avahi_strerror(error));
547
/* Create the service browser */
548
if (!(sb = avahi_s_service_browser_new(server, if_nametoindex("eth0"), AVAHI_PROTO_INET6, "_mandos._tcp", NULL, 0, browse_callback, server))) {
549
fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_server_errno(server)));
553
/* Run the main loop */
554
avahi_simple_poll_loop(simple_poll);
562
avahi_s_service_browser_free(sb);
565
avahi_server_free(server);
568
avahi_simple_poll_free(simple_poll);