1 /* packet-gopher.c
2  * Routines for RFC 1436 Gopher protocol dissection
3  * Copyright 2010, Gerald Combs <gerald@wireshark.org>
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * Copied from packet-banana.c
10  *
11  * SPDX-License-Identifier: GPL-2.0-or-later
12  */
13 
14 /*
15  * RFC 1436: https://tools.ietf.org/html/rfc1436
16  * http://en.wikipedia.org/wiki/Gopher_%28protocol%29
17  */
18 
19 #include "config.h"
20 
21 #include <epan/packet.h>
22 #include <epan/prefs.h>
23 
24 void proto_register_gopher(void);
25 void proto_reg_handoff_gopher(void);
26 
27 /* Initialize the protocol and registered fields */
28 static int proto_gopher = -1;
29 static int hf_gopher_request = -1;
30 static int hf_gopher_dir_item = -1;
31 static int hf_gopher_di_type = -1;
32 static int hf_gopher_di_name = -1;
33 static int hf_gopher_di_selector = -1;
34 static int hf_gopher_di_host = -1;
35 static int hf_gopher_di_port = -1;
36 static int hf_gopher_unknown = -1;
37 
38 /* Initialize the subtree pointers */
39 static gint ett_gopher = -1;
40 static gint ett_dir_item = -1;
41 
42 static dissector_handle_t gopher_handle;
43 
44 /* RFC 1436 section 3.8 */
45 static const value_string item_types[] = {
46     { '+',  "Redundant server" },
47     { '0',  "Text file" },
48     { '1',  "Menu" },
49     { '2',  "CSO phone book entity" },
50     { '3',  "Error" },
51     { '4',  "BinHexed Macintosh file" },
52     { '5',  "DOS binary file" },
53     { '6',  "Uuencoded file" },
54     { '7',  "Index server" },
55     { '8',  "Telnet session" },
56     { '9',  "Binary file" },
57     { 'g',  "GIF file" },
58     { 'h',  "HTML file" },              /* Not in RFC 1436 */
59     { 'i',  "Informational message"},   /* Not in RFC 1436 */
60     { 'I',  "Image file" },
61     { 's',  "Audio file" },             /* Not in RFC 1436 */
62     { 'T',  "Tn3270 session" },
63     { 0, NULL }
64 };
65 
66 #define TCP_DEFAULT_RANGE "70"
67 
68 static range_t *gopher_tcp_range = NULL;
69 
70 /* Returns TRUE if the packet is from a client */
71 static gboolean
is_client(packet_info * pinfo)72 is_client(packet_info *pinfo) {
73     return value_is_in_range(gopher_tcp_range, pinfo->destport);
74 }
75 
76 /* Name + Tab + Selector + Tab + Host + Tab + Port */
77 #define MAX_DIR_LINE_LEN (70 + 1 + 255 + 1 + 255 + 1 + 5)
78 #define MIN_DIR_LINE_LEN (0 + 1 + 0 + 1 + 1 + 1 + 1)
79 static gboolean
find_dir_tokens(tvbuff_t * tvb,gint name_start,gint * sel_start,gint * host_start,gint * port_start,gint * line_len,gint * next_offset)80 find_dir_tokens(tvbuff_t *tvb, gint name_start, gint *sel_start, gint *host_start, gint *port_start, gint *line_len, gint *next_offset) {
81     gint remain;
82 
83     if (tvb_captured_length_remaining(tvb, name_start) < MIN_DIR_LINE_LEN)
84         return FALSE;
85 
86     if (! (sel_start && host_start && port_start && line_len && next_offset) )
87         return FALSE;
88 
89     *line_len = tvb_find_line_end(tvb, name_start, MAX_DIR_LINE_LEN, next_offset, FALSE);
90     if (*line_len < MIN_DIR_LINE_LEN)
91         return FALSE;
92 
93     remain = *line_len;
94     *sel_start = tvb_find_guint8(tvb, name_start, remain, '\t') + 1;
95     if (*sel_start < name_start + 1)
96         return FALSE;
97 
98     remain -= *sel_start - name_start;
99     *host_start = tvb_find_guint8(tvb, *sel_start, remain, '\t') + 1;
100     if (*host_start < *sel_start + 1)
101         return FALSE;
102 
103     remain -= *host_start - *sel_start;
104     *port_start = tvb_find_guint8(tvb, *host_start, remain, '\t') + 1;
105     if (*port_start < *host_start + 1)
106         return FALSE;
107 
108     return TRUE;
109 }
110 
111 /* Dissect the packets */
112 
113 static int
dissect_gopher(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)114 dissect_gopher(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) {
115     proto_item *ti;
116     proto_tree *gopher_tree, *dir_tree = NULL;
117     gboolean client = is_client(pinfo);
118     gint line_len;
119     const gchar *request = "[Invalid request]";
120     gboolean is_dir = FALSE;
121     gint offset = 0, next_offset;
122     gint sel_start, host_start, port_start;
123     gchar *name;
124 
125     /* Fill in our protocol and info columns */
126     col_set_str(pinfo->cinfo, COL_PROTOCOL, "Gopher");
127 
128     if (client) {
129         line_len = tvb_find_line_end(tvb, 0, -1, NULL, FALSE);
130         if (line_len == 0) {
131             request = "[Directory list]";
132         } else if (line_len > 0) {
133             request = tvb_get_string_enc(pinfo->pool, tvb, 0, line_len, ENC_ASCII);
134         }
135         col_add_fstr(pinfo->cinfo, COL_INFO, "Request: %s", request);
136     } else {
137         col_add_fstr(pinfo->cinfo, COL_INFO, "Response");
138     }
139 
140     if (tree) {
141         /* Create display subtree for the protocol */
142         ti = proto_tree_add_item(tree, proto_gopher, tvb, 0, -1, ENC_NA);
143         gopher_tree = proto_item_add_subtree(ti, ett_gopher);
144 
145         if (client) {
146             proto_item_append_text(ti, " request: %s", request);
147             proto_tree_add_string(gopher_tree, hf_gopher_request, tvb,
148                                   0, -1, request);
149         } else {
150             proto_item_append_text(ti, " response: ");
151 
152             while (find_dir_tokens(tvb, offset + 1, &sel_start, &host_start, &port_start, &line_len, &next_offset)) {
153                 if (!is_dir) { /* First time */
154                     proto_item_append_text(ti, "[Directory list]");
155                     col_append_str(pinfo->cinfo, COL_INFO, ": [Directory list]");
156                 }
157 
158                 name = tvb_get_string_enc(pinfo->pool, tvb, offset + 1, sel_start - offset - 2, ENC_ASCII);
159                 ti = proto_tree_add_string(gopher_tree, hf_gopher_dir_item, tvb,
160                                 offset, line_len + 1, name);
161                 dir_tree = proto_item_add_subtree(ti, ett_dir_item);
162                 proto_tree_add_item(dir_tree, hf_gopher_di_type, tvb, offset, 1, ENC_ASCII|ENC_NA);
163                 proto_tree_add_item(dir_tree, hf_gopher_di_name, tvb, offset + 1,
164                                     sel_start - offset - 2, ENC_ASCII|ENC_NA);
165                 proto_tree_add_item(dir_tree, hf_gopher_di_selector, tvb, sel_start,
166                                     host_start - sel_start - 1, ENC_ASCII|ENC_NA);
167                 proto_tree_add_item(dir_tree, hf_gopher_di_host, tvb, host_start,
168                                     port_start - host_start - 1, ENC_ASCII|ENC_NA);
169                 proto_tree_add_item(dir_tree, hf_gopher_di_port, tvb, port_start,
170                                     line_len - (port_start - offset - 1), ENC_ASCII|ENC_NA);
171                 is_dir = TRUE;
172                 offset = next_offset;
173             }
174 
175             if (!is_dir) {
176                 proto_item_append_text(ti, "[Unknown]");
177                 proto_tree_add_item(gopher_tree, hf_gopher_unknown, tvb, 0, -1, ENC_ASCII|ENC_NA);
178             }
179         }
180 
181     }
182 
183     /* Return the amount of data this dissector was able to dissect */
184     return tvb_captured_length(tvb);
185 }
186 
187 /* Preference callbacks */
188 static void
gopher_prefs_apply(void)189 gopher_prefs_apply(void) {
190 
191     gopher_tcp_range = prefs_get_range_value("gopher", "tcp.port");
192 }
193 
194 /* Register the protocol with Wireshark */
195 
196 void
proto_register_gopher(void)197 proto_register_gopher(void)
198 {
199     static hf_register_info hf[] = {
200         { &hf_gopher_request,
201             { "Gopher client request", "gopher.request",
202                 FT_STRING, BASE_NONE, NULL, 0,
203                 NULL, HFILL }
204         },
205 
206         { &hf_gopher_dir_item,
207             { "Directory item", "gopher.directory",
208                 FT_STRING, BASE_NONE, NULL, 0,
209                 NULL, HFILL }
210         },
211         { &hf_gopher_di_type,
212             { "Type", "gopher.directory.type",
213                 FT_CHAR, BASE_HEX, VALS(item_types), 0,
214                 NULL, HFILL }
215         },
216         { &hf_gopher_di_name,
217             { "Name", "gopher.directory.name",
218                 FT_STRING, BASE_NONE, NULL, 0,
219                 NULL, HFILL }
220         },
221         { &hf_gopher_di_selector,
222             { "Selector", "gopher.directory.selector",
223                 FT_STRING, BASE_NONE, NULL, 0,
224                 NULL, HFILL }
225         },
226         { &hf_gopher_di_host,
227             { "Host", "gopher.directory.host",
228                 FT_STRING, BASE_NONE, NULL, 0,
229                 NULL, HFILL }
230         },
231         { &hf_gopher_di_port,
232             { "Port", "gopher.directory.port",
233                 FT_STRING, BASE_NONE, NULL, 0,
234                 NULL, HFILL }
235         },
236 
237         { &hf_gopher_unknown,
238             { "Unknown Gopher transaction data", "gopher.unknown",
239                 FT_STRING, BASE_NONE, NULL, 0,
240                 NULL, HFILL }
241         }
242     };
243 
244     /* Setup protocol subtree array */
245     static gint *ett[] = {
246         &ett_gopher,
247         &ett_dir_item
248     };
249 
250     /* Register the protocol name and description */
251     proto_gopher = proto_register_protocol("Gopher", "Gopher", "gopher");
252 
253     /* Required function calls to register the header fields and subtrees used */
254     proto_register_field_array(proto_gopher, hf, array_length(hf));
255     proto_register_subtree_array(ett, array_length(ett));
256 
257     /* Preferences for this module are generated when registering with
258        dissector tables and need the callback function to get the
259        port range values
260      */
261     prefs_register_protocol(proto_gopher, gopher_prefs_apply);
262 }
263 
264 void
proto_reg_handoff_gopher(void)265 proto_reg_handoff_gopher(void)
266 {
267     gopher_handle = create_dissector_handle(dissect_gopher, proto_gopher);
268     dissector_add_uint_range_with_preference("tcp.port", TCP_DEFAULT_RANGE, gopher_handle);
269     gopher_prefs_apply();
270 }
271 
272 /*
273  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
274  *
275  * Local variables:
276  * c-basic-offset: 4
277  * tab-width: 8
278  * indent-tabs-mode: nil
279  * End:
280  *
281  * vi: set shiftwidth=4 tabstop=8 expandtab:
282  * :indentSize=4:tabSize=8:noTabs=true:
283  */
284 
285 
286