/src/wireshark/wiretap/systemd_journal.c
Line | Count | Source |
1 | | /* systemd_journal.c |
2 | | * |
3 | | * Wiretap Library |
4 | | * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> |
5 | | * |
6 | | * SPDX-License-Identifier: GPL-2.0-or-later |
7 | | */ |
8 | | |
9 | | #include "config.h" |
10 | | #include "systemd_journal.h" |
11 | | |
12 | | #include <errno.h> |
13 | | #include <string.h> |
14 | | #include <stdlib.h> |
15 | | |
16 | | #include <wsutil/pint.h> |
17 | | |
18 | | #include "wtap_module.h" |
19 | | #include "pcapng_module.h" |
20 | | #include "file_wrappers.h" |
21 | | |
22 | | // To do: |
23 | | // - Request a pcap encapsulation type. |
24 | | // - Should we add separate types for binary, plain, and JSON or add a metadata header? |
25 | | |
26 | | // Systemd journals are stored in the following formats: |
27 | | // Journal File Format (native binary): https://www.freedesktop.org/wiki/Software/systemd/journal-files/ |
28 | | // Journal Export Format: https://www.freedesktop.org/wiki/Software/systemd/export/ |
29 | | // Journal JSON format: https://www.freedesktop.org/wiki/Software/systemd/json/ |
30 | | // This reads Journal Export Format files but could be extended to support |
31 | | // the binary and JSON formats. |
32 | | |
33 | | // Example data: |
34 | | // __CURSOR=s=1d56bab64d414960b9907ab0cc7f7c62;i=2;b=1497926e8b4b4d3ca6a5805e157fa73c;m=5d0ae5;t=56f2f5b66ce6f;x=20cb01e28bb496a8 |
35 | | // __REALTIME_TIMESTAMP=1529624071163503 |
36 | | // __MONOTONIC_TIMESTAMP=6097637 |
37 | | // _BOOT_ID=1497926e8b4b4d3ca6a5805e157fa73c |
38 | | // PRIORITY=6 |
39 | | // _MACHINE_ID=62c342838a6e436dacea041aa4b5064b |
40 | | // _HOSTNAME=example.wireshark.org |
41 | | // _SOURCE_MONOTONIC_TIMESTAMP=0 |
42 | | // _TRANSPORT=kernel |
43 | | // SYSLOG_FACILITY=0 |
44 | | // SYSLOG_IDENTIFIER=kernel |
45 | | // MESSAGE=Initializing cgroup subsys cpuset |
46 | | |
47 | | static bool systemd_journal_read(wtap *wth, wtap_rec *rec, int *err, |
48 | | char **err_info, int64_t *data_offset); |
49 | | static bool systemd_journal_seek_read(wtap *wth, int64_t seek_off, |
50 | | wtap_rec *rec, int *err, char **err_info); |
51 | | static bool systemd_journal_read_export_entry(FILE_T fh, wtap_rec *rec, |
52 | | int *err, char **err_info); |
53 | | |
54 | | // The Journal Export Format specification doesn't place limits on entry |
55 | | // lengths or lines per entry. We do. |
56 | 0 | #define MAX_EXPORT_ENTRY_LENGTH WTAP_MAX_PACKET_SIZE_STANDARD |
57 | 0 | #define MAX_EXPORT_ENTRY_LINES 100 |
58 | | |
59 | | // Strictly speaking, we only need __REALTIME_TIMESTAMP= since we use |
60 | | // that to set the packet timestamp. According to |
61 | | // https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html |
62 | | // __CURSOR= and __MONOTONIC_TIMESTAMP= should be present as well, so |
63 | | // check for them order to improve our heuristics. |
64 | 0 | #define FLD__CURSOR "__CURSOR=" |
65 | 0 | #define FLD__REALTIME_TIMESTAMP "__REALTIME_TIMESTAMP=" |
66 | 0 | #define FLD__MONOTONIC_TIMESTAMP "__MONOTONIC_TIMESTAMP=" |
67 | | |
68 | | static int systemd_journal_file_type_subtype = -1; |
69 | | |
70 | | void register_systemd_journal(void); |
71 | | |
72 | | wtap_open_return_val systemd_journal_open(wtap *wth, int *err _U_, char **err_info _U_) |
73 | 0 | { |
74 | 0 | char *entry_buff = (char*) g_malloc(MAX_EXPORT_ENTRY_LENGTH); |
75 | 0 | char *entry_line = NULL; |
76 | 0 | bool got_cursor = false; |
77 | 0 | bool got_rt_ts = false; |
78 | 0 | bool got_mt_ts = false; |
79 | 0 | int line_num; |
80 | |
|
81 | 0 | errno = 0; |
82 | 0 | for (line_num = 0; line_num < MAX_EXPORT_ENTRY_LINES; line_num++) { |
83 | 0 | entry_line = file_gets(entry_buff, MAX_EXPORT_ENTRY_LENGTH, wth->fh); |
84 | 0 | if (!entry_line) { |
85 | 0 | break; |
86 | 0 | } |
87 | 0 | if (entry_line[0] == '\n') { |
88 | 0 | break; |
89 | 0 | } else if (strncmp(entry_line, FLD__CURSOR, strlen(FLD__CURSOR)) == 0) { |
90 | 0 | got_cursor = true; |
91 | 0 | } else if (strncmp(entry_line, FLD__REALTIME_TIMESTAMP, strlen(FLD__REALTIME_TIMESTAMP)) == 0) { |
92 | 0 | got_rt_ts = true; |
93 | 0 | } else if (strncmp(entry_line, FLD__MONOTONIC_TIMESTAMP, strlen(FLD__MONOTONIC_TIMESTAMP)) == 0) { |
94 | 0 | got_mt_ts = true; |
95 | 0 | } |
96 | 0 | } |
97 | 0 | g_free(entry_buff); |
98 | |
|
99 | 0 | if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) { |
100 | 0 | return WTAP_OPEN_ERROR; |
101 | 0 | } |
102 | | |
103 | 0 | if (!got_cursor || !got_rt_ts || !got_mt_ts) { |
104 | 0 | return WTAP_OPEN_NOT_MINE; |
105 | 0 | } |
106 | | |
107 | 0 | wth->file_type_subtype = systemd_journal_file_type_subtype; |
108 | 0 | wth->subtype_read = systemd_journal_read; |
109 | 0 | wth->subtype_seek_read = systemd_journal_seek_read; |
110 | 0 | wth->file_encap = WTAP_ENCAP_SYSTEMD_JOURNAL; |
111 | 0 | wth->file_tsprec = WTAP_TSPREC_USEC; |
112 | | |
113 | | /* |
114 | | * Add an IDB; we don't know how many interfaces were |
115 | | * involved, so we just say one interface, about which |
116 | | * we only know the link-layer type, snapshot length, |
117 | | * and time stamp resolution. |
118 | | */ |
119 | 0 | wtap_add_generated_idb(wth); |
120 | |
|
121 | 0 | return WTAP_OPEN_MINE; |
122 | 0 | } |
123 | | |
124 | | /* Read the next packet */ |
125 | | static bool systemd_journal_read(wtap *wth, wtap_rec *rec, int *err, |
126 | | char **err_info, int64_t *data_offset) |
127 | 0 | { |
128 | 0 | *data_offset = file_tell(wth->fh); |
129 | | |
130 | | /* Read record. */ |
131 | 0 | if (!systemd_journal_read_export_entry(wth->fh, rec, err, err_info)) { |
132 | | /* Read error or EOF */ |
133 | 0 | return false; |
134 | 0 | } |
135 | | |
136 | 0 | return true; |
137 | 0 | } |
138 | | |
139 | | static bool systemd_journal_seek_read(wtap *wth, int64_t seek_off, |
140 | | wtap_rec *rec, int *err, char **err_info) |
141 | 0 | { |
142 | 0 | if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) |
143 | 0 | return false; |
144 | | |
145 | | /* Read record. */ |
146 | 0 | if (!systemd_journal_read_export_entry(wth->random_fh, rec, err, err_info)) { |
147 | | /* Read error or EOF */ |
148 | 0 | if (*err == 0) { |
149 | | /* EOF means "short read" in random-access mode */ |
150 | 0 | *err = WTAP_ERR_SHORT_READ; |
151 | 0 | } |
152 | 0 | return false; |
153 | 0 | } |
154 | 0 | return true; |
155 | 0 | } |
156 | | |
157 | | static bool systemd_journal_read_export_entry(FILE_T fh, wtap_rec *rec, |
158 | | int *err, char **err_info) |
159 | 0 | { |
160 | 0 | char *entry_line = NULL; |
161 | 0 | bool got_cursor = false; |
162 | 0 | bool got_rt_ts = false; |
163 | 0 | bool got_mt_ts = false; |
164 | 0 | bool got_double_newline = false; |
165 | 0 | int line_num; |
166 | 0 | size_t rt_ts_len = strlen(FLD__REALTIME_TIMESTAMP); |
167 | |
|
168 | 0 | ws_buffer_assure_space(&rec->data, MAX_EXPORT_ENTRY_LENGTH); |
169 | |
|
170 | 0 | for (line_num = 0; line_num < MAX_EXPORT_ENTRY_LINES; line_num++) { |
171 | | // XXX - Should there be a file_gets_buffer() ? |
172 | 0 | entry_line = file_gets((char*)ws_buffer_end_ptr(&rec->data), MAX_EXPORT_ENTRY_LENGTH - (int) ws_buffer_length(&rec->data), fh); |
173 | 0 | if (!entry_line) { |
174 | 0 | break; |
175 | 0 | } |
176 | 0 | ws_buffer_increase_length(&rec->data, strlen(entry_line)); |
177 | 0 | if (entry_line[0] == '\n') { |
178 | 0 | got_double_newline = true; |
179 | 0 | break; |
180 | 0 | } else if (strncmp(entry_line, FLD__CURSOR, strlen(FLD__CURSOR)) == 0) { |
181 | 0 | got_cursor = true; |
182 | 0 | } else if (strncmp(entry_line, FLD__REALTIME_TIMESTAMP, rt_ts_len) == 0) { |
183 | 0 | errno = 0; |
184 | 0 | unsigned long rt_ts = strtoul(entry_line+rt_ts_len, NULL, 10); |
185 | 0 | if (!errno) { |
186 | 0 | rec->ts.secs = (time_t) rt_ts / 1000000; |
187 | 0 | rec->ts.nsecs = (rt_ts % 1000000) * 1000; |
188 | 0 | rec->tsprec = WTAP_TSPREC_USEC; |
189 | 0 | got_rt_ts = true; |
190 | 0 | } |
191 | 0 | } else if (strncmp(entry_line, FLD__MONOTONIC_TIMESTAMP, strlen(FLD__MONOTONIC_TIMESTAMP)) == 0) { |
192 | 0 | got_mt_ts = true; |
193 | 0 | } else if (!strstr(entry_line, "=")) { |
194 | | // Start of binary data. |
195 | 0 | if (ws_buffer_length(&rec->data) >= MAX_EXPORT_ENTRY_LENGTH - 8) { |
196 | 0 | *err = WTAP_ERR_BAD_FILE; |
197 | 0 | *err_info = ws_strdup_printf("systemd: binary length too long"); |
198 | 0 | return false; |
199 | 0 | } |
200 | 0 | uint64_t data_len; |
201 | 0 | if (!wtap_read_bytes_buffer(fh, &rec->data, 8, err, err_info)) { |
202 | 0 | return false; |
203 | 0 | } |
204 | 0 | data_len = pletohu64(&ws_buffer_end_ptr(&rec->data)[-8]); |
205 | 0 | if (data_len < 1 || data_len - 1 >= MAX_EXPORT_ENTRY_LENGTH - ws_buffer_length(&rec->data)) { |
206 | 0 | *err = WTAP_ERR_BAD_FILE; |
207 | 0 | *err_info = ws_strdup_printf("systemd: binary data too long"); |
208 | 0 | return false; |
209 | 0 | } |
210 | | // Data + trailing \n |
211 | 0 | if (!wtap_read_bytes_buffer(fh, &rec->data, (unsigned) data_len + 1, err, err_info)) { |
212 | 0 | return false; |
213 | 0 | } |
214 | 0 | } |
215 | 0 | if (MAX_EXPORT_ENTRY_LENGTH < ws_buffer_length(&rec->data) + 2) { // \n\0 |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | 0 | if (!got_cursor || !got_rt_ts || !got_mt_ts) { |
221 | 0 | return false; |
222 | 0 | } |
223 | | |
224 | 0 | if (!got_double_newline && !file_eof(fh)) { |
225 | 0 | return false; |
226 | 0 | } |
227 | | |
228 | 0 | wtap_setup_systemd_journal_export_rec(rec); |
229 | 0 | rec->block = wtap_block_create(WTAP_BLOCK_SYSTEMD_JOURNAL_EXPORT); |
230 | 0 | rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN; |
231 | 0 | rec->rec_header.systemd_journal_export_header.record_len = (uint32_t) ws_buffer_length(&rec->data); |
232 | |
|
233 | 0 | return true; |
234 | 0 | } |
235 | | |
236 | | static const struct supported_block_type systemd_journal_blocks_supported[] = { |
237 | | /* |
238 | | * We support systemd journal blocks, with no comments or other options. |
239 | | */ |
240 | | { WTAP_BLOCK_SYSTEMD_JOURNAL_EXPORT, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } |
241 | | }; |
242 | | |
243 | | static const struct file_type_subtype_info systemd_journal_info = { |
244 | | "systemd journal export", "systemd_journal", NULL, NULL, |
245 | | false, BLOCKS_SUPPORTED(systemd_journal_blocks_supported), |
246 | | NULL, NULL, NULL |
247 | | }; |
248 | | |
249 | | void register_systemd_journal(void) |
250 | 14 | { |
251 | 14 | systemd_journal_file_type_subtype = wtap_register_file_type_subtype(&systemd_journal_info); |
252 | | |
253 | | /* |
254 | | * Register name for backwards compatibility with the |
255 | | * wtap_filetypes table in Lua. |
256 | | */ |
257 | 14 | wtap_register_backwards_compatibility_lua_name("SYSTEMD_JOURNAL", |
258 | 14 | systemd_journal_file_type_subtype); |
259 | 14 | } |
260 | | |
261 | | /* |
262 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
263 | | * |
264 | | * Local variables: |
265 | | * c-basic-offset: 4 |
266 | | * tab-width: 8 |
267 | | * indent-tabs-mode: nil |
268 | | * End: |
269 | | * |
270 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
271 | | * :indentSize=4:tabSize=8:noTabs=true: |
272 | | */ |