aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-rsh.c
blob: 23cdedd1a96a91d253c9b792821db300eff8825c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
/* packet-rsh.c
 * Routines for rsh (Remote Shell) dissection
 * Copyright 2012, Stephen Fisher (see AUTHORS file)
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This dissector is a modified copy of packet-exec.c due to
 * protocol similarities and replaces the original rsh dissector
 * by Robert Tsai <rtsai@netapp.com>.  It is further based on BSD's
 * rshd code and man page.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/conversation.h>
#include <epan/prefs.h>
#include <wsutil/str_util.h>

/* The rsh protocol uses TCP port 512 per its IANA assignment */
#define RSH_PORT 514

void proto_register_rsh(void);
void proto_reg_handoff_rsh(void);

static dissector_handle_t rsh_handle;

/* Variables for our preferences */
static gboolean preference_info_show_client_username = FALSE;
static gboolean preference_info_show_server_username = TRUE;
static gboolean preference_info_show_command = FALSE;

/* Initialize the protocol and registered fields */
static int proto_rsh;

static int hf_rsh_stderr_port;
static int hf_rsh_client_username;
static int hf_rsh_server_username;
static int hf_rsh_command;
static int hf_rsh_client_server_data;
static int hf_rsh_server_client_data;

/* Initialize the subtree pointers */
static gint ett_rsh;

#define RSH_STDERR_PORT_LEN 5
#define RSH_CLIENT_USERNAME_LEN 16
#define RSH_SERVER_USERNAME_LEN 16
#define RSH_COMMAND_LEN 256 /* Based on the size of the system's argument list */

/* Initialize the structure that will be tied to each conversation.
 * This is used to display the username and/or command in the INFO column of
 * each packet of the conversation. */

typedef enum {
    NONE,
    WAIT_FOR_STDERR_PORT,
    WAIT_FOR_CLIENT_USERNAME,
    WAIT_FOR_SERVER_USERNAME,
    WAIT_FOR_COMMAND,
    WAIT_FOR_DATA
} rsh_session_state_t;


typedef struct {
    /* Packet number within the conversation */
    guint first_packet_number, second_packet_number;
    guint third_packet_number, fourth_packet_number;

    /* The following variables are given values from session_state_t
     * above to keep track of where we are in the beginning of the session
     * (when the username and other fields show up).  This is necessary for
     * when the user clicks randomly through the initial packets instead of
     * going in order.
     */

    /* Track where we are in the conversation */
    rsh_session_state_t state;
    rsh_session_state_t first_packet_state, second_packet_state;
    rsh_session_state_t third_packet_state, fourth_packet_state;

    gchar *client_username;
    gchar *server_username;
    gchar *command;
} rsh_hash_entry_t;


static int
dissect_rsh(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    /* Set up structures needed to add the protocol subtree and manage it */
    proto_item *ti;
    proto_tree *rsh_tree=NULL;

    /* Variables for extracting and displaying data from the packet */
    guchar *field_stringz; /* Temporary storage for each field we extract */

    gint length;
    guint offset = 0;
    conversation_t *conversation;
    rsh_hash_entry_t *hash_info;

    conversation = find_or_create_conversation(pinfo);

    /* Retrieve information from conversation
     * or add it if it isn't there yet
     */
    hash_info = (rsh_hash_entry_t *)conversation_get_proto_data(conversation, proto_rsh);
    if(!hash_info){
        hash_info = wmem_new(wmem_file_scope(), rsh_hash_entry_t);

        hash_info->first_packet_number = pinfo->num;
        hash_info->second_packet_number = 0;
        hash_info->third_packet_number  = 0;
        hash_info->fourth_packet_number  = 0;

        hash_info->state = WAIT_FOR_STDERR_PORT; /* The first field we'll see */

        /* Start with empty username and command strings */
        hash_info->client_username=NULL;
        hash_info->server_username=NULL;
        hash_info->command=NULL;

        /* These will be set on the first pass by the first
         * four packets of the conversation
         */
        hash_info->first_packet_state  = NONE;
        hash_info->second_packet_state = NONE;
        hash_info->third_packet_state  = NONE;
        hash_info->fourth_packet_state  = NONE;

        conversation_add_proto_data(conversation, proto_rsh, hash_info);
    }

    /* Store the number of the first three packets of this conversation
     * as we reach them the first time */

    if(!hash_info->second_packet_number
            && pinfo->num > hash_info->first_packet_number){
        /* We're on the second packet of the conversation */
        hash_info->second_packet_number = pinfo->num;
    } else if(hash_info->second_packet_number
            && !hash_info->third_packet_number
            && pinfo->num > hash_info->second_packet_number) {
        /* We're on the third packet of the conversation */
        hash_info->third_packet_number = pinfo->num;
    } else if(hash_info->third_packet_number
            && !hash_info->fourth_packet_number
            && pinfo->num > hash_info->third_packet_number) {
        /* We're on the fourth packet of the conversation */
        hash_info->fourth_packet_number = pinfo->num;
    }

    /* Save this packet's state so we can retrieve it if this packet
     * is selected again later.  If the packet's state was already stored,
     * then retrieve it */
    if(pinfo->num == hash_info->first_packet_number){
        if(hash_info->first_packet_state == NONE){
            hash_info->first_packet_state = hash_info->state;
        } else {
            hash_info->state = hash_info->first_packet_state;
        }
    }

    if(pinfo->num == hash_info->second_packet_number){
        if(hash_info->second_packet_state == NONE){
            hash_info->second_packet_state = hash_info->state;
        } else {
            hash_info->state = hash_info->second_packet_state;
        }
    }

    if(pinfo->num == hash_info->third_packet_number){
        if(hash_info->third_packet_state == NONE){
            hash_info->third_packet_state = hash_info->state;
        } else {
            hash_info->state = hash_info->third_packet_state;
        }
    }

    if(pinfo->num == hash_info->fourth_packet_number){
        if(hash_info->fourth_packet_state == NONE){
            hash_info->fourth_packet_state = hash_info->state;
        } else {
            hash_info->state = hash_info->fourth_packet_state;
        }
    }

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "RSH");

    /* First, clear the info column */
    col_clear(pinfo->cinfo, COL_INFO);

    /* Client username */
    if(hash_info->client_username && preference_info_show_client_username == TRUE){
        col_append_fstr(pinfo->cinfo, COL_INFO, "Client username:%s ", hash_info->client_username);
    }

    /* Server username */
    if(hash_info->server_username && preference_info_show_server_username == TRUE){
        col_append_fstr(pinfo->cinfo, COL_INFO, "Server username:%s ", hash_info->server_username);
    }

    /* Command */
    if(hash_info->command && preference_info_show_command == TRUE){
        col_append_fstr(pinfo->cinfo, COL_INFO, "Command:%s ", hash_info->command);
    }

    /* create display subtree for the protocol */
    ti = proto_tree_add_item(tree, proto_rsh, tvb, 0, -1, ENC_NA);
    rsh_tree = proto_item_add_subtree(ti, ett_rsh);

    /* If this packet doesn't end with a null terminated string,
     * then it must be session data only and we can skip looking
     * for the other fields.
     */
    if(tvb_find_guint8(tvb, tvb_captured_length(tvb)-1, 1, '\0') == -1){
        hash_info->state = WAIT_FOR_DATA;
    }

    if(hash_info->state == WAIT_FOR_STDERR_PORT
            && tvb_reported_length_remaining(tvb, offset)){
        field_stringz = tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);

        /* Check if this looks like the stderr_port field.
         * It is optional, so it may only be 1 character long
         * (the NULL)
         */
        if(length == 1 || (isdigit_string(field_stringz)
                    && length <= RSH_STDERR_PORT_LEN)){
            proto_tree_add_string(rsh_tree, hf_rsh_stderr_port, tvb, offset, length, (gchar*)field_stringz);
            /* Next field we need */
            hash_info->state = WAIT_FOR_CLIENT_USERNAME;
        } else {
            /* Since the data doesn't match this field, it must be data only */
            hash_info->state = WAIT_FOR_DATA;
        }

        /* Used if the next field is in the same packet */
        offset += length;
    }


    if(hash_info->state == WAIT_FOR_CLIENT_USERNAME
            && tvb_reported_length_remaining(tvb, offset)){
        field_stringz = tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);

        /* Check if this looks like the username field */
        if(length != 1 && length <= RSH_CLIENT_USERNAME_LEN
                && isprint_string(field_stringz)){
            proto_tree_add_string(rsh_tree, hf_rsh_client_username, tvb, offset, length, (gchar*)field_stringz);

            /* Store the client username so we can display it in the
             * info column of the entire conversation
             */
            if(!hash_info->client_username){
                hash_info->client_username=wmem_strdup(wmem_file_scope(), (gchar*)field_stringz);
            }

            /* Next field we need */
            hash_info->state = WAIT_FOR_SERVER_USERNAME;
        } else {
            /* Since the data doesn't match this field, it must be data only */
            hash_info->state = WAIT_FOR_DATA;
        }

        /* Used if the next field is in the same packet */
        offset += length;
    }


    if(hash_info->state == WAIT_FOR_SERVER_USERNAME
            && tvb_reported_length_remaining(tvb, offset)){
        field_stringz = tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);

        /* Check if this looks like the password field */
        if(length != 1 && length <= RSH_SERVER_USERNAME_LEN
                && isprint_string(field_stringz)){
            proto_tree_add_string(rsh_tree, hf_rsh_server_username, tvb, offset, length, (gchar*)field_stringz);

            /* Store the server username so we can display it in the
             * info column of the entire conversation
             */
            if(!hash_info->server_username){
                hash_info->server_username=wmem_strdup(wmem_file_scope(), (gchar*)field_stringz);
            }

        }

        /* Used if the next field is in the same packet */
        offset += length;
        /* Next field we are looking for */
        hash_info->state = WAIT_FOR_COMMAND;
    }


    if(hash_info->state == WAIT_FOR_COMMAND
            && tvb_reported_length_remaining(tvb, offset)){
        field_stringz = tvb_get_stringz_enc(pinfo->pool, tvb, offset, &length, ENC_ASCII);

        /* Check if this looks like the command field */
        if(length != 1 && length <= RSH_COMMAND_LEN
                && isprint_string(field_stringz)){
            proto_tree_add_string(rsh_tree, hf_rsh_command, tvb, offset, length, (gchar*)field_stringz);

            /* Store the command so we can display it in the
             * info column of the entire conversation
             */
            if(!hash_info->command){
                hash_info->command=wmem_strdup(wmem_file_scope(), (gchar*)field_stringz);
            }

        } else {
            /* Since the data doesn't match this field, it must be data only */
            hash_info->state = WAIT_FOR_DATA;
        }
    }


    if(hash_info->state == WAIT_FOR_DATA
            && tvb_reported_length_remaining(tvb, offset)){
        if(pinfo->destport == RSH_PORT){
            /* Packet going to the server */
            /* offset = 0 since the whole packet is data */
            proto_tree_add_item(rsh_tree, hf_rsh_client_server_data, tvb, 0, -1, ENC_NA);

            col_append_str(pinfo->cinfo, COL_INFO, "Client -> Server data");
        } else {
            /* This packet must be going back to the client */
            /* offset = 0 since the whole packet is data */
            proto_tree_add_item(rsh_tree, hf_rsh_server_client_data, tvb, 0, -1, ENC_NA);

            col_append_str(pinfo->cinfo, COL_INFO, "Server -> Client Data");
        }
    }

    /* We haven't seen all of the fields yet */
    if(hash_info->state < WAIT_FOR_DATA){
        col_set_str(pinfo->cinfo, COL_INFO, "Session Establishment");
    }
    return tvb_captured_length(tvb);
}

void
proto_register_rsh(void)
{
    static hf_register_info hf[] = {
        { &hf_rsh_stderr_port, { "Stderr port (optional)", "rsh.stderr_port",
        FT_STRINGZ, BASE_NONE, NULL, 0,
        "Client port that is listening for stderr stream from server", HFILL } },

        { &hf_rsh_client_username, { "Client username", "rsh.client_username",
        FT_STRINGZ, BASE_NONE, NULL, 0,
        "User's identity on the client machine", HFILL } },

        { &hf_rsh_server_username, { "Server username", "rsh.server_username",
        FT_STRINGZ, BASE_NONE, NULL, 0,
        "User's identity on the server machine", HFILL } },

        { &hf_rsh_command, { "Command to execute", "rsh.command",
        FT_STRINGZ, BASE_NONE, NULL, 0,
        "Command client is requesting the server to run", HFILL } },

        { &hf_rsh_client_server_data, { "Client -> Server Data", "rsh.client_server_data",
        FT_BYTES, BASE_NONE, NULL, 0,
        NULL, HFILL } },

        { &hf_rsh_server_client_data, { "Server -> Client Data", "rsh.server_client_data",
        FT_BYTES, BASE_NONE, NULL, 0,
        NULL, HFILL } },

    };

    static gint *ett[] =
    {
        &ett_rsh
    };

    module_t *rsh_module;

    /* Register the protocol name and description */
    proto_rsh = proto_register_protocol("Remote Shell", "RSH", "rsh");

    /* Required function calls to register the header fields and subtrees used */
    proto_register_field_array(proto_rsh, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    /* Register the dissector handle */
    rsh_handle = register_dissector("rsh", dissect_rsh, proto_rsh);

    /* Register preferences module */
    rsh_module = prefs_register_protocol(proto_rsh, NULL);

    /* Register our preferences */
    prefs_register_bool_preference(rsh_module, "info_show_client_username",
         "Show client username in info column",
         "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.",
         &preference_info_show_client_username);

    prefs_register_bool_preference(rsh_module, "info_show_server_username",
         "Show server username in info column",
         "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.",
         &preference_info_show_server_username);

    prefs_register_bool_preference(rsh_module, "info_show_command",
         "Show command in info column",
         "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.",
         &preference_info_show_command);
}


/* Entry function */
void
proto_reg_handoff_rsh(void)
{
    dissector_add_uint_with_preference("tcp.port", RSH_PORT, rsh_handle);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */