Put the "-s" option in the SYNOPSIS section.
[obnox/wireshark/wip.git] / packet-telnet.c
1 /* packet-telnet.c
2  * Routines for telnet packet dissection
3  * Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com>
4  *
5  * $Id: packet-telnet.c,v 1.27 2001/12/10 00:25:40 guy Exp $
6  *
7  * Ethereal - Network traffic analyzer
8  * By Gerald Combs <gerald@ethereal.com>
9  * Copyright 1998 Gerald Combs
10  *
11  * Copied from packet-pop.c
12  * 
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  * 
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  * 
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <stdio.h>
33
34 #ifdef HAVE_SYS_TYPES_H
35 # include <sys/types.h>
36 #endif
37
38 #ifdef HAVE_NETINET_IN_H
39 # include <netinet/in.h>
40 #endif
41
42 #include <string.h>
43 #include <glib.h>
44 #include "packet.h"
45 #include "strutil.h"
46
47 static int proto_telnet = -1;
48
49 static gint ett_telnet = -1;
50 static gint ett_telnet_subopt = -1;
51
52 /* Some defines for Telnet */
53
54 #define TCP_PORT_TELNET                 23
55
56 #define TN_IAC   255
57 #define TN_DONT  254
58 #define TN_DO    253
59 #define TN_WONT  252
60 #define TN_WILL  251
61 #define TN_SB    250
62 #define TN_GA    249
63 #define TN_EL    248
64 #define TN_EC    247
65 #define TN_AYT   246
66 #define TN_AO    245
67 #define TN_IP    244
68 #define TN_BRK   243
69 #define TN_DM    242
70 #define TN_NOP   241
71 #define TN_SE    240
72 #define TN_EOR   239
73 #define TN_ABORT 238
74 #define TN_SUSP  237
75 #define TN_EOF   236
76
77 static const char *options[] = {
78   "Binary Transmission",
79   "Echo",
80   "Reconnection",
81   "Suppress Go Ahead",
82   "Approx Message Size Negotiation",
83   "Status",
84   "Timing Mark",
85   "Remote Controlled Trans and Echo",
86   "Output Line Width",
87   "Output Page Size",
88   "Output Carriage-Return Disposition",
89   "Output Horizontal Tab Stops",
90   "Output Horizontal Tab Disposition",
91   "Output Formfeed Disposition",
92   "Output Vertical Tabstops",
93   "Output Vertical Tab Disposition",
94   "Output Linefeed Disposition",
95   "Extended ASCII",
96   "Logout",
97   "Byte Macro",
98   "Data Entry Terminal",
99   "SUPDUP",
100   "SUPDUP Output",
101   "Send Location",
102   "Terminal Type",
103   "End of Record",
104   "TACACS User Identification",
105   "Output Marking",
106   "Terminal Location Number",
107   "Telnet 3270 Regime",
108   "X.3 PAD",
109   "Negotiate About Window Size",
110   "Terminal Speed",
111   "Remote Flow Control",
112   "Linemode",
113   "X Display Location",
114   "Environment Option",
115   "Authentication Option",
116   "Encryption Option",
117   "New Environment Option",
118   "TN3270E"
119 };
120
121 #define NOPTIONS        (sizeof options / sizeof options[0])
122
123 static int
124 telnet_sub_option(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
125 {
126   proto_tree *ti, *option_tree;
127   int offset = start_offset;
128   guint8 opt_byte;
129   int subneg_len, req;
130   const u_char *opt;
131   guint len;
132
133   offset += 2;  /* skip IAC and SB */
134
135   /* Figure out the option and type */
136   opt_byte = tvb_get_guint8(tvb, offset);
137   if (opt_byte > NOPTIONS)
138     opt = "<unknown option>";
139   else
140     opt = options[opt_byte];
141   offset++;
142   req = tvb_get_guint8(tvb, offset);
143   offset++;
144
145   /* Search for an IAC. */
146   len = tvb_length_remaining(tvb, offset);
147   offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
148   if (offset == -1) {
149     /* None found - run to the end of the packet. */
150     offset += len;
151   }
152
153   subneg_len = offset - start_offset;
154
155   if (subneg_len > 0) {
156       ti = proto_tree_add_text(telnet_tree, tvb, start_offset, subneg_len,
157                 "Suboption Begin: %s", opt);
158
159       option_tree = proto_item_add_subtree(ti, ett_telnet_subopt);
160
161       proto_tree_add_text(option_tree, tvb, start_offset + 2, 2,
162                 "%s %s", (req ? "Send your" : "Here's my"), opt);
163
164       if (req == 0) {  /* Add the value */
165         proto_tree_add_text(option_tree, tvb, start_offset + 4, subneg_len - 4,
166         "Value: %s", tvb_format_text(tvb, start_offset + 4, subneg_len - 4));
167       }
168   }
169   return offset;
170 }
171
172 static int
173 telnet_will_wont_do_dont(proto_tree *telnet_tree, tvbuff_t *tvb,
174                         int start_offset, char *type)
175 {
176   int offset = start_offset;
177   guint8 opt_byte;
178   const char *opt;
179
180   offset += 2;  /* skip IAC and WILL,WONT,DO,DONT} */
181   opt_byte = tvb_get_guint8(tvb, offset);
182   if (opt_byte > NOPTIONS)
183     opt = "<unknown option>";
184   else
185     opt = options[opt_byte];
186   offset++;
187                       
188   proto_tree_add_text(telnet_tree, tvb, start_offset, 3,
189                         "Command: %s %s", type, opt);
190   return offset;
191 }
192
193 static int
194 telnet_command(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
195 {
196   int offset = start_offset;
197   u_char optcode;
198   
199   offset += 1;  /* skip IAC */
200   optcode = tvb_get_guint8(tvb, offset);
201   offset++;
202   switch(optcode) {
203
204   case TN_EOF:
205     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
206                         "Command: End of File");
207     break;
208
209   case TN_SUSP:
210     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
211                         "Command: Suspend Current Process");
212     break;
213
214   case TN_ABORT:
215     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
216                         "Command: Abort Process");
217     break;
218
219   case TN_EOR:
220     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
221                         "Command: End of Record");
222     break;
223
224   case TN_SE:
225     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
226                         "Command: Suboption End");
227     break;
228
229   case TN_NOP:
230     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
231                         "Command: No Operation");
232     break;
233
234   case TN_DM:
235     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
236                         "Command: Data Mark");
237     break;
238
239   case TN_BRK:
240     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
241                         "Command: Break");
242     break;
243
244   case TN_IP:
245     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
246                         "Command: Interrupt Process");
247     break;
248
249   case TN_AO:
250     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
251                         "Command: Abort Output");
252     break;
253
254   case TN_AYT:
255     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
256                         "Command: Are You There?");
257     break;
258
259   case TN_EC:
260     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
261                         "Command: Escape Character");
262     break;
263
264   case TN_EL:
265     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
266                         "Command: Erase Line");
267     break;
268
269   case TN_GA:
270     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
271                         "Command: Go Ahead");
272     break;
273
274   case TN_SB:
275     offset = telnet_sub_option(telnet_tree, tvb, start_offset);
276     break;
277
278   case TN_WILL:
279     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
280                                         "Will");
281     break;
282
283   case TN_WONT:
284     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
285                                         "Won't");
286     break;
287
288   case TN_DO:
289     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
290                                         "Do");
291     break;
292
293   case TN_DONT:
294     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
295                                         "Don't");
296     break;
297   }
298
299   return offset;
300 }
301
302 static void
303 telnet_add_text(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
304 {
305   gint next_offset;
306   int linelen;
307   guint8 c;
308   gboolean last_char_was_cr;
309
310   while (len != 0 && tvb_offset_exists(tvb, offset)) {
311     /*
312      * Find the end of the line.
313      */
314     linelen = tvb_find_line_end(tvb, offset, len, &next_offset);
315     len -= next_offset - offset;        /* subtract out the line's characters */
316
317     /*
318      * In Telnet, CR NUL is the way you send a CR by itself in the
319      * default ASCII mode; don't treat CR by itself as a line ending,
320      * treat only CR NUL, CR LF, or LF by itself as a line ending.
321      */
322     if (next_offset == offset + linelen + 1 && len >= 1) {
323       /*
324        * Well, we saw a one-character line ending, so either it's a CR
325        * or an LF; we have at least two characters left, including the
326        * CR.
327        *
328        * If the line ending is a CR, skip all subsequent CRs; at
329        * least one capture appeared to have multiple CRs at the end of
330        * a line.
331        */
332       if (tvb_get_guint8(tvb, offset + linelen) == '\r') {
333         last_char_was_cr = TRUE;
334         while (len != 0 && tvb_offset_exists(tvb, next_offset)) {
335           c = tvb_get_guint8(tvb, next_offset);
336           next_offset++;        /* skip over that character */
337           len--;
338           if (c == '\n' || (c == '\0' && last_char_was_cr)) {
339             /*
340              * LF is a line ending, whether preceded by CR or not.
341              * NUL is a line ending if preceded by CR.
342              */
343             break;
344           }
345           last_char_was_cr = (c == '\r');
346         }
347       }
348     }
349
350     /*
351      * Now compute the length of the line *including* the end-of-line
352      * indication, if any; we display it all.
353      */
354     linelen = next_offset - offset;
355
356     proto_tree_add_text(tree, tvb, offset, linelen,
357                         "Data: %s",
358                         tvb_format_text(tvb, offset, linelen));
359     offset = next_offset;
360   }
361 }
362
363 static void
364 dissect_telnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
365 {
366         proto_tree      *telnet_tree, *ti;
367
368         if (check_col(pinfo->cinfo, COL_PROTOCOL))
369                 col_set_str(pinfo->cinfo, COL_PROTOCOL, "TELNET");
370
371         if (check_col(pinfo->cinfo, COL_INFO))
372                 col_add_fstr(pinfo->cinfo, COL_INFO, "Telnet Data ...");
373
374         if (tree) {
375           gint offset = 0;
376           guint len;
377           int data_len;
378           gint iac_offset;
379
380           ti = proto_tree_add_item(tree, proto_telnet, tvb, offset,
381             tvb_length_remaining(tvb, offset), FALSE);
382           telnet_tree = proto_item_add_subtree(ti, ett_telnet);
383
384           /*
385            * Scan through the buffer looking for an IAC byte.
386            */
387           while ((len = tvb_length_remaining(tvb, offset)) > 0) {
388             iac_offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
389             if (iac_offset != -1) {
390               /*
391                * We found an IAC byte.
392                * If there's any data before it, add that data to the
393                * tree, a line at a time.
394                */
395               data_len = iac_offset - offset;
396               if (data_len > 0)
397                 telnet_add_text(telnet_tree, tvb, offset, data_len);
398               
399               /*
400                * Now interpret the command.
401                */
402               offset = telnet_command(telnet_tree, tvb, iac_offset);
403             }
404             else {
405               /*
406                * We found no IAC byte, so what remains in the buffer
407                * is the last of the data in the packet.
408                * Add it to the tree, a line at a time, and then quit.
409                */
410               telnet_add_text(telnet_tree, tvb, offset, len);
411               break;
412             }
413           }
414         }
415 }
416
417 void
418 proto_register_telnet(void)
419 {
420 /*        static hf_register_info hf[] = {
421                 { &variable,
422                 { "Name",           "telnet.abbreviation", TYPE, VALS_POINTER }},
423         };*/
424         static gint *ett[] = {
425                 &ett_telnet,
426                 &ett_telnet_subopt,
427         };
428
429         proto_telnet = proto_register_protocol("Telnet", "TELNET", "telnet");
430  /*       proto_register_field_array(proto_telnet, hf, array_length(hf));*/
431         proto_register_subtree_array(ett, array_length(ett));
432 }
433
434 void
435 proto_reg_handoff_telnet(void)
436 {
437         dissector_handle_t telnet_handle;
438
439         telnet_handle = create_dissector_handle(dissect_telnet, proto_telnet);
440         dissector_add("tcp.port", TCP_PORT_TELNET, telnet_handle);
441 }