Frame numbers are unsigned.
[obnox/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.40 2003/04/30 02:35:20 gerald 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 static gint ett_xauth_subopt = -1;
76 static gint ett_charset_subopt = -1;
77 static gint ett_rsp_subopt = -1;
78 static gint ett_comport_subopt = -1;
79
80
81 /* Some defines for Telnet */
82
83 #define TCP_PORT_TELNET                 23
84
85 #define TN_IAC   255
86 #define TN_DONT  254
87 #define TN_DO    253
88 #define TN_WONT  252
89 #define TN_WILL  251
90 #define TN_SB    250
91 #define TN_GA    249
92 #define TN_EL    248
93 #define TN_EC    247
94 #define TN_AYT   246
95 #define TN_AO    245
96 #define TN_IP    244
97 #define TN_BRK   243
98 #define TN_DM    242
99 #define TN_NOP   241
100 #define TN_SE    240
101 #define TN_EOR   239
102 #define TN_ABORT 238
103 #define TN_SUSP  237
104 #define TN_EOF   236
105
106
107 typedef enum {
108   NO_LENGTH,            /* option has no data, hence no length */
109   FIXED_LENGTH,         /* option always has the same length */
110   VARIABLE_LENGTH       /* option is variable-length - optlen is minimum */
111 } tn_opt_len_type;
112
113 /* Member of table of IP or TCP options. */
114 typedef struct tn_opt {
115   char  *name;                  /* name of option */
116   gint  *subtree_index;         /* pointer to subtree index for option */
117   tn_opt_len_type len_type;     /* type of option length field */
118   int   optlen;                 /* value length should be (minimum if VARIABLE) */
119   void  (*dissect)(const char *, tvbuff_t *, int, int, proto_tree *);
120                                 /* routine to dissect option */
121 } tn_opt;
122
123 static void
124 dissect_string_subopt(const char *optname, tvbuff_t *tvb, int offset, int len,
125                       proto_tree *tree)
126 {
127   guint8 cmd;
128
129   cmd = tvb_get_guint8(tvb, offset);
130   switch (cmd) {
131
132   case 0:       /* IS */
133     proto_tree_add_text(tree, tvb, offset, 1, "Here's my %s", optname);
134     offset++;
135     len--;
136     if (len > 0) {
137       proto_tree_add_text(tree, tvb, offset, len, "Value: %s",
138                           tvb_format_text(tvb, offset, len));
139     }
140     break;
141
142   case 1:       /* SEND */
143     proto_tree_add_text(tree, tvb, offset, 1, "Send your %s", optname);
144     offset++;
145     len--;
146     if (len > 0)
147       proto_tree_add_text(tree, tvb, offset, len, "Extra data");
148     break;
149
150   default:
151     proto_tree_add_text(tree, tvb, offset, 1, "Invalid %s subcommand %u",
152                         optname, cmd);
153     offset++;
154     len--;
155     if (len > 0)
156       proto_tree_add_text(tree, tvb, offset, len, "Subcommand data");
157     break;
158   }
159 }
160
161 static void
162 dissect_outmark_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
163                        int len, proto_tree *tree)
164 {
165   guint8 cmd;
166   int gs_offset, datalen;
167
168   while (len > 0) {
169     cmd = tvb_get_guint8(tvb, offset);
170     switch (cmd) {
171
172     case 6:     /* ACK */
173       proto_tree_add_text(tree, tvb, offset, 1, "ACK");
174       break;
175
176     case 21:    /* NAK */
177       proto_tree_add_text(tree, tvb, offset, 1, "NAK");
178       break;
179
180     case 'D':
181       proto_tree_add_text(tree, tvb, offset, 1, "Default");
182       break;
183
184     case 'T':
185       proto_tree_add_text(tree, tvb, offset, 1, "Top");
186       break;
187
188     case 'B':
189       proto_tree_add_text(tree, tvb, offset, 1, "Bottom");
190       break;
191
192     case 'L':
193       proto_tree_add_text(tree, tvb, offset, 1, "Left");
194       break;
195
196     case 'R':
197       proto_tree_add_text(tree, tvb, offset, 1, "Right");
198       break;
199
200     default:
201       proto_tree_add_text(tree, tvb, offset, 1, "Bogus value: %u", cmd);
202       break;
203     }
204     offset++;
205     len--;
206
207     /* Look for a GS */
208     gs_offset = tvb_find_guint8(tvb, offset, len, 29);
209     if (gs_offset == -1) {
210       /* None found - run to the end of the packet. */
211       gs_offset = offset + len;
212     }
213     datalen = gs_offset - offset;
214     if (datalen > 0) {
215       proto_tree_add_text(tree, tvb, offset, datalen, "Banner: %s",
216                           tvb_format_text(tvb, offset, datalen));
217       offset += datalen;
218       len -= datalen;
219     }
220   }
221 }
222
223 static void
224 dissect_htstops_subopt(const char *optname, tvbuff_t *tvb, int offset, int len,
225                        proto_tree *tree)
226 {
227   guint8 cmd;
228   guint8 tabval;
229
230   cmd = tvb_get_guint8(tvb, offset);
231   switch (cmd) {
232
233   case 0:       /* IS */
234     proto_tree_add_text(tree, tvb, offset, 1, "Here's my %s", optname);
235     offset++;
236     len--;
237     break;
238
239   case 1:       /* SEND */
240     proto_tree_add_text(tree, tvb, offset, 1, "Send your %s", optname);
241     offset++;
242     len--;
243     break;
244
245   default:
246     proto_tree_add_text(tree, tvb, offset, 1, "Invalid %s subcommand %u",
247                         optname, cmd);
248     offset++;
249     len--;
250     if (len > 0)
251       proto_tree_add_text(tree, tvb, offset, len, "Subcommand data");
252     return;
253   }
254
255   while (len > 0) {
256     tabval = tvb_get_guint8(tvb, offset);
257     switch (tabval) {
258
259     case 0:
260       proto_tree_add_text(tree, tvb, offset, 1,
261                           "Sender wants to handle tab stops");
262       break;
263
264     default:
265       proto_tree_add_text(tree, tvb, offset, 1,
266                           "Sender wants receiver to handle tab stop at %u",
267                           tabval);
268       break;
269
270     case 251:
271     case 252:
272     case 253:
273     case 254:
274       proto_tree_add_text(tree, tvb, offset, 1,
275                           "Invalid value: %u", tabval);
276       break;
277
278     case 255:
279       proto_tree_add_text(tree, tvb, offset, 1,
280                           "Sender wants receiver to handle tab stops");
281       break;
282     }
283     offset++;
284     len--;
285   }
286 }
287
288 static void
289 dissect_naws_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
290                     int len _U_, proto_tree *tree)
291 {
292   proto_tree_add_text(tree, tvb, offset, 2, "Width: %u",
293                       tvb_get_ntohs(tvb, offset));
294   offset += 2;
295   proto_tree_add_text(tree, tvb, offset, 2, "Height: %u",
296                       tvb_get_ntohs(tvb, offset));
297 }
298
299 /* BEGIN RFC-2217 (COM Port Control) Definitions */
300
301 #define TNCOMPORT_SIGNATURE             0
302 #define TNCOMPORT_SETBAUDRATE           1
303 #define TNCOMPORT_SETDATASIZE           2
304 #define TNCOMPORT_SETPARITY             3
305 #define TNCOMPORT_SETSTOPSIZE           4
306 #define TNCOMPORT_SETCONTROL            5
307 #define TNCOMPORT_NOTIFYLINESTATE       6
308 #define TNCOMPORT_NOTIFYMODEMSTATE      7
309 #define TNCOMPORT_FLOWCONTROLSUSPEND    8
310 #define TNCOMPORT_FLOWCONTROLRESUME      9
311 #define TNCOMPORT_SETLINESTATEMASK      10
312 #define TNCOMPORT_SETMODEMSTATEMASK     11
313 #define TNCOMPORT_PURGEDATA             12
314
315 /* END RFC-2217 (COM Port Control) Definitions */
316
317 static void
318 dissect_comport_subopt(const char *optname, tvbuff_t *tvb, int offset, int len,
319                        proto_tree *tree)
320 {static const char *datasizes[] = {
321     "Request",
322     "<invalid>",
323     "<invalid>",
324     "<invalid>",
325     "<invalid>",
326     "5",
327     "6",
328     "7",
329     "8"
330  };
331  static const char *parities[] = {
332     "Request",
333     "None",
334     "Odd",
335     "Even",
336     "Mark",
337     "Space"
338  };
339  static const char *stops[] = {
340     "Request",
341     "1",
342     "2",
343     "1.5"
344  };
345  static const char *control[] = {
346     "Output Flow Control Request",
347     "Output Flow: None",
348     "Output Flow: XON/XOFF",
349     "Output Flow: CTS/RTS",
350     "Break Request",
351     "Break: ON",
352     "Break: OFF",
353     "DTR Request",
354     "DTR: ON",
355     "DTR: OFF",
356     "RTS Request",
357     "RTS: ON",
358     "RTS: OFF",
359     "Input Flow Control Request",
360     "Input Flow: None",
361     "Input Flow: XON/XOFF",
362     "Input Flow: CTS/RTS",
363     "Output Flow: DCD",
364     "Input Flow: DTR",
365     "Output Flow: DSR"
366  };
367  static const char *linestate_bits[] = {
368     "Data Ready",
369     "Overrun Error",
370     "Parity Error",
371     "Framing Error",
372     "Break Detected",
373     "Transfer Holding Register Empty",
374     "Transfer Shift Register Empty",
375     "Timeout Error"
376  };
377  static const char *modemstate_bits[] = {
378      "DCTS",
379      "DDSR",
380      "TERI",
381      "DDCD",
382      "CTS",
383      "DSR",
384      "RI",
385      "DCD"
386  };
387  static const char *purges[] = {
388      "Purge None",
389      "Purge RX",
390      "Purge TX",
391      "Purge RX/TX"
392  };
393   
394   guint8 cmd;
395   guint8 isservercmd;
396   char *source;
397   
398   cmd = tvb_get_guint8(tvb, offset);
399   isservercmd = cmd > 99;
400   cmd = (isservercmd) ? (cmd - 100) : cmd;
401   source = (isservercmd) ? "Server" : "Client";
402   switch (cmd) {
403
404   case TNCOMPORT_SIGNATURE:
405     len--;
406     if (len == 0) {
407         proto_tree_add_text(tree, tvb, offset, 1, "%s Requests Signature",source);
408     } else {
409         guint8 *sig = g_malloc(len);
410         gint siglen = tvb_get_nstringz0(tvb, offset+1, len, sig);
411         proto_tree_add_text(tree, tvb, offset, 1 + siglen, "%s Signature: %s",source, sig);
412         g_free(sig);
413     }
414     break;
415
416   case TNCOMPORT_SETBAUDRATE:
417     len--;
418     if (len >= 4) {
419             guint32 baud = tvb_get_ntohl(tvb, offset+1);
420         if (baud == 0) {
421             proto_tree_add_text(tree, tvb, offset, 5, "%s Requests Baud Rate",source);            
422         } else {
423             proto_tree_add_text(tree, tvb, offset, 5, "%s Baud Rate: %d",source,baud);
424         }
425     } else {
426         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Baud Rate Packet>",source);
427     }
428     break;
429     
430   case TNCOMPORT_SETDATASIZE:
431     len--;
432     if (len >= 1) {
433             guint8 datasize = tvb_get_guint8(tvb, offset+1);
434         const char *ds = (datasize > 8) ? "<invalid>" : datasizes[datasize];
435         proto_tree_add_text(tree, tvb, offset, 2, "%s Data Size: %s",source,ds);
436     } else {
437         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Data Size Packet>",source);
438     }
439     break;
440
441   case TNCOMPORT_SETPARITY:
442     len--;
443     if (len >= 1) {
444             guint8 parity = tvb_get_guint8(tvb, offset+1);
445         const char *pr = (parity > 5) ? "<invalid>" : parities[parity];
446         proto_tree_add_text(tree, tvb, offset, 2, "%s Parity: %s",source,pr);
447     } else {
448         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Parity Packet>",source);
449     }
450     break;
451     
452   case TNCOMPORT_SETSTOPSIZE:
453     len--;
454     if (len >= 1) {
455             guint8 stop = tvb_get_guint8(tvb, offset+1);
456         const char *st = (stop > 3) ? "<invalid>" : stops[stop];
457         proto_tree_add_text(tree, tvb, offset, 2, "%s Stop: %s",source,st);
458     } else {
459         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Stop Packet>",source);
460     }
461     break;
462
463   case TNCOMPORT_SETCONTROL:
464     len--;
465     if (len >= 1) {
466             guint8 crt = tvb_get_guint8(tvb, offset+1);
467         const char *c = (crt > 19) ? "Control: <invalid>" : control[crt];
468         proto_tree_add_text(tree, tvb, offset, 2, "%s %s",source,c);
469     } else {
470         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Control Packet>",source);
471     }
472     break;
473     
474   case TNCOMPORT_SETLINESTATEMASK:
475   case TNCOMPORT_NOTIFYLINESTATE:
476     len--;
477     if (len >= 1) {
478         const char *print_pattern = (cmd == TNCOMPORT_SETLINESTATEMASK) ?
479                                         "%s Set Linestate Mask: %s" : "%s Linestate: %s";
480         char ls_buffer[512];
481             guint8 ls = tvb_get_guint8(tvb, offset+1);
482         int print_count = 0;
483         int idx;
484         ls_buffer[0] = '\0';
485         for (idx = 0; idx < 8; idx++) {
486             int bit = ls & 1;
487             if (bit) {
488                 if (print_count != 0) {
489                     strcat(ls_buffer,", ");
490                 }
491                 strcat(ls_buffer,linestate_bits[idx]);
492                 print_count++;
493             }
494             ls = ls >> 1;
495         }
496         proto_tree_add_text(tree, tvb, offset, 2, print_pattern, source, ls_buffer);
497     } else {
498         const char *print_pattern = (cmd == TNCOMPORT_SETLINESTATEMASK) ?
499                                         "%s <Invalid Linestate Mask>" : "%s <Invalid Linestate Packet>";
500         proto_tree_add_text(tree, tvb, offset, 1 + len, print_pattern, source);
501     }
502     break;
503     
504   case TNCOMPORT_SETMODEMSTATEMASK:
505   case TNCOMPORT_NOTIFYMODEMSTATE:
506     len--;
507     if (len >= 1) {
508         const char *print_pattern = (cmd == TNCOMPORT_SETMODEMSTATEMASK) ?
509                                         "%s Set Modemstate Mask: %s" : "%s Modemstate: %s";
510         char ms_buffer[256];
511             guint8 ms = tvb_get_guint8(tvb, offset+1);
512         int print_count = 0;
513         int idx;
514         ms_buffer[0] = '\0';
515         for (idx = 0; idx < 8; idx++) {
516             int bit = ms & 1;
517             if (bit) {
518                 if (print_count != 0) {
519                     strcat(ms_buffer,", ");
520                 }
521                 strcat(ms_buffer,modemstate_bits[idx]);
522                 print_count++;
523             }
524             ms = ms >> 1;
525         }
526         proto_tree_add_text(tree, tvb, offset, 2, print_pattern, source, ms_buffer);
527     } else {
528         const char *print_pattern = (cmd == TNCOMPORT_SETMODEMSTATEMASK) ?
529                                          "%s <Invalid Modemstate Mask>" : "%s <Invalid Modemstate Packet>";
530         proto_tree_add_text(tree, tvb, offset, 1 + len, print_pattern, source);
531     }
532     break;
533
534   case TNCOMPORT_FLOWCONTROLSUSPEND:
535     len--;
536     proto_tree_add_text(tree, tvb, offset, 1, "%s Flow Control Suspend",source);
537     break;
538   
539   case TNCOMPORT_FLOWCONTROLRESUME:
540     len--;
541     proto_tree_add_text(tree, tvb, offset, 1, "%s Flow Control Resume",source);
542     break;
543   
544   case TNCOMPORT_PURGEDATA:
545     len--;
546     if (len >= 1) {
547             guint8 purge = tvb_get_guint8(tvb, offset+1);
548         const char *p = (purge > 3) ? "<Purge invalid>" : purges[purge];
549         proto_tree_add_text(tree, tvb, offset, 2, "%s %s",source,p);
550     } else {
551         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Purge Packet>",source);
552     }
553     break;
554   
555   default:
556     proto_tree_add_text(tree, tvb, offset, 1, "Invalid %s subcommand %u",
557                         optname, cmd);
558     offset++;
559     len--;
560     if (len > 0)
561       proto_tree_add_text(tree, tvb, offset, len, "Subcommand data");
562     return;
563   }
564
565 }
566
567 static const value_string rfc_opt_vals[] = {
568         { 0, "OFF" },
569         { 1, "ON" },
570         { 2, "RESTART-ANY" },
571         { 3, "RESTART-XON" },
572         { 0, NULL }
573 };
574
575 static void
576 dissect_rfc_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
577                    int len _U_, proto_tree *tree)
578 {
579   guint8 cmd;
580
581   cmd = tvb_get_guint8(tvb, offset);
582   proto_tree_add_text(tree, tvb, offset, 2, "%s",
583                       val_to_str(cmd, rfc_opt_vals, "Unknown (%u)"));
584 }
585
586 static tn_opt options[] = {
587   {
588     "Binary Transmission",                      /* RFC 856 */
589     NULL,                                       /* no suboption negotiation */
590     NO_LENGTH,
591     0,
592     NULL
593   },
594   {
595     "Echo",                                     /* RFC 857 */
596     NULL,                                       /* no suboption negotiation */
597     NO_LENGTH,
598     0,
599     NULL
600   },
601   {    
602     "Reconnection",                             /* DOD Protocol Handbook */
603     NULL,
604     NO_LENGTH,
605     0,
606     NULL
607   },
608   {
609     "Suppress Go Ahead",                        /* RFC 858 */
610     NULL,                                       /* no suboption negotiation */
611     NO_LENGTH,
612     0,
613     NULL
614   },
615   {
616     "Approx Message Size Negotiation",          /* Ethernet spec(!) */
617     NULL,
618     NO_LENGTH,
619     0,
620     NULL
621   },
622   {
623     "Status",                                   /* RFC 859 */
624     &ett_status_subopt,
625     VARIABLE_LENGTH,
626     1,
627     NULL                                        /* XXX - fill me in */
628   },
629   {
630     "Timing Mark",                              /* RFC 860 */
631     NULL,                                       /* no suboption negotiation */
632     NO_LENGTH,
633     0,
634     NULL
635   },
636   {
637     "Remote Controlled Trans and Echo",         /* RFC 726 */
638     &ett_rcte_subopt,
639     VARIABLE_LENGTH,
640     1,
641     NULL                                        /* XXX - fill me in */
642   },
643   {
644     "Output Line Width",                        /* DOD Protocol Handbook */
645     &ett_olw_subopt,
646     VARIABLE_LENGTH,                            /* XXX - fill me in */
647     0,                                          /* XXX - fill me in */
648     NULL                                        /* XXX - fill me in */
649   },
650   {
651     "Output Page Size",                         /* DOD Protocol Handbook */
652     &ett_ops_subopt,
653     VARIABLE_LENGTH,                            /* XXX - fill me in */
654     0,                                          /* XXX - fill me in */
655     NULL                                        /* XXX - fill me in */
656   },
657   {
658     "Output Carriage-Return Disposition",       /* RFC 652 */
659     &ett_crdisp_subopt,
660     FIXED_LENGTH,
661     2,
662     NULL                                        /* XXX - fill me in */
663   },
664   {
665     "Output Horizontal Tab Stops",              /* RFC 653 */
666     &ett_htstops_subopt,
667     VARIABLE_LENGTH,
668     1,
669     dissect_htstops_subopt
670   },
671   {
672     "Output Horizontal Tab Disposition",        /* RFC 654 */
673     &ett_htdisp_subopt,
674     FIXED_LENGTH,
675     2,
676     NULL                                        /* XXX - fill me in */
677   },
678   {
679     "Output Formfeed Disposition",              /* RFC 655 */
680     &ett_ffdisp_subopt,
681     FIXED_LENGTH,
682     2,
683     NULL                                        /* XXX - fill me in */
684   },
685   {
686     "Output Vertical Tabstops",                 /* RFC 656 */
687     &ett_vtstops_subopt,
688     VARIABLE_LENGTH,
689     1,
690     NULL                                        /* XXX - fill me in */
691   },
692   {
693     "Output Vertical Tab Disposition",          /* RFC 657 */
694     &ett_vtdisp_subopt,
695     FIXED_LENGTH,
696     2,
697     NULL                                        /* XXX - fill me in */
698   },
699   {
700     "Output Linefeed Disposition",              /* RFC 658 */
701     &ett_lfdisp_subopt,
702     FIXED_LENGTH,
703     2,
704     NULL                                        /* XXX - fill me in */
705   },
706   {
707     "Extended ASCII",                           /* RFC 698 */
708     &ett_extasc_subopt,
709     FIXED_LENGTH,
710     2,
711     NULL                                        /* XXX - fill me in */
712   },
713   {
714     "Logout",                                   /* RFC 727 */
715     NULL,                                       /* no suboption negotiation */
716     NO_LENGTH,
717     0,
718     NULL
719   },
720   {
721     "Byte Macro",                               /* RFC 735 */
722     &ett_bytemacro_subopt,
723     VARIABLE_LENGTH,
724     2,
725     NULL                                        /* XXX - fill me in */
726   },
727   {
728     "Data Entry Terminal",                      /* RFC 732, RFC 1043 */
729     &ett_det_subopt,
730     VARIABLE_LENGTH,
731     2,
732     NULL                                        /* XXX - fill me in */
733   },
734   {
735     "SUPDUP",                                   /* RFC 734, RFC 736 */
736     NULL,                                       /* no suboption negotiation */
737     NO_LENGTH,
738     0,
739     NULL
740   },
741   {
742     "SUPDUP Output",                            /* RFC 749 */
743     &ett_supdupout_subopt,
744     VARIABLE_LENGTH,
745     1,
746     NULL                                        /* XXX - fill me in */
747   },
748   {
749     "Send Location",                            /* RFC 779 */
750     &ett_sendloc_subopt,
751     VARIABLE_LENGTH,
752     0,
753     NULL                                        /* XXX - fill me in */
754   },
755   {
756     "Terminal Type",                            /* RFC 1091 */
757     &ett_termtype_subopt,
758     VARIABLE_LENGTH,
759     1,
760     dissect_string_subopt
761   },
762   {
763     "End of Record",                            /* RFC 885 */
764     NULL,                                       /* no suboption negotiation */
765     NO_LENGTH,
766     0,
767     NULL
768   },
769   {
770     "TACACS User Identification",               /* RFC 927 */
771     &ett_tacacsui_subopt,
772     FIXED_LENGTH,
773     4,
774     NULL                                        /* XXX - fill me in */
775   },
776   {
777     "Output Marking",                           /* RFC 933 */
778     &ett_outmark_subopt,
779     VARIABLE_LENGTH,
780     1,
781     dissect_outmark_subopt,
782   },
783   {
784     "Terminal Location Number",                 /* RFC 946 */
785     &ett_tlocnum_subopt,
786     VARIABLE_LENGTH,
787     1,
788     NULL                                        /* XXX - fill me in */
789   },
790   {
791     "Telnet 3270 Regime",                       /* RFC 1041 */
792     &ett_tn3270reg_subopt,
793     VARIABLE_LENGTH,
794     1,
795     NULL                                        /* XXX - fill me in */
796   },
797   {
798     "X.3 PAD",                                  /* RFC 1053 */
799     &ett_x3pad_subopt,
800     VARIABLE_LENGTH,
801     1,
802     NULL                                        /* XXX - fill me in */
803   },
804   {
805     "Negotiate About Window Size",              /* RFC 1073, DW183 */
806     &ett_naws_subopt,
807     FIXED_LENGTH,
808     4,
809     dissect_naws_subopt
810   },
811   {
812     "Terminal Speed",                           /* RFC 1079 */
813     &ett_tspeed_subopt,
814     VARIABLE_LENGTH,
815     1,
816     NULL                                        /* XXX - fill me in */
817   },
818   {
819     "Remote Flow Control",                      /* RFC 1372 */
820     &ett_rfc_subopt,
821     FIXED_LENGTH,
822     1,
823     dissect_rfc_subopt
824   },
825   {
826     "Linemode",                                 /* RFC 1184 */
827     &ett_linemode_subopt,
828     VARIABLE_LENGTH,
829     1,
830     NULL                                        /* XXX - fill me in */
831   },
832   {
833     "X Display Location",                       /* RFC 1096 */
834     &ett_xdpyloc_subopt,
835     VARIABLE_LENGTH,
836     1,
837     dissect_string_subopt
838   },
839   {
840     "Environment Option",                       /* RFC 1408, RFC 1571 */
841     &ett_env_subopt,
842     VARIABLE_LENGTH,
843     1,
844     NULL                                        /* XXX - fill me in */
845   },
846   {
847     "Authentication Option",                    /* RFC 2941 */
848     &ett_auth_subopt,
849     VARIABLE_LENGTH,
850     1,
851     NULL                                        /* XXX - fill me in */
852   },
853   {
854     "Encryption Option",                        /* RFC 2946 */
855     &ett_enc_subopt,
856     VARIABLE_LENGTH,
857     1,
858     NULL                                        /* XXX - fill me in */
859   },
860   {
861     "New Environment Option",                   /* RFC 1572 */
862     &ett_newenv_subopt,
863     VARIABLE_LENGTH,
864     1,
865     NULL                                        /* XXX - fill me in */
866   },
867   {
868     "TN3270E",                                  /* RFC 1647 */
869     &ett_tn3270e_subopt,
870     VARIABLE_LENGTH,
871     1,
872     NULL                                        /* XXX - fill me in */
873   },
874   {
875     "XAUTH",                                    /* XAUTH  */
876     &ett_xauth_subopt,
877     VARIABLE_LENGTH,
878     1,
879     NULL                                        /* XXX - fill me in */
880   },
881   {
882     "CHARSET",                                  /* CHARSET  */
883     &ett_charset_subopt,
884     VARIABLE_LENGTH,
885     1,
886     NULL                                        /* XXX - fill me in */
887   },
888   {
889     "Remote Serial Port",                               /* Remote Serial Port */
890     &ett_rsp_subopt,
891     VARIABLE_LENGTH,
892     1,
893     NULL                                        /* XXX - fill me in */
894   },
895   {
896     "COM Port Control",                                 /* RFC 2217 */
897     &ett_comport_subopt,
898     VARIABLE_LENGTH,
899     1,
900     dissect_comport_subopt
901   },
902   
903 };
904
905 #define NOPTIONS        (sizeof options / sizeof options[0])
906
907 static int
908 telnet_sub_option(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
909 {
910   proto_tree *ti, *option_tree;
911   int offset = start_offset;
912   guint8 opt_byte;
913   int subneg_len;
914   const char *opt;
915   gint ett;
916   int iac_offset;
917   guint len;
918   void (*dissect)(const char *, tvbuff_t *, int, int, proto_tree *);
919   gint cur_offset;
920   gboolean iac_found;
921
922   offset += 2;  /* skip IAC and SB */
923
924   /* Get the option code */
925   opt_byte = tvb_get_guint8(tvb, offset);
926   if (opt_byte > NOPTIONS) {
927     opt = "<unknown option>";
928     ett = ett_telnet_subopt;
929     dissect = NULL;
930   } else {
931     opt = options[opt_byte].name;
932     if (options[opt_byte].subtree_index != NULL)
933       ett = *(options[opt_byte].subtree_index);
934     else
935       ett = ett_telnet_subopt;
936     dissect = options[opt_byte].dissect;
937   }
938   offset++;
939
940   /* Search for an unescaped IAC. */
941   cur_offset = offset;
942   iac_found = FALSE;
943   len = tvb_length_remaining(tvb, offset);
944   do {
945       iac_offset = tvb_find_guint8(tvb, cur_offset, len, TN_IAC);
946       iac_found = TRUE;
947       if (iac_offset == -1) {
948         /* None found - run to the end of the packet. */
949         offset += len;
950       } else {
951         if (((guint)(iac_offset + 1) >= len) ||
952              (tvb_get_guint8(tvb, iac_offset + 1) != TN_IAC)) {
953             /* We really found a single IAC, so we're done */
954             offset = iac_offset;
955         } else {
956             /*
957              * We saw an escaped IAC, so we have to move ahead to the
958              * next section
959              */
960             iac_found = FALSE;
961             cur_offset = iac_offset + 2;
962         }
963       }
964
965   } while (!iac_found);
966   
967   subneg_len = offset - start_offset;
968
969   ti = proto_tree_add_text(telnet_tree, tvb, start_offset, subneg_len,
970                            "Suboption Begin: %s", opt);
971   option_tree = proto_item_add_subtree(ti, ett);
972   start_offset += 3;    /* skip IAC, SB, and option code */
973   subneg_len -= 3;
974
975   if (subneg_len > 0) {
976     switch (options[opt_byte].len_type) {
977
978     case NO_LENGTH:
979       /* There isn't supposed to *be* sub-option negotiation for this. */
980       proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
981                           "Bogus suboption data");
982       return offset;
983
984     case FIXED_LENGTH:
985       /* Make sure the length is what it's supposed to be. */
986       if (subneg_len != options[opt_byte].optlen) {
987         proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
988                           "Suboption parameter length is %d, should be %d",
989                           subneg_len, options[opt_byte].optlen);
990         return offset;
991       }
992       break;
993
994     case VARIABLE_LENGTH:
995       /* Make sure the length is greater than the minimum. */
996       if (subneg_len < options[opt_byte].optlen) {
997         proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
998                             "Suboption parameter length is %d, should be at least %d",
999                             subneg_len, options[opt_byte].optlen);
1000         return offset;
1001       }
1002       break;
1003     }
1004
1005     /* Now dissect the suboption parameters. */
1006     if (dissect != NULL) {
1007       /* We have a dissector for this suboption's parameters; call it. */
1008       (*dissect)(opt, tvb, start_offset, subneg_len, option_tree);
1009     } else {
1010       /* We don't have a dissector for them; just show them as data. */
1011       proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
1012                           "Option data");
1013     }
1014   }
1015   return offset;
1016 }
1017
1018 static int
1019 telnet_will_wont_do_dont(proto_tree *telnet_tree, tvbuff_t *tvb,
1020                         int start_offset, char *type)
1021 {
1022   int offset = start_offset;
1023   guint8 opt_byte;
1024   const char *opt;
1025
1026   offset += 2;  /* skip IAC and WILL,WONT,DO,DONT} */
1027   opt_byte = tvb_get_guint8(tvb, offset);
1028   if (opt_byte > NOPTIONS)
1029     opt = "<unknown option>";
1030   else
1031     opt = options[opt_byte].name;
1032   offset++;
1033
1034   proto_tree_add_text(telnet_tree, tvb, start_offset, 3,
1035                         "Command: %s %s", type, opt);
1036   return offset;
1037 }
1038
1039 static int
1040 telnet_command(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
1041 {
1042   int offset = start_offset;
1043   guchar optcode;
1044
1045   offset += 1;  /* skip IAC */
1046   optcode = tvb_get_guint8(tvb, offset);
1047   offset++;
1048   switch(optcode) {
1049
1050   case TN_EOF:
1051     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1052                         "Command: End of File");
1053     break;
1054
1055   case TN_SUSP:
1056     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1057                         "Command: Suspend Current Process");
1058     break;
1059
1060   case TN_ABORT:
1061     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1062                         "Command: Abort Process");
1063     break;
1064
1065   case TN_EOR:
1066     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1067                         "Command: End of Record");
1068     break;
1069
1070   case TN_SE:
1071     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1072                         "Command: Suboption End");
1073     break;
1074
1075   case TN_NOP:
1076     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1077                         "Command: No Operation");
1078     break;
1079
1080   case TN_DM:
1081     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1082                         "Command: Data Mark");
1083     break;
1084
1085   case TN_BRK:
1086     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1087                         "Command: Break");
1088     break;
1089
1090   case TN_IP:
1091     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1092                         "Command: Interrupt Process");
1093     break;
1094
1095   case TN_AO:
1096     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1097                         "Command: Abort Output");
1098     break;
1099
1100   case TN_AYT:
1101     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1102                         "Command: Are You There?");
1103     break;
1104
1105   case TN_EC:
1106     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1107                         "Command: Escape Character");
1108     break;
1109
1110   case TN_EL:
1111     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1112                         "Command: Erase Line");
1113     break;
1114
1115   case TN_GA:
1116     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1117                         "Command: Go Ahead");
1118     break;
1119
1120   case TN_SB:
1121     offset = telnet_sub_option(telnet_tree, tvb, start_offset);
1122     break;
1123
1124   case TN_WILL:
1125     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1126                                         "Will");
1127     break;
1128
1129   case TN_WONT:
1130     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1131                                         "Won't");
1132     break;
1133
1134   case TN_DO:
1135     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1136                                         "Do");
1137     break;
1138
1139   case TN_DONT:
1140     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1141                                         "Don't");
1142     break;
1143
1144   default:
1145     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1146                         "Command: Unknown (0x%02x)", optcode);
1147     break;
1148   }
1149
1150   return offset;
1151 }
1152
1153 static void
1154 telnet_add_text(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
1155 {
1156   gint next_offset;
1157   int linelen;
1158   guint8 c;
1159   gboolean last_char_was_cr;
1160
1161   while (len != 0 && tvb_offset_exists(tvb, offset)) {
1162     /*
1163      * Find the end of the line.
1164      */
1165     linelen = tvb_find_line_end(tvb, offset, len, &next_offset, FALSE);
1166     len -= next_offset - offset;        /* subtract out the line's characters */
1167
1168     /*
1169      * In Telnet, CR NUL is the way you send a CR by itself in the
1170      * default ASCII mode; don't treat CR by itself as a line ending,
1171      * treat only CR NUL, CR LF, or LF by itself as a line ending.
1172      */
1173     if (next_offset == offset + linelen + 1 && len >= 1) {
1174       /*
1175        * Well, we saw a one-character line ending, so either it's a CR
1176        * or an LF; we have at least two characters left, including the
1177        * CR.
1178        *
1179        * If the line ending is a CR, skip all subsequent CRs; at
1180        * least one capture appeared to have multiple CRs at the end of
1181        * a line.
1182        */
1183       if (tvb_get_guint8(tvb, offset + linelen) == '\r') {
1184         last_char_was_cr = TRUE;
1185         while (len != 0 && tvb_offset_exists(tvb, next_offset)) {
1186           c = tvb_get_guint8(tvb, next_offset);
1187           next_offset++;        /* skip over that character */
1188           len--;
1189           if (c == '\n' || (c == '\0' && last_char_was_cr)) {
1190             /*
1191              * LF is a line ending, whether preceded by CR or not.
1192              * NUL is a line ending if preceded by CR.
1193              */
1194             break;
1195           }
1196           last_char_was_cr = (c == '\r');
1197         }
1198       }
1199     }
1200
1201     /*
1202      * Now compute the length of the line *including* the end-of-line
1203      * indication, if any; we display it all.
1204      */
1205     linelen = next_offset - offset;
1206
1207     proto_tree_add_text(tree, tvb, offset, linelen,
1208                         "Data: %s",
1209                         tvb_format_text(tvb, offset, linelen));
1210     offset = next_offset;
1211   }
1212 }
1213
1214 static void
1215 dissect_telnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
1216 {
1217         proto_tree      *telnet_tree, *ti;
1218
1219         if (check_col(pinfo->cinfo, COL_PROTOCOL))
1220                 col_set_str(pinfo->cinfo, COL_PROTOCOL, "TELNET");
1221
1222         if (check_col(pinfo->cinfo, COL_INFO))
1223                 col_add_fstr(pinfo->cinfo, COL_INFO, "Telnet Data ...");
1224
1225         if (tree) {
1226           gint offset = 0;
1227           guint len;
1228           int data_len;
1229           gint iac_offset;
1230
1231           ti = proto_tree_add_item(tree, proto_telnet, tvb, offset, -1, FALSE);
1232           telnet_tree = proto_item_add_subtree(ti, ett_telnet);
1233
1234           /*
1235            * Scan through the buffer looking for an IAC byte.
1236            */
1237           while ((len = tvb_length_remaining(tvb, offset)) > 0) {
1238             iac_offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
1239             if (iac_offset != -1) {
1240               /*
1241                * We found an IAC byte.
1242                * If there's any data before it, add that data to the
1243                * tree, a line at a time.
1244                */
1245               data_len = iac_offset - offset;
1246               if (data_len > 0)
1247                 telnet_add_text(telnet_tree, tvb, offset, data_len);
1248
1249               /*
1250                * Now interpret the command.
1251                */
1252               offset = telnet_command(telnet_tree, tvb, iac_offset);
1253             }
1254             else {
1255               /*
1256                * We found no IAC byte, so what remains in the buffer
1257                * is the last of the data in the packet.
1258                * Add it to the tree, a line at a time, and then quit.
1259                */
1260               telnet_add_text(telnet_tree, tvb, offset, len);
1261               break;
1262             }
1263           }
1264         }
1265 }
1266
1267 void
1268 proto_register_telnet(void)
1269 {
1270 /*        static hf_register_info hf[] = {
1271                 { &variable,
1272                 { "Name",           "telnet.abbreviation", TYPE, VALS_POINTER }},
1273         };*/
1274         static gint *ett[] = {
1275                 &ett_telnet,
1276                 &ett_telnet_subopt,
1277                 &ett_status_subopt,
1278                 &ett_rcte_subopt,
1279                 &ett_olw_subopt,
1280                 &ett_ops_subopt,
1281                 &ett_crdisp_subopt,
1282                 &ett_htstops_subopt,
1283                 &ett_htdisp_subopt,
1284                 &ett_ffdisp_subopt,
1285                 &ett_vtstops_subopt,
1286                 &ett_vtdisp_subopt,
1287                 &ett_lfdisp_subopt,
1288                 &ett_extasc_subopt,
1289                 &ett_bytemacro_subopt,
1290                 &ett_det_subopt,
1291                 &ett_supdupout_subopt,
1292                 &ett_sendloc_subopt,
1293                 &ett_termtype_subopt,
1294                 &ett_tacacsui_subopt,
1295                 &ett_outmark_subopt,
1296                 &ett_tlocnum_subopt,
1297                 &ett_tn3270reg_subopt,
1298                 &ett_x3pad_subopt,
1299                 &ett_naws_subopt,
1300                 &ett_tspeed_subopt,
1301                 &ett_rfc_subopt,
1302                 &ett_linemode_subopt,
1303                 &ett_xdpyloc_subopt,
1304                 &ett_env_subopt,
1305                 &ett_auth_subopt,
1306                 &ett_enc_subopt,
1307                 &ett_newenv_subopt,
1308                 &ett_tn3270e_subopt,
1309                 &ett_xauth_subopt,
1310                 &ett_charset_subopt,
1311                 &ett_rsp_subopt,
1312                 &ett_comport_subopt,
1313         };
1314
1315         proto_telnet = proto_register_protocol("Telnet", "TELNET", "telnet");
1316  /*       proto_register_field_array(proto_telnet, hf, array_length(hf));*/
1317         proto_register_subtree_array(ett, array_length(ett));
1318 }
1319
1320 void
1321 proto_reg_handoff_telnet(void)
1322 {
1323         dissector_handle_t telnet_handle;
1324
1325         telnet_handle = create_dissector_handle(dissect_telnet, proto_telnet);
1326         dissector_add("tcp.port", TCP_PORT_TELNET, telnet_handle);
1327 }