Coverage Report

Created: 2025-12-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-rsh.c
Line
Count
Source
1
/* packet-rsh.c
2
 * Routines for rsh (Remote Shell) dissection
3
 * Copyright 2012, Stephen Fisher (see AUTHORS file)
4
 *
5
 * Wireshark - Network traffic analyzer
6
 * By Gerald Combs <gerald@wireshark.org>
7
 * Copyright 1998 Gerald Combs
8
 *
9
 * This dissector is a modified copy of packet-exec.c due to
10
 * protocol similarities and replaces the original rsh dissector
11
 * by Robert Tsai <rtsai@netapp.com>.  It is further based on BSD's
12
 * rshd code and man page.
13
 *
14
 * SPDX-License-Identifier: GPL-2.0-or-later
15
 */
16
17
#include "config.h"
18
19
#include <epan/packet.h>
20
#include <epan/conversation.h>
21
#include <epan/prefs.h>
22
#include <wsutil/str_util.h>
23
24
/* The rsh protocol uses TCP port 512 per its IANA assignment */
25
75
#define RSH_PORT 514
26
27
void proto_register_rsh(void);
28
void proto_reg_handoff_rsh(void);
29
30
static dissector_handle_t rsh_handle;
31
32
/* Variables for our preferences */
33
static bool preference_info_show_client_username;
34
static bool preference_info_show_server_username = true;
35
static bool preference_info_show_command;
36
37
/* Initialize the protocol and registered fields */
38
static int proto_rsh;
39
40
static int hf_rsh_stderr_port;
41
static int hf_rsh_client_username;
42
static int hf_rsh_server_username;
43
static int hf_rsh_command;
44
static int hf_rsh_client_server_data;
45
static int hf_rsh_server_client_data;
46
47
/* Initialize the subtree pointers */
48
static int ett_rsh;
49
50
1
#define RSH_STDERR_PORT_LEN 5
51
14
#define RSH_CLIENT_USERNAME_LEN 16
52
8
#define RSH_SERVER_USERNAME_LEN 16
53
7
#define RSH_COMMAND_LEN 256 /* Based on the size of the system's argument list */
54
55
/* Initialize the structure that will be tied to each conversation.
56
 * This is used to display the username and/or command in the INFO column of
57
 * each packet of the conversation. */
58
59
typedef enum {
60
    NONE,
61
    WAIT_FOR_STDERR_PORT,
62
    WAIT_FOR_CLIENT_USERNAME,
63
    WAIT_FOR_SERVER_USERNAME,
64
    WAIT_FOR_COMMAND,
65
    WAIT_FOR_DATA
66
} rsh_session_state_t;
67
68
69
typedef struct {
70
    /* Packet number within the conversation */
71
    unsigned first_packet_number, second_packet_number;
72
    unsigned third_packet_number, fourth_packet_number;
73
74
    /* The following variables are given values from session_state_t
75
     * above to keep track of where we are in the beginning of the session
76
     * (when the username and other fields show up).  This is necessary for
77
     * when the user clicks randomly through the initial packets instead of
78
     * going in order.
79
     */
80
81
    /* Track where we are in the conversation */
82
    rsh_session_state_t state;
83
    rsh_session_state_t first_packet_state, second_packet_state;
84
    rsh_session_state_t third_packet_state, fourth_packet_state;
85
86
    char *client_username;
87
    char *server_username;
88
    char *command;
89
} rsh_hash_entry_t;
90
91
92
static int
93
dissect_rsh(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
94
65
{
95
    /* Set up structures needed to add the protocol subtree and manage it */
96
65
    proto_item *ti;
97
65
    proto_tree *rsh_tree=NULL;
98
99
    /* Variables for extracting and displaying data from the packet */
100
65
    const char *field_stringz; /* Temporary storage for each field we extract */
101
102
65
    unsigned length;
103
65
    unsigned offset = 0;
104
65
    conversation_t *conversation;
105
65
    rsh_hash_entry_t *hash_info;
106
107
65
    conversation = find_or_create_conversation(pinfo);
108
109
    /* Retrieve information from conversation
110
     * or add it if it isn't there yet
111
     */
112
65
    hash_info = (rsh_hash_entry_t *)conversation_get_proto_data(conversation, proto_rsh);
113
65
    if(!hash_info){
114
30
        hash_info = wmem_new(wmem_file_scope(), rsh_hash_entry_t);
115
116
30
        hash_info->first_packet_number = pinfo->num;
117
30
        hash_info->second_packet_number = 0;
118
30
        hash_info->third_packet_number  = 0;
119
30
        hash_info->fourth_packet_number  = 0;
120
121
30
        hash_info->state = WAIT_FOR_STDERR_PORT; /* The first field we'll see */
122
123
        /* Start with empty username and command strings */
124
30
        hash_info->client_username=NULL;
125
30
        hash_info->server_username=NULL;
126
30
        hash_info->command=NULL;
127
128
        /* These will be set on the first pass by the first
129
         * four packets of the conversation
130
         */
131
30
        hash_info->first_packet_state  = NONE;
132
30
        hash_info->second_packet_state = NONE;
133
30
        hash_info->third_packet_state  = NONE;
134
30
        hash_info->fourth_packet_state  = NONE;
135
136
30
        conversation_add_proto_data(conversation, proto_rsh, hash_info);
137
30
    }
138
139
    /* Store the number of the first three packets of this conversation
140
     * as we reach them the first time */
141
142
65
    if(!hash_info->second_packet_number
143
42
            && pinfo->num > hash_info->first_packet_number){
144
        /* We're on the second packet of the conversation */
145
10
        hash_info->second_packet_number = pinfo->num;
146
55
    } else if(hash_info->second_packet_number
147
23
            && !hash_info->third_packet_number
148
7
            && pinfo->num > hash_info->second_packet_number) {
149
        /* We're on the third packet of the conversation */
150
4
        hash_info->third_packet_number = pinfo->num;
151
51
    } else if(hash_info->third_packet_number
152
16
            && !hash_info->fourth_packet_number
153
7
            && pinfo->num > hash_info->third_packet_number) {
154
        /* We're on the fourth packet of the conversation */
155
3
        hash_info->fourth_packet_number = pinfo->num;
156
3
    }
157
158
    /* Save this packet's state so we can retrieve it if this packet
159
     * is selected again later.  If the packet's state was already stored,
160
     * then retrieve it */
161
65
    if(pinfo->num == hash_info->first_packet_number){
162
32
        if(hash_info->first_packet_state == NONE){
163
30
            hash_info->first_packet_state = hash_info->state;
164
30
        } else {
165
2
            hash_info->state = hash_info->first_packet_state;
166
2
        }
167
32
    }
168
169
65
    if(pinfo->num == hash_info->second_packet_number){
170
13
        if(hash_info->second_packet_state == NONE){
171
10
            hash_info->second_packet_state = hash_info->state;
172
10
        } else {
173
3
            hash_info->state = hash_info->second_packet_state;
174
3
        }
175
13
    }
176
177
65
    if(pinfo->num == hash_info->third_packet_number){
178
8
        if(hash_info->third_packet_state == NONE){
179
4
            hash_info->third_packet_state = hash_info->state;
180
4
        } else {
181
4
            hash_info->state = hash_info->third_packet_state;
182
4
        }
183
8
    }
184
185
65
    if(pinfo->num == hash_info->fourth_packet_number){
186
9
        if(hash_info->fourth_packet_state == NONE){
187
3
            hash_info->fourth_packet_state = hash_info->state;
188
6
        } else {
189
6
            hash_info->state = hash_info->fourth_packet_state;
190
6
        }
191
9
    }
192
193
65
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "RSH");
194
195
    /* First, clear the info column */
196
65
    col_clear(pinfo->cinfo, COL_INFO);
197
198
    /* Client username */
199
65
    if(hash_info->client_username && preference_info_show_client_username == true){
200
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "Client username:%s ", hash_info->client_username);
201
0
    }
202
203
    /* Server username */
204
65
    if(hash_info->server_username && preference_info_show_server_username == true){
205
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "Server username:%s ", hash_info->server_username);
206
0
    }
207
208
    /* Command */
209
65
    if(hash_info->command && preference_info_show_command == true){
210
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "Command:%s ", hash_info->command);
211
0
    }
212
213
    /* create display subtree for the protocol */
214
65
    ti = proto_tree_add_item(tree, proto_rsh, tvb, 0, -1, ENC_NA);
215
65
    rsh_tree = proto_item_add_subtree(ti, ett_rsh);
216
217
    /* If this packet doesn't end with a null terminated string,
218
     * then it must be session data only and we can skip looking
219
     * for the other fields.
220
     */
221
65
    if(tvb_find_uint8(tvb, tvb_captured_length(tvb)-1, 1, '\0') == -1){
222
45
        hash_info->state = WAIT_FOR_DATA;
223
45
    }
224
225
65
    if(hash_info->state == WAIT_FOR_STDERR_PORT
226
17
            && tvb_reported_length_remaining(tvb, offset)){
227
17
        field_stringz = (char*)tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);
228
229
        /* Check if this looks like the stderr_port field.
230
         * It is optional, so it may only be 1 character long
231
         * (the NULL)
232
         */
233
17
        if(length == 1 || (isdigit_string(field_stringz)
234
10
                    && length <= RSH_STDERR_PORT_LEN)){
235
10
            proto_tree_add_string(rsh_tree, hf_rsh_stderr_port, tvb, offset, length, (char*)field_stringz);
236
            /* Next field we need */
237
10
            hash_info->state = WAIT_FOR_CLIENT_USERNAME;
238
10
        } else {
239
            /* Since the data doesn't match this field, it must be data only */
240
7
            hash_info->state = WAIT_FOR_DATA;
241
7
        }
242
243
        /* Used if the next field is in the same packet */
244
17
        offset += length;
245
17
    }
246
247
248
65
    if(hash_info->state == WAIT_FOR_CLIENT_USERNAME
249
10
            && tvb_reported_length_remaining(tvb, offset)){
250
8
        field_stringz = (char*)tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);
251
252
        /* Check if this looks like the username field */
253
8
        if(length != 1 && length <= RSH_CLIENT_USERNAME_LEN
254
6
                && isprint_string(field_stringz)){
255
5
            proto_tree_add_string(rsh_tree, hf_rsh_client_username, tvb, offset, length, (char*)field_stringz);
256
257
            /* Store the client username so we can display it in the
258
             * info column of the entire conversation
259
             */
260
5
            if(!hash_info->client_username){
261
5
                hash_info->client_username=wmem_strdup(wmem_file_scope(), (char*)field_stringz);
262
5
            }
263
264
            /* Next field we need */
265
5
            hash_info->state = WAIT_FOR_SERVER_USERNAME;
266
5
        } else {
267
            /* Since the data doesn't match this field, it must be data only */
268
3
            hash_info->state = WAIT_FOR_DATA;
269
3
        }
270
271
        /* Used if the next field is in the same packet */
272
8
        offset += length;
273
8
    }
274
275
276
65
    if(hash_info->state == WAIT_FOR_SERVER_USERNAME
277
5
            && tvb_reported_length_remaining(tvb, offset)){
278
5
        field_stringz = (char*)tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);
279
280
        /* Check if this looks like the password field */
281
5
        if(length != 1 && length <= RSH_SERVER_USERNAME_LEN
282
2
                && isprint_string(field_stringz)){
283
0
            proto_tree_add_string(rsh_tree, hf_rsh_server_username, tvb, offset, length, (char*)field_stringz);
284
285
            /* Store the server username so we can display it in the
286
             * info column of the entire conversation
287
             */
288
0
            if(!hash_info->server_username){
289
0
                hash_info->server_username=wmem_strdup(wmem_file_scope(), (char*)field_stringz);
290
0
            }
291
292
0
        }
293
294
        /* Used if the next field is in the same packet */
295
5
        offset += length;
296
        /* Next field we are looking for */
297
5
        hash_info->state = WAIT_FOR_COMMAND;
298
5
    }
299
300
301
65
    if(hash_info->state == WAIT_FOR_COMMAND
302
5
            && tvb_reported_length_remaining(tvb, offset)){
303
5
        field_stringz = (char*)tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);
304
305
        /* Check if this looks like the command field */
306
5
        if(length != 1 && length <= RSH_COMMAND_LEN
307
2
                && isprint_string(field_stringz)){
308
0
            proto_tree_add_string(rsh_tree, hf_rsh_command, tvb, offset, length, (char*)field_stringz);
309
310
            /* Store the command so we can display it in the
311
             * info column of the entire conversation
312
             */
313
0
            if(!hash_info->command){
314
0
                hash_info->command=wmem_strdup(wmem_file_scope(), (char*)field_stringz);
315
0
            }
316
317
5
        } else {
318
            /* Since the data doesn't match this field, it must be data only */
319
5
            hash_info->state = WAIT_FOR_DATA;
320
5
        }
321
5
    }
322
323
324
65
    if(hash_info->state == WAIT_FOR_DATA
325
63
            && tvb_reported_length_remaining(tvb, offset)){
326
61
        if(pinfo->destport == RSH_PORT){
327
            /* Packet going to the server */
328
            /* offset = 0 since the whole packet is data */
329
28
            proto_tree_add_item(rsh_tree, hf_rsh_client_server_data, tvb, 0, -1, ENC_NA);
330
331
28
            col_append_str(pinfo->cinfo, COL_INFO, "Client -> Server data");
332
33
        } else {
333
            /* This packet must be going back to the client */
334
            /* offset = 0 since the whole packet is data */
335
33
            proto_tree_add_item(rsh_tree, hf_rsh_server_client_data, tvb, 0, -1, ENC_NA);
336
337
33
            col_append_str(pinfo->cinfo, COL_INFO, "Server -> Client Data");
338
33
        }
339
61
    }
340
341
    /* We haven't seen all of the fields yet */
342
65
    if(hash_info->state < WAIT_FOR_DATA){
343
2
        col_set_str(pinfo->cinfo, COL_INFO, "Session Establishment");
344
2
    }
345
65
    return tvb_captured_length(tvb);
346
65
}
347
348
void
349
proto_register_rsh(void)
350
14
{
351
14
    static hf_register_info hf[] = {
352
14
        { &hf_rsh_stderr_port, { "Stderr port (optional)", "rsh.stderr_port",
353
14
        FT_STRINGZ, BASE_NONE, NULL, 0,
354
14
        "Client port that is listening for stderr stream from server", HFILL } },
355
356
14
        { &hf_rsh_client_username, { "Client username", "rsh.client_username",
357
14
        FT_STRINGZ, BASE_NONE, NULL, 0,
358
14
        "User's identity on the client machine", HFILL } },
359
360
14
        { &hf_rsh_server_username, { "Server username", "rsh.server_username",
361
14
        FT_STRINGZ, BASE_NONE, NULL, 0,
362
14
        "User's identity on the server machine", HFILL } },
363
364
14
        { &hf_rsh_command, { "Command to execute", "rsh.command",
365
14
        FT_STRINGZ, BASE_NONE, NULL, 0,
366
14
        "Command client is requesting the server to run", HFILL } },
367
368
14
        { &hf_rsh_client_server_data, { "Client -> Server Data", "rsh.client_server_data",
369
14
        FT_BYTES, BASE_NONE, NULL, 0,
370
14
        NULL, HFILL } },
371
372
14
        { &hf_rsh_server_client_data, { "Server -> Client Data", "rsh.server_client_data",
373
14
        FT_BYTES, BASE_NONE, NULL, 0,
374
14
        NULL, HFILL } },
375
376
14
    };
377
378
14
    static int *ett[] =
379
14
    {
380
14
        &ett_rsh
381
14
    };
382
383
14
    module_t *rsh_module;
384
385
    /* Register the protocol name and description */
386
14
    proto_rsh = proto_register_protocol("Remote Shell", "RSH", "rsh");
387
388
    /* Required function calls to register the header fields and subtrees used */
389
14
    proto_register_field_array(proto_rsh, hf, array_length(hf));
390
14
    proto_register_subtree_array(ett, array_length(ett));
391
392
    /* Register the dissector handle */
393
14
    rsh_handle = register_dissector("rsh", dissect_rsh, proto_rsh);
394
395
    /* Register preferences module */
396
14
    rsh_module = prefs_register_protocol(proto_rsh, NULL);
397
398
    /* Register our preferences */
399
14
    prefs_register_bool_preference(rsh_module, "info_show_client_username",
400
14
         "Show client username in info column",
401
14
         "Controls the display of the session's client username in the info column.  This is only displayed if the packet containing it was seen during this capture session.",
402
14
         &preference_info_show_client_username);
403
404
14
    prefs_register_bool_preference(rsh_module, "info_show_server_username",
405
14
         "Show server username in info column",
406
14
         "Controls the display of the session's server username in the info column.  This is only displayed if the packet containing it was seen during this capture session.",
407
14
         &preference_info_show_server_username);
408
409
14
    prefs_register_bool_preference(rsh_module, "info_show_command",
410
14
         "Show command in info column",
411
14
         "Controls the display of the command being run on the server by this session in the info column.  This is only displayed if the packet containing it was seen during this capture session.",
412
14
         &preference_info_show_command);
413
14
}
414
415
416
/* Entry function */
417
void
418
proto_reg_handoff_rsh(void)
419
14
{
420
14
    dissector_add_uint_with_preference("tcp.port", RSH_PORT, rsh_handle);
421
14
}
422
423
/*
424
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
425
 *
426
 * Local variables:
427
 * c-basic-offset: 4
428
 * tab-width: 8
429
 * indent-tabs-mode: nil
430
 * End:
431
 *
432
 * vi: set shiftwidth=4 tabstop=8 expandtab:
433
 * :indentSize=4:tabSize=8:noTabs=true:
434
 */