LATER... ei_kerberos_kdc_session_key ...
[wireshark-sm.git] / extcap / sdjournal.c
blobe2f8ba81a8b4ed2a002308c954af64e046d20d1e
1 /* sdjournal.c
2 * sdjournal is an extcap tool used to dump systemd journal entries.
4 * Adapted from sshdump.
5 * Copyright 2018, Gerald Combs and Dario Lombardo
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * SPDX-License-Identifier: GPL-2.0-or-later
15 * To do:
16 * - Add an option for sd_journal_open flags, e.g. SD_JOURNAL_LOCAL_ONLY.
17 * - Add journalctl options - --boot, --machine, --directory, etc.
20 #include "config.h"
21 #define WS_LOG_DOMAIN "sdjournal"
23 #include <extcap/extcap-base.h>
24 #include <wsutil/interface.h>
25 #include <wsutil/file_util.h>
26 #include <wsutil/filesystem.h>
27 #include <wsutil/privileges.h>
28 #include <wsutil/wslog.h>
29 #include <writecap/pcapio.h>
30 #include <wiretap/wtap.h>
32 #include <systemd/sd-journal.h>
33 #include <systemd/sd-id128.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <fcntl.h>
39 #define SDJOURNAL_VERSION_MAJOR "1"
40 #define SDJOURNAL_VERSION_MINOR "0"
41 #define SDJOURNAL_VERSION_RELEASE "0"
43 #define SDJOURNAL_EXTCAP_INTERFACE "sdjournal"
44 #define BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT 0x00000009
46 enum {
47 EXTCAP_BASE_OPTIONS_ENUM,
48 OPT_HELP,
49 OPT_VERSION,
50 OPT_START_FROM
53 static const struct ws_option longopts[] = {
54 EXTCAP_BASE_OPTIONS,
55 { "help", ws_no_argument, NULL, OPT_HELP},
56 { "version", ws_no_argument, NULL, OPT_VERSION},
57 { "start-from", ws_required_argument, NULL, OPT_START_FROM},
58 { 0, 0, 0, 0}
61 #define FLD_BOOT_ID "_BOOT_ID="
62 #define FLD_BOOT_ID_LEN (8 + 1 + 33 + 1)
64 // The Journal Export Format specification doesn't place limits on entry
65 // lengths or lines per entry. We do.
66 #define ENTRY_BUF_LENGTH WTAP_MAX_PACKET_SIZE_STANDARD
67 #define MAX_EXPORT_ENTRY_LENGTH (ENTRY_BUF_LENGTH - 4 - 4 - 4) // Block type - total length - total length
69 static int sdj_dump_entries(sd_journal *jnl, FILE* fp)
71 int ret = EXIT_SUCCESS;
72 uint8_t *entry_buff = g_new(uint8_t, ENTRY_BUF_LENGTH);
73 int jr = 0;
76 * Read journal entries and write them as packets. Output must
77 * match `journalctl --output=export`.
79 while (jr == 0) {
80 char *cursor;
81 uint64_t pkt_rt_ts, mono_ts;
82 sd_id128_t boot_id;
83 char boot_id_str[FLD_BOOT_ID_LEN] = FLD_BOOT_ID;
84 uint32_t block_type = BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT;
85 uint32_t data_end = 8; // Block type + total length
86 const void *fld_data;
87 size_t fld_len;
88 uint64_t bytes_written = 0;
89 int err;
91 memcpy(entry_buff, &block_type, 4);
93 jr = sd_journal_next(jnl);
94 ws_debug("sd_journal_next: %d", jr);
95 if (jr < 0) {
96 ws_warning("Error fetching journal entry: %s", g_strerror(jr));
97 goto end;
98 } else if (jr == 0) {
99 sd_journal_wait(jnl, (uint64_t) -1);
100 continue;
103 jr = sd_journal_get_cursor(jnl, &cursor);
104 if (jr < 0) {
105 ws_warning("Error fetching cursor: %s", g_strerror(jr));
106 goto end;
108 data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__CURSOR=%s\n", cursor);
109 free(cursor);
111 jr = sd_journal_get_realtime_usec(jnl, &pkt_rt_ts);
112 if (jr < 0) {
113 ws_warning("Error fetching realtime timestamp: %s", g_strerror(jr));
114 goto end;
116 data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__REALTIME_TIMESTAMP=%" PRIu64 "\n", pkt_rt_ts);
118 jr = sd_journal_get_monotonic_usec(jnl, &mono_ts, &boot_id);
119 if (jr < 0) {
120 ws_warning("Error fetching monotonic timestamp: %s", g_strerror(jr));
121 goto end;
123 sd_id128_to_string(boot_id, boot_id_str + strlen(FLD_BOOT_ID));
124 data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__MONOTONIC_TIMESTAMP=%" PRIu64 "\n%s\n", mono_ts, boot_id_str);
125 ws_debug("Entry header is %u bytes", data_end);
127 SD_JOURNAL_FOREACH_DATA(jnl, fld_data, fld_len) {
128 uint8_t *eq_ptr = (uint8_t *) memchr(fld_data, '=', fld_len);
129 if (!eq_ptr) {
130 ws_warning("Invalid field.");
131 goto end;
133 if (g_utf8_validate((const char *) fld_data, (ssize_t) fld_len, NULL)) {
134 // Allow for two trailing newlines, one here and one
135 // at the end of the buffer.
136 if (fld_len > MAX_EXPORT_ENTRY_LENGTH-data_end-2) {
137 ws_debug("Breaking on UTF-8 field: %u + %zd", data_end, fld_len);
138 break;
140 memcpy(entry_buff+data_end, fld_data, fld_len);
141 data_end += (uint32_t) fld_len;
142 entry_buff[data_end] = '\n';
143 data_end++;
144 } else {
145 // \n + 64-bit size + \n + trailing \n = 11
146 if (fld_len > MAX_EXPORT_ENTRY_LENGTH-data_end-11) {
147 ws_debug("Breaking on binary field: %u + %zd", data_end, fld_len);
148 break;
150 ptrdiff_t name_len = eq_ptr - (const uint8_t *) fld_data;
151 uint64_t le_data_len;
152 le_data_len = htole64(fld_len - name_len - 1);
153 memcpy(entry_buff+data_end, fld_data, name_len);
154 data_end+= name_len;
155 entry_buff[data_end] = '\n';
156 data_end++;
157 memcpy(entry_buff+data_end, &le_data_len, 8);
158 data_end += 8;
159 memcpy(entry_buff+data_end, (const uint8_t *) fld_data + name_len + 1, fld_len - name_len);
160 data_end += fld_len - name_len;
164 if (data_end % 4) {
165 size_t pad_len = 4 - (data_end % 4);
166 memset(entry_buff+data_end, '\0', pad_len);
167 data_end += pad_len;
170 uint32_t total_len = data_end + 4;
171 memcpy (entry_buff+4, &total_len, 4);
172 memcpy (entry_buff+data_end, &total_len, 4);
174 ws_debug("Attempting to write %u bytes", total_len);
175 if (!pcapng_write_block(fp, entry_buff, total_len, &bytes_written, &err)) {
176 ws_warning("Can't write event: %s", strerror(err));
177 ret = EXIT_FAILURE;
178 break;
181 fflush(fp);
184 end:
185 g_free(entry_buff);
186 return ret;
189 static int sdj_start_export(const int start_from_entries, const bool start_from_end, const char* fifo)
191 FILE* fp = stdout;
192 uint64_t bytes_written = 0;
193 int err;
194 sd_journal *jnl = NULL;
195 sd_id128_t boot_id;
196 char boot_id_str[FLD_BOOT_ID_LEN] = FLD_BOOT_ID;
197 int ret = EXIT_FAILURE;
198 char* err_info = NULL;
199 char *appname;
200 bool success;
201 int jr = 0;
203 if (g_strcmp0(fifo, "-")) {
204 /* Open or create the output file */
205 fp = fopen(fifo, "wb");
206 if (fp == NULL) {
207 ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno));
208 return EXIT_FAILURE;
213 appname = ws_strdup_printf(SDJOURNAL_EXTCAP_INTERFACE " (Wireshark) %s.%s.%s",
214 SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR, SDJOURNAL_VERSION_RELEASE);
215 success = pcapng_write_section_header_block(fp,
216 NULL, /* Comment */
217 NULL, /* HW */
218 NULL, /* OS */
219 appname,
220 -1, /* section_length */
221 &bytes_written,
222 &err);
223 g_free(appname);
225 if (!success) {
226 ws_warning("Can't write pcapng file header");
227 goto cleanup;
230 jr = sd_journal_open(&jnl, 0);
231 if (jr < 0) {
232 ws_warning("Error opening journal: %s", g_strerror(jr));
233 goto cleanup;
236 jr = sd_id128_get_boot(&boot_id);
237 if (jr < 0) {
238 ws_warning("Error fetching system boot ID: %s", g_strerror(jr));
239 goto cleanup;
242 sd_id128_to_string(boot_id, boot_id_str + strlen(FLD_BOOT_ID));
243 jr = sd_journal_add_match(jnl, boot_id_str, strlen(boot_id_str));
244 if (jr < 0) {
245 ws_warning("Error adding match: %s", g_strerror(jr));
246 goto cleanup;
249 // According to the documentation, fields *might be* truncated to 64K.
250 // Let's assume that 2048 is a good balance between fetching entire fields
251 // and being able to fit as many fields as possible into a packet.
252 sd_journal_set_data_threshold(jnl, 2048);
254 if (start_from_end) {
255 ws_debug("Attempting to seek %d entries from the end", start_from_entries);
256 jr = sd_journal_seek_tail(jnl);
257 if (jr < 0) {
258 ws_warning("Error starting at end: %s", g_strerror(jr));
259 goto cleanup;
261 jr = sd_journal_previous_skip(jnl, (uint64_t) start_from_entries + 1);
262 if (jr < 0) {
263 ws_warning("Error skipping backward: %s", g_strerror(jr));
264 goto cleanup;
266 } else {
267 ws_debug("Attempting to seek %d entries from the beginning", start_from_entries);
268 jr = sd_journal_seek_head(jnl);
269 if (jr < 0) {
270 ws_warning("Error starting at beginning: %s", g_strerror(jr));
271 goto cleanup;
273 if (start_from_entries > 0) {
274 jr = sd_journal_next_skip(jnl, (uint64_t) start_from_entries);
275 if (jr < 0) {
276 ws_warning("Error skipping forward: %s", g_strerror(jr));
277 goto cleanup;
282 /* read from channel and write into fp */
283 if (sdj_dump_entries(jnl, fp) != 0) {
284 ws_warning("Error dumping entries");
285 goto cleanup;
288 ret = EXIT_SUCCESS;
290 cleanup:
291 if (jnl) {
292 sd_journal_close(jnl);
295 if (err_info) {
296 ws_warning("%s", err_info);
299 g_free(err_info);
301 /* clean up and exit */
302 if (g_strcmp0(fifo, "-")) {
303 fclose(fp);
305 return ret;
308 static int list_config(char *interface)
310 unsigned inc = 0;
312 if (!interface) {
313 ws_warning("ERROR: No interface specified.");
314 return EXIT_FAILURE;
317 if (g_strcmp0(interface, SDJOURNAL_EXTCAP_INTERFACE)) {
318 ws_warning("ERROR: interface must be %s", SDJOURNAL_EXTCAP_INTERFACE);
319 return EXIT_FAILURE;
322 printf("arg {number=%u}{call=--start-from}{display=Starting position}"
323 "{type=string}{tooltip=The journal starting position. Values "
324 "with a leading \"+\" start from the beginning, similar to the "
325 "\"tail\" command}{required=false}{group=Journal}\n", inc++);
327 extcap_config_debug(&inc);
329 return EXIT_SUCCESS;
332 int main(int argc, char **argv)
334 char* configuration_init_error;
335 int result;
336 int option_idx = 0;
337 int start_from_entries = 10;
338 bool start_from_end = true;
339 int ret = EXIT_FAILURE;
340 extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
341 char* help_url;
342 char* help_header = NULL;
344 /* Initialize log handler early so we can have proper logging during startup. */
345 extcap_log_init("sdjournal");
348 * Get credential information for later use.
350 init_process_policies();
353 * Attempt to get the pathname of the directory containing the
354 * executable file.
356 configuration_init_error = configuration_init(argv[0], NULL);
357 if (configuration_init_error != NULL) {
358 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
359 configuration_init_error);
360 g_free(configuration_init_error);
363 help_url = data_file_url("sdjournal.html");
364 extcap_base_set_util_info(extcap_conf, argv[0], SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR,
365 SDJOURNAL_VERSION_RELEASE, help_url);
366 g_free(help_url);
367 // We don't have an SDJOURNAL DLT, so use USER0 (147).
368 extcap_base_register_interface(extcap_conf, SDJOURNAL_EXTCAP_INTERFACE, "systemd Journal Export", 147, "USER0");
370 help_header = ws_strdup_printf(
371 " %s --extcap-interfaces\n"
372 " %s --extcap-interface=%s --extcap-dlts\n"
373 " %s --extcap-interface=%s --extcap-config\n"
374 " %s --extcap-interface=%s --start-from=+0 --fifo=FILENAME --capture\n",
375 argv[0],
376 argv[0], SDJOURNAL_EXTCAP_INTERFACE,
377 argv[0], SDJOURNAL_EXTCAP_INTERFACE,
378 argv[0], SDJOURNAL_EXTCAP_INTERFACE);
379 extcap_help_add_header(extcap_conf, help_header);
380 g_free(help_header);
381 extcap_help_add_option(extcap_conf, "--help", "print this help");
382 extcap_help_add_option(extcap_conf, "--version", "print the version");
383 extcap_help_add_option(extcap_conf, "--start-from <entry count>", "starting position");
385 ws_opterr = 0;
386 ws_optind = 0;
388 if (argc == 1) {
389 extcap_help_print(extcap_conf);
390 goto end;
393 while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
395 switch (result) {
397 case OPT_HELP:
398 extcap_help_print(extcap_conf);
399 ret = EXIT_SUCCESS;
400 goto end;
402 case OPT_VERSION:
403 extcap_version_print(extcap_conf);
404 ret = EXIT_SUCCESS;
405 goto end;
407 case OPT_START_FROM:
408 start_from_entries = (int) strtol(ws_optarg, NULL, 10);
409 if (errno == EINVAL) {
410 ws_warning("Invalid entry count: %s", ws_optarg);
411 goto end;
413 if (strlen(ws_optarg) > 0 && ws_optarg[0] == '+') {
414 start_from_end = false;
416 if (start_from_entries < 0) {
417 start_from_end = true;
418 start_from_entries *= -1;
420 ws_debug("start %d from %s", start_from_entries, start_from_end ? "end" : "beginning");
421 break;
423 case ':':
424 /* missing option argument */
425 ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
426 break;
428 default:
429 if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
430 ws_warning("Invalid option: %s", argv[ws_optind - 1]);
431 goto end;
436 extcap_cmdline_debug(argv, argc);
438 if (extcap_base_handle_interface(extcap_conf)) {
439 ret = EXIT_SUCCESS;
440 goto end;
443 if (extcap_conf->show_config) {
444 ret = list_config(extcap_conf->interface);
445 goto end;
448 if (extcap_conf->capture) {
449 ret = sdj_start_export(start_from_entries, start_from_end, extcap_conf->fifo);
450 } else {
451 ws_debug("You should not come here... maybe some parameter missing?");
452 ret = EXIT_FAILURE;
455 end:
456 /* clean up stuff */
457 extcap_base_cleanup(&extcap_conf);
458 return ret;
462 * Editor modelines - https://www.wireshark.org/tools/modelines.html
464 * Local variables:
465 * c-basic-offset: 8
466 * tab-width: 8
467 * indent-tabs-mode: t
468 * End:
470 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
471 * :indentSize=8:tabSize=8:noTabs=false: