~ubuntu-branches/ubuntu/oneiric/rng-tools/oneiric

« back to all changes in this revision

Viewing changes to rngd.c

  • Committer: Bazaar Package Importer
  • Author(s): Henrique de Moraes Holschuh
  • Date: 2004-06-11 17:08:59 UTC
  • Revision ID: james.westby@ubuntu.com-20040611170859-22rp5c1wavpvrq3f
Tags: upstream-1.1-unofficial-mt.2
ImportĀ upstreamĀ versionĀ 1.1-unofficial-mt.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * rngd.c -- Random Number Generator daemon
 
3
 *
 
4
 * rngd reads data from a hardware random number generator, verifies it
 
5
 * looks like random data, and adds it to /dev/random's entropy store.
 
6
 * 
 
7
 * In theory, this should allow you to read very quickly from
 
8
 * /dev/random; rngd also adds bytes to the entropy store periodically
 
9
 * when it's full, which makes predicting the entropy store's contents
 
10
 * harder.
 
11
 *
 
12
 * Copyright (C) 2001 Philipp Rumpf
 
13
 * Copyright (C) 2004 Henrique de Moraes Holschuh <hmh@debian.org>
 
14
 *
 
15
 * This program is free software; you can redistribute it and/or modify
 
16
 * it under the terms of the GNU General Public License as published by
 
17
 * the Free Software Foundation; either version 2 of the License, or
 
18
 * (at your option) any later version.
 
19
 * 
 
20
 * This program is distributed in the hope that it will be useful,
 
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
23
 * GNU General Public License for more details.
 
24
 *
 
25
 * You should have received a copy of the GNU General Public License
 
26
 * along with this program; if not, write to the Free Software
 
27
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
28
 */
 
29
 
 
30
#define _GNU_SOURCE
 
31
 
 
32
#ifndef HAVE_CONFIG_H
 
33
#error Invalid or missing autoconf build environment
 
34
#endif
 
35
 
 
36
#include "rng-tools-config.h"
 
37
 
 
38
#include <unistd.h>
 
39
#include <stdint.h>
 
40
#include <stdio.h>
 
41
#include <stdlib.h>
 
42
#include <sys/types.h>
 
43
#include <sys/stat.h>
 
44
#include <fcntl.h>
 
45
#include <errno.h>
 
46
#include <string.h>
 
47
#include <argp.h>
 
48
#include <syslog.h>
 
49
#include <pthread.h>
 
50
#include <signal.h>
 
51
 
 
52
#include "rngd.h"
 
53
#include "fips.h"
 
54
#include "exits.h"
 
55
#include "stats.h"
 
56
#include "rngd_threads.h"
 
57
#include "rngd_entsource.h"
 
58
#include "rngd_linux.h"
 
59
 
 
60
#ifdef HAVE_FLOCK
 
61
#  include <sys/file.h>
 
62
#endif
 
63
 
 
64
#define STR(x) #x
 
65
#define PROGNAME "rngd"
 
66
 
 
67
/*
 
68
 * Globals
 
69
 */
 
70
 
 
71
/* Statistics */
 
72
struct rng_stats rng_stats;
 
73
 
 
74
/* Background/daemon mode */
 
75
pid_t masterprocess;                    /* PID of the master process */
 
76
int am_daemon;                          /* Nonzero if we went daemon */
 
77
int exitstatus = EXIT_SUCCESS;          /* Exit status on SIGTERM */
 
78
static FILE *daemon_lockfp = NULL;      /* Lockfile file pointer */
 
79
static int daemon_lockfd;               /* Lockfile file descriptior */
 
80
 
 
81
/* Signals */
 
82
volatile int gotsigterm = 0;            /* Received a TERM signal */
 
83
static volatile int gotsigusr1 = 0;     /* Received a USR1 signal */
 
84
 
 
85
/* Command line arguments and processing */
 
86
const char *argp_program_version = 
 
87
        PROGNAME " " VERSION "\n"
 
88
        "Copyright (c) 2001 by Philipp Rumpf\n"
 
89
        "This is free software; see the source for copying conditions.  There is NO "
 
90
        "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.";
 
91
const char *argp_program_bug_address = PACKAGE_BUGREPORT;
 
92
error_t argp_err_exit_status = EXIT_USAGE;
 
93
 
 
94
static char doc[] =
 
95
        "Check and feed random data from hardware device to kernel entropy pool.\n";
 
96
 
 
97
#define ARGP_RNGD_CMDLINE_TRNG 0x81
 
98
        
 
99
static struct argp_option options[] = {
 
100
        { "foreground", 'f', 0, 0, "Do not fork and become a daemon" },
 
101
 
 
102
        { "background", 'b', 0, 0, "Become a daemon (default)" },
 
103
 
 
104
        { "random-device", 'o', "file", 0,
 
105
          "Kernel device used for entropy output (default: " DEVRANDOM ")" },
 
106
 
 
107
        { "rng-device", 'r', "file", 0,
 
108
          "Entropy source (default: " DEVHWRANDOM ")" },
 
109
 
 
110
        { "random-step", 's', "n", 0,
 
111
          "Number of bytes written to random-device at a time (default: 64), 8 <= n <= " STR(FIPS_RNG_BUFFER_SIZE) ", n must be even" },
 
112
 
 
113
        { "fill-watermark", 'W', "n", 0,
 
114
          "Do not stop feeding entropy to random-device until at least n bits of entropy are available in the pool (default: 2048), 0 <= n <= 4096" },
 
115
 
 
116
        { "timeout", 't', "n", 0,
 
117
          "Interval written to random-device when the entropy pool is full, in seconds (default: 60)" },
 
118
 
 
119
        { "pidfile", 'p', "file", 0,
 
120
          "Path to file to write PID to in daemon mode (default: " PIDFILE ")" },
 
121
 
 
122
        { "rng-entropy", 'H', "n", 0,
 
123
          "Entropy per bit of the hardware RNG (default: 1.0), 0 < n <= 1.0" },
 
124
 
 
125
        { "rng-buffers", 'B', "n", 0,
 
126
          "Number of buffers (default: 3),  0 < n <= " STR(MAX_RNG_BUFFERS) },
 
127
 
 
128
        { "trng", ARGP_RNGD_CMDLINE_TRNG, "name", 0,
 
129
          "Load known-good defaults for a given TRNG.  Use --trng=help to get a list of known TRNGs" },
 
130
 
 
131
        { 0 },
 
132
};
 
133
static struct arguments default_arguments = {
 
134
        .rng_name       = DEVHWRANDOM,
 
135
        .random_name    = DEVRANDOM,
 
136
        .pidfile_name   = PIDFILE,
 
137
        .poll_timeout   = 60,
 
138
        .random_step    = 64,
 
139
        .fill_watermark = 2048,
 
140
        .daemon         = 1,
 
141
        .rng_entropy    = 1.0,
 
142
        .rng_buffers    = 3,
 
143
};
 
144
struct arguments *arguments = &default_arguments;
 
145
 
 
146
/* Predefined known-good values for TRNGs */
 
147
struct trng_params {
 
148
        char *tag;              /* Short name of TRNG */
 
149
        char *name;             /* Full Name of TRNG */
 
150
        int width;              /* Best width for continuous run test */
 
151
        int buffers;            /* Recommended value for rng-buffers */
 
152
        double entropy;         /* Recommended value for rng-entropy */
 
153
};
 
154
static struct trng_params trng_parameters[] = {
 
155
        /* Device: Intel FWH TRNG (82802AB/82802AC)
 
156
         * Kernel driver: hw_random or i810_rng
 
157
         * Device width: 8 bits
 
158
         * Entropy: H > 0.999
 
159
         * 
 
160
         * Slow, about 20Kibits/s (variable bitrate) with current
 
161
         * kernel drivers, but the hardware should be capable of
 
162
         * about 75kbit/s.  The kernel driver uses a lot of CPU
 
163
         * time.  It is often misdetected (false positive).
 
164
         *
 
165
         * Whitepaper: Cryptographic Research
 
166
         * http://www.cryptography.com/resources/whitepapers/IntelRNG.pdf
 
167
         */
 
168
        { .name         = "Intel FWH (82802AB/AC) TRNG",
 
169
          .tag          = "intel",
 
170
          .width        = 32,
 
171
          .buffers      = 5,
 
172
          .entropy      = 0.998 
 
173
        },
 
174
 
 
175
        /* Device: VIA Padlock (Nehemiah CPU core) TRNG
 
176
         * Kernel driver: hw_random
 
177
         * Device width: 8 bits (internal), 64 bits (external)
 
178
         * Entropy: H > 0.75 (whitener disabled)
 
179
         *          H > 0.99 (whitener enabled)
 
180
         *
 
181
         * Very fast, about 30-50 Mibits/s with the whitener disabled,
 
182
         * and 4-9 Mibits/s with whitener enabled.  The kernel drivers
 
183
         * need patching to archieve better performance (patches and
 
184
         * data from http://peertech.org/hardware/viarng/).
 
185
         *
 
186
         * The hardware has 4 64bit FIFOs to store TRNG data.
 
187
         * 
 
188
         * Whitepaper: Cryptographic Research
 
189
         * http://www.cryptography.com/resources/whitepapers/VIA_rng.pdf
 
190
         */
 
191
        { .name         = "VIA Padlock (Nehemiah) TRNG",
 
192
          .tag          = "via",
 
193
          .width        = 64,
 
194
          .buffers      = 3,
 
195
          .entropy      = 0.75 
 
196
        },
 
197
        { NULL },
 
198
};
 
199
 
 
200
/*
 
201
 * command line processing
 
202
 */
 
203
#define SEEN_OPT_RNGBUFFERS     0x01
 
204
#define SEEN_OPT_RNGENTROPY     0x02
 
205
 
 
206
static error_t parse_opt (int key, char *arg, struct argp_state *state)
 
207
{
 
208
        struct arguments *arguments = state->input;
 
209
        static unsigned int seen_opt = 0;
 
210
        
 
211
        switch(key) {
 
212
        case 'o':
 
213
                arguments->random_name = arg;
 
214
                break;
 
215
        case 'r':
 
216
                arguments->rng_name = arg;
 
217
                break;
 
218
        case 'p':
 
219
                arguments->pidfile_name = arg;
 
220
                break;
 
221
        case 't': {
 
222
                float f;
 
223
                if (sscanf(arg, "%f", &f) == 0)
 
224
                        argp_usage(state);
 
225
                else
 
226
                        arguments->poll_timeout = f;
 
227
                break;
 
228
        }
 
229
 
 
230
        case 'f':
 
231
                arguments->daemon = 0;
 
232
                break;
 
233
        case 'b':
 
234
                arguments->daemon = 1;
 
235
                break;
 
236
        case 's': {
 
237
                int n;
 
238
                if ((sscanf(arg, "%i", &n) == 0) || (n < 8) || 
 
239
                        (n > FIPS_RNG_BUFFER_SIZE) || (n & 1))
 
240
                        argp_usage(state);
 
241
                else
 
242
                        arguments->random_step = n;
 
243
                break;
 
244
        }
 
245
        case 'W': {
 
246
                int n;
 
247
                if ((sscanf(arg, "%i", &n) == 0) || (n < 0) || (n > 4096))
 
248
                        argp_usage(state);
 
249
                else
 
250
                        arguments->fill_watermark = n;
 
251
                break;
 
252
        }
 
253
 
 
254
        case 'H': {
 
255
                float H;
 
256
                if ((sscanf(arg, "%f", &H) == 0) || (H <= 0) || (H > 1))
 
257
                        argp_usage(state);
 
258
                else
 
259
                        arguments->rng_entropy = H;
 
260
                        seen_opt |= SEEN_OPT_RNGENTROPY;
 
261
                break;
 
262
        }
 
263
 
 
264
        case 'B': {
 
265
                int n;
 
266
                if ((sscanf(arg, "%i", &n) == 0) || (n < 1) || (n > MAX_RNG_BUFFERS ))
 
267
                        argp_usage(state);
 
268
                else
 
269
                        arguments->rng_buffers = n;
 
270
                        seen_opt |= SEEN_OPT_RNGBUFFERS;
 
271
                break;
 
272
        }
 
273
 
 
274
        case ARGP_RNGD_CMDLINE_TRNG: {  /* --trng */
 
275
                int i = 0;
 
276
                if (strcasecmp(arg, "help") == 0) {
 
277
                        fprintf(state->out_stream,
 
278
                                "TRNG      Description\n");
 
279
                        while (trng_parameters[i].tag) {
 
280
                                fprintf(state->out_stream, "%-8s  \"%s\"\n",
 
281
                                        trng_parameters[i].tag,
 
282
                                        trng_parameters[i].name);
 
283
                                fprintf(state->out_stream,
 
284
                                        "%-10s"
 
285
                                        "rng-entropy=%0.3f, "
 
286
                                        "rng-buffers=%d;\n",
 
287
                                        " ", trng_parameters[i].entropy,
 
288
                                        trng_parameters[i].buffers);
 
289
                                i++;
 
290
                        }
 
291
                        exit(EXIT_SUCCESS);
 
292
                }
 
293
                while (trng_parameters[i].tag) {
 
294
                        if (strcasecmp(arg, trng_parameters[i].tag) == 0) {
 
295
                                if (! (seen_opt & SEEN_OPT_RNGENTROPY))
 
296
                                        arguments->rng_entropy =
 
297
                                                trng_parameters[i].entropy;
 
298
                                if (! (seen_opt & SEEN_OPT_RNGBUFFERS))
 
299
                                        arguments->rng_buffers =
 
300
                                                trng_parameters[i].buffers;
 
301
                                break;
 
302
                        }
 
303
                        i++;
 
304
                }
 
305
                if (!trng_parameters[i].tag)
 
306
                        argp_failure(state, argp_err_exit_status, 0,
 
307
                                "Unknown TRNG, try --trng=help");
 
308
                break;
 
309
        }
 
310
 
 
311
        default:
 
312
                return ARGP_ERR_UNKNOWN;
 
313
        }
 
314
 
 
315
        return 0;
 
316
}
 
317
static struct argp argp = { options, parse_opt, NULL, doc };
 
318
 
 
319
/*
 
320
 * Daemon needs
 
321
 */
 
322
void die(int status)
 
323
{
 
324
        if (am_daemon) syslog(LOG_ERR, "Exiting with status %d...", status);
 
325
        exit(status);
 
326
}
 
327
 
 
328
/* 
 
329
 * Write our pid to our pidfile, and lock it
 
330
 */
 
331
static void get_lock(const char* pidfile_name)
 
332
{
 
333
    int otherpid = 0;
 
334
 
 
335
    if (!daemon_lockfp) {
 
336
            if (((daemon_lockfd = open(pidfile_name, O_RDWR|O_CREAT, 0644)) == -1)
 
337
                || ((daemon_lockfp = fdopen(daemon_lockfd, "r+"))) == NULL) {
 
338
                    message(LOG_ERR, "can't open or create %s", pidfile_name);
 
339
                   die(EXIT_USAGE);
 
340
            }
 
341
    }
 
342
 
 
343
#ifdef HAVE_FLOCK
 
344
    if ( flock(daemon_lockfd, LOCK_EX|LOCK_NB) != 0 ) {
 
345
#else
 
346
    if ( lockf(fileno(daemon_lockfp), F_TLOCK, 0) != 0 ) {
 
347
#endif
 
348
                rewind(daemon_lockfp);
 
349
                fscanf(daemon_lockfp, "%d", &otherpid);
 
350
                message(LOG_ERR, "can't lock %s, running daemon's pid may be %d",
 
351
                      pidfile_name, otherpid);
 
352
                die(EXIT_USAGE);
 
353
            }
 
354
 
 
355
    fcntl(daemon_lockfd, F_SETFD, 1);
 
356
 
 
357
    rewind(daemon_lockfp);
 
358
    fprintf(daemon_lockfp, "%d\n", (int) getpid());
 
359
    fflush(daemon_lockfp);
 
360
    ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp));
 
361
}
 
362
 
 
363
/*
 
364
 * Signal handling
 
365
 */
 
366
static void sigterm_handler(int sig)
 
367
{
 
368
        gotsigterm = 128 | sig;
 
369
}
 
370
 
 
371
static void sigusr1_handler(int sig)
 
372
{
 
373
        gotsigusr1 = 1;
 
374
}
 
375
 
 
376
static void init_sighandlers(void)
 
377
{
 
378
        struct sigaction action;
 
379
 
 
380
        sigemptyset(&action.sa_mask);
 
381
        action.sa_flags = 0;
 
382
        action.sa_handler = sigterm_handler;
 
383
 
 
384
        /* Handle SIGTERM and SIGINT the same way */
 
385
        if (sigaction(SIGTERM, &action, NULL) < 0) {
 
386
                message(LOG_ERR,
 
387
                        "unable to install signal handler for SIGTERM: %s",
 
388
                        strerror(errno));
 
389
                die(EXIT_OSERR);
 
390
        }
 
391
        if (sigaction(SIGINT, &action, NULL) < 0) {
 
392
                message(LOG_ERR,
 
393
                        "unable to install signal handler for SIGINT: %s",
 
394
                        strerror(errno));
 
395
                die(EXIT_OSERR);
 
396
        }
 
397
 
 
398
        /* Handle SIGUSR1 in a more friendly way */
 
399
        action.sa_flags = SA_RESTART;
 
400
        action.sa_handler = sigusr1_handler;
 
401
        if (sigaction(SIGUSR1, &action, NULL) < 0) {
 
402
                message(LOG_ERR,
 
403
                        "unable to install signal handler for SIGUSR1: %s",
 
404
                        strerror(errno));
 
405
                die(EXIT_OSERR);
 
406
        }
 
407
}
 
408
 
 
409
/*
 
410
 * Statistics, n is the number of rng buffers
 
411
 */
 
412
static void init_rng_stats(int n)
 
413
{
 
414
        set_stat_prefix("stats: ");
 
415
 
 
416
        memset(&rng_stats, 0, sizeof(rng_stats));
 
417
        rng_stats.buffer_lowmark = n - 1; /* one is always in use */
 
418
 
 
419
        pthread_mutex_init(&rng_stats.group1_mutex, NULL);
 
420
        pthread_mutex_init(&rng_stats.group2_mutex, NULL);
 
421
        pthread_mutex_init(&rng_stats.group3_mutex, NULL);
 
422
}
 
423
 
 
424
static void dump_rng_stats(void)
 
425
{
 
426
        int j;
 
427
        char buf[256];
 
428
 
 
429
        pthread_mutex_lock(&rng_stats.group1_mutex);
 
430
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
431
                        "bits received from TRNG source",
 
432
                        rng_stats.bytes_received * 8));
 
433
        pthread_mutex_unlock(&rng_stats.group1_mutex);
 
434
        pthread_mutex_lock(&rng_stats.group3_mutex);
 
435
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
436
                        "bits sent to kernel pool",
 
437
                        rng_stats.bytes_sent * 8));
 
438
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
439
                        "entropy added to kernel pool",
 
440
                        rng_stats.entropy_sent));
 
441
        pthread_mutex_unlock(&rng_stats.group3_mutex);
 
442
        pthread_mutex_lock(&rng_stats.group2_mutex);
 
443
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
444
                        "FIPS 140-2 successes",
 
445
                        rng_stats.good_fips_blocks));
 
446
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
447
                        "FIPS 140-2 failures",
 
448
                        rng_stats.bad_fips_blocks));
 
449
        for (j = 0; j < N_FIPS_TESTS; j++)
 
450
                message(LOG_INFO, dump_stat_counter(buf, sizeof(buf), fips_test_names[j],
 
451
                                rng_stats.fips_failures[j]));
 
452
        pthread_mutex_unlock(&rng_stats.group2_mutex);
 
453
        pthread_mutex_lock(&rng_stats.group1_mutex);
 
454
        message(LOG_INFO, dump_stat_bw(buf, sizeof(buf),
 
455
                        "TRNG source speed", "bits",
 
456
                        &rng_stats.source_blockfill, FIPS_RNG_BUFFER_SIZE*8));
 
457
        pthread_mutex_unlock(&rng_stats.group1_mutex);
 
458
        pthread_mutex_lock(&rng_stats.group2_mutex);
 
459
        message(LOG_INFO, dump_stat_bw(buf, sizeof(buf),
 
460
                        "FIPS tests speed", "bits",
 
461
                        &rng_stats.fips_blockfill, FIPS_RNG_BUFFER_SIZE*8));
 
462
        pthread_mutex_unlock(&rng_stats.group2_mutex);
 
463
        pthread_mutex_lock(&rng_stats.group3_mutex);
 
464
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
465
                        "Lowest ready-buffers level",
 
466
                        rng_stats.buffer_lowmark));
 
467
        message(LOG_INFO, dump_stat_counter(buf, sizeof(buf),
 
468
                        "Entropy starvations",
 
469
                        rng_stats.sink_starved));
 
470
        message(LOG_INFO, dump_stat_stat(buf, sizeof(buf),
 
471
                        "Time spent starving for entropy",
 
472
                        "us",
 
473
                        &rng_stats.sink_wait));
 
474
        pthread_mutex_unlock(&rng_stats.group3_mutex);
 
475
}
 
476
 
 
477
int main(int argc, char **argv)
 
478
{
 
479
        int fd;
 
480
        pthread_t t1,t2,t3;
 
481
        int sleeptime;
 
482
 
 
483
        argp_parse(&argp, argc, argv, 0, 0, arguments);
 
484
 
 
485
        /* close useless FDs we might have gotten somehow */
 
486
        for(fd = 3; fd < 250; fd++) (void) close(fd);
 
487
 
 
488
        /* Init statistics */
 
489
        init_rng_stats(arguments->rng_buffers);
 
490
 
 
491
        /* Init entropy source, and open TRNG device */
 
492
        init_entropy_source(arguments->rng_name);
 
493
 
 
494
        /* Init entropy sink and open random device */
 
495
        init_kernel_rng(arguments->random_name);
 
496
 
 
497
        if (arguments->daemon) {
 
498
                /* check if another rngd is running, 
 
499
                 * create pidfile and lock it */
 
500
                get_lock(arguments->pidfile_name);
 
501
 
 
502
                if (daemon(0, 0) < 0) {
 
503
                        message(LOG_ERR, "can't daemonize: %s",
 
504
                                        strerror(errno));
 
505
                        return EXIT_OSERR;
 
506
                }
 
507
 
 
508
                openlog(PROGNAME, 0, SYSLOG_FACILITY);
 
509
                am_daemon = 1;
 
510
 
 
511
                /* update pidfile */
 
512
                get_lock(arguments->pidfile_name);
 
513
        }
 
514
 
 
515
        masterprocess = getpid();
 
516
        message(LOG_INFO, PROGNAME " " VERSION " starting up...");
 
517
 
 
518
        /* post-fork initialization */
 
519
        init_rng_buffers(arguments->rng_buffers);
 
520
        init_sighandlers();
 
521
 
 
522
        /* Fire up worker threads */
 
523
        if (pthread_create(&t1, NULL, &do_rng_data_source_loop, NULL) |
 
524
            pthread_create(&t2, NULL, &do_rng_fips_test_loop, NULL ) |
 
525
            pthread_create(&t3, NULL, &do_rng_data_sink_loop, NULL )) {
 
526
                message(LOG_ERR, "Insufficient resources to start threads");
 
527
                die(EXIT_OSERR);
 
528
        }
 
529
 
 
530
        /* 
 
531
         * All we can do now is spin around waiting for a hit to the head.
 
532
         * Dump stats every hour, and at exit...
 
533
         */
 
534
        sleeptime = 3600;
 
535
        while (!gotsigterm) {
 
536
                sleeptime = sleep(sleeptime);
 
537
                if ((sleeptime == 0) || gotsigusr1 || gotsigterm) {
 
538
                        dump_rng_stats();
 
539
                        sleeptime = 3600;
 
540
                        gotsigusr1 = 0;
 
541
                }
 
542
        }
 
543
 
 
544
        if (exitstatus == EXIT_SUCCESS)
 
545
                message(LOG_INFO, "Exiting...");
 
546
        else
 
547
                message(LOG_ERR, 
 
548
                        "Exiting with status %d", exitstatus);
 
549
 
 
550
        exit(exitstatus);
 
551
}