Get rid of the default "dissect_subopt()" suboption negotiation data
[metze/wireshark/wip.git] / packet-telnet.c
1 /* packet-telnet.c
2  * Routines for Telnet packet dissection; see RFC 854 and RFC 855
3  * Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com>
4  *
5  * $Id: packet-telnet.c,v 1.36 2003/02/24 19:25:00 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 #include <string.h>
35 #include <glib.h>
36 #include <epan/packet.h>
37 #include <epan/strutil.h>
38
39 static int proto_telnet = -1;
40
41 static gint ett_telnet = -1;
42 static gint ett_telnet_subopt = -1;
43 static gint ett_status_subopt = -1;
44 static gint ett_rcte_subopt = -1;
45 static gint ett_olw_subopt = -1;
46 static gint ett_ops_subopt = -1;
47 static gint ett_crdisp_subopt = -1;
48 static gint ett_htstops_subopt = -1;
49 static gint ett_htdisp_subopt = -1;
50 static gint ett_ffdisp_subopt = -1;
51 static gint ett_vtstops_subopt = -1;
52 static gint ett_vtdisp_subopt = -1;
53 static gint ett_lfdisp_subopt = -1;
54 static gint ett_extasc_subopt = -1;
55 static gint ett_bytemacro_subopt = -1;
56 static gint ett_det_subopt = -1;
57 static gint ett_supdupout_subopt = -1;
58 static gint ett_sendloc_subopt = -1;
59 static gint ett_termtype_subopt = -1;
60 static gint ett_tacacsui_subopt = -1;
61 static gint ett_outmark_subopt = -1;
62 static gint ett_tlocnum_subopt = -1;
63 static gint ett_tn3270reg_subopt = -1;
64 static gint ett_x3pad_subopt = -1;
65 static gint ett_naws_subopt = -1;
66 static gint ett_tspeed_subopt = -1;
67 static gint ett_rfc_subopt = -1;
68 static gint ett_linemode_subopt = -1;
69 static gint ett_xdpyloc_subopt = -1;
70 static gint ett_env_subopt = -1;
71 static gint ett_auth_subopt = -1;
72 static gint ett_enc_subopt = -1;
73 static gint ett_newenv_subopt = -1;
74 static gint ett_tn3270e_subopt = -1;
75
76 /* Some defines for Telnet */
77
78 #define TCP_PORT_TELNET                 23
79
80 #define TN_IAC   255
81 #define TN_DONT  254
82 #define TN_DO    253
83 #define TN_WONT  252
84 #define TN_WILL  251
85 #define TN_SB    250
86 #define TN_GA    249
87 #define TN_EL    248
88 #define TN_EC    247
89 #define TN_AYT   246
90 #define TN_AO    245
91 #define TN_IP    244
92 #define TN_BRK   243
93 #define TN_DM    242
94 #define TN_NOP   241
95 #define TN_SE    240
96 #define TN_EOR   239
97 #define TN_ABORT 238
98 #define TN_SUSP  237
99 #define TN_EOF   236
100
101 typedef enum {
102   NO_LENGTH,            /* option has no data, hence no length */
103   FIXED_LENGTH,         /* option always has the same length */
104   VARIABLE_LENGTH       /* option is variable-length - optlen is minimum */
105 } tn_opt_len_type;
106
107 /* Member of table of IP or TCP options. */
108 typedef struct tn_opt {
109   char  *name;                  /* name of option */
110   gint  *subtree_index;         /* pointer to subtree index for option */
111   tn_opt_len_type len_type;     /* type of option length field */
112   int   optlen;                 /* value length should be (minimum if VARIABLE) */
113   void  (*dissect)(const char *, tvbuff_t *, int, int, proto_tree *);
114                                 /* routine to dissect option */
115 } tn_opt;
116
117 static void
118 dissect_string_subopt(const char *optname, tvbuff_t *tvb, int offset, int len,
119                       proto_tree *tree)
120 {
121   guint8 cmd;
122
123   cmd = tvb_get_guint8(tvb, offset);
124   switch (cmd) {
125
126   case 0:       /* IS */
127     proto_tree_add_text(tree, tvb, offset, 1, "Here's my %s", optname);
128     offset++;
129     len--;
130     if (len > 0) {
131       proto_tree_add_text(tree, tvb, offset, len, "Value: %s",
132                           tvb_format_text(tvb, offset, len));
133     }
134     break;
135
136   case 1:       /* SEND */
137     proto_tree_add_text(tree, tvb, offset, 1, "Send your %s", optname);
138     offset++;
139     len--;
140     if (len > 0)
141       proto_tree_add_text(tree, tvb, offset, len, "Extra data");
142     break;
143
144   default:
145     proto_tree_add_text(tree, tvb, offset, 1, "Invalid %s subcommand %u",
146                         optname, cmd);
147     offset++;
148     len--;
149     if (len > 0)
150       proto_tree_add_text(tree, tvb, offset, len, "Subcommand data");
151     break;
152   }
153 }
154
155 static void
156 dissect_outmark_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
157                        int len, proto_tree *tree)
158 {
159   guint8 cmd;
160   int gs_offset, datalen;
161
162   while (len > 0) {
163     cmd = tvb_get_guint8(tvb, offset);
164     switch (cmd) {
165
166     case 6:     /* ACK */
167       proto_tree_add_text(tree, tvb, offset, 1, "ACK");
168       break;
169
170     case 21:    /* NAK */
171       proto_tree_add_text(tree, tvb, offset, 1, "NAK");
172       break;
173
174     case 'D':
175       proto_tree_add_text(tree, tvb, offset, 1, "Default");
176       break;
177
178     case 'T':
179       proto_tree_add_text(tree, tvb, offset, 1, "Top");
180       break;
181
182     case 'B':
183       proto_tree_add_text(tree, tvb, offset, 1, "Bottom");
184       break;
185
186     case 'L':
187       proto_tree_add_text(tree, tvb, offset, 1, "Left");
188       break;
189
190     case 'R':
191       proto_tree_add_text(tree, tvb, offset, 1, "Right");
192       break;
193
194     default:
195       proto_tree_add_text(tree, tvb, offset, 1, "Bogus value: %u", cmd);
196       break;
197     }
198     offset++;
199     len--;
200
201     /* Look for a GS */
202     gs_offset = tvb_find_guint8(tvb, offset, len, 29);
203     if (gs_offset == -1) {
204       /* None found - run to the end of the packet. */
205       gs_offset = offset + len;
206     }
207     datalen = gs_offset - offset;
208     if (datalen > 0) {
209       proto_tree_add_text(tree, tvb, offset, datalen, "Banner: %s",
210                           tvb_format_text(tvb, offset, datalen));
211       offset += datalen;
212       len -= datalen;
213     }
214   }
215 }
216
217 static void
218 dissect_htstops_subopt(const char *optname, tvbuff_t *tvb, int offset, int len,
219                        proto_tree *tree)
220 {
221   guint8 cmd;
222   guint8 tabval;
223
224   cmd = tvb_get_guint8(tvb, offset);
225   switch (cmd) {
226
227   case 0:       /* IS */
228     proto_tree_add_text(tree, tvb, offset, 1, "Here's my %s", optname);
229     offset++;
230     len--;
231     break;
232
233   case 1:       /* SEND */
234     proto_tree_add_text(tree, tvb, offset, 1, "Send your %s", optname);
235     offset++;
236     len--;
237     break;
238
239   default:
240     proto_tree_add_text(tree, tvb, offset, 1, "Invalid %s subcommand %u",
241                         optname, cmd);
242     offset++;
243     len--;
244     if (len > 0)
245       proto_tree_add_text(tree, tvb, offset, len, "Subcommand data");
246     return;
247   }
248
249   while (len > 0) {
250     tabval = tvb_get_guint8(tvb, offset);
251     switch (tabval) {
252
253     case 0:
254       proto_tree_add_text(tree, tvb, offset, 1,
255                           "Sender wants to handle tab stops");
256       break;
257
258     default:
259       proto_tree_add_text(tree, tvb, offset, 1,
260                           "Sender wants receiver to handle tab stop at %u",
261                           tabval);
262       break;
263
264     case 251:
265     case 252:
266     case 253:
267     case 254:
268       proto_tree_add_text(tree, tvb, offset, 1,
269                           "Invalid value: %u", tabval);
270       break;
271
272     case 255:
273       proto_tree_add_text(tree, tvb, offset, 1,
274                           "Sender wants receiver to handle tab stops");
275       break;
276     }
277     offset++;
278     len--;
279   }
280 }
281
282 static void
283 dissect_naws_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
284                     int len _U_, proto_tree *tree)
285 {
286   proto_tree_add_text(tree, tvb, offset, 2, "Width: %u",
287                       tvb_get_ntohs(tvb, offset));
288   offset += 2;
289   proto_tree_add_text(tree, tvb, offset, 2, "Height: %u",
290                       tvb_get_ntohs(tvb, offset));
291 }
292
293 static const value_string rfc_opt_vals[] = {
294         { 0, "OFF" },
295         { 1, "ON" },
296         { 2, "RESTART-ANY" },
297         { 3, "RESTART-XON" },
298         { 0, NULL }
299 };
300
301 static void
302 dissect_rfc_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
303                    int len _U_, proto_tree *tree)
304 {
305   guint8 cmd;
306
307   cmd = tvb_get_guint8(tvb, offset);
308   proto_tree_add_text(tree, tvb, offset, 2, "%s",
309                       val_to_str(cmd, rfc_opt_vals, "Unknown (%u)"));
310 }
311
312 static tn_opt options[] = {
313   {
314     "Binary Transmission",                      /* RFC 856 */
315     NULL,                                       /* no suboption negotiation */
316     NO_LENGTH,
317     0,
318     NULL
319   },
320   {
321     "Echo",                                     /* RFC 857 */
322     NULL,                                       /* no suboption negotiation */
323     NO_LENGTH,
324     0,
325     NULL
326   },
327   {    
328     "Reconnection",                             /* DOD Protocol Handbook */
329     NULL,
330     NO_LENGTH,
331     0,
332     NULL
333   },
334   {
335     "Suppress Go Ahead",                        /* RFC 858 */
336     NULL,                                       /* no suboption negotiation */
337     NO_LENGTH,
338     0,
339     NULL
340   },
341   {
342     "Approx Message Size Negotiation",          /* Ethernet spec(!) */
343     NULL,
344     NO_LENGTH,
345     0,
346     NULL
347   },
348   {
349     "Status",                                   /* RFC 859 */
350     &ett_status_subopt,
351     VARIABLE_LENGTH,
352     1,
353     NULL                                        /* XXX - fill me in */
354   },
355   {
356     "Timing Mark",                              /* RFC 860 */
357     NULL,                                       /* no suboption negotiation */
358     NO_LENGTH,
359     0,
360     NULL
361   },
362   {
363     "Remote Controlled Trans and Echo",         /* RFC 726 */
364     &ett_rcte_subopt,
365     VARIABLE_LENGTH,
366     1,
367     NULL                                        /* XXX - fill me in */
368   },
369   {
370     "Output Line Width",                        /* DOD Protocol Handbook */
371     &ett_olw_subopt,
372     VARIABLE_LENGTH,                            /* XXX - fill me in */
373     0,                                          /* XXX - fill me in */
374     NULL                                        /* XXX - fill me in */
375   },
376   {
377     "Output Page Size",                         /* DOD Protocol Handbook */
378     &ett_ops_subopt,
379     VARIABLE_LENGTH,                            /* XXX - fill me in */
380     0,                                          /* XXX - fill me in */
381     NULL                                        /* XXX - fill me in */
382   },
383   {
384     "Output Carriage-Return Disposition",       /* RFC 652 */
385     &ett_crdisp_subopt,
386     FIXED_LENGTH,
387     2,
388     NULL                                        /* XXX - fill me in */
389   },
390   {
391     "Output Horizontal Tab Stops",              /* RFC 653 */
392     &ett_htstops_subopt,
393     VARIABLE_LENGTH,
394     1,
395     dissect_htstops_subopt
396   },
397   {
398     "Output Horizontal Tab Disposition",        /* RFC 654 */
399     &ett_htdisp_subopt,
400     FIXED_LENGTH,
401     2,
402     NULL                                        /* XXX - fill me in */
403   },
404   {
405     "Output Formfeed Disposition",              /* RFC 655 */
406     &ett_ffdisp_subopt,
407     FIXED_LENGTH,
408     2,
409     NULL                                        /* XXX - fill me in */
410   },
411   {
412     "Output Vertical Tabstops",                 /* RFC 656 */
413     &ett_vtstops_subopt,
414     VARIABLE_LENGTH,
415     1,
416     NULL                                        /* XXX - fill me in */
417   },
418   {
419     "Output Vertical Tab Disposition",          /* RFC 657 */
420     &ett_vtdisp_subopt,
421     FIXED_LENGTH,
422     2,
423     NULL                                        /* XXX - fill me in */
424   },
425   {
426     "Output Linefeed Disposition",              /* RFC 658 */
427     &ett_lfdisp_subopt,
428     FIXED_LENGTH,
429     2,
430     NULL                                        /* XXX - fill me in */
431   },
432   {
433     "Extended ASCII",                           /* RFC 698 */
434     &ett_extasc_subopt,
435     FIXED_LENGTH,
436     2,
437     NULL                                        /* XXX - fill me in */
438   },
439   {
440     "Logout",                                   /* RFC 727 */
441     NULL,                                       /* no suboption negotiation */
442     NO_LENGTH,
443     0,
444     NULL
445   },
446   {
447     "Byte Macro",                               /* RFC 735 */
448     &ett_bytemacro_subopt,
449     VARIABLE_LENGTH,
450     2,
451     NULL                                        /* XXX - fill me in */
452   },
453   {
454     "Data Entry Terminal",                      /* RFC 732, RFC 1043 */
455     &ett_det_subopt,
456     VARIABLE_LENGTH,
457     2,
458     NULL                                        /* XXX - fill me in */
459   },
460   {
461     "SUPDUP",                                   /* RFC 734, RFC 736 */
462     NULL,                                       /* no suboption negotiation */
463     NO_LENGTH,
464     0,
465     NULL
466   },
467   {
468     "SUPDUP Output",                            /* RFC 749 */
469     &ett_supdupout_subopt,
470     VARIABLE_LENGTH,
471     1,
472     NULL                                        /* XXX - fill me in */
473   },
474   {
475     "Send Location",                            /* RFC 779 */
476     &ett_sendloc_subopt,
477     VARIABLE_LENGTH,
478     0,
479     NULL                                        /* XXX - fill me in */
480   },
481   {
482     "Terminal Type",                            /* RFC 1091 */
483     &ett_termtype_subopt,
484     VARIABLE_LENGTH,
485     1,
486     dissect_string_subopt
487   },
488   {
489     "End of Record",                            /* RFC 885 */
490     NULL,                                       /* no suboption negotiation */
491     NO_LENGTH,
492     0,
493     NULL
494   },
495   {
496     "TACACS User Identification",               /* RFC 927 */
497     &ett_tacacsui_subopt,
498     FIXED_LENGTH,
499     4,
500     NULL                                        /* XXX - fill me in */
501   },
502   {
503     "Output Marking",                           /* RFC 933 */
504     &ett_outmark_subopt,
505     VARIABLE_LENGTH,
506     1,
507     dissect_outmark_subopt,
508   },
509   {
510     "Terminal Location Number",                 /* RFC 946 */
511     &ett_tlocnum_subopt,
512     VARIABLE_LENGTH,
513     1,
514     NULL                                        /* XXX - fill me in */
515   },
516   {
517     "Telnet 3270 Regime",                       /* RFC 1041 */
518     &ett_tn3270reg_subopt,
519     VARIABLE_LENGTH,
520     1,
521     NULL                                        /* XXX - fill me in */
522   },
523   {
524     "X.3 PAD",                                  /* RFC 1053 */
525     &ett_x3pad_subopt,
526     VARIABLE_LENGTH,
527     1,
528     NULL                                        /* XXX - fill me in */
529   },
530   {
531     "Negotiate About Window Size",              /* RFC 1073, DW183 */
532     &ett_naws_subopt,
533     FIXED_LENGTH,
534     4,
535     dissect_naws_subopt
536   },
537   {
538     "Terminal Speed",                           /* RFC 1079 */
539     &ett_tspeed_subopt,
540     VARIABLE_LENGTH,
541     1,
542     NULL                                        /* XXX - fill me in */
543   },
544   {
545     "Remote Flow Control",                      /* RFC 1372 */
546     &ett_rfc_subopt,
547     FIXED_LENGTH,
548     1,
549     dissect_rfc_subopt
550   },
551   {
552     "Linemode",                                 /* RFC 1184 */
553     &ett_linemode_subopt,
554     VARIABLE_LENGTH,
555     1,
556     NULL                                        /* XXX - fill me in */
557   },
558   {
559     "X Display Location",                       /* RFC 1096 */
560     &ett_xdpyloc_subopt,
561     VARIABLE_LENGTH,
562     1,
563     dissect_string_subopt
564   },
565   {
566     "Environment Option",                       /* RFC 1408, RFC 1571 */
567     &ett_env_subopt,
568     VARIABLE_LENGTH,
569     1,
570     NULL                                        /* XXX - fill me in */
571   },
572   {
573     "Authentication Option",                    /* RFC 2941 */
574     &ett_auth_subopt,
575     VARIABLE_LENGTH,
576     1,
577     NULL                                        /* XXX - fill me in */
578   },
579   {
580     "Encryption Option",                        /* RFC 2946 */
581     &ett_enc_subopt,
582     VARIABLE_LENGTH,
583     1,
584     NULL                                        /* XXX - fill me in */
585   },
586   {
587     "New Environment Option",                   /* RFC 1572 */
588     &ett_newenv_subopt,
589     VARIABLE_LENGTH,
590     1,
591     NULL                                        /* XXX - fill me in */
592   },
593   {
594     "TN3270E",                                  /* RFC 1647 */
595     &ett_tn3270e_subopt,
596     VARIABLE_LENGTH,
597     1,
598     NULL                                        /* XXX - fill me in */
599   },
600 };
601
602 #define NOPTIONS        (sizeof options / sizeof options[0])
603
604 static int
605 telnet_sub_option(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
606 {
607   proto_tree *ti, *option_tree;
608   int offset = start_offset;
609   guint8 opt_byte;
610   int subneg_len;
611   const char *opt;
612   gint ett;
613   int iac_offset;
614   guint len;
615   void (*dissect)(const char *, tvbuff_t *, int, int, proto_tree *);
616
617   offset += 2;  /* skip IAC and SB */
618
619   /* Get the option code */
620   opt_byte = tvb_get_guint8(tvb, offset);
621   if (opt_byte > NOPTIONS) {
622     opt = "<unknown option>";
623     ett = ett_telnet_subopt;
624     dissect = NULL;
625   } else {
626     opt = options[opt_byte].name;
627     if (options[opt_byte].subtree_index != NULL)
628       ett = *(options[opt_byte].subtree_index);
629     else
630       ett = ett_telnet_subopt;
631     dissect = options[opt_byte].dissect;
632   }
633   offset++;
634
635   /* Search for an IAC. */
636   len = tvb_length_remaining(tvb, offset);
637   iac_offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
638   if (iac_offset == -1) {
639     /* None found - run to the end of the packet. */
640     offset += len;
641   } else
642     offset = iac_offset;
643
644   subneg_len = offset - start_offset;
645
646   ti = proto_tree_add_text(telnet_tree, tvb, start_offset, subneg_len,
647                            "Suboption Begin: %s", opt);
648   option_tree = proto_item_add_subtree(ti, ett);
649   start_offset += 3;    /* skip IAC, SB, and option code */
650   subneg_len -= 3;
651
652   if (subneg_len > 0) {
653     switch (options[opt_byte].len_type) {
654
655     case NO_LENGTH:
656       /* There isn't supposed to *be* sub-option negotiation for this. */
657       proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
658                           "Bogus suboption data");
659       return offset;
660
661     case FIXED_LENGTH:
662       /* Make sure the length is what it's supposed to be. */
663       if (subneg_len != options[opt_byte].optlen) {
664         proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
665                           "Suboption parameter length is %d, should be %d",
666                           subneg_len, options[opt_byte].optlen);
667         return offset;
668       }
669       break;
670
671     case VARIABLE_LENGTH:
672       /* Make sure the length is greater than the minimum. */
673       if (subneg_len < options[opt_byte].optlen) {
674         proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
675                             "Suboption parameter length is %d, should be at least %d",
676                             subneg_len, options[opt_byte].optlen);
677         return offset;
678       }
679       break;
680     }
681
682     /* Now dissect the suboption parameters. */
683     if (dissect != NULL) {
684       /* We have a dissector for this suboption's parameters; call it. */
685       (*dissect)(opt, tvb, start_offset, subneg_len, option_tree);
686     } else {
687       /* We don't have a dissector for them; just show them as data. */
688       proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
689                           "Option data");
690     }
691   }
692   return offset;
693 }
694
695 static int
696 telnet_will_wont_do_dont(proto_tree *telnet_tree, tvbuff_t *tvb,
697                         int start_offset, char *type)
698 {
699   int offset = start_offset;
700   guint8 opt_byte;
701   const char *opt;
702
703   offset += 2;  /* skip IAC and WILL,WONT,DO,DONT} */
704   opt_byte = tvb_get_guint8(tvb, offset);
705   if (opt_byte > NOPTIONS)
706     opt = "<unknown option>";
707   else
708     opt = options[opt_byte].name;
709   offset++;
710
711   proto_tree_add_text(telnet_tree, tvb, start_offset, 3,
712                         "Command: %s %s", type, opt);
713   return offset;
714 }
715
716 static int
717 telnet_command(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
718 {
719   int offset = start_offset;
720   guchar optcode;
721
722   offset += 1;  /* skip IAC */
723   optcode = tvb_get_guint8(tvb, offset);
724   offset++;
725   switch(optcode) {
726
727   case TN_EOF:
728     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
729                         "Command: End of File");
730     break;
731
732   case TN_SUSP:
733     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
734                         "Command: Suspend Current Process");
735     break;
736
737   case TN_ABORT:
738     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
739                         "Command: Abort Process");
740     break;
741
742   case TN_EOR:
743     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
744                         "Command: End of Record");
745     break;
746
747   case TN_SE:
748     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
749                         "Command: Suboption End");
750     break;
751
752   case TN_NOP:
753     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
754                         "Command: No Operation");
755     break;
756
757   case TN_DM:
758     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
759                         "Command: Data Mark");
760     break;
761
762   case TN_BRK:
763     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
764                         "Command: Break");
765     break;
766
767   case TN_IP:
768     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
769                         "Command: Interrupt Process");
770     break;
771
772   case TN_AO:
773     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
774                         "Command: Abort Output");
775     break;
776
777   case TN_AYT:
778     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
779                         "Command: Are You There?");
780     break;
781
782   case TN_EC:
783     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
784                         "Command: Escape Character");
785     break;
786
787   case TN_EL:
788     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
789                         "Command: Erase Line");
790     break;
791
792   case TN_GA:
793     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
794                         "Command: Go Ahead");
795     break;
796
797   case TN_SB:
798     offset = telnet_sub_option(telnet_tree, tvb, start_offset);
799     break;
800
801   case TN_WILL:
802     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
803                                         "Will");
804     break;
805
806   case TN_WONT:
807     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
808                                         "Won't");
809     break;
810
811   case TN_DO:
812     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
813                                         "Do");
814     break;
815
816   case TN_DONT:
817     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
818                                         "Don't");
819     break;
820
821   default:
822     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
823                         "Command: Unknown (0x%02x)", optcode);
824     break;
825   }
826
827   return offset;
828 }
829
830 static void
831 telnet_add_text(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
832 {
833   gint next_offset;
834   int linelen;
835   guint8 c;
836   gboolean last_char_was_cr;
837
838   while (len != 0 && tvb_offset_exists(tvb, offset)) {
839     /*
840      * Find the end of the line.
841      */
842     linelen = tvb_find_line_end(tvb, offset, len, &next_offset, FALSE);
843     len -= next_offset - offset;        /* subtract out the line's characters */
844
845     /*
846      * In Telnet, CR NUL is the way you send a CR by itself in the
847      * default ASCII mode; don't treat CR by itself as a line ending,
848      * treat only CR NUL, CR LF, or LF by itself as a line ending.
849      */
850     if (next_offset == offset + linelen + 1 && len >= 1) {
851       /*
852        * Well, we saw a one-character line ending, so either it's a CR
853        * or an LF; we have at least two characters left, including the
854        * CR.
855        *
856        * If the line ending is a CR, skip all subsequent CRs; at
857        * least one capture appeared to have multiple CRs at the end of
858        * a line.
859        */
860       if (tvb_get_guint8(tvb, offset + linelen) == '\r') {
861         last_char_was_cr = TRUE;
862         while (len != 0 && tvb_offset_exists(tvb, next_offset)) {
863           c = tvb_get_guint8(tvb, next_offset);
864           next_offset++;        /* skip over that character */
865           len--;
866           if (c == '\n' || (c == '\0' && last_char_was_cr)) {
867             /*
868              * LF is a line ending, whether preceded by CR or not.
869              * NUL is a line ending if preceded by CR.
870              */
871             break;
872           }
873           last_char_was_cr = (c == '\r');
874         }
875       }
876     }
877
878     /*
879      * Now compute the length of the line *including* the end-of-line
880      * indication, if any; we display it all.
881      */
882     linelen = next_offset - offset;
883
884     proto_tree_add_text(tree, tvb, offset, linelen,
885                         "Data: %s",
886                         tvb_format_text(tvb, offset, linelen));
887     offset = next_offset;
888   }
889 }
890
891 static void
892 dissect_telnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
893 {
894         proto_tree      *telnet_tree, *ti;
895
896         if (check_col(pinfo->cinfo, COL_PROTOCOL))
897                 col_set_str(pinfo->cinfo, COL_PROTOCOL, "TELNET");
898
899         if (check_col(pinfo->cinfo, COL_INFO))
900                 col_add_fstr(pinfo->cinfo, COL_INFO, "Telnet Data ...");
901
902         if (tree) {
903           gint offset = 0;
904           guint len;
905           int data_len;
906           gint iac_offset;
907
908           ti = proto_tree_add_item(tree, proto_telnet, tvb, offset, -1, FALSE);
909           telnet_tree = proto_item_add_subtree(ti, ett_telnet);
910
911           /*
912            * Scan through the buffer looking for an IAC byte.
913            */
914           while ((len = tvb_length_remaining(tvb, offset)) > 0) {
915             iac_offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
916             if (iac_offset != -1) {
917               /*
918                * We found an IAC byte.
919                * If there's any data before it, add that data to the
920                * tree, a line at a time.
921                */
922               data_len = iac_offset - offset;
923               if (data_len > 0)
924                 telnet_add_text(telnet_tree, tvb, offset, data_len);
925
926               /*
927                * Now interpret the command.
928                */
929               offset = telnet_command(telnet_tree, tvb, iac_offset);
930             }
931             else {
932               /*
933                * We found no IAC byte, so what remains in the buffer
934                * is the last of the data in the packet.
935                * Add it to the tree, a line at a time, and then quit.
936                */
937               telnet_add_text(telnet_tree, tvb, offset, len);
938               break;
939             }
940           }
941         }
942 }
943
944 void
945 proto_register_telnet(void)
946 {
947 /*        static hf_register_info hf[] = {
948                 { &variable,
949                 { "Name",           "telnet.abbreviation", TYPE, VALS_POINTER }},
950         };*/
951         static gint *ett[] = {
952                 &ett_telnet,
953                 &ett_telnet_subopt,
954                 &ett_status_subopt,
955                 &ett_rcte_subopt,
956                 &ett_olw_subopt,
957                 &ett_ops_subopt,
958                 &ett_crdisp_subopt,
959                 &ett_htstops_subopt,
960                 &ett_htdisp_subopt,
961                 &ett_ffdisp_subopt,
962                 &ett_vtstops_subopt,
963                 &ett_vtdisp_subopt,
964                 &ett_lfdisp_subopt,
965                 &ett_extasc_subopt,
966                 &ett_bytemacro_subopt,
967                 &ett_det_subopt,
968                 &ett_supdupout_subopt,
969                 &ett_sendloc_subopt,
970                 &ett_termtype_subopt,
971                 &ett_tacacsui_subopt,
972                 &ett_outmark_subopt,
973                 &ett_tlocnum_subopt,
974                 &ett_tn3270reg_subopt,
975                 &ett_x3pad_subopt,
976                 &ett_naws_subopt,
977                 &ett_tspeed_subopt,
978                 &ett_rfc_subopt,
979                 &ett_linemode_subopt,
980                 &ett_xdpyloc_subopt,
981                 &ett_env_subopt,
982                 &ett_auth_subopt,
983                 &ett_enc_subopt,
984                 &ett_newenv_subopt,
985                 &ett_tn3270e_subopt,
986         };
987
988         proto_telnet = proto_register_protocol("Telnet", "TELNET", "telnet");
989  /*       proto_register_field_array(proto_telnet, hf, array_length(hf));*/
990         proto_register_subtree_array(ett, array_length(ett));
991 }
992
993 void
994 proto_reg_handoff_telnet(void)
995 {
996         dissector_handle_t telnet_handle;
997
998         telnet_handle = create_dissector_handle(dissect_telnet, proto_telnet);
999         dissector_add("tcp.port", TCP_PORT_TELNET, telnet_handle);
1000 }