26
25
* along with this program. If not, see
27
26
* <http://www.gnu.org/licenses/>.
29
* Contact the authors at <mandos@fukt.bsnet.se>.
28
* Contact the authors at <https://www.fukt.bsnet.se/~belorn/> and
29
* <https://www.fukt.bsnet.se/~teddy/>.
32
/* Needed by GPGME, specifically gpgme_data_seek() */
33
#ifndef _LARGEFILE_SOURCE
32
#define _FORTIFY_SOURCE 2
34
34
#define _LARGEFILE_SOURCE
36
#ifndef _FILE_OFFSET_BITS
37
35
#define _FILE_OFFSET_BITS 64
40
#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */
42
#include <stdio.h> /* fprintf(), stderr, fwrite(),
43
stdout, ferror(), remove() */
44
#include <stdint.h> /* uint16_t, uint32_t */
45
#include <stddef.h> /* NULL, size_t, ssize_t */
46
#include <stdlib.h> /* free(), EXIT_SUCCESS, EXIT_FAILURE,
47
srand(), strtof(), abort() */
48
#include <stdbool.h> /* bool, false, true */
49
#include <string.h> /* memset(), strcmp(), strlen(),
50
strerror(), asprintf(), strcpy() */
51
#include <sys/ioctl.h> /* ioctl */
52
#include <sys/types.h> /* socket(), inet_pton(), sockaddr,
53
sockaddr_in6, PF_INET6,
54
SOCK_STREAM, uid_t, gid_t, open(),
56
#include <sys/stat.h> /* open() */
57
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
58
inet_pton(), connect() */
59
#include <fcntl.h> /* open() */
60
#include <dirent.h> /* opendir(), struct dirent, readdir()
62
#include <inttypes.h> /* PRIu16, PRIdMAX, intmax_t,
64
#include <assert.h> /* assert() */
65
#include <errno.h> /* perror(), errno */
66
#include <time.h> /* nanosleep(), time() */
67
#include <net/if.h> /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP,
68
SIOCSIFFLAGS, if_indextoname(),
69
if_nametoindex(), IF_NAMESIZE */
70
#include <netinet/in.h> /* IN6_IS_ADDR_LINKLOCAL,
71
INET_ADDRSTRLEN, INET6_ADDRSTRLEN
73
#include <unistd.h> /* close(), SEEK_SET, off_t, write(),
74
getuid(), getgid(), seteuid(),
76
#include <arpa/inet.h> /* inet_pton(), htons */
77
#include <iso646.h> /* not, or, and */
78
#include <argp.h> /* struct argp_option, error_t, struct
79
argp_state, struct argp,
80
argp_parse(), ARGP_KEY_ARG,
81
ARGP_KEY_END, ARGP_ERR_UNKNOWN */
82
#include <signal.h> /* sigemptyset(), sigaddset(),
83
sigaction(), SIGTERM, sig_atomic_t,
87
#include <sys/klog.h> /* klogctl() */
88
#endif /* __linux__ */
91
/* All Avahi types, constants and functions
41
#include <net/if.h> /* if_nametoindex */
42
#include <sys/ioctl.h> // ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS
43
#include <net/if.h> // ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS
94
45
#include <avahi-core/core.h>
95
46
#include <avahi-core/lookup.h>
96
47
#include <avahi-core/log.h>
98
49
#include <avahi-common/malloc.h>
99
50
#include <avahi-common/error.h>
102
#include <gnutls/gnutls.h> /* All GnuTLS types, constants and
105
init_gnutls_session(),
107
#include <gnutls/openpgp.h>
108
/* gnutls_certificate_set_openpgp_key_file(),
109
GNUTLS_OPENPGP_FMT_BASE64 */
112
#include <gpgme.h> /* All GPGME types, constants and
115
GPGME_PROTOCOL_OpenPGP,
53
#include <sys/types.h> /* socket(), inet_pton() */
54
#include <sys/socket.h> /* socket(), struct sockaddr_in6,
55
struct in6_addr, inet_pton() */
56
#include <gnutls/gnutls.h> /* All GnuTLS stuff */
57
#include <gnutls/openpgp.h> /* GnuTLS with openpgp stuff */
59
#include <unistd.h> /* close() */
60
#include <netinet/in.h>
61
#include <stdbool.h> /* true */
62
#include <string.h> /* memset */
63
#include <arpa/inet.h> /* inet_pton() */
64
#include <iso646.h> /* not */
67
#include <errno.h> /* perror() */
118
73
#define BUFFER_SIZE 256
120
#define PATHDIR "/conf/conf.d/mandos"
121
#define SECKEY "seckey.txt"
122
#define PUBKEY "pubkey.txt"
76
const char *certdir = "/conf/conf.d/cryptkeyreq/";
77
const char *certfile = "openpgp-client.txt";
78
const char *certkey = "openpgp-client-key.txt";
124
80
bool debug = false;
125
static const char mandos_protocol_version[] = "1";
126
const char *argp_program_version = "mandos-client " VERSION;
127
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
129
/* Used for passing in values through the Avahi callback functions */
131
AvahiSimplePoll *simple_poll;
83
gnutls_session_t session;
133
84
gnutls_certificate_credentials_t cred;
134
unsigned int dh_bits;
135
85
gnutls_dh_params_t dh_params;
136
const char *priority;
89
ssize_t pgp_packet_decrypt (char *packet, size_t packet_size,
90
char **new_packet, const char *homedir){
91
gpgme_data_t dh_crypto, dh_plain;
140
/* global context so signal handler can reach it*/
141
mandos_context mc = { .simple_poll = NULL, .server = NULL,
142
.dh_bits = 1024, .priority = "SECURE256"
143
":!CTYPE-X.509:+CTYPE-OPENPGP" };
145
sig_atomic_t quit_now = 0;
146
int signal_received = 0;
149
* Make additional room in "buffer" for at least BUFFER_SIZE more
150
* bytes. "buffer_capacity" is how much is currently allocated,
151
* "buffer_length" is how much is already used.
153
size_t incbuffer(char **buffer, size_t buffer_length,
154
size_t buffer_capacity){
155
if(buffer_length + BUFFER_SIZE > buffer_capacity){
156
*buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE);
160
buffer_capacity += BUFFER_SIZE;
162
return buffer_capacity;
168
static bool init_gpgme(const char *seckey,
169
const char *pubkey, const char *tempdir){
95
ssize_t new_packet_capacity = 0;
96
ssize_t new_packet_length = 0;
171
97
gpgme_engine_info_t engine_info;
175
* Helper function to insert pub and seckey to the engine keyring.
177
bool import_key(const char *filename){
180
gpgme_data_t pgp_data;
182
fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
188
rc = gpgme_data_new_from_fd(&pgp_data, fd);
189
if(rc != GPG_ERR_NO_ERROR){
190
fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n",
191
gpgme_strsource(rc), gpgme_strerror(rc));
195
rc = gpgme_op_import(mc.ctx, pgp_data);
196
if(rc != GPG_ERR_NO_ERROR){
197
fprintf(stderr, "bad gpgme_op_import: %s: %s\n",
198
gpgme_strsource(rc), gpgme_strerror(rc));
202
ret = (int)TEMP_FAILURE_RETRY(close(fd));
206
gpgme_data_release(pgp_data);
211
fprintf(stderr, "Initializing GPGME\n");
100
fprintf(stderr, "Trying to decrypt OpenPGP packet\n");
215
104
gpgme_check_version(NULL);
216
105
rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
217
if(rc != GPG_ERR_NO_ERROR){
106
if (rc != GPG_ERR_NO_ERROR){
218
107
fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n",
219
108
gpgme_strsource(rc), gpgme_strerror(rc));
223
/* Set GPGME home directory for the OpenPGP engine only */
224
rc = gpgme_get_engine_info(&engine_info);
225
if(rc != GPG_ERR_NO_ERROR){
112
/* Set GPGME home directory */
113
rc = gpgme_get_engine_info (&engine_info);
114
if (rc != GPG_ERR_NO_ERROR){
226
115
fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n",
227
116
gpgme_strsource(rc), gpgme_strerror(rc));
230
119
while(engine_info != NULL){
231
120
if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){
232
121
gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,
233
engine_info->file_name, tempdir);
122
engine_info->file_name, homedir);
236
125
engine_info = engine_info->next;
238
127
if(engine_info == NULL){
239
fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir);
243
/* Create new GPGME "context" */
244
rc = gpgme_new(&(mc.ctx));
245
if(rc != GPG_ERR_NO_ERROR){
246
fprintf(stderr, "bad gpgme_new: %s: %s\n",
247
gpgme_strsource(rc), gpgme_strerror(rc));
251
if(not import_key(pubkey) or not import_key(seckey)){
259
* Decrypt OpenPGP data.
260
* Returns -1 on error
262
static ssize_t pgp_packet_decrypt(const char *cryptotext,
265
gpgme_data_t dh_crypto, dh_plain;
268
size_t plaintext_capacity = 0;
269
ssize_t plaintext_length = 0;
272
fprintf(stderr, "Trying to decrypt OpenPGP data\n");
275
/* Create new GPGME data buffer from memory cryptotext */
276
rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size,
278
if(rc != GPG_ERR_NO_ERROR){
128
fprintf(stderr, "Could not set home dir to %s\n", homedir);
132
/* Create new GPGME data buffer from packet buffer */
133
rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0);
134
if (rc != GPG_ERR_NO_ERROR){
279
135
fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n",
280
136
gpgme_strsource(rc), gpgme_strerror(rc));
330
fprintf(stderr, "Decryption of OpenPGP data succeeded\n");
198
/* Delete the GPGME FILE pointer cryptotext data buffer */
199
gpgme_data_release(dh_crypto);
333
201
/* Seek back to the beginning of the GPGME plaintext data buffer */
334
if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){
335
perror("gpgme_data_seek");
336
plaintext_length = -1;
202
if (gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET) == -1){
203
perror("pgpme_data_seek");
342
plaintext_capacity = incbuffer(plaintext,
343
(size_t)plaintext_length,
345
if(plaintext_capacity == 0){
347
plaintext_length = -1;
208
if (new_packet_length + BUFFER_SIZE > new_packet_capacity){
209
*new_packet = realloc(*new_packet,
210
(unsigned int)new_packet_capacity
212
if (*new_packet == NULL){
216
new_packet_capacity += BUFFER_SIZE;
351
ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length,
219
ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length,
353
221
/* Print the data, if any */
359
226
perror("gpgme_data_read");
360
plaintext_length = -1;
363
plaintext_length += ret;
367
fprintf(stderr, "Decrypted password is: ");
368
for(ssize_t i = 0; i < plaintext_length; i++){
369
fprintf(stderr, "%02hhX ", (*plaintext)[i]);
371
fprintf(stderr, "\n");
376
/* Delete the GPGME cryptotext data buffer */
377
gpgme_data_release(dh_crypto);
229
new_packet_length += ret;
232
/* FIXME: check characters before printing to screen so to not print
233
terminal control characters */
235
/* fprintf(stderr, "decrypted password is: "); */
236
/* fwrite(*new_packet, 1, new_packet_length, stderr); */
237
/* fprintf(stderr, "\n"); */
379
240
/* Delete the GPGME plaintext data buffer */
380
241
gpgme_data_release(dh_plain);
381
return plaintext_length;
242
return new_packet_length;
384
static const char * safer_gnutls_strerror(int value){
385
const char *ret = gnutls_strerror(value); /* Spurious warning from
386
-Wunreachable-code */
245
static const char * safer_gnutls_strerror (int value) {
246
const char *ret = gnutls_strerror (value);
388
248
ret = "(unknown)";
392
/* GnuTLS log function callback */
393
static void debuggnutls(__attribute__((unused)) int level,
395
fprintf(stderr, "GnuTLS: %s", string);
252
void debuggnutls(__attribute__((unused)) int level,
254
fprintf(stderr, "%s", string);
398
static int init_gnutls_global(const char *pubkeyfilename,
399
const char *seckeyfilename){
257
int initgnutls(encrypted_session *es){
403
262
fprintf(stderr, "Initializing GnuTLS\n");
406
ret = gnutls_global_init();
407
if(ret != GNUTLS_E_SUCCESS){
408
fprintf(stderr, "GnuTLS global_init: %s\n",
409
safer_gnutls_strerror(ret));
265
if ((ret = gnutls_global_init ())
266
!= GNUTLS_E_SUCCESS) {
267
fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret));
414
/* "Use a log level over 10 to enable all debugging options."
417
272
gnutls_global_set_log_level(11);
418
273
gnutls_global_set_log_function(debuggnutls);
421
/* OpenPGP credentials */
422
gnutls_certificate_allocate_credentials(&mc.cred);
423
if(ret != GNUTLS_E_SUCCESS){
424
fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning
428
safer_gnutls_strerror(ret));
429
gnutls_global_deinit();
276
/* openpgp credentials */
277
if ((ret = gnutls_certificate_allocate_credentials (&es->cred))
278
!= GNUTLS_E_SUCCESS) {
279
fprintf (stderr, "memory error: %s\n",
280
safer_gnutls_strerror(ret));
434
fprintf(stderr, "Attempting to use OpenPGP public key %s and"
435
" secret key %s as GnuTLS credentials\n", pubkeyfilename,
285
fprintf(stderr, "Attempting to use OpenPGP certificate %s"
286
" and keyfile %s as GnuTLS credentials\n", certfile,
439
290
ret = gnutls_certificate_set_openpgp_key_file
440
(mc.cred, pubkeyfilename, seckeyfilename,
441
GNUTLS_OPENPGP_FMT_BASE64);
442
if(ret != GNUTLS_E_SUCCESS){
444
"Error[%d] while reading the OpenPGP key pair ('%s',"
445
" '%s')\n", ret, pubkeyfilename, seckeyfilename);
446
fprintf(stderr, "The GnuTLS error is: %s\n",
447
safer_gnutls_strerror(ret));
451
/* GnuTLS server initialization */
452
ret = gnutls_dh_params_init(&mc.dh_params);
453
if(ret != GNUTLS_E_SUCCESS){
454
fprintf(stderr, "Error in GnuTLS DH parameter initialization:"
455
" %s\n", safer_gnutls_strerror(ret));
458
ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits);
459
if(ret != GNUTLS_E_SUCCESS){
460
fprintf(stderr, "Error in GnuTLS prime generation: %s\n",
461
safer_gnutls_strerror(ret));
465
gnutls_certificate_set_dh_params(mc.cred, mc.dh_params);
471
gnutls_certificate_free_credentials(mc.cred);
472
gnutls_global_deinit();
473
gnutls_dh_params_deinit(mc.dh_params);
477
static int init_gnutls_session(gnutls_session_t *session){
479
/* GnuTLS session creation */
481
ret = gnutls_init(session, GNUTLS_SERVER);
485
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
486
if(ret != GNUTLS_E_SUCCESS){
291
(es->cred, certfile, certkey, GNUTLS_OPENPGP_FMT_BASE64);
292
if (ret != GNUTLS_E_SUCCESS) {
294
(stderr, "Error[%d] while reading the OpenPGP key pair ('%s',"
296
ret, certfile, certkey);
297
fprintf(stdout, "The Error is: %s\n",
298
safer_gnutls_strerror(ret));
302
//GnuTLS server initialization
303
if ((ret = gnutls_dh_params_init (&es->dh_params))
304
!= GNUTLS_E_SUCCESS) {
305
fprintf (stderr, "Error in dh parameter initialization: %s\n",
306
safer_gnutls_strerror(ret));
310
if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS))
311
!= GNUTLS_E_SUCCESS) {
312
fprintf (stderr, "Error in prime generation: %s\n",
313
safer_gnutls_strerror(ret));
317
gnutls_certificate_set_dh_params (es->cred, es->dh_params);
319
// GnuTLS session creation
320
if ((ret = gnutls_init (&es->session, GNUTLS_SERVER))
321
!= GNUTLS_E_SUCCESS){
487
322
fprintf(stderr, "Error in GnuTLS session initialization: %s\n",
488
323
safer_gnutls_strerror(ret));
494
ret = gnutls_priority_set_direct(*session, mc.priority, &err);
496
gnutls_deinit(*session);
499
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
500
if(ret != GNUTLS_E_SUCCESS){
501
fprintf(stderr, "Syntax error at: %s\n", err);
502
fprintf(stderr, "GnuTLS error: %s\n",
503
safer_gnutls_strerror(ret));
504
gnutls_deinit(*session);
326
if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err))
327
!= GNUTLS_E_SUCCESS) {
328
fprintf(stderr, "Syntax error at: %s\n", err);
329
fprintf(stderr, "GnuTLS error: %s\n",
330
safer_gnutls_strerror(ret));
510
ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE,
513
gnutls_deinit(*session);
516
} while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN);
517
if(ret != GNUTLS_E_SUCCESS){
518
fprintf(stderr, "Error setting GnuTLS credentials: %s\n",
334
if ((ret = gnutls_credentials_set
335
(es->session, GNUTLS_CRD_CERTIFICATE, es->cred))
336
!= GNUTLS_E_SUCCESS) {
337
fprintf(stderr, "Error setting a credentials set: %s\n",
519
338
safer_gnutls_strerror(ret));
520
gnutls_deinit(*session);
524
342
/* ignore client certificate if any. */
525
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE);
343
gnutls_certificate_server_set_request (es->session,
527
gnutls_dh_set_prime_bits(*session, mc.dh_bits);
346
gnutls_dh_set_prime_bits (es->session, DH_BITS);
532
/* Avahi log function callback */
533
static void empty_log(__attribute__((unused)) AvahiLogLevel level,
534
__attribute__((unused)) const char *txt){}
351
void empty_log(__attribute__((unused)) AvahiLogLevel level,
352
__attribute__((unused)) const char *txt){}
536
/* Called when a Mandos server is found */
537
static int start_mandos_communication(const char *ip, uint16_t port,
538
AvahiIfIndex if_index,
540
int ret, tcp_sd = -1;
543
struct sockaddr_in in;
544
struct sockaddr_in6 in6;
354
int start_mandos_communication(const char *ip, uint16_t port,
355
unsigned int if_index){
357
struct sockaddr_in6 to;
358
encrypted_session es;
546
359
char *buffer = NULL;
547
char *decrypted_buffer = NULL;
360
char *decrypted_buffer;
548
361
size_t buffer_length = 0;
549
362
size_t buffer_capacity = 0;
552
gnutls_session_t session;
553
int pf; /* Protocol family */
567
fprintf(stderr, "Bad address family: %d\n", af);
571
ret = init_gnutls_session(&session);
363
ssize_t decrypted_buffer_size;
366
char interface[IF_NAMESIZE];
577
fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16
369
fprintf(stderr, "Setting up a tcp connection to %s\n", ip);
581
tcp_sd = socket(pf, SOCK_STREAM, 0);
372
tcp_sd = socket(PF_INET6, SOCK_STREAM, 0);
583
374
perror("socket");
591
memset(&to, 0, sizeof(to));
593
to.in6.sin6_family = (sa_family_t)af;
594
ret = inet_pton(af, ip, &to.in6.sin6_addr);
596
to.in.sin_family = (sa_family_t)af;
597
ret = inet_pton(af, ip, &to.in.sin_addr);
379
if(if_indextoname(if_index, interface) == NULL){
381
perror("if_indextoname");
386
fprintf(stderr, "Binding to interface %s\n", interface);
389
memset(&to,0,sizeof(to)); /* Spurious warning */
390
to.sin6_family = AF_INET6;
391
ret = inet_pton(AF_INET6, ip, &to.sin6_addr);
600
393
perror("inet_pton");
604
397
fprintf(stderr, "Bad address: %s\n", ip);
608
to.in6.sin6_port = htons(port); /* Spurious warnings from
610
-Wunreachable-code */
612
if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */
613
(&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and
615
if(if_index == AVAHI_IF_UNSPEC){
616
fprintf(stderr, "An IPv6 link-local address is incomplete"
617
" without a network interface\n");
620
/* Set the network interface number as scope */
621
to.in6.sin6_scope_id = (uint32_t)if_index;
624
to.in.sin_port = htons(port); /* Spurious warnings from
626
-Wunreachable-code */
400
to.sin6_port = htons(port); /* Spurious warning */
402
to.sin6_scope_id = (uint32_t)if_index;
634
if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){
635
char interface[IF_NAMESIZE];
636
if(if_indextoname((unsigned int)if_index, interface) == NULL){
637
perror("if_indextoname");
639
fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n",
640
ip, interface, port);
643
fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip,
646
char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ?
647
INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = "";
650
pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr,
653
pcret = inet_ntop(af, &(to.in.sin_addr), addrstr,
659
if(strcmp(addrstr, ip) != 0){
660
fprintf(stderr, "Canonical address form: %s\n", addrstr);
670
ret = connect(tcp_sd, &to.in6, sizeof(to));
672
ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */
405
fprintf(stderr, "Connection to: %s\n", ip);
408
ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to));
675
410
perror("connect");
683
const char *out = mandos_protocol_version;
686
size_t out_size = strlen(out);
687
ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written,
688
out_size - written));
693
written += (size_t)ret;
694
if(written < out_size){
697
if(out == mandos_protocol_version){
414
ret = initgnutls (&es);
420
gnutls_transport_set_ptr (es.session,
421
(gnutls_transport_ptr_t) tcp_sd);
711
424
fprintf(stderr, "Establishing TLS session with %s\n", ip);
718
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd);
725
ret = gnutls_handshake(session);
729
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
731
if(ret != GNUTLS_E_SUCCESS){
427
ret = gnutls_handshake (es.session);
429
if (ret != GNUTLS_E_SUCCESS){
733
fprintf(stderr, "*** GnuTLS Handshake failed ***\n");
431
fprintf(stderr, "\n*** Handshake failed ***\n");
739
/* Read OpenPGP packet that contains the wanted password */
438
//Retrieve OpenPGP packet that contains the wanted password
742
fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n",
441
fprintf(stderr, "Retrieving pgp encrypted password from %s\n",
752
buffer_capacity = incbuffer(&buffer, buffer_length,
754
if(buffer_capacity == 0){
763
sret = gnutls_record_recv(session, buffer+buffer_length,
446
if (buffer_length + BUFFER_SIZE > buffer_capacity){
447
buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE);
452
buffer_capacity += BUFFER_SIZE;
455
ret = gnutls_record_recv
456
(es.session, buffer+buffer_length, BUFFER_SIZE);
770
462
case GNUTLS_E_INTERRUPTED:
771
463
case GNUTLS_E_AGAIN:
773
465
case GNUTLS_E_REHANDSHAKE:
775
ret = gnutls_handshake(session);
780
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
782
fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n");
466
ret = gnutls_handshake (es.session);
468
fprintf(stderr, "\n*** Handshake failed ***\n");
788
475
fprintf(stderr, "Unknown error while reading data from"
789
" encrypted session with Mandos server\n");
790
gnutls_bye(session, GNUTLS_SHUT_RDWR);
476
" encrypted session with mandos server\n");
478
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
794
buffer_length += (size_t) sret;
799
fprintf(stderr, "Closing TLS session\n");
807
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
811
} while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED);
813
if(buffer_length > 0){
814
ssize_t decrypted_buffer_size;
482
buffer_length += (size_t) ret;
486
if (buffer_length > 0){
815
487
decrypted_buffer_size = pgp_packet_decrypt(buffer,
818
if(decrypted_buffer_size >= 0){
821
while(written < (size_t) decrypted_buffer_size){
826
ret = (int)fwrite(decrypted_buffer + written, 1,
827
(size_t)decrypted_buffer_size - written,
491
if (decrypted_buffer_size >= 0){
492
while(written < (size_t)decrypted_buffer_size){
493
ret = (int)fwrite (decrypted_buffer + written, 1,
494
(size_t)decrypted_buffer_size - written,
829
496
if(ret == 0 and ferror(stdout)){
831
498
fprintf(stderr, "Error writing encrypted data: %s\n",
832
499
strerror(errno));
836
504
written += (size_t)ret;
506
free(decrypted_buffer);
842
/* Shutdown procedure */
845
free(decrypted_buffer);
515
fprintf(stderr, "Closing TLS session\n");
848
ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd));
853
gnutls_deinit(session);
519
gnutls_bye (es.session, GNUTLS_SHUT_RDWR);
522
gnutls_deinit (es.session);
523
gnutls_certificate_free_credentials (es.cred);
524
gnutls_global_deinit ();
860
static void resolve_callback(AvahiSServiceResolver *r,
861
AvahiIfIndex interface,
863
AvahiResolverEvent event,
867
const char *host_name,
868
const AvahiAddress *address,
870
AVAHI_GCC_UNUSED AvahiStringList *txt,
871
AVAHI_GCC_UNUSED AvahiLookupResultFlags
873
AVAHI_GCC_UNUSED void* userdata){
528
static AvahiSimplePoll *simple_poll = NULL;
529
static AvahiServer *server = NULL;
531
static void resolve_callback(
532
AvahiSServiceResolver *r,
533
AvahiIfIndex interface,
534
AVAHI_GCC_UNUSED AvahiProtocol protocol,
535
AvahiResolverEvent event,
539
const char *host_name,
540
const AvahiAddress *address,
542
AVAHI_GCC_UNUSED AvahiStringList *txt,
543
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
544
AVAHI_GCC_UNUSED void* userdata) {
546
assert(r); /* Spurious warning */
876
548
/* Called whenever a service has been resolved successfully or
885
553
case AVAHI_RESOLVER_FAILURE:
886
fprintf(stderr, "(Avahi Resolver) Failed to resolve service '%s'"
887
" of type '%s' in domain '%s': %s\n", name, type, domain,
888
avahi_strerror(avahi_server_errno(mc.server)));
554
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of"
555
" type '%s' in domain '%s': %s\n", name, type, domain,
556
avahi_strerror(avahi_server_errno(server)));
891
559
case AVAHI_RESOLVER_FOUND:
893
561
char ip[AVAHI_ADDRESS_STR_MAX];
894
562
avahi_address_snprint(ip, sizeof(ip), address);
896
fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %"
897
PRIdMAX ") on port %" PRIu16 "\n", name, host_name,
898
ip, (intmax_t)interface, port);
564
fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on"
565
" port %d\n", name, host_name, ip, port);
900
int ret = start_mandos_communication(ip, port, interface,
901
avahi_proto_to_af(proto));
903
avahi_simple_poll_quit(mc.simple_poll);
567
int ret = start_mandos_communication(ip, port,
568
(unsigned int) interface);
907
574
avahi_s_service_resolver_free(r);
910
static void browse_callback(AvahiSServiceBrowser *b,
911
AvahiIfIndex interface,
912
AvahiProtocol protocol,
913
AvahiBrowserEvent event,
917
AVAHI_GCC_UNUSED AvahiLookupResultFlags
919
AVAHI_GCC_UNUSED void* userdata){
922
/* Called whenever a new services becomes available on the LAN or
923
is removed from the LAN */
931
case AVAHI_BROWSER_FAILURE:
933
fprintf(stderr, "(Avahi browser) %s\n",
934
avahi_strerror(avahi_server_errno(mc.server)));
935
avahi_simple_poll_quit(mc.simple_poll);
938
case AVAHI_BROWSER_NEW:
939
/* We ignore the returned Avahi resolver object. In the callback
940
function we free it. If the Avahi server is terminated before
941
the callback function is called the Avahi server will free the
944
if(avahi_s_service_resolver_new(mc.server, interface, protocol,
945
name, type, domain, protocol, 0,
946
resolve_callback, NULL) == NULL)
947
fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n",
948
name, avahi_strerror(avahi_server_errno(mc.server)));
951
case AVAHI_BROWSER_REMOVE:
954
case AVAHI_BROWSER_ALL_FOR_NOW:
955
case AVAHI_BROWSER_CACHE_EXHAUSTED:
957
fprintf(stderr, "No Mandos server found, still searching...\n");
577
static void browse_callback(
578
AvahiSServiceBrowser *b,
579
AvahiIfIndex interface,
580
AvahiProtocol protocol,
581
AvahiBrowserEvent event,
585
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
588
AvahiServer *s = userdata;
589
assert(b); /* Spurious warning */
591
/* Called whenever a new services becomes available on the LAN or
592
is removed from the LAN */
596
case AVAHI_BROWSER_FAILURE:
598
fprintf(stderr, "(Browser) %s\n",
599
avahi_strerror(avahi_server_errno(server)));
600
avahi_simple_poll_quit(simple_poll);
603
case AVAHI_BROWSER_NEW:
604
/* We ignore the returned resolver object. In the callback
605
function we free it. If the server is terminated before
606
the callback function is called the server will free
607
the resolver for us. */
609
if (!(avahi_s_service_resolver_new(s, interface, protocol, name,
611
AVAHI_PROTO_INET6, 0,
612
resolve_callback, s)))
613
fprintf(stderr, "Failed to resolve service '%s': %s\n", name,
614
avahi_strerror(avahi_server_errno(s)));
617
case AVAHI_BROWSER_REMOVE:
620
case AVAHI_BROWSER_ALL_FOR_NOW:
621
case AVAHI_BROWSER_CACHE_EXHAUSTED:
963
/* stop main loop after sigterm has been called */
964
static void handle_sigterm(int sig){
969
signal_received = sig;
970
int old_errno = errno;
971
if(mc.simple_poll != NULL){
972
avahi_simple_poll_quit(mc.simple_poll);
977
int main(int argc, char *argv[]){
978
AvahiSServiceBrowser *sb = NULL;
626
/* combinds file name and path and returns the malloced new string. som sane checks could/should be added */
627
const char *combinepath(const char *first, const char *second){
983
int exitcode = EXIT_SUCCESS;
984
const char *interface = "eth0";
985
struct ifreq network;
987
bool take_down_interface = false;
990
char *connect_to = NULL;
991
char tempdir[] = "/tmp/mandosXXXXXX";
992
bool tempdir_created = false;
993
AvahiIfIndex if_index = AVAHI_IF_UNSPEC;
994
const char *seckey = PATHDIR "/" SECKEY;
995
const char *pubkey = PATHDIR "/" PUBKEY;
997
bool gnutls_initialized = false;
998
bool gpgme_initialized = false;
1001
struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL };
1002
struct sigaction sigterm_action = { .sa_handler = handle_sigterm };
1007
/* Lower any group privileges we might have, just to be safe */
1014
/* Lower user privileges (temporarily) */
1026
struct argp_option options[] = {
1027
{ .name = "debug", .key = 128,
1028
.doc = "Debug mode", .group = 3 },
1029
{ .name = "connect", .key = 'c',
1030
.arg = "ADDRESS:PORT",
1031
.doc = "Connect directly to a specific Mandos server",
1033
{ .name = "interface", .key = 'i',
1035
.doc = "Network interface that will be used to search for"
1038
{ .name = "seckey", .key = 's',
1040
.doc = "OpenPGP secret key file base name",
1042
{ .name = "pubkey", .key = 'p',
1044
.doc = "OpenPGP public key file base name",
1046
{ .name = "dh-bits", .key = 129,
1048
.doc = "Bit length of the prime number used in the"
1049
" Diffie-Hellman key exchange",
1051
{ .name = "priority", .key = 130,
1053
.doc = "GnuTLS priority string for the TLS handshake",
1055
{ .name = "delay", .key = 131,
1057
.doc = "Maximum delay to wait for interface startup",
629
tmp = malloc(strlen(first) + strlen(second) + 2);
635
if (first[0] != '\0' and first[strlen(first) - 1] != '/'){
643
int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) {
644
AvahiServerConfig config;
645
AvahiSServiceBrowser *sb = NULL;
648
int returncode = EXIT_SUCCESS;
649
const char *interface = "eth0";
650
struct ifreq network;
1062
error_t parse_opt(int key, char *arg,
1063
struct argp_state *state){
1065
case 128: /* --debug */
1068
case 'c': /* --connect */
1071
case 'i': /* --interface */
1074
case 's': /* --seckey */
1077
case 'p': /* --pubkey */
1080
case 129: /* --dh-bits */
1082
tmpmax = strtoimax(arg, &tmp, 10);
1083
if(errno != 0 or tmp == arg or *tmp != '\0'
1084
or tmpmax != (typeof(mc.dh_bits))tmpmax){
1085
fprintf(stderr, "Bad number of DH bits\n");
1088
mc.dh_bits = (typeof(mc.dh_bits))tmpmax;
1090
case 130: /* --priority */
1093
case 131: /* --delay */
1095
delay = strtof(arg, &tmp);
1096
if(errno != 0 or tmp == arg or *tmp != '\0'){
1097
fprintf(stderr, "Bad delay\n");
654
static struct option long_options[] = {
655
{"debug", no_argument, (int *)&debug, 1},
656
{"interface", required_argument, 0, 'i'},
657
{"certdir", required_argument, 0, 'd'},
658
{"certkey", required_argument, 0, 'c'},
659
{"certfile", required_argument, 0, 'k'},
662
int option_index = 0;
663
ret = getopt_long (argc, argv, "i:", long_options,
1106
return ARGP_ERR_UNKNOWN;
1111
struct argp argp = { .options = options, .parser = parse_opt,
1113
.doc = "Mandos client -- Get and decrypt"
1114
" passwords from a Mandos server" };
1115
ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
1116
if(ret == ARGP_ERR_UNKNOWN){
1117
fprintf(stderr, "Unknown error while parsing arguments\n");
1118
exitcode = EXIT_FAILURE;
1124
avahi_set_log_function(empty_log);
1127
/* Initialize Avahi early so avahi_simple_poll_quit() can be called
1128
from the signal handler */
1129
/* Initialize the pseudo-RNG for Avahi */
1130
srand((unsigned int) time(NULL));
1131
mc.simple_poll = avahi_simple_poll_new();
1132
if(mc.simple_poll == NULL){
1133
fprintf(stderr, "Avahi: Failed to create simple poll object.\n");
1134
exitcode = EXIT_FAILURE;
1138
sigemptyset(&sigterm_action.sa_mask);
1139
ret = sigaddset(&sigterm_action.sa_mask, SIGINT);
1141
perror("sigaddset");
1142
exitcode = EXIT_FAILURE;
1145
ret = sigaddset(&sigterm_action.sa_mask, SIGHUP);
1147
perror("sigaddset");
1148
exitcode = EXIT_FAILURE;
1151
ret = sigaddset(&sigterm_action.sa_mask, SIGTERM);
1153
perror("sigaddset");
1154
exitcode = EXIT_FAILURE;
1157
/* Need to check if the handler is SIG_IGN before handling:
1158
| [[info:libc:Initial Signal Actions]] |
1159
| [[info:libc:Basic Signal Handling]] |
1161
ret = sigaction(SIGINT, NULL, &old_sigterm_action);
1163
perror("sigaction");
1164
return EXIT_FAILURE;
1166
if(old_sigterm_action.sa_handler != SIG_IGN){
1167
ret = sigaction(SIGINT, &sigterm_action, NULL);
1169
perror("sigaction");
1170
exitcode = EXIT_FAILURE;
1174
ret = sigaction(SIGHUP, NULL, &old_sigterm_action);
1176
perror("sigaction");
1177
return EXIT_FAILURE;
1179
if(old_sigterm_action.sa_handler != SIG_IGN){
1180
ret = sigaction(SIGHUP, &sigterm_action, NULL);
1182
perror("sigaction");
1183
exitcode = EXIT_FAILURE;
1187
ret = sigaction(SIGTERM, NULL, &old_sigterm_action);
1189
perror("sigaction");
1190
return EXIT_FAILURE;
1192
if(old_sigterm_action.sa_handler != SIG_IGN){
1193
ret = sigaction(SIGTERM, &sigterm_action, NULL);
1195
perror("sigaction");
1196
exitcode = EXIT_FAILURE;
1201
/* If the interface is down, bring it up */
1202
if(interface[0] != '\0'){
1203
if_index = (AvahiIfIndex) if_nametoindex(interface);
1205
fprintf(stderr, "No such interface: \"%s\"\n", interface);
1206
exitcode = EXIT_FAILURE;
1214
/* Re-raise priviliges */
1222
/* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO
1223
messages to mess up the prompt */
1224
ret = klogctl(8, NULL, 5);
1225
bool restore_loglevel = true;
1227
restore_loglevel = false;
1230
#endif /* __linux__ */
690
certfile = combinepath(certdir, certfile);
691
if (certfile == NULL){
692
returncode = EXIT_FAILURE;
696
certkey = combinepath(certdir, certkey);
697
if (certkey == NULL){
698
returncode = EXIT_FAILURE;
1232
702
sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
1234
704
perror("socket");
1235
exitcode = EXIT_FAILURE;
1237
if(restore_loglevel){
1238
ret = klogctl(7, NULL, 0);
1243
#endif /* __linux__ */
1244
/* Lower privileges */
705
returncode = EXIT_FAILURE;
1252
strcpy(network.ifr_name, interface);
708
strcpy(network.ifr_name, interface);
1253
709
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1255
712
perror("ioctl SIOCGIFFLAGS");
1257
if(restore_loglevel){
1258
ret = klogctl(7, NULL, 0);
1263
#endif /* __linux__ */
1264
exitcode = EXIT_FAILURE;
1265
/* Lower privileges */
713
returncode = EXIT_FAILURE;
1273
716
if((network.ifr_flags & IFF_UP) == 0){
1274
717
network.ifr_flags |= IFF_UP;
1275
take_down_interface = true;
1276
718
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1278
take_down_interface = false;
1279
720
perror("ioctl SIOCSIFFLAGS");
1280
exitcode = EXIT_FAILURE;
1282
if(restore_loglevel){
1283
ret = klogctl(7, NULL, 0);
1288
#endif /* __linux__ */
1289
/* Lower privileges */
1298
/* sleep checking until interface is running */
1299
for(int i=0; i < delay * 4; i++){
1300
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1302
perror("ioctl SIOCGIFFLAGS");
1303
} else if(network.ifr_flags & IFF_RUNNING){
1306
struct timespec sleeptime = { .tv_nsec = 250000000 };
1307
ret = nanosleep(&sleeptime, NULL);
1308
if(ret == -1 and errno != EINTR){
1309
perror("nanosleep");
1312
if(not take_down_interface){
1313
/* We won't need the socket anymore */
1314
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1320
if(restore_loglevel){
1321
/* Restores kernel loglevel to default */
1322
ret = klogctl(7, NULL, 0);
1327
#endif /* __linux__ */
1328
/* Lower privileges */
1330
if(take_down_interface){
1331
/* Lower privileges */
1337
/* Lower privileges permanently */
1349
ret = init_gnutls_global(pubkey, seckey);
1351
fprintf(stderr, "init_gnutls_global failed\n");
1352
exitcode = EXIT_FAILURE;
1355
gnutls_initialized = true;
1362
tempdir_created = true;
1363
if(mkdtemp(tempdir) == NULL){
1364
tempdir_created = false;
1373
if(not init_gpgme(pubkey, seckey, tempdir)){
1374
fprintf(stderr, "init_gpgme failed\n");
1375
exitcode = EXIT_FAILURE;
1378
gpgme_initialized = true;
1385
if(connect_to != NULL){
1386
/* Connect directly, do not use Zeroconf */
1387
/* (Mainly meant for debugging) */
1388
char *address = strrchr(connect_to, ':');
1389
if(address == NULL){
1390
fprintf(stderr, "No colon in address\n");
1391
exitcode = EXIT_FAILURE;
1401
tmpmax = strtoimax(address+1, &tmp, 10);
1402
if(errno != 0 or tmp == address+1 or *tmp != '\0'
1403
or tmpmax != (uint16_t)tmpmax){
1404
fprintf(stderr, "Bad port number\n");
1405
exitcode = EXIT_FAILURE;
1413
port = (uint16_t)tmpmax;
1415
address = connect_to;
1416
/* Colon in address indicates IPv6 */
1418
if(strchr(address, ':') != NULL){
1428
ret = start_mandos_communication(address, port, if_index, af);
1430
exitcode = EXIT_FAILURE;
1432
exitcode = EXIT_SUCCESS;
1442
AvahiServerConfig config;
1443
/* Do not publish any local Zeroconf records */
721
returncode = EXIT_FAILURE;
728
avahi_set_log_function(empty_log);
731
/* Initialize the psuedo-RNG */
732
srand((unsigned int) time(NULL));
734
/* Allocate main loop object */
735
if (!(simple_poll = avahi_simple_poll_new())) {
736
fprintf(stderr, "Failed to create simple poll object.\n");
737
returncode = EXIT_FAILURE;
741
/* Do not publish any local records */
1444
742
avahi_server_config_init(&config);
1445
743
config.publish_hinfo = 0;
1446
744
config.publish_addresses = 0;
1447
745
config.publish_workstation = 0;
1448
746
config.publish_domain = 0;
1450
748
/* Allocate a new server */
1451
mc.server = avahi_server_new(avahi_simple_poll_get
1452
(mc.simple_poll), &config, NULL,
1455
/* Free the Avahi configuration data */
749
server = avahi_server_new(avahi_simple_poll_get(simple_poll),
750
&config, NULL, NULL, &error);
752
/* Free the configuration data */
1456
753
avahi_server_config_free(&config);
1459
/* Check if creating the Avahi server object succeeded */
1460
if(mc.server == NULL){
1461
fprintf(stderr, "Failed to create Avahi server: %s\n",
1462
avahi_strerror(error));
1463
exitcode = EXIT_FAILURE;
1471
/* Create the Avahi service browser */
1472
sb = avahi_s_service_browser_new(mc.server, if_index,
1473
AVAHI_PROTO_UNSPEC, "_mandos._tcp",
1474
NULL, 0, browse_callback, NULL);
1476
fprintf(stderr, "Failed to create service browser: %s\n",
1477
avahi_strerror(avahi_server_errno(mc.server)));
1478
exitcode = EXIT_FAILURE;
1486
/* Run the main loop */
1489
fprintf(stderr, "Starting Avahi loop search\n");
1492
avahi_simple_poll_loop(mc.simple_poll);
1497
fprintf(stderr, "%s exiting\n", argv[0]);
1500
/* Cleanup things */
1502
avahi_s_service_browser_free(sb);
1504
if(mc.server != NULL)
1505
avahi_server_free(mc.server);
1507
if(mc.simple_poll != NULL)
1508
avahi_simple_poll_free(mc.simple_poll);
1510
if(gnutls_initialized){
1511
gnutls_certificate_free_credentials(mc.cred);
1512
gnutls_global_deinit();
1513
gnutls_dh_params_deinit(mc.dh_params);
1516
if(gpgme_initialized){
1517
gpgme_release(mc.ctx);
1520
/* Take down the network interface */
1521
if(take_down_interface){
1522
/* Re-raise priviliges */
1529
ret = ioctl(sd, SIOCGIFFLAGS, &network);
1531
perror("ioctl SIOCGIFFLAGS");
1532
} else if(network.ifr_flags & IFF_UP) {
1533
network.ifr_flags &= ~IFF_UP; /* clear flag */
1534
ret = ioctl(sd, SIOCSIFFLAGS, &network);
1536
perror("ioctl SIOCSIFFLAGS");
1539
ret = (int)TEMP_FAILURE_RETRY(close(sd));
1543
/* Lower privileges permanently */
1552
/* Removes the temp directory used by GPGME */
1553
if(tempdir_created){
1555
struct dirent *direntry;
1556
d = opendir(tempdir);
1558
if(errno != ENOENT){
1563
direntry = readdir(d);
1564
if(direntry == NULL){
1567
/* Skip "." and ".." */
1568
if(direntry->d_name[0] == '.'
1569
and (direntry->d_name[1] == '\0'
1570
or (direntry->d_name[1] == '.'
1571
and direntry->d_name[2] == '\0'))){
1574
char *fullname = NULL;
1575
ret = asprintf(&fullname, "%s/%s", tempdir,
1581
ret = remove(fullname);
1583
fprintf(stderr, "remove(\"%s\"): %s\n", fullname,
1590
ret = rmdir(tempdir);
1591
if(ret == -1 and errno != ENOENT){
1597
sigemptyset(&old_sigterm_action.sa_mask);
1598
old_sigterm_action.sa_handler = SIG_DFL;
1599
ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
1600
&old_sigterm_action,
1603
perror("sigaction");
1606
ret = raise(signal_received);
1607
} while(ret != 0 and errno == EINTR);
1612
TEMP_FAILURE_RETRY(pause());
755
/* Check if creating the server object succeeded */
757
fprintf(stderr, "Failed to create server: %s\n",
758
avahi_strerror(error));
759
returncode = EXIT_FAILURE;
763
/* Create the service browser */
764
sb = avahi_s_service_browser_new(server,
766
if_nametoindex(interface),
768
"_mandos._tcp", NULL, 0,
769
browse_callback, server);
771
fprintf(stderr, "Failed to create service browser: %s\n",
772
avahi_strerror(avahi_server_errno(server)));
773
returncode = EXIT_FAILURE;
777
/* Run the main loop */
780
fprintf(stderr, "Starting avahi loop search\n");
783
avahi_simple_poll_loop(simple_poll);
788
fprintf(stderr, "%s exiting\n", argv[0]);
793
avahi_s_service_browser_free(sb);
796
avahi_server_free(server);
799
avahi_simple_poll_free(simple_poll);