Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
 */