/src/wireshark/epan/dissectors/packet-rsync.c
Line | Count | Source |
1 | | /* packet-rsync.c |
2 | | * Routines for rsync dissection |
3 | | * [ very rough, but mininally functional ] |
4 | | * Copyright 2003, Brad Hards <bradh@frogmouth.net> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | #include "config.h" |
13 | | |
14 | | |
15 | | #include <epan/packet.h> |
16 | | #include <epan/conversation.h> |
17 | | #include <epan/prefs.h> |
18 | | #include <epan/proto_data.h> |
19 | | |
20 | | void proto_register_rsync(void); |
21 | | void proto_reg_handoff_rsync(void); |
22 | | |
23 | | #define RSYNCD_MAGIC_HEADER "@RSYNCD:" |
24 | 4 | #define RSYNCD_MAGIC_HEADER_LEN 8 |
25 | | |
26 | 0 | #define RSYNCD_AUTHREQD "@RSYNCD: AUTHREQD " |
27 | 0 | #define RSYNCD_AUTHREQD_LEN 18 |
28 | | |
29 | 0 | #define RSYNCD_EXIT "@RSYNCD: EXIT" |
30 | 0 | #define RSYNCD_EXIT_LEN 13 |
31 | | |
32 | 0 | #define RSYNC_MODULE_LIST_QUERY "\n" |
33 | 2 | #define RSYNC_MODULE_LIST_QUERY_LEN 1 |
34 | | |
35 | | /* what states make sense here ? */ |
36 | | typedef enum _rsync_state { |
37 | | RSYNC_INIT = 0, |
38 | | RSYNC_SERV_INIT = 1, |
39 | | RSYNC_CLIENT_QUERY = 2, |
40 | | RSYNC_MODULE_LIST = 4, |
41 | | RSYNC_COMMAND = 5, |
42 | | RSYNC_SERV_MOTD = 6, |
43 | | RSYNC_DATA = 7 |
44 | | } rsync_state_t; |
45 | | |
46 | | enum rsync_who { |
47 | | CLIENT, |
48 | | SERVER |
49 | | }; |
50 | | |
51 | | static bool rsync_desegment = true; |
52 | | |
53 | | /* this is a guide to the current conversation state */ |
54 | | struct rsync_conversation_data { |
55 | | rsync_state_t client_state; |
56 | | rsync_state_t server_state; |
57 | | }; |
58 | | |
59 | | struct rsync_frame_data { |
60 | | rsync_state_t state; |
61 | | }; |
62 | | |
63 | | static int proto_rsync; |
64 | | |
65 | | static int hf_rsync_command_string; |
66 | | static int hf_rsync_data; |
67 | | static int hf_rsync_hdr_magic; |
68 | | static int hf_rsync_hdr_version; |
69 | | static int hf_rsync_module_list_string; |
70 | | static int hf_rsync_motd_string; |
71 | | static int hf_rsync_query_string; |
72 | | static int hf_rsync_rsyncdok_string; |
73 | | |
74 | | static int ett_rsync; |
75 | | |
76 | | static dissector_handle_t rsync_handle; |
77 | | |
78 | | |
79 | 16 | #define TCP_PORT_RSYNC 873 |
80 | | |
81 | | static range_t *glb_rsync_tcp_range; |
82 | | |
83 | | #define VERSION_LEN 4 /* 2 digits for main version; '.'; 1 digit for sub version */ |
84 | | |
85 | | static void |
86 | | dissect_rsync_version_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *rsync_tree, enum rsync_who me) |
87 | 2 | { |
88 | 2 | int offset = 0; |
89 | 2 | uint8_t *version; |
90 | 2 | unsigned len; |
91 | | |
92 | 2 | proto_tree_add_item(rsync_tree, hf_rsync_hdr_magic, tvb, offset, RSYNCD_MAGIC_HEADER_LEN, ENC_ASCII); |
93 | 2 | offset += RSYNCD_MAGIC_HEADER_LEN; |
94 | 2 | offset += 1; /* skip the space */ |
95 | 2 | proto_tree_add_item(rsync_tree, hf_rsync_hdr_version, tvb, offset, -1, ENC_ASCII); |
96 | 2 | len = tvb_reported_length_remaining(tvb, offset); |
97 | 2 | version = tvb_get_string_enc(pinfo->pool, tvb, offset, len, ENC_ASCII|ENC_NA); |
98 | | |
99 | | /* VERSION string can contain undesirable char (like \n) at the end. Trim it. */ |
100 | 2 | if (len > 0 && version[len - 1] == '\n') |
101 | 0 | version[len - 1] = 0x0; |
102 | | |
103 | 2 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s Initialisation (Version %s)", (me == SERVER ? "Server" : "Client"), version); |
104 | 2 | } |
105 | | |
106 | | /* Packet dissection routine called by tcp (& udp) when port 873 detected */ |
107 | | static int |
108 | | dissect_rsync_encap(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, |
109 | | bool desegment _U_) |
110 | 4 | { |
111 | 4 | conversation_t *conversation; |
112 | 4 | struct rsync_conversation_data *conversation_data; |
113 | 4 | struct rsync_frame_data *rsync_frame_data_p; |
114 | 4 | proto_item *ti; |
115 | 4 | proto_tree *rsync_tree; |
116 | 4 | enum rsync_who me; |
117 | 4 | int offset = 0; |
118 | 4 | unsigned buff_length; |
119 | | |
120 | 4 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "RSYNC"); |
121 | | |
122 | 4 | col_clear(pinfo->cinfo, COL_INFO); |
123 | | |
124 | 4 | me = value_is_in_range(glb_rsync_tcp_range, pinfo->srcport) ? SERVER : CLIENT; |
125 | | |
126 | 4 | conversation = find_or_create_conversation(pinfo); |
127 | | |
128 | 4 | conversation_data = (struct rsync_conversation_data *)conversation_get_proto_data(conversation, proto_rsync); |
129 | | |
130 | 4 | if (conversation_data == NULL) { /* new conversation */ |
131 | 2 | conversation_data = wmem_new(wmem_file_scope(), struct rsync_conversation_data); |
132 | 2 | conversation_data->client_state = RSYNC_INIT; |
133 | 2 | conversation_data->server_state = RSYNC_SERV_INIT; |
134 | 2 | conversation_add_proto_data(conversation, proto_rsync, conversation_data); |
135 | 2 | } |
136 | | |
137 | 4 | conversation_set_dissector(conversation, rsync_handle); |
138 | | |
139 | 4 | ti = proto_tree_add_item(tree, proto_rsync, tvb, 0, -1, ENC_NA); |
140 | | |
141 | 4 | rsync_tree = proto_item_add_subtree(ti, ett_rsync); |
142 | | |
143 | 4 | rsync_frame_data_p = (struct rsync_frame_data *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rsync, 0); |
144 | 4 | if (!rsync_frame_data_p) { |
145 | | /* then we haven't seen this frame before */ |
146 | 4 | rsync_frame_data_p = wmem_new(wmem_file_scope(), struct rsync_frame_data); |
147 | 4 | rsync_frame_data_p->state = (me == SERVER) ? conversation_data->server_state : conversation_data->client_state; |
148 | 4 | p_add_proto_data(wmem_file_scope(), pinfo, proto_rsync, 0, rsync_frame_data_p); |
149 | 4 | } |
150 | | |
151 | 4 | if (me == SERVER) { |
152 | 1 | switch (rsync_frame_data_p->state) { |
153 | 1 | case RSYNC_SERV_INIT: |
154 | 1 | dissect_rsync_version_header(tvb, pinfo, rsync_tree, me); |
155 | | |
156 | 1 | conversation_data->server_state = RSYNC_SERV_MOTD; |
157 | | |
158 | 1 | break; |
159 | | |
160 | 0 | case RSYNC_SERV_MOTD: |
161 | 0 | proto_tree_add_item(rsync_tree, hf_rsync_motd_string, tvb, offset, -1, ENC_ASCII); |
162 | |
|
163 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Server MOTD"); |
164 | |
|
165 | 0 | conversation_data->server_state = RSYNC_SERV_MOTD; |
166 | |
|
167 | 0 | break; |
168 | | |
169 | 0 | case RSYNC_MODULE_LIST: |
170 | | /* there are two cases - file list, or authentication */ |
171 | 0 | if (0 == tvb_strneql(tvb, offset, RSYNCD_AUTHREQD, RSYNCD_AUTHREQD_LEN)) { |
172 | | /* matches, so we assume it's an authentication message */ |
173 | 0 | proto_tree_add_item(rsync_tree, hf_rsync_rsyncdok_string, tvb, offset, -1, ENC_ASCII); |
174 | |
|
175 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Authentication"); |
176 | 0 | conversation_data->server_state = RSYNC_DATA; |
177 | |
|
178 | 0 | } else { /* it didn't match, so it is probably a module list */ |
179 | |
|
180 | 0 | proto_tree_add_item(rsync_tree, hf_rsync_module_list_string, tvb, offset, -1, ENC_ASCII); |
181 | | |
182 | | /* we need to check the end of the buffer for magic string */ |
183 | 0 | buff_length = tvb_captured_length_remaining(tvb, offset); |
184 | 0 | if (buff_length > RSYNCD_EXIT_LEN && |
185 | 0 | 0 == tvb_strneql(tvb, buff_length-RSYNCD_EXIT_LEN-1, RSYNCD_EXIT, RSYNCD_EXIT_LEN)) { |
186 | | /* that's all, folks */ |
187 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Final module list"); |
188 | 0 | conversation_data->server_state = RSYNC_DATA; |
189 | 0 | } else { /* there must be more data */ |
190 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Module list"); |
191 | 0 | conversation_data->server_state = RSYNC_MODULE_LIST; |
192 | 0 | } |
193 | 0 | } |
194 | |
|
195 | 0 | break; |
196 | | |
197 | 0 | case RSYNC_DATA: |
198 | 0 | proto_tree_add_item(rsync_tree, hf_rsync_data, tvb, offset, -1, ENC_NA); |
199 | |
|
200 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Data"); |
201 | |
|
202 | 0 | conversation_data->server_state = RSYNC_DATA; |
203 | |
|
204 | 0 | break; |
205 | | |
206 | 0 | default: |
207 | | /* Unknown state */ |
208 | 0 | break; |
209 | 1 | } |
210 | 3 | } else { /* me == CLIENT */ |
211 | 3 | switch (rsync_frame_data_p->state) { |
212 | 1 | case RSYNC_INIT: |
213 | 1 | dissect_rsync_version_header(tvb, pinfo, rsync_tree, me); |
214 | | |
215 | 1 | conversation_data->client_state = RSYNC_CLIENT_QUERY; |
216 | | |
217 | 1 | break; |
218 | | |
219 | 1 | case RSYNC_CLIENT_QUERY: |
220 | 1 | proto_tree_add_item(rsync_tree, hf_rsync_query_string, tvb, offset, -1, ENC_ASCII); |
221 | | |
222 | 1 | col_set_str(pinfo->cinfo, COL_INFO, "Client Query"); |
223 | | |
224 | 1 | conversation_data->client_state = RSYNC_COMMAND; |
225 | | |
226 | 1 | if (tvb_captured_length(tvb) == RSYNC_MODULE_LIST_QUERY_LEN && |
227 | 0 | 0 == tvb_strneql(tvb, offset, RSYNC_MODULE_LIST_QUERY, RSYNC_MODULE_LIST_QUERY_LEN)) { |
228 | 0 | conversation_data->server_state = RSYNC_MODULE_LIST; |
229 | 1 | } else { |
230 | 1 | conversation_data->server_state = RSYNC_DATA; |
231 | 1 | } |
232 | | |
233 | 1 | break; |
234 | | |
235 | 1 | case RSYNC_COMMAND: |
236 | | /* then we are still sending commands */ |
237 | 1 | proto_tree_add_item(rsync_tree, hf_rsync_command_string, tvb, offset, -1, ENC_ASCII); |
238 | | |
239 | 1 | col_set_str(pinfo->cinfo, COL_INFO, "Client Command"); |
240 | | |
241 | 1 | conversation_data->client_state = RSYNC_COMMAND; |
242 | | |
243 | 1 | break; |
244 | | |
245 | 0 | case RSYNC_DATA: |
246 | | /* then we are still sending commands */ |
247 | 0 | proto_tree_add_item(rsync_tree, hf_rsync_data, tvb, offset, -1, ENC_NA); |
248 | |
|
249 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Data"); |
250 | |
|
251 | 0 | conversation_data->client_state = RSYNC_DATA; |
252 | |
|
253 | 0 | break; |
254 | | |
255 | 0 | default: |
256 | | /* Unknown state */ |
257 | 0 | break; |
258 | 3 | } |
259 | 3 | } |
260 | 4 | return tvb_captured_length(tvb); |
261 | 4 | } |
262 | | |
263 | | /* Packet dissection routine called by tcp (& udp) when port 873 detected */ |
264 | | static int |
265 | | dissect_rsync(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) |
266 | 4 | { |
267 | 4 | return dissect_rsync_encap(tvb, pinfo, tree, rsync_desegment); |
268 | 4 | } |
269 | | |
270 | | static void |
271 | | apply_rsync_prefs(void) |
272 | 16 | { |
273 | | /* Rsync uses the port preference to determine client/server */ |
274 | 16 | glb_rsync_tcp_range = prefs_get_range_value("rsync", "tcp.port"); |
275 | 16 | } |
276 | | |
277 | | /* Register protocol with Wireshark. */ |
278 | | void |
279 | | proto_register_rsync(void) |
280 | 16 | { |
281 | 16 | static hf_register_info hf[] = { |
282 | 16 | { &hf_rsync_hdr_magic, |
283 | 16 | { "Magic Header", "rsync.hdr_magic", |
284 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
285 | 16 | NULL, HFILL } |
286 | 16 | }, |
287 | 16 | { &hf_rsync_hdr_version, |
288 | 16 | { "Header Version", "rsync.hdr_version", |
289 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
290 | 16 | NULL, HFILL } |
291 | 16 | }, |
292 | 16 | { &hf_rsync_query_string, |
293 | 16 | { "Client Query String", "rsync.query", |
294 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
295 | 16 | NULL, HFILL } |
296 | 16 | }, |
297 | 16 | { &hf_rsync_motd_string, |
298 | 16 | { "Server MOTD String", "rsync.motd", |
299 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
300 | 16 | NULL, HFILL } |
301 | 16 | }, |
302 | 16 | { &hf_rsync_module_list_string, |
303 | 16 | { "Server Module List", "rsync.module_list", |
304 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
305 | 16 | NULL, HFILL } |
306 | 16 | }, |
307 | 16 | { &hf_rsync_rsyncdok_string, |
308 | 16 | { "RSYNCD Response String", "rsync.response", |
309 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
310 | 16 | NULL, HFILL } |
311 | 16 | }, |
312 | 16 | { &hf_rsync_command_string, |
313 | 16 | { "Client Command String", "rsync.command", |
314 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
315 | 16 | NULL, HFILL } |
316 | 16 | }, |
317 | 16 | { &hf_rsync_data, |
318 | 16 | { "rsync data", "rsync.data", |
319 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
320 | 16 | NULL, HFILL } |
321 | 16 | }, |
322 | 16 | }; |
323 | | |
324 | 16 | static int *ett[] = { |
325 | 16 | &ett_rsync, |
326 | 16 | }; |
327 | | |
328 | 16 | module_t *rsync_module; |
329 | | |
330 | 16 | proto_rsync = proto_register_protocol("RSYNC File Synchroniser", "RSYNC", "rsync"); |
331 | 16 | proto_register_field_array(proto_rsync, hf, array_length(hf)); |
332 | 16 | proto_register_subtree_array(ett, array_length(ett)); |
333 | | |
334 | 16 | rsync_module = prefs_register_protocol(proto_rsync, apply_rsync_prefs); |
335 | 16 | prefs_register_bool_preference(rsync_module, "desegment", |
336 | 16 | "Reassemble RSYNC messages spanning multiple TCP segments", |
337 | 16 | "Whether the RSYNC dissector should reassemble messages spanning multiple TCP segments." |
338 | 16 | " To use this option, you must also enable" |
339 | 16 | " \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", |
340 | 16 | &rsync_desegment); |
341 | | |
342 | 16 | rsync_handle = register_dissector("rsync", dissect_rsync, proto_rsync); |
343 | 16 | } |
344 | | void |
345 | | proto_reg_handoff_rsync(void) |
346 | 16 | { |
347 | 16 | dissector_add_uint_with_preference("tcp.port", TCP_PORT_RSYNC, rsync_handle); |
348 | 16 | apply_rsync_prefs(); |
349 | 16 | } |
350 | | |
351 | | /* |
352 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
353 | | * |
354 | | * Local variables: |
355 | | * c-basic-offset: 4 |
356 | | * tab-width: 8 |
357 | | * indent-tabs-mode: nil |
358 | | * End: |
359 | | * |
360 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
361 | | * :indentSize=4:tabSize=8:noTabs=true: |
362 | | */ |