chiark / gitweb /
journal: implement basic journal file verification logic
[elogind.git] / src / journal / journalctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <stddef.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <sys/poll.h>
30 #include <time.h>
31 #include <getopt.h>
32 #include <sys/stat.h>
33
34 #include <systemd/sd-journal.h>
35
36 #include "log.h"
37 #include "util.h"
38 #include "path-util.h"
39 #include "build.h"
40 #include "pager.h"
41 #include "logs-show.h"
42 #include "strv.h"
43 #include "journal-internal.h"
44 #include "fsprg.h"
45 #include "journal-def.h"
46
47 #define DEFAULT_FSPRG_INTERVAL_USEC (15*USEC_PER_MINUTE)
48
49 static OutputMode arg_output = OUTPUT_SHORT;
50 static bool arg_follow = false;
51 static bool arg_show_all = false;
52 static bool arg_no_pager = false;
53 static int arg_lines = -1;
54 static bool arg_no_tail = false;
55 static bool arg_quiet = false;
56 static bool arg_local = false;
57 static bool arg_this_boot = false;
58 static const char *arg_directory = NULL;
59 static int arg_priorities = 0xFF;
60
61 static enum {
62         ACTION_SHOW,
63         ACTION_NEW_ID128,
64         ACTION_PRINT_HEADER,
65         ACTION_SETUP_KEYS,
66         ACTION_VERIFY
67 } arg_action = ACTION_SHOW;
68
69 static int help(void) {
70
71         printf("%s [OPTIONS...] [MATCH]\n\n"
72                "Send control commands to or query the journal.\n\n"
73                "  -h --help           Show this help\n"
74                "     --version        Show package version\n"
75                "     --no-pager       Do not pipe output into a pager\n"
76                "  -a --all            Show all fields, including long and unprintable\n"
77                "  -f --follow         Follow journal\n"
78                "  -n --lines=INTEGER  Journal entries to show\n"
79                "     --no-tail        Show all lines, even in follow mode\n"
80                "  -o --output=STRING  Change journal output mode (short, short-monotonic,\n"
81                "                      verbose, export, json, cat)\n"
82                "  -q --quiet          Don't show privilege warning\n"
83                "  -l --local          Only local entries\n"
84                "  -b --this-boot      Show data only from current boot\n"
85                "  -D --directory=PATH Show journal files from directory\n"
86                "  -p --priority=RANGE Show only messages within the specified priority range\n\n"
87                "Commands:\n"
88                "     --new-id128      Generate a new 128 Bit ID\n"
89                "     --header         Show journal header information\n"
90                "     --setup-keys     Generate new FSPRG key pair\n"
91                "     --verify         Verify journal file consistency\n",
92                program_invocation_short_name);
93
94         return 0;
95 }
96
97 static int parse_argv(int argc, char *argv[]) {
98
99         enum {
100                 ARG_VERSION = 0x100,
101                 ARG_NO_PAGER,
102                 ARG_NO_TAIL,
103                 ARG_NEW_ID128,
104                 ARG_HEADER,
105                 ARG_SETUP_KEYS,
106                 ARG_VERIFY
107         };
108
109         static const struct option options[] = {
110                 { "help",      no_argument,       NULL, 'h'           },
111                 { "version" ,  no_argument,       NULL, ARG_VERSION   },
112                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
113                 { "follow",    no_argument,       NULL, 'f'           },
114                 { "output",    required_argument, NULL, 'o'           },
115                 { "all",       no_argument,       NULL, 'a'           },
116                 { "lines",     required_argument, NULL, 'n'           },
117                 { "no-tail",   no_argument,       NULL, ARG_NO_TAIL   },
118                 { "new-id128", no_argument,       NULL, ARG_NEW_ID128 },
119                 { "quiet",     no_argument,       NULL, 'q'           },
120                 { "local",     no_argument,       NULL, 'l'           },
121                 { "this-boot", no_argument,       NULL, 'b'           },
122                 { "directory", required_argument, NULL, 'D'           },
123                 { "header",    no_argument,       NULL, ARG_HEADER    },
124                 { "priority",  no_argument,       NULL, 'p'           },
125                 { "setup-keys",no_argument,       NULL, ARG_SETUP_KEYS},
126                 { "verify",    no_argument,       NULL, ARG_VERIFY    },
127                 { NULL,        0,                 NULL, 0             }
128         };
129
130         int c, r;
131
132         assert(argc >= 0);
133         assert(argv);
134
135         while ((c = getopt_long(argc, argv, "hfo:an:qlbD:p:", options, NULL)) >= 0) {
136
137                 switch (c) {
138
139                 case 'h':
140                         help();
141                         return 0;
142
143                 case ARG_VERSION:
144                         puts(PACKAGE_STRING);
145                         puts(DISTRIBUTION);
146                         puts(SYSTEMD_FEATURES);
147                         return 0;
148
149                 case ARG_NO_PAGER:
150                         arg_no_pager = true;
151                         break;
152
153                 case 'f':
154                         arg_follow = true;
155                         break;
156
157                 case 'o':
158                         arg_output =  output_mode_from_string(optarg);
159                         if (arg_output < 0) {
160                                 log_error("Unknown output '%s'.", optarg);
161                                 return -EINVAL;
162                         }
163
164                         break;
165
166                 case 'a':
167                         arg_show_all = true;
168                         break;
169
170                 case 'n':
171                         r = safe_atoi(optarg, &arg_lines);
172                         if (r < 0 || arg_lines < 0) {
173                                 log_error("Failed to parse lines '%s'", optarg);
174                                 return -EINVAL;
175                         }
176                         break;
177
178                 case ARG_NO_TAIL:
179                         arg_no_tail = true;
180                         break;
181
182                 case ARG_NEW_ID128:
183                         arg_action = ACTION_NEW_ID128;
184                         break;
185
186                 case 'q':
187                         arg_quiet = true;
188                         break;
189
190                 case 'l':
191                         arg_local = true;
192                         break;
193
194                 case 'b':
195                         arg_this_boot = true;
196                         break;
197
198                 case 'D':
199                         arg_directory = optarg;
200                         break;
201
202                 case ARG_HEADER:
203                         arg_action = ACTION_PRINT_HEADER;
204                         break;
205
206                 case ARG_SETUP_KEYS:
207                         arg_action = ACTION_SETUP_KEYS;
208                         break;
209
210                 case ARG_VERIFY:
211                         arg_action = ACTION_VERIFY;
212                         break;
213
214                 case 'p': {
215                         const char *dots;
216
217                         dots = strstr(optarg, "..");
218                         if (dots) {
219                                 char *a;
220                                 int from, to, i;
221
222                                 /* a range */
223                                 a = strndup(optarg, dots - optarg);
224                                 if (!a)
225                                         return log_oom();
226
227                                 from = log_level_from_string(a);
228                                 to = log_level_from_string(dots + 2);
229                                 free(a);
230
231                                 if (from < 0 || to < 0) {
232                                         log_error("Failed to parse log level range %s", optarg);
233                                         return -EINVAL;
234                                 }
235
236                                 arg_priorities = 0;
237
238                                 if (from < to) {
239                                         for (i = from; i <= to; i++)
240                                                 arg_priorities |= 1 << i;
241                                 } else {
242                                         for (i = to; i <= from; i++)
243                                                 arg_priorities |= 1 << i;
244                                 }
245
246                         } else {
247                                 int p, i;
248
249                                 p = log_level_from_string(optarg);
250                                 if (p < 0) {
251                                         log_error("Unknown log level %s", optarg);
252                                         return -EINVAL;
253                                 }
254
255                                 arg_priorities = 0;
256
257                                 for (i = 0; i <= p; i++)
258                                         arg_priorities |= 1 << i;
259                         }
260
261                         break;
262                 }
263
264                 case '?':
265                         return -EINVAL;
266
267                 default:
268                         log_error("Unknown option code %c", c);
269                         return -EINVAL;
270                 }
271         }
272
273         if (arg_follow && !arg_no_tail && arg_lines < 0)
274                 arg_lines = 10;
275
276         return 1;
277 }
278
279 static bool on_tty(void) {
280         static int t = -1;
281
282         /* Note that this is invoked relatively early, before we start
283          * the pager. That means the value we return reflects whether
284          * we originally were started on a tty, not if we currently
285          * are. But this is intended, since we want colour and so on
286          * when run in our own pager. */
287
288         if (_unlikely_(t < 0))
289                 t = isatty(STDOUT_FILENO) > 0;
290
291         return t;
292 }
293
294 static int generate_new_id128(void) {
295         sd_id128_t id;
296         int r;
297         unsigned i;
298
299         r = sd_id128_randomize(&id);
300         if (r < 0) {
301                 log_error("Failed to generate ID: %s", strerror(-r));
302                 return r;
303         }
304
305         printf("As string:\n"
306                SD_ID128_FORMAT_STR "\n\n"
307                "As UUID:\n"
308                "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
309                "As macro:\n"
310               "#define MESSAGE_XYZ SD_ID128_MAKE(",
311                SD_ID128_FORMAT_VAL(id),
312                SD_ID128_FORMAT_VAL(id));
313
314         for (i = 0; i < 16; i++)
315                 printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
316
317         fputs(")\n", stdout);
318
319         return 0;
320 }
321
322 static int add_matches(sd_journal *j, char **args) {
323         char **i;
324         int r;
325
326         assert(j);
327
328         STRV_FOREACH(i, args) {
329
330                 if (streq(*i, "+"))
331                         r = sd_journal_add_disjunction(j);
332                 else if (path_is_absolute(*i)) {
333                         char *p, *t = NULL;
334                         const char *path;
335                         struct stat st;
336
337                         p = canonicalize_file_name(*i);
338                         path = p ? p : *i;
339
340                         if (stat(path, &st) < 0)  {
341                                 free(p);
342                                 log_error("Couldn't stat file: %m");
343                                 return -errno;
344                         }
345
346                         if (S_ISREG(st.st_mode) && (0111 & st.st_mode))
347                                 t = strappend("_EXE=", path);
348                         else if (S_ISCHR(st.st_mode))
349                                 asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev));
350                         else if (S_ISBLK(st.st_mode))
351                                 asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev));
352                         else {
353                                 free(p);
354                                 log_error("File is not a device node, regular file or is not executable: %s", *i);
355                                 return -EINVAL;
356                         }
357
358                         free(p);
359
360                         if (!t)
361                                 return log_oom();
362
363                         r = sd_journal_add_match(j, t, 0);
364                         free(t);
365                 } else
366                         r = sd_journal_add_match(j, *i, 0);
367
368                 if (r < 0) {
369                         log_error("Failed to add match '%s': %s", *i, strerror(-r));
370                         return r;
371                 }
372         }
373
374         return 0;
375 }
376
377 static int add_this_boot(sd_journal *j) {
378         char match[9+32+1] = "_BOOT_ID=";
379         sd_id128_t boot_id;
380         int r;
381
382         assert(j);
383
384         if (!arg_this_boot)
385                 return 0;
386
387         r = sd_id128_get_boot(&boot_id);
388         if (r < 0) {
389                 log_error("Failed to get boot id: %s", strerror(-r));
390                 return r;
391         }
392
393         sd_id128_to_string(boot_id, match + 9);
394         r = sd_journal_add_match(j, match, strlen(match));
395         if (r < 0) {
396                 log_error("Failed to add match: %s", strerror(-r));
397                 return r;
398         }
399
400         return 0;
401 }
402
403 static int add_priorities(sd_journal *j) {
404         char match[] = "PRIORITY=0";
405         int i, r;
406
407         assert(j);
408
409         if (arg_priorities == 0xFF)
410                 return 0;
411
412         for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
413                 if (arg_priorities & (1 << i)) {
414                         match[sizeof(match)-2] = '0' + i;
415
416                         log_info("adding match %s", match);
417
418                         r = sd_journal_add_match(j, match, strlen(match));
419                         if (r < 0) {
420                                 log_error("Failed to add match: %s", strerror(-r));
421                                 return r;
422                         }
423                 }
424
425         return 0;
426 }
427
428 static int setup_keys(void) {
429 #ifdef HAVE_GCRYPT
430         size_t mpk_size, seed_size, state_size, i;
431         uint8_t *mpk, *seed, *state;
432         ssize_t l;
433         int fd = -1, r;
434         sd_id128_t machine, boot;
435         char *p = NULL, *k = NULL;
436         struct FSPRGHeader h;
437         uint64_t n, interval;
438
439         r = sd_id128_get_machine(&machine);
440         if (r < 0) {
441                 log_error("Failed to get machine ID: %s", strerror(-r));
442                 return r;
443         }
444
445         r = sd_id128_get_boot(&boot);
446         if (r < 0) {
447                 log_error("Failed to get boot ID: %s", strerror(-r));
448                 return r;
449         }
450
451         if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fsprg",
452                      SD_ID128_FORMAT_VAL(machine)) < 0)
453                 return log_oom();
454
455         if (access(p, F_OK) >= 0) {
456                 log_error("Evolving key file %s exists already.", p);
457                 r = -EEXIST;
458                 goto finish;
459         }
460
461         if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fsprg.tmp.XXXXXX",
462                      SD_ID128_FORMAT_VAL(machine)) < 0) {
463                 r = log_oom();
464                 goto finish;
465         }
466
467         mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
468         mpk = alloca(mpk_size);
469
470         seed_size = FSPRG_RECOMMENDED_SEEDLEN;
471         seed = alloca(seed_size);
472
473         state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
474         state = alloca(state_size);
475
476         fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
477         if (fd < 0) {
478                 log_error("Failed to open /dev/random: %m");
479                 r = -errno;
480                 goto finish;
481         }
482
483         log_info("Generating seed...");
484         l = loop_read(fd, seed, seed_size, true);
485         if (l < 0 || (size_t) l != seed_size) {
486                 log_error("Failed to read random seed: %s", strerror(EIO));
487                 r = -EIO;
488                 goto finish;
489         }
490
491         log_info("Generating key pair...");
492         FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
493
494         log_info("Generating evolving key...");
495         FSPRG_GenState0(state, mpk, seed, seed_size);
496
497         interval = DEFAULT_FSPRG_INTERVAL_USEC;
498         n = now(CLOCK_REALTIME);
499         n /= interval;
500
501         close_nointr_nofail(fd);
502         fd = mkostemp(k, O_WRONLY|O_CLOEXEC|O_NOCTTY);
503         if (fd < 0) {
504                 log_error("Failed to open %s: %m", k);
505                 r = -errno;
506                 goto finish;
507         }
508
509         zero(h);
510         memcpy(h.signature, "KSHHRHLP", 8);
511         h.machine_id = machine;
512         h.boot_id = boot;
513         h.header_size = htole64(sizeof(h));
514         h.fsprg_start_usec = htole64(n * interval);
515         h.fsprg_interval_usec = htole64(interval);
516         h.secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
517         h.state_size = htole64(state_size);
518
519         l = loop_write(fd, &h, sizeof(h), false);
520         if (l < 0 || (size_t) l != sizeof(h)) {
521                 log_error("Failed to write header: %s", strerror(EIO));
522                 r = -EIO;
523                 goto finish;
524         }
525
526         l = loop_write(fd, state, state_size, false);
527         if (l < 0 || (size_t) l != state_size) {
528                 log_error("Failed to write state: %s", strerror(EIO));
529                 r = -EIO;
530                 goto finish;
531         }
532
533         if (link(k, p) < 0) {
534                 log_error("Failed to link file: %m");
535                 r = -errno;
536                 goto finish;
537         }
538
539         if (isatty(STDOUT_FILENO)) {
540                 fprintf(stderr,
541                         "\n"
542                         "The new key pair has been generated. The evolving key has been written to the\n"
543                         "following file. It will be used to protect local journal files. This file does\n"
544                         "not need to be kept secret. It should not be used on multiple hosts.\n"
545                         "\n"
546                         "\t%s\n"
547                         "\n"
548                         "Please write down the following " ANSI_HIGHLIGHT_ON "secret" ANSI_HIGHLIGHT_OFF " seed value. It should not be stored\n"
549                         "locally on disk, and may be used to verify journal files from this host.\n"
550                         "\n\t" ANSI_HIGHLIGHT_RED_ON, p);
551                 fflush(stderr);
552         }
553         for (i = 0; i < seed_size; i++) {
554                 if (i > 0 && i % 3 == 0)
555                         putchar('-');
556                 printf("%02x", ((uint8_t*) seed)[i]);
557         }
558
559         printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) interval);
560
561         if (isatty(STDOUT_FILENO))
562                 fputs(ANSI_HIGHLIGHT_OFF "\n", stderr);
563
564         r = 0;
565
566 finish:
567         if (fd >= 0)
568                 close_nointr_nofail(fd);
569
570         if (k) {
571                 unlink(k);
572                 free(k);
573         }
574
575         free(p);
576
577         return r;
578 #else
579         log_error("Forward-secure journal verification not available.");
580 #endif
581 }
582
583 static int verify(sd_journal *j) {
584         int r = 0;
585         Iterator i;
586         JournalFile *f;
587
588         assert(j);
589
590         HASHMAP_FOREACH(f, j->files, i) {
591                 int k;
592
593                 k = journal_file_verify(f, NULL);
594                 if (k < 0) {
595                         log_warning("FAIL: %s (%s)", f->path, strerror(-k));
596                         r = -r;
597                 } else
598                         log_info("PASS: %s", f->path);
599         }
600
601         return r;
602 }
603
604 int main(int argc, char *argv[]) {
605         int r;
606         sd_journal *j = NULL;
607         unsigned line = 0;
608         bool need_seek = false;
609         sd_id128_t previous_boot_id;
610         bool previous_boot_id_valid = false;
611         bool have_pager;
612
613         log_parse_environment();
614         log_open();
615
616         r = parse_argv(argc, argv);
617         if (r <= 0)
618                 goto finish;
619
620         if (arg_action == ACTION_NEW_ID128) {
621                 r = generate_new_id128();
622                 goto finish;
623         }
624
625         if (arg_action == ACTION_SETUP_KEYS) {
626                 r = setup_keys();
627                 goto finish;
628         }
629
630         if (arg_directory)
631                 r = sd_journal_open_directory(&j, arg_directory, 0);
632         else
633                 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
634
635         if (r < 0) {
636                 log_error("Failed to open journal: %s", strerror(-r));
637                 goto finish;
638         }
639
640         if (arg_action == ACTION_VERIFY) {
641                 r = verify(j);
642                 goto finish;
643         }
644
645         if (arg_action == ACTION_PRINT_HEADER) {
646                 journal_print_header(j);
647                 r = 0;
648                 goto finish;
649         }
650
651 #ifdef HAVE_ACL
652         if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
653                 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
654 #endif
655
656         r = add_this_boot(j);
657         if (r < 0)
658                 goto finish;
659
660         r = add_matches(j, argv + optind);
661         if (r < 0)
662                 goto finish;
663
664         r = add_priorities(j);
665         if (r < 0)
666                 goto finish;
667
668         if (!arg_quiet) {
669                 usec_t start, end;
670                 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
671
672                 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
673                 if (r < 0) {
674                         log_error("Failed to get cutoff: %s", strerror(-r));
675                         goto finish;
676                 }
677
678                 if (r > 0) {
679                         if (arg_follow)
680                                 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
681                         else
682                                 printf("Logs begin at %s, end at %s.\n",
683                                        format_timestamp(start_buf, sizeof(start_buf), start),
684                                        format_timestamp(end_buf, sizeof(end_buf), end));
685                 }
686         }
687
688         if (arg_lines >= 0) {
689                 r = sd_journal_seek_tail(j);
690                 if (r < 0) {
691                         log_error("Failed to seek to tail: %s", strerror(-r));
692                         goto finish;
693                 }
694
695                 r = sd_journal_previous_skip(j, arg_lines);
696         } else {
697                 r = sd_journal_seek_head(j);
698                 if (r < 0) {
699                         log_error("Failed to seek to head: %s", strerror(-r));
700                         goto finish;
701                 }
702
703                 r = sd_journal_next(j);
704         }
705
706         if (r < 0) {
707                 log_error("Failed to iterate through journal: %s", strerror(-r));
708                 goto finish;
709         }
710
711         on_tty();
712         have_pager = !arg_no_pager && !arg_follow && pager_open();
713
714         if (arg_output == OUTPUT_JSON) {
715                 fputc('[', stdout);
716                 fflush(stdout);
717         }
718
719         for (;;) {
720                 for (;;) {
721                         sd_id128_t boot_id;
722                         int flags =
723                                 arg_show_all * OUTPUT_SHOW_ALL |
724                                 have_pager * OUTPUT_FULL_WIDTH |
725                                 on_tty() * OUTPUT_COLOR;
726
727                         if (need_seek) {
728                                 r = sd_journal_next(j);
729                                 if (r < 0) {
730                                         log_error("Failed to iterate through journal: %s", strerror(-r));
731                                         goto finish;
732                                 }
733                         }
734
735                         if (r == 0)
736                                 break;
737
738                         r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
739                         if (r >= 0) {
740                                 if (previous_boot_id_valid &&
741                                     !sd_id128_equal(boot_id, previous_boot_id))
742                                         printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
743
744                                 previous_boot_id = boot_id;
745                                 previous_boot_id_valid = true;
746                         }
747
748                         line ++;
749
750                         r = output_journal(j, arg_output, line, 0, flags);
751                         if (r < 0)
752                                 goto finish;
753
754                         need_seek = true;
755                 }
756
757                 if (!arg_follow)
758                         break;
759
760                 r = sd_journal_wait(j, (uint64_t) -1);
761                 if (r < 0) {
762                         log_error("Couldn't wait for log event: %s", strerror(-r));
763                         goto finish;
764                 }
765         }
766
767         if (arg_output == OUTPUT_JSON)
768                 fputs("\n]\n", stdout);
769
770 finish:
771         if (j)
772                 sd_journal_close(j);
773
774         pager_close();
775
776         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
777 }