46
103
#include <avahi-common/malloc.h>
47
104
#include <avahi-common/error.h>
50
#include <sys/types.h> /* socket(), inet_pton() */
51
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
52
struct in6_addr, inet_pton() */
53
#include <gnutls/gnutls.h> /* All GnuTLS stuff */
54
#include <gnutls/openpgp.h> /* GnuTLS with openpgp stuff */
56
#include <unistd.h> /* close() */
57
#include <netinet/in.h>
58
#include <stdbool.h> /* true */
59
#include <string.h> /* memset */
60
#include <arpa/inet.h> /* inet_pton() */
61
#include <iso646.h> /* not */
64
#include <errno.h> /* perror() */
107
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
110
init_gnutls_session(),
112
#include <gnutls/openpgp.h>
113
/* gnutls_certificate_set_openpgp_key_file(),
114
GNUTLS_OPENPGP_FMT_BASE64 */
117
#include <gpgme.h> /* All GPGME types, constants and
120
GPGME_PROTOCOL_OpenPGP,
70
123
#define BUFFER_SIZE 256
73
const char *certdir = "/conf/conf.d/cryptkeyreq/";
74
const char *certfile = "openpgp-client.txt";
75
const char *certkey = "openpgp-client-key.txt";
125
#define PATHDIR "/conf/conf.d/mandos"
126
#define SECKEY "seckey.txt"
127
#define PUBKEY "pubkey.txt"
128
#define HOOKDIR "/lib/mandos/network-hooks.d"
77
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 */
80
gnutls_session_t session;
151
AvahiSimplePoll *simple_poll;
81
153
gnutls_certificate_credentials_t cred;
154
unsigned int dh_bits;
82
155
gnutls_dh_params_t dh_params;
86
ssize_t pgp_packet_decrypt (char *packet, size_t packet_size,
87
char **new_packet, const char *homedir){
88
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){
92
ssize_t new_packet_capacity = 0;
93
ssize_t new_packet_length = 0;
94
237
gpgme_engine_info_t engine_info;
97
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");
101
284
gpgme_check_version(NULL);
102
285
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
103
if (rc != GPG_ERR_NO_ERROR){
104
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
286
if(rc != GPG_ERR_NO_ERROR){
287
fprintf(stderr, "Mandos plugin mandos-client: "
288
"bad gpgme_engine_check_version: %s: %s\n",
105
289
gpgme_strsource(rc), gpgme_strerror(rc));
109
/* Set GPGME home directory */
110
rc = gpgme_get_engine_info (&engine_info);
111
if (rc != GPG_ERR_NO_ERROR){
112
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",
113
298
gpgme_strsource(rc), gpgme_strerror(rc));
116
301
while(engine_info != NULL){
117
302
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
118
303
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
119
engine_info->file_name, homedir);
304
engine_info->file_name, tempdir);
122
307
engine_info = engine_info->next;
124
309
if(engine_info == NULL){
125
fprintf(stderr, "Could not set home dir to %s\n", homedir);
129
/* Create new GPGME data buffer from packet buffer */
130
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
131
if (rc != GPG_ERR_NO_ERROR){
132
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",
133
355
gpgme_strsource(rc), gpgme_strerror(rc));
137
359
/* Create new empty GPGME data buffer for the plaintext */
138
360
rc = gpgme_data_new(&dh_plain);
139
if (rc != GPG_ERR_NO_ERROR){
140
fprintf(stderr, "bad gpgme_data_new: %s: %s\n",
141
gpgme_strsource(rc), gpgme_strerror(rc));
145
/* Create new GPGME "context" */
146
rc = gpgme_new(&ctx);
147
if (rc != GPG_ERR_NO_ERROR){
148
fprintf(stderr, "bad gpgme_new: %s: %s\n",
149
gpgme_strsource(rc), gpgme_strerror(rc));
153
/* Decrypt data from the FILE pointer to the plaintext data
155
rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain);
156
if (rc != GPG_ERR_NO_ERROR){
157
fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n",
158
gpgme_strsource(rc), gpgme_strerror(rc));
163
fprintf(stderr, "Decryption of OpenPGP packet succeeded\n");
167
gpgme_decrypt_result_t result;
168
result = gpgme_op_decrypt_result(ctx);
170
fprintf(stderr, "gpgme_op_decrypt_result failed\n");
172
fprintf(stderr, "Unsupported algorithm: %s\n",
173
result->unsupported_algorithm);
174
fprintf(stderr, "Wrong key usage: %d\n",
175
result->wrong_key_usage);
176
if(result->file_name != NULL){
177
fprintf(stderr, "File name: %s\n", result->file_name);
179
gpgme_recipient_t recipient;
180
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;
182
396
while(recipient != NULL){
183
fprintf(stderr, "Public key algorithm: %s\n",
397
fprintf(stderr, "Mandos plugin mandos-client: "
398
"Public key algorithm: %s\n",
184
399
gpgme_pubkey_algo_name(recipient->pubkey_algo));
185
fprintf(stderr, "Key ID: %s\n", recipient->keyid);
186
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",
187
404
recipient->status == GPG_ERR_NO_SECKEY
189
406
recipient = recipient->next;
195
/* Delete the GPGME FILE pointer cryptotext data buffer */
196
gpgme_data_release(dh_crypto);
414
fprintf(stderr, "Mandos plugin mandos-client: "
415
"Decryption of OpenPGP data succeeded\n");
198
418
/* Seek back to the beginning of the GPGME plaintext data buffer */
199
if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){
200
perror("pgpme_data_seek");
419
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
420
perror_plus("gpgme_data_seek");
421
plaintext_length = -1;
205
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
206
*new_packet = realloc(*new_packet,
207
(unsigned int)new_packet_capacity
209
if (*new_packet == NULL){
213
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;
216
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length,
436
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
218
438
/* Print the data, if any */
223
perror("gpgme_data_read");
226
new_packet_length += ret;
229
/* FIXME: check characters before printing to screen so to not print
230
terminal control characters */
232
/* fprintf(stderr, "decrypted password is: "); */
233
/* fwrite(*new_packet, 1, new_packet_length, stderr); */
234
/* 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);
237
465
/* Delete the GPGME plaintext data buffer */
238
466
gpgme_data_release(dh_plain);
239
return new_packet_length;
467
return plaintext_length;
242
static const char * safer_gnutls_strerror (int value) {
243
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 */
245
474
ret = "(unknown)";
249
void debuggnutls(__attribute__((unused)) int level,
251
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);
254
int initgnutls(encrypted_session *es){
484
static int init_gnutls_global(const char *pubkeyfilename,
485
const char *seckeyfilename){
259
fprintf(stderr, "Initializing GnuTLS\n");
489
fprintf(stderr, "Mandos plugin mandos-client: "
490
"Initializing GnuTLS\n");
262
if ((ret = gnutls_global_init ())
263
!= GNUTLS_E_SUCCESS) {
264
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."
269
504
gnutls_global_set_log_level(11);
270
505
gnutls_global_set_log_function(debuggnutls);
273
/* openpgp credentials */
274
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
275
!= GNUTLS_E_SUCCESS) {
276
fprintf (stderr, "memory error: %s\n",
277
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();
282
fprintf(stderr, "Attempting to use OpenPGP certificate %s"
283
" 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,
287
524
ret = gnutls_certificate_set_openpgp_key_file
288
(es->cred, certfile, certkey, GNUTLS_OPENPGP_FMT_BASE64);
289
if (ret != GNUTLS_E_SUCCESS) {
291
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s',"
293
ret, certfile, certkey);
294
fprintf(stdout, "The Error is: %s\n",
295
safer_gnutls_strerror(ret));
299
//GnuTLS server initialization
300
if ((ret = gnutls_dh_params_init (&es->dh_params))
301
!= GNUTLS_E_SUCCESS) {
302
fprintf (stderr, "Error in dh parameter initialization: %s\n",
303
safer_gnutls_strerror(ret));
307
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
308
!= GNUTLS_E_SUCCESS) {
309
fprintf (stderr, "Error in prime generation: %s\n",
310
safer_gnutls_strerror(ret));
314
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
316
// GnuTLS session creation
317
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
318
!= GNUTLS_E_SUCCESS){
319
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
320
safer_gnutls_strerror(ret));
323
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
324
!= GNUTLS_E_SUCCESS) {
325
fprintf(stderr, "Syntax error at: %s\n", err);
326
fprintf(stderr, "GnuTLS error: %s\n",
327
safer_gnutls_strerror(ret));
331
if ((ret = gnutls_credentials_set
332
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
333
!= GNUTLS_E_SUCCESS) {
334
fprintf(stderr, "Error setting a credentials set: %s\n",
335
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);
339
615
/* ignore client certificate if any. */
340
gnutls_certificate_server_set_request (es->session,
616
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
343
gnutls_dh_set_prime_bits (es->session, DH_BITS);
618
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
348
void empty_log(__attribute__((unused)) AvahiLogLevel level,
349
__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){}
351
int start_mandos_communication(const char *ip, uint16_t port,
352
AvahiIfIndex if_index){
354
struct sockaddr_in6 to;
355
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;
356
637
char *buffer = NULL;
357
char *decrypted_buffer;
638
char *decrypted_buffer = NULL;
358
639
size_t buffer_length = 0;
359
640
size_t buffer_capacity = 0;
360
ssize_t decrypted_buffer_size;
363
char interface[IF_NAMESIZE];
366
fprintf(stderr, "Setting up a tcp connection to %s, port %d\n",
370
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
376
if(if_indextoname((unsigned int)if_index, interface) == NULL){
378
perror("if_indextoname");
384
fprintf(stderr, "Binding to interface %s\n", interface);
387
memset(&to,0,sizeof(to)); /* Spurious warning */
388
to.sin6_family = AF_INET6;
389
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");
395
fprintf(stderr, "Bad address: %s\n", ip);
398
to.sin6_port = htons(port); /* Spurious warning */
400
to.sin6_scope_id = (uint32_t)if_index;
403
fprintf(stderr, "Connection to: %s, port %d\n", ip, port);
404
/* char addrstr[INET6_ADDRSTRLEN]; */
405
/* if(inet_ntop(to.sin6_family, &(to.sin6_addr), addrstr, */
406
/* sizeof(addrstr)) == NULL){ */
407
/* perror("inet_ntop"); */
409
/* fprintf(stderr, "Really connecting to: %s, port %d\n", */
410
/* addrstr, ntohs(to.sin6_port)); */
414
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
420
ret = initgnutls (&es);
426
gnutls_transport_set_ptr (es.session,
427
(gnutls_transport_ptr_t) tcp_sd);
430
fprintf(stderr, "Establishing TLS session with %s\n", ip);
433
ret = gnutls_handshake (es.session);
435
if (ret != GNUTLS_E_SUCCESS){
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){
437
fprintf(stderr, "\n*** Handshake failed ***\n");
857
fprintf(stderr, "Mandos plugin mandos-client: "
858
"*** GnuTLS Handshake failed ***\n");
444
//Retrieve OpenPGP packet that contains the wanted password
865
/* Read OpenPGP packet that contains the wanted password */
447
fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
868
fprintf(stderr, "Mandos plugin mandos-client: "
869
"Retrieving OpenPGP encrypted password from %s\n", ip);
452
if (buffer_length + BUFFER_SIZE > buffer_capacity){
453
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
458
buffer_capacity += BUFFER_SIZE;
461
ret = gnutls_record_recv
462
(es.session, buffer+buffer_length, BUFFER_SIZE);
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,
468
900
case GNUTLS_E_INTERRUPTED:
469
901
case GNUTLS_E_AGAIN:
471
903
case GNUTLS_E_REHANDSHAKE:
472
ret = gnutls_handshake (es.session);
474
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");
481
fprintf(stderr, "Unknown error while reading data from"
482
" encrypted session with mandos server\n");
484
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);
488
buffer_length += (size_t) ret;
492
if (buffer_length > 0){
493
decrypted_buffer_size = pgp_packet_decrypt(buffer,
497
if (decrypted_buffer_size >= 0){
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){
498
958
while(written < (size_t) decrypted_buffer_size){
499
ret = (int)fwrite (decrypted_buffer + written, 1,
500
(size_t)decrypted_buffer_size - written,
964
ret = (int)fwrite(decrypted_buffer + written, 1,
965
(size_t)decrypted_buffer_size - written,
502
967
if(ret == 0 and ferror(stdout)){
504
fprintf(stderr, "Error writing encrypted data: %s\n",
970
fprintf(stderr, "Mandos plugin mandos-client: "
971
"Error writing encrypted data: %s\n",
505
972
strerror(errno));
510
977
written += (size_t)ret;
512
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);
521
fprintf(stderr, "Closing TLS session\n");
525
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
528
gnutls_deinit (es.session);
529
gnutls_certificate_free_credentials (es.cred);
530
gnutls_global_deinit ();
534
static AvahiSimplePoll *simple_poll = NULL;
535
static AvahiServer *server = NULL;
537
static void resolve_callback(
538
AvahiSServiceResolver *r,
539
AvahiIfIndex interface,
540
AVAHI_GCC_UNUSED AvahiProtocol protocol,
541
AvahiResolverEvent event,
545
const char *host_name,
546
const AvahiAddress *address,
548
AVAHI_GCC_UNUSED AvahiStringList *txt,
549
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
550
AVAHI_GCC_UNUSED void* userdata) {
552
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){
554
1025
/* Called whenever a service has been resolved successfully or
559
1034
case AVAHI_RESOLVER_FAILURE:
560
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of"
561
" type '%s' in domain '%s': %s\n", name, type, domain,
562
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)));
565
1041
case AVAHI_RESOLVER_FOUND:
567
1043
char ip[AVAHI_ADDRESS_STR_MAX];
568
1044
avahi_address_snprint(ip, sizeof(ip), address);
570
fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on"
571
" port %d\n", name, 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);
573
int ret = start_mandos_communication(ip, port, 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));
579
1061
avahi_s_service_resolver_free(r);
582
static void browse_callback(
583
AvahiSServiceBrowser *b,
584
AvahiIfIndex interface,
585
AvahiProtocol protocol,
586
AvahiBrowserEvent event,
590
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
593
AvahiServer *s = userdata;
594
assert(b); /* Spurious warning */
596
/* Called whenever a new services becomes available on the LAN or
597
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);
601
case AVAHI_BROWSER_FAILURE:
603
fprintf(stderr, "(Browser) %s\n",
604
avahi_strerror(avahi_server_errno(server)));
605
avahi_simple_poll_quit(simple_poll);
608
case AVAHI_BROWSER_NEW:
609
/* We ignore the returned resolver object. In the callback
610
function we free it. If the server is terminated before
611
the callback function is called the server will free
612
the resolver for us. */
614
if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
616
AVAHI_PROTO_INET6, 0,
617
resolve_callback, s)))
618
fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
619
avahi_strerror(avahi_server_errno(s)));
622
case AVAHI_BROWSER_REMOVE:
625
case AVAHI_BROWSER_ALL_FOR_NOW:
626
case AVAHI_BROWSER_CACHE_EXHAUSTED:
631
/* combinds file name and path and returns the malloced new string. som sane checks could/should be added */
632
const char *combinepath(const char *first, const char *second){
634
tmp = malloc(strlen(first) + strlen(second) + 2);
640
if (first[0] != '\0' and first[strlen(first) - 1] != '/'){
648
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("DEVICE", interface, 1);
1693
perror_plus("setenv");
1696
ret = setenv("VERBOSE", debug ? "1" : "0", 1);
1698
perror_plus("setenv");
1701
ret = setenv("MODE", "start", 1);
1703
perror_plus("setenv");
1707
ret = asprintf(&delaystring, "%f", delay);
1709
perror_plus("asprintf");
1712
ret = setenv("DELAY", delaystring, 1);
1715
perror_plus("setenv");
1719
ret = execl(fullname, direntry->d_name, "start", NULL);
1720
perror_plus("execl");
1723
if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1724
perror_plus("waitpid");
1728
if(WIFEXITED(status)){
1729
if(WEXITSTATUS(status) != 0){
1730
fprintf(stderr, "Mandos plugin mandos-client: "
1731
"Warning: network hook \"%s\" exited"
1732
" with status %d\n", direntry->d_name,
1733
WEXITSTATUS(status));
1737
} else if(WIFSIGNALED(status)){
1738
fprintf(stderr, "Mandos plugin mandos-client: "
1739
"Warning: network hook \"%s\" died by"
1740
" signal %d\n", direntry->d_name,
1745
fprintf(stderr, "Mandos plugin mandos-client: "
1746
"Warning: network hook \"%s\" crashed\n",
1762
avahi_set_log_function(empty_log);
1765
if(interface[0] == '\0'){
1766
struct dirent **direntries;
1767
/* First look for interfaces that are up */
1768
ret = scandir(sys_class_net, &direntries, up_interface,
1771
/* No up interfaces, look for any good interfaces */
1773
ret = scandir(sys_class_net, &direntries, good_interface,
1777
/* Pick the first interface returned */
1778
interface = strdup(direntries[0]->d_name);
1780
fprintf(stderr, "Mandos plugin mandos-client: "
1781
"Using interface \"%s\"\n", interface);
1783
if(interface == NULL){
1784
perror_plus("malloc");
1786
exitcode = EXIT_FAILURE;
1792
fprintf(stderr, "Mandos plugin mandos-client: "
1793
"Could not find a network interface\n");
1794
exitcode = EXIT_FAILURE;
1799
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1800
from the signal handler */
1801
/* Initialize the pseudo-RNG for Avahi */
1802
srand((unsigned int) time(NULL));
1803
mc.simple_poll = avahi_simple_poll_new();
1804
if(mc.simple_poll == NULL){
1805
fprintf(stderr, "Mandos plugin mandos-client: "
1806
"Avahi: Failed to create simple poll object.\n");
1807
exitcode = EX_UNAVAILABLE;
1811
sigemptyset(&sigterm_action.sa_mask);
1812
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1814
perror_plus("sigaddset");
1815
exitcode = EX_OSERR;
1818
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1820
perror_plus("sigaddset");
1821
exitcode = EX_OSERR;
1824
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1826
perror_plus("sigaddset");
1827
exitcode = EX_OSERR;
1830
/* Need to check if the handler is SIG_IGN before handling:
1831
| [[info:libc:Initial Signal Actions]] |
1832
| [[info:libc:Basic Signal Handling]] |
1834
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1836
perror_plus("sigaction");
1839
if(old_sigterm_action.sa_handler != SIG_IGN){
1840
ret = sigaction(SIGINT, &sigterm_action, NULL);
1842
perror_plus("sigaction");
1843
exitcode = EX_OSERR;
1847
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1849
perror_plus("sigaction");
1852
if(old_sigterm_action.sa_handler != SIG_IGN){
1853
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1855
perror_plus("sigaction");
1856
exitcode = EX_OSERR;
1860
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1862
perror_plus("sigaction");
1865
if(old_sigterm_action.sa_handler != SIG_IGN){
1866
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1868
perror_plus("sigaction");
1869
exitcode = EX_OSERR;
1874
/* If the interface is down, bring it up */
1875
if(strcmp(interface, "none") != 0){
1876
if_index = (AvahiIfIndex) if_nametoindex(interface);
1878
fprintf(stderr, "Mandos plugin mandos-client: "
1879
"No such interface: \"%s\"\n", interface);
1880
exitcode = EX_UNAVAILABLE;
1888
/* Re-raise priviliges */
1892
perror_plus("seteuid");
1896
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1897
messages about the network interface to mess up the prompt */
1898
ret = klogctl(8, NULL, 5);
1899
bool restore_loglevel = true;
1901
restore_loglevel = false;
1902
perror_plus("klogctl");
1904
#endif /* __linux__ */
1906
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1908
perror_plus("socket");
1909
exitcode = EX_OSERR;
1911
if(restore_loglevel){
1912
ret = klogctl(7, NULL, 0);
1914
perror_plus("klogctl");
1917
#endif /* __linux__ */
1918
/* Lower privileges */
1922
perror_plus("seteuid");
1926
strcpy(network.ifr_name, interface);
1927
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1929
perror_plus("ioctl SIOCGIFFLAGS");
1931
if(restore_loglevel){
1932
ret = klogctl(7, NULL, 0);
1934
perror_plus("klogctl");
1937
#endif /* __linux__ */
1938
exitcode = EX_OSERR;
1939
/* Lower privileges */
1943
perror_plus("seteuid");
1947
if((network.ifr_flags & IFF_UP) == 0){
1948
network.ifr_flags |= IFF_UP;
1949
take_down_interface = true;
1950
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1952
take_down_interface = false;
1953
perror_plus("ioctl SIOCSIFFLAGS +IFF_UP");
1954
exitcode = EX_OSERR;
1956
if(restore_loglevel){
1957
ret = klogctl(7, NULL, 0);
1959
perror_plus("klogctl");
1962
#endif /* __linux__ */
1963
/* Lower privileges */
1967
perror_plus("seteuid");
1972
/* Sleep checking until interface is running.
1973
Check every 0.25s, up to total time of delay */
1974
for(int i=0; i < delay * 4; i++){
1975
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1977
perror_plus("ioctl SIOCGIFFLAGS");
1978
} else if(network.ifr_flags & IFF_RUNNING){
1981
struct timespec sleeptime = { .tv_nsec = 250000000 };
1982
ret = nanosleep(&sleeptime, NULL);
1983
if(ret == -1 and errno != EINTR){
1984
perror_plus("nanosleep");
1987
if(not take_down_interface){
1988
/* We won't need the socket anymore */
1989
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1991
perror_plus("close");
1995
if(restore_loglevel){
1996
/* Restores kernel loglevel to default */
1997
ret = klogctl(7, NULL, 0);
1999
perror_plus("klogctl");
2002
#endif /* __linux__ */
2003
/* Lower privileges */
2005
if(take_down_interface){
2006
/* Lower privileges */
2009
perror_plus("seteuid");
2012
/* Lower privileges permanently */
2015
perror_plus("setuid");
2024
ret = init_gnutls_global(pubkey, seckey);
2026
fprintf(stderr, "Mandos plugin mandos-client: "
2027
"init_gnutls_global failed\n");
2028
exitcode = EX_UNAVAILABLE;
2031
gnutls_initialized = true;
2038
if(mkdtemp(tempdir) == NULL){
2039
perror_plus("mkdtemp");
2042
tempdir_created = true;
2048
if(not init_gpgme(pubkey, seckey, tempdir)){
2049
fprintf(stderr, "Mandos plugin mandos-client: "
2050
"init_gpgme failed\n");
2051
exitcode = EX_UNAVAILABLE;
2054
gpgme_initialized = true;
2061
if(connect_to != NULL){
2062
/* Connect directly, do not use Zeroconf */
2063
/* (Mainly meant for debugging) */
2064
char *address = strrchr(connect_to, ':');
2065
if(address == NULL){
2066
fprintf(stderr, "Mandos plugin mandos-client: "
2067
"No colon in address\n");
2068
exitcode = EX_USAGE;
2078
tmpmax = strtoimax(address+1, &tmp, 10);
2079
if(errno != 0 or tmp == address+1 or *tmp != '\0'
2080
or tmpmax != (uint16_t)tmpmax){
2081
fprintf(stderr, "Mandos plugin mandos-client: "
2082
"Bad port number\n");
2083
exitcode = EX_USAGE;
2091
port = (uint16_t)tmpmax;
2093
/* Colon in address indicates IPv6 */
2095
if(strchr(connect_to, ':') != NULL){
2097
/* Accept [] around IPv6 address - see RFC 5952 */
2098
if(connect_to[0] == '[' and address[-1] == ']')
2106
address = connect_to;
2112
while(not quit_now){
2113
ret = start_mandos_communication(address, port, if_index, af);
2114
if(quit_now or ret == 0){
2118
fprintf(stderr, "Mandos plugin mandos-client: "
2119
"Retrying in %d seconds\n", (int)retry_interval);
2121
sleep((int)retry_interval);
2125
exitcode = EXIT_SUCCESS;
649
2136
AvahiServerConfig config;
650
AvahiSServiceBrowser *sb = NULL;
653
int returncode = EXIT_SUCCESS;
654
const char *interface = NULL;
655
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
656
char *connect_to = NULL;
659
static struct option long_options[] = {
660
{"debug", no_argument, (int *)&debug, 1},
661
{"connect", required_argument, 0, 'C'},
662
{"interface", required_argument, 0, 'i'},
663
{"certdir", required_argument, 0, 'd'},
664
{"certkey", required_argument, 0, 'c'},
665
{"certfile", required_argument, 0, 'k'},
668
int option_index = 0;
669
ret = getopt_long (argc, argv, "i:", long_options,
699
certfile = combinepath(certdir, certfile);
700
if (certfile == NULL){
704
if(interface != NULL){
705
if_index = (AvahiIfIndex) if_nametoindex(interface);
707
fprintf(stderr, "No such interface: \"%s\"\n", interface);
712
if(connect_to != NULL){
713
/* Connect directly, do not use Zeroconf */
714
/* (Mainly meant for debugging) */
715
char *address = strrchr(connect_to, ':');
717
fprintf(stderr, "No colon in address\n");
721
uint16_t port = (uint16_t) strtol(address+1, NULL, 10);
723
perror("Bad port number");
727
address = connect_to;
728
ret = start_mandos_communication(address, port, if_index);
736
certkey = combinepath(certdir, certkey);
737
if (certkey == NULL){
742
avahi_set_log_function(empty_log);
745
/* Initialize the psuedo-RNG */
746
srand((unsigned int) time(NULL));
748
/* Allocate main loop object */
749
if (!(simple_poll = avahi_simple_poll_new())) {
750
fprintf(stderr, "Failed to create simple poll object.\n");
755
/* Do not publish any local records */
2137
/* Do not publish any local Zeroconf records */
756
2138
avahi_server_config_init(&config);
757
2139
config.publish_hinfo = 0;
758
2140
config.publish_addresses = 0;
759
2141
config.publish_workstation = 0;
760
2142
config.publish_domain = 0;
762
2144
/* Allocate a new server */
763
server = avahi_server_new(avahi_simple_poll_get(simple_poll),
764
&config, NULL, NULL, &error);
766
/* Free the configuration data */
2145
mc.server = avahi_server_new(avahi_simple_poll_get
2146
(mc.simple_poll), &config, NULL,
2149
/* Free the Avahi configuration data */
767
2150
avahi_server_config_free(&config);
769
/* Check if creating the server object succeeded */
771
fprintf(stderr, "Failed to create server: %s\n",
772
avahi_strerror(error));
773
returncode = EXIT_FAILURE;
777
/* Create the service browser */
778
sb = avahi_s_service_browser_new(server, if_index,
780
"_mandos._tcp", NULL, 0,
781
browse_callback, server);
783
fprintf(stderr, "Failed to create service browser: %s\n",
784
avahi_strerror(avahi_server_errno(server)));
785
returncode = EXIT_FAILURE;
789
/* Run the main loop */
792
fprintf(stderr, "Starting avahi loop search\n");
795
avahi_simple_poll_loop(simple_poll);
800
fprintf(stderr, "%s exiting\n", argv[0]);
805
avahi_s_service_browser_free(sb);
808
avahi_server_free(server);
811
avahi_simple_poll_free(simple_poll);
2153
/* Check if creating the Avahi server object succeeded */
2154
if(mc.server == NULL){
2155
fprintf(stderr, "Mandos plugin mandos-client: "
2156
"Failed to create Avahi server: %s\n",
2157
avahi_strerror(error));
2158
exitcode = EX_UNAVAILABLE;
2166
/* Create the Avahi service browser */
2167
sb = avahi_s_service_browser_new(mc.server, if_index,
2168
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
2169
NULL, 0, browse_callback, NULL);
2171
fprintf(stderr, "Mandos plugin mandos-client: "
2172
"Failed to create service browser: %s\n",
2173
avahi_strerror(avahi_server_errno(mc.server)));
2174
exitcode = EX_UNAVAILABLE;
2182
/* Run the main loop */
2185
fprintf(stderr, "Mandos plugin mandos-client: "
2186
"Starting Avahi loop search\n");
2189
ret = avahi_loop_with_timeout(mc.simple_poll,
2190
(int)(retry_interval * 1000));
2192
fprintf(stderr, "Mandos plugin mandos-client: "
2193
"avahi_loop_with_timeout exited %s\n",
2194
(ret == 0) ? "successfully" : "with error");
2200
fprintf(stderr, "Mandos plugin mandos-client: "
2201
"%s exiting\n", argv[0]);
2204
/* Cleanup things */
2206
avahi_s_service_browser_free(sb);
2208
if(mc.server != NULL)
2209
avahi_server_free(mc.server);
2211
if(mc.simple_poll != NULL)
2212
avahi_simple_poll_free(mc.simple_poll);
2214
if(gnutls_initialized){
2215
gnutls_certificate_free_credentials(mc.cred);
2216
gnutls_global_deinit();
2217
gnutls_dh_params_deinit(mc.dh_params);
2220
if(gpgme_initialized){
2221
gpgme_release(mc.ctx);
2224
/* Cleans up the circular linked list of Mandos servers the client
2226
if(mc.current_server != NULL){
2227
mc.current_server->prev->next = NULL;
2228
while(mc.current_server != NULL){
2229
server *next = mc.current_server->next;
2230
free(mc.current_server);
2231
mc.current_server = next;
2235
/* XXX run network hooks "stop" here */
2237
/* Take down the network interface */
2238
if(take_down_interface){
2239
/* Re-raise priviliges */
2243
perror_plus("seteuid");
2246
ret = ioctl(sd, SIOCGIFFLAGS, &network);
2248
perror_plus("ioctl SIOCGIFFLAGS");
2249
} else if(network.ifr_flags & IFF_UP){
2250
network.ifr_flags &= ~(short)IFF_UP; /* clear flag */
2251
ret = ioctl(sd, SIOCSIFFLAGS, &network);
2253
perror_plus("ioctl SIOCSIFFLAGS -IFF_UP");
2256
ret = (int)TEMP_FAILURE_RETRY(close(sd));
2258
perror_plus("close");
2260
/* Lower privileges permanently */
2264
perror_plus("setuid");
2269
/* Removes the GPGME temp directory and all files inside */
2270
if(tempdir_created){
2271
struct dirent **direntries = NULL;
2272
struct dirent *direntry = NULL;
2273
int numentries = scandir(tempdir, &direntries, notdotentries,
2275
if (numentries > 0){
2276
for(int i = 0; i < numentries; i++){
2277
direntry = direntries[i];
2278
char *fullname = NULL;
2279
ret = asprintf(&fullname, "%s/%s", tempdir,
2282
perror_plus("asprintf");
2285
ret = remove(fullname);
2287
fprintf(stderr, "Mandos plugin mandos-client: "
2288
"remove(\"%s\"): %s\n", fullname, strerror(errno));
2294
/* need to clean even if 0 because man page doesn't specify */
2296
if (numentries == -1){
2297
perror_plus("scandir");
2299
ret = rmdir(tempdir);
2300
if(ret == -1 and errno != ENOENT){
2301
perror_plus("rmdir");
2306
sigemptyset(&old_sigterm_action.sa_mask);
2307
old_sigterm_action.sa_handler = SIG_DFL;
2308
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
2309
&old_sigterm_action,
2312
perror_plus("sigaction");
2315
ret = raise(signal_received);
2316
} while(ret != 0 and errno == EINTR);
2318
perror_plus("raise");
2321
TEMP_FAILURE_RETRY(pause());