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