epan/dissectors/pidl/ C99 drsuapi
[wireshark-sm.git] / extcap / sdjournal.c
blob48df43377609ce6c71b76e79c90297c14e7ed9bd
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 /* Set the program name. */
345 g_set_prgname("sdjournal");
347 /* Initialize log handler early so we can have proper logging during startup. */
348 extcap_log_init();
351 * Get credential information for later use.
353 init_process_policies();
356 * Attempt to get the pathname of the directory containing the
357 * executable file.
359 configuration_init_error = configuration_init(argv[0]);
360 if (configuration_init_error != NULL) {
361 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
362 configuration_init_error);
363 g_free(configuration_init_error);
366 help_url = data_file_url("sdjournal.html");
367 extcap_base_set_util_info(extcap_conf, argv[0], SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR,
368 SDJOURNAL_VERSION_RELEASE, help_url);
369 g_free(help_url);
370 // We don't have an SDJOURNAL DLT, so use USER0 (147).
371 extcap_base_register_interface(extcap_conf, SDJOURNAL_EXTCAP_INTERFACE, "systemd Journal Export", 147, "USER0");
373 help_header = ws_strdup_printf(
374 " %s --extcap-interfaces\n"
375 " %s --extcap-interface=%s --extcap-dlts\n"
376 " %s --extcap-interface=%s --extcap-config\n"
377 " %s --extcap-interface=%s --start-from=+0 --fifo=FILENAME --capture\n",
378 argv[0],
379 argv[0], SDJOURNAL_EXTCAP_INTERFACE,
380 argv[0], SDJOURNAL_EXTCAP_INTERFACE,
381 argv[0], SDJOURNAL_EXTCAP_INTERFACE);
382 extcap_help_add_header(extcap_conf, help_header);
383 g_free(help_header);
384 extcap_help_add_option(extcap_conf, "--help", "print this help");
385 extcap_help_add_option(extcap_conf, "--version", "print the version");
386 extcap_help_add_option(extcap_conf, "--start-from <entry count>", "starting position");
388 ws_opterr = 0;
389 ws_optind = 0;
391 if (argc == 1) {
392 extcap_help_print(extcap_conf);
393 goto end;
396 while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
398 switch (result) {
400 case OPT_HELP:
401 extcap_help_print(extcap_conf);
402 ret = EXIT_SUCCESS;
403 goto end;
405 case OPT_VERSION:
406 extcap_version_print(extcap_conf);
407 ret = EXIT_SUCCESS;
408 goto end;
410 case OPT_START_FROM:
411 start_from_entries = (int) strtol(ws_optarg, NULL, 10);
412 if (errno == EINVAL) {
413 ws_warning("Invalid entry count: %s", ws_optarg);
414 goto end;
416 if (strlen(ws_optarg) > 0 && ws_optarg[0] == '+') {
417 start_from_end = false;
419 if (start_from_entries < 0) {
420 start_from_end = true;
421 start_from_entries *= -1;
423 ws_debug("start %d from %s", start_from_entries, start_from_end ? "end" : "beginning");
424 break;
426 case ':':
427 /* missing option argument */
428 ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
429 break;
431 default:
432 if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
433 ws_warning("Invalid option: %s", argv[ws_optind - 1]);
434 goto end;
439 extcap_cmdline_debug(argv, argc);
441 if (extcap_base_handle_interface(extcap_conf)) {
442 ret = EXIT_SUCCESS;
443 goto end;
446 if (extcap_conf->show_config) {
447 ret = list_config(extcap_conf->interface);
448 goto end;
451 if (extcap_conf->capture) {
452 ret = sdj_start_export(start_from_entries, start_from_end, extcap_conf->fifo);
453 } else {
454 ws_debug("You should not come here... maybe some parameter missing?");
455 ret = EXIT_FAILURE;
458 end:
459 /* clean up stuff */
460 extcap_base_cleanup(&extcap_conf);
461 return ret;
465 * Editor modelines - https://www.wireshark.org/tools/modelines.html
467 * Local variables:
468 * c-basic-offset: 8
469 * tab-width: 8
470 * indent-tabs-mode: t
471 * End:
473 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
474 * :indentSize=8:tabSize=8:noTabs=false: