Don't use "cf_read_error_message()" when reporting errors not from
[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.41 2003/06/12 08:33:30 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 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 = tvb_get_string(tvb, offset + 1, len);
410         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s Signature: %s",source, sig);
411         g_free(sig);
412     }
413     break;
414
415   case TNCOMPORT_SETBAUDRATE:
416     len--;
417     if (len >= 4) {
418         guint32 baud = tvb_get_ntohl(tvb, offset+1);
419         if (baud == 0) {
420             proto_tree_add_text(tree, tvb, offset, 5, "%s Requests Baud Rate",source);            
421         } else {
422             proto_tree_add_text(tree, tvb, offset, 5, "%s Baud Rate: %d",source,baud);
423         }
424     } else {
425         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Baud Rate Packet>",source);
426     }
427     break;
428     
429   case TNCOMPORT_SETDATASIZE:
430     len--;
431     if (len >= 1) {
432         guint8 datasize = tvb_get_guint8(tvb, offset+1);
433         const char *ds = (datasize > 8) ? "<invalid>" : datasizes[datasize];
434         proto_tree_add_text(tree, tvb, offset, 2, "%s Data Size: %s",source,ds);
435     } else {
436         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Data Size Packet>",source);
437     }
438     break;
439
440   case TNCOMPORT_SETPARITY:
441     len--;
442     if (len >= 1) {
443         guint8 parity = tvb_get_guint8(tvb, offset+1);
444         const char *pr = (parity > 5) ? "<invalid>" : parities[parity];
445         proto_tree_add_text(tree, tvb, offset, 2, "%s Parity: %s",source,pr);
446     } else {
447         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Parity Packet>",source);
448     }
449     break;
450     
451   case TNCOMPORT_SETSTOPSIZE:
452     len--;
453     if (len >= 1) {
454         guint8 stop = tvb_get_guint8(tvb, offset+1);
455         const char *st = (stop > 3) ? "<invalid>" : stops[stop];
456         proto_tree_add_text(tree, tvb, offset, 2, "%s Stop: %s",source,st);
457     } else {
458         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Stop Packet>",source);
459     }
460     break;
461
462   case TNCOMPORT_SETCONTROL:
463     len--;
464     if (len >= 1) {
465         guint8 crt = tvb_get_guint8(tvb, offset+1);
466         const char *c = (crt > 19) ? "Control: <invalid>" : control[crt];
467         proto_tree_add_text(tree, tvb, offset, 2, "%s %s",source,c);
468     } else {
469         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Control Packet>",source);
470     }
471     break;
472     
473   case TNCOMPORT_SETLINESTATEMASK:
474   case TNCOMPORT_NOTIFYLINESTATE:
475     len--;
476     if (len >= 1) {
477         const char *print_pattern = (cmd == TNCOMPORT_SETLINESTATEMASK) ?
478                                         "%s Set Linestate Mask: %s" : "%s Linestate: %s";
479         char ls_buffer[512];
480         guint8 ls = tvb_get_guint8(tvb, offset+1);
481         int print_count = 0;
482         int idx;
483         ls_buffer[0] = '\0';
484         for (idx = 0; idx < 8; idx++) {
485             int bit = ls & 1;
486             if (bit) {
487                 if (print_count != 0) {
488                     strcat(ls_buffer,", ");
489                 }
490                 strcat(ls_buffer,linestate_bits[idx]);
491                 print_count++;
492             }
493             ls = ls >> 1;
494         }
495         proto_tree_add_text(tree, tvb, offset, 2, print_pattern, source, ls_buffer);
496     } else {
497         const char *print_pattern = (cmd == TNCOMPORT_SETLINESTATEMASK) ?
498                                         "%s <Invalid Linestate Mask>" : "%s <Invalid Linestate Packet>";
499         proto_tree_add_text(tree, tvb, offset, 1 + len, print_pattern, source);
500     }
501     break;
502     
503   case TNCOMPORT_SETMODEMSTATEMASK:
504   case TNCOMPORT_NOTIFYMODEMSTATE:
505     len--;
506     if (len >= 1) {
507         const char *print_pattern = (cmd == TNCOMPORT_SETMODEMSTATEMASK) ?
508                                         "%s Set Modemstate Mask: %s" : "%s Modemstate: %s";
509         char ms_buffer[256];
510             guint8 ms = tvb_get_guint8(tvb, offset+1);
511         int print_count = 0;
512         int idx;
513         ms_buffer[0] = '\0';
514         for (idx = 0; idx < 8; idx++) {
515             int bit = ms & 1;
516             if (bit) {
517                 if (print_count != 0) {
518                     strcat(ms_buffer,", ");
519                 }
520                 strcat(ms_buffer,modemstate_bits[idx]);
521                 print_count++;
522             }
523             ms = ms >> 1;
524         }
525         proto_tree_add_text(tree, tvb, offset, 2, print_pattern, source, ms_buffer);
526     } else {
527         const char *print_pattern = (cmd == TNCOMPORT_SETMODEMSTATEMASK) ?
528                                          "%s <Invalid Modemstate Mask>" : "%s <Invalid Modemstate Packet>";
529         proto_tree_add_text(tree, tvb, offset, 1 + len, print_pattern, source);
530     }
531     break;
532
533   case TNCOMPORT_FLOWCONTROLSUSPEND:
534     len--;
535     proto_tree_add_text(tree, tvb, offset, 1, "%s Flow Control Suspend",source);
536     break;
537   
538   case TNCOMPORT_FLOWCONTROLRESUME:
539     len--;
540     proto_tree_add_text(tree, tvb, offset, 1, "%s Flow Control Resume",source);
541     break;
542   
543   case TNCOMPORT_PURGEDATA:
544     len--;
545     if (len >= 1) {
546             guint8 purge = tvb_get_guint8(tvb, offset+1);
547         const char *p = (purge > 3) ? "<Purge invalid>" : purges[purge];
548         proto_tree_add_text(tree, tvb, offset, 2, "%s %s",source,p);
549     } else {
550         proto_tree_add_text(tree, tvb, offset, 1 + len, "%s <Invalid Purge Packet>",source);
551     }
552     break;
553   
554   default:
555     proto_tree_add_text(tree, tvb, offset, 1, "Invalid %s subcommand %u",
556                         optname, cmd);
557     offset++;
558     len--;
559     if (len > 0)
560       proto_tree_add_text(tree, tvb, offset, len, "Subcommand data");
561     return;
562   }
563
564 }
565
566 static const value_string rfc_opt_vals[] = {
567         { 0, "OFF" },
568         { 1, "ON" },
569         { 2, "RESTART-ANY" },
570         { 3, "RESTART-XON" },
571         { 0, NULL }
572 };
573
574 static void
575 dissect_rfc_subopt(const char *optname _U_, tvbuff_t *tvb, int offset,
576                    int len _U_, proto_tree *tree)
577 {
578   guint8 cmd;
579
580   cmd = tvb_get_guint8(tvb, offset);
581   proto_tree_add_text(tree, tvb, offset, 2, "%s",
582                       val_to_str(cmd, rfc_opt_vals, "Unknown (%u)"));
583 }
584
585 static tn_opt options[] = {
586   {
587     "Binary Transmission",                      /* RFC 856 */
588     NULL,                                       /* no suboption negotiation */
589     NO_LENGTH,
590     0,
591     NULL
592   },
593   {
594     "Echo",                                     /* RFC 857 */
595     NULL,                                       /* no suboption negotiation */
596     NO_LENGTH,
597     0,
598     NULL
599   },
600   {    
601     "Reconnection",                             /* DOD Protocol Handbook */
602     NULL,
603     NO_LENGTH,
604     0,
605     NULL
606   },
607   {
608     "Suppress Go Ahead",                        /* RFC 858 */
609     NULL,                                       /* no suboption negotiation */
610     NO_LENGTH,
611     0,
612     NULL
613   },
614   {
615     "Approx Message Size Negotiation",          /* Ethernet spec(!) */
616     NULL,
617     NO_LENGTH,
618     0,
619     NULL
620   },
621   {
622     "Status",                                   /* RFC 859 */
623     &ett_status_subopt,
624     VARIABLE_LENGTH,
625     1,
626     NULL                                        /* XXX - fill me in */
627   },
628   {
629     "Timing Mark",                              /* RFC 860 */
630     NULL,                                       /* no suboption negotiation */
631     NO_LENGTH,
632     0,
633     NULL
634   },
635   {
636     "Remote Controlled Trans and Echo",         /* RFC 726 */
637     &ett_rcte_subopt,
638     VARIABLE_LENGTH,
639     1,
640     NULL                                        /* XXX - fill me in */
641   },
642   {
643     "Output Line Width",                        /* DOD Protocol Handbook */
644     &ett_olw_subopt,
645     VARIABLE_LENGTH,                            /* XXX - fill me in */
646     0,                                          /* XXX - fill me in */
647     NULL                                        /* XXX - fill me in */
648   },
649   {
650     "Output Page Size",                         /* DOD Protocol Handbook */
651     &ett_ops_subopt,
652     VARIABLE_LENGTH,                            /* XXX - fill me in */
653     0,                                          /* XXX - fill me in */
654     NULL                                        /* XXX - fill me in */
655   },
656   {
657     "Output Carriage-Return Disposition",       /* RFC 652 */
658     &ett_crdisp_subopt,
659     FIXED_LENGTH,
660     2,
661     NULL                                        /* XXX - fill me in */
662   },
663   {
664     "Output Horizontal Tab Stops",              /* RFC 653 */
665     &ett_htstops_subopt,
666     VARIABLE_LENGTH,
667     1,
668     dissect_htstops_subopt
669   },
670   {
671     "Output Horizontal Tab Disposition",        /* RFC 654 */
672     &ett_htdisp_subopt,
673     FIXED_LENGTH,
674     2,
675     NULL                                        /* XXX - fill me in */
676   },
677   {
678     "Output Formfeed Disposition",              /* RFC 655 */
679     &ett_ffdisp_subopt,
680     FIXED_LENGTH,
681     2,
682     NULL                                        /* XXX - fill me in */
683   },
684   {
685     "Output Vertical Tabstops",                 /* RFC 656 */
686     &ett_vtstops_subopt,
687     VARIABLE_LENGTH,
688     1,
689     NULL                                        /* XXX - fill me in */
690   },
691   {
692     "Output Vertical Tab Disposition",          /* RFC 657 */
693     &ett_vtdisp_subopt,
694     FIXED_LENGTH,
695     2,
696     NULL                                        /* XXX - fill me in */
697   },
698   {
699     "Output Linefeed Disposition",              /* RFC 658 */
700     &ett_lfdisp_subopt,
701     FIXED_LENGTH,
702     2,
703     NULL                                        /* XXX - fill me in */
704   },
705   {
706     "Extended ASCII",                           /* RFC 698 */
707     &ett_extasc_subopt,
708     FIXED_LENGTH,
709     2,
710     NULL                                        /* XXX - fill me in */
711   },
712   {
713     "Logout",                                   /* RFC 727 */
714     NULL,                                       /* no suboption negotiation */
715     NO_LENGTH,
716     0,
717     NULL
718   },
719   {
720     "Byte Macro",                               /* RFC 735 */
721     &ett_bytemacro_subopt,
722     VARIABLE_LENGTH,
723     2,
724     NULL                                        /* XXX - fill me in */
725   },
726   {
727     "Data Entry Terminal",                      /* RFC 732, RFC 1043 */
728     &ett_det_subopt,
729     VARIABLE_LENGTH,
730     2,
731     NULL                                        /* XXX - fill me in */
732   },
733   {
734     "SUPDUP",                                   /* RFC 734, RFC 736 */
735     NULL,                                       /* no suboption negotiation */
736     NO_LENGTH,
737     0,
738     NULL
739   },
740   {
741     "SUPDUP Output",                            /* RFC 749 */
742     &ett_supdupout_subopt,
743     VARIABLE_LENGTH,
744     1,
745     NULL                                        /* XXX - fill me in */
746   },
747   {
748     "Send Location",                            /* RFC 779 */
749     &ett_sendloc_subopt,
750     VARIABLE_LENGTH,
751     0,
752     NULL                                        /* XXX - fill me in */
753   },
754   {
755     "Terminal Type",                            /* RFC 1091 */
756     &ett_termtype_subopt,
757     VARIABLE_LENGTH,
758     1,
759     dissect_string_subopt
760   },
761   {
762     "End of Record",                            /* RFC 885 */
763     NULL,                                       /* no suboption negotiation */
764     NO_LENGTH,
765     0,
766     NULL
767   },
768   {
769     "TACACS User Identification",               /* RFC 927 */
770     &ett_tacacsui_subopt,
771     FIXED_LENGTH,
772     4,
773     NULL                                        /* XXX - fill me in */
774   },
775   {
776     "Output Marking",                           /* RFC 933 */
777     &ett_outmark_subopt,
778     VARIABLE_LENGTH,
779     1,
780     dissect_outmark_subopt,
781   },
782   {
783     "Terminal Location Number",                 /* RFC 946 */
784     &ett_tlocnum_subopt,
785     VARIABLE_LENGTH,
786     1,
787     NULL                                        /* XXX - fill me in */
788   },
789   {
790     "Telnet 3270 Regime",                       /* RFC 1041 */
791     &ett_tn3270reg_subopt,
792     VARIABLE_LENGTH,
793     1,
794     NULL                                        /* XXX - fill me in */
795   },
796   {
797     "X.3 PAD",                                  /* RFC 1053 */
798     &ett_x3pad_subopt,
799     VARIABLE_LENGTH,
800     1,
801     NULL                                        /* XXX - fill me in */
802   },
803   {
804     "Negotiate About Window Size",              /* RFC 1073, DW183 */
805     &ett_naws_subopt,
806     FIXED_LENGTH,
807     4,
808     dissect_naws_subopt
809   },
810   {
811     "Terminal Speed",                           /* RFC 1079 */
812     &ett_tspeed_subopt,
813     VARIABLE_LENGTH,
814     1,
815     NULL                                        /* XXX - fill me in */
816   },
817   {
818     "Remote Flow Control",                      /* RFC 1372 */
819     &ett_rfc_subopt,
820     FIXED_LENGTH,
821     1,
822     dissect_rfc_subopt
823   },
824   {
825     "Linemode",                                 /* RFC 1184 */
826     &ett_linemode_subopt,
827     VARIABLE_LENGTH,
828     1,
829     NULL                                        /* XXX - fill me in */
830   },
831   {
832     "X Display Location",                       /* RFC 1096 */
833     &ett_xdpyloc_subopt,
834     VARIABLE_LENGTH,
835     1,
836     dissect_string_subopt
837   },
838   {
839     "Environment Option",                       /* RFC 1408, RFC 1571 */
840     &ett_env_subopt,
841     VARIABLE_LENGTH,
842     1,
843     NULL                                        /* XXX - fill me in */
844   },
845   {
846     "Authentication Option",                    /* RFC 2941 */
847     &ett_auth_subopt,
848     VARIABLE_LENGTH,
849     1,
850     NULL                                        /* XXX - fill me in */
851   },
852   {
853     "Encryption Option",                        /* RFC 2946 */
854     &ett_enc_subopt,
855     VARIABLE_LENGTH,
856     1,
857     NULL                                        /* XXX - fill me in */
858   },
859   {
860     "New Environment Option",                   /* RFC 1572 */
861     &ett_newenv_subopt,
862     VARIABLE_LENGTH,
863     1,
864     NULL                                        /* XXX - fill me in */
865   },
866   {
867     "TN3270E",                                  /* RFC 1647 */
868     &ett_tn3270e_subopt,
869     VARIABLE_LENGTH,
870     1,
871     NULL                                        /* XXX - fill me in */
872   },
873   {
874     "XAUTH",                                    /* XAUTH  */
875     &ett_xauth_subopt,
876     VARIABLE_LENGTH,
877     1,
878     NULL                                        /* XXX - fill me in */
879   },
880   {
881     "CHARSET",                                  /* CHARSET  */
882     &ett_charset_subopt,
883     VARIABLE_LENGTH,
884     1,
885     NULL                                        /* XXX - fill me in */
886   },
887   {
888     "Remote Serial Port",                               /* Remote Serial Port */
889     &ett_rsp_subopt,
890     VARIABLE_LENGTH,
891     1,
892     NULL                                        /* XXX - fill me in */
893   },
894   {
895     "COM Port Control",                                 /* RFC 2217 */
896     &ett_comport_subopt,
897     VARIABLE_LENGTH,
898     1,
899     dissect_comport_subopt
900   },
901   
902 };
903
904 #define NOPTIONS        (sizeof options / sizeof options[0])
905
906 static int
907 telnet_sub_option(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
908 {
909   proto_tree *ti, *option_tree;
910   int offset = start_offset;
911   guint8 opt_byte;
912   int subneg_len;
913   const char *opt;
914   gint ett;
915   int iac_offset;
916   guint len;
917   void (*dissect)(const char *, tvbuff_t *, int, int, proto_tree *);
918   gint cur_offset;
919   gboolean iac_found;
920
921   offset += 2;  /* skip IAC and SB */
922
923   /* Get the option code */
924   opt_byte = tvb_get_guint8(tvb, offset);
925   if (opt_byte > NOPTIONS) {
926     opt = "<unknown option>";
927     ett = ett_telnet_subopt;
928     dissect = NULL;
929   } else {
930     opt = options[opt_byte].name;
931     if (options[opt_byte].subtree_index != NULL)
932       ett = *(options[opt_byte].subtree_index);
933     else
934       ett = ett_telnet_subopt;
935     dissect = options[opt_byte].dissect;
936   }
937   offset++;
938
939   /* Search for an unescaped IAC. */
940   cur_offset = offset;
941   iac_found = FALSE;
942   len = tvb_length_remaining(tvb, offset);
943   do {
944       iac_offset = tvb_find_guint8(tvb, cur_offset, len, TN_IAC);
945       iac_found = TRUE;
946       if (iac_offset == -1) {
947         /* None found - run to the end of the packet. */
948         offset += len;
949       } else {
950         if (((guint)(iac_offset + 1) >= len) ||
951              (tvb_get_guint8(tvb, iac_offset + 1) != TN_IAC)) {
952             /* We really found a single IAC, so we're done */
953             offset = iac_offset;
954         } else {
955             /*
956              * We saw an escaped IAC, so we have to move ahead to the
957              * next section
958              */
959             iac_found = FALSE;
960             cur_offset = iac_offset + 2;
961         }
962       }
963
964   } while (!iac_found);
965   
966   subneg_len = offset - start_offset;
967
968   ti = proto_tree_add_text(telnet_tree, tvb, start_offset, subneg_len,
969                            "Suboption Begin: %s", opt);
970   option_tree = proto_item_add_subtree(ti, ett);
971   start_offset += 3;    /* skip IAC, SB, and option code */
972   subneg_len -= 3;
973
974   if (subneg_len > 0) {
975     switch (options[opt_byte].len_type) {
976
977     case NO_LENGTH:
978       /* There isn't supposed to *be* sub-option negotiation for this. */
979       proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
980                           "Bogus suboption data");
981       return offset;
982
983     case FIXED_LENGTH:
984       /* Make sure the length is what it's supposed to be. */
985       if (subneg_len != options[opt_byte].optlen) {
986         proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
987                           "Suboption parameter length is %d, should be %d",
988                           subneg_len, options[opt_byte].optlen);
989         return offset;
990       }
991       break;
992
993     case VARIABLE_LENGTH:
994       /* Make sure the length is greater than the minimum. */
995       if (subneg_len < options[opt_byte].optlen) {
996         proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
997                             "Suboption parameter length is %d, should be at least %d",
998                             subneg_len, options[opt_byte].optlen);
999         return offset;
1000       }
1001       break;
1002     }
1003
1004     /* Now dissect the suboption parameters. */
1005     if (dissect != NULL) {
1006       /* We have a dissector for this suboption's parameters; call it. */
1007       (*dissect)(opt, tvb, start_offset, subneg_len, option_tree);
1008     } else {
1009       /* We don't have a dissector for them; just show them as data. */
1010       proto_tree_add_text(option_tree, tvb, start_offset, subneg_len,
1011                           "Option data");
1012     }
1013   }
1014   return offset;
1015 }
1016
1017 static int
1018 telnet_will_wont_do_dont(proto_tree *telnet_tree, tvbuff_t *tvb,
1019                         int start_offset, char *type)
1020 {
1021   int offset = start_offset;
1022   guint8 opt_byte;
1023   const char *opt;
1024
1025   offset += 2;  /* skip IAC and WILL,WONT,DO,DONT} */
1026   opt_byte = tvb_get_guint8(tvb, offset);
1027   if (opt_byte > NOPTIONS)
1028     opt = "<unknown option>";
1029   else
1030     opt = options[opt_byte].name;
1031   offset++;
1032
1033   proto_tree_add_text(telnet_tree, tvb, start_offset, 3,
1034                         "Command: %s %s", type, opt);
1035   return offset;
1036 }
1037
1038 static int
1039 telnet_command(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
1040 {
1041   int offset = start_offset;
1042   guchar optcode;
1043
1044   offset += 1;  /* skip IAC */
1045   optcode = tvb_get_guint8(tvb, offset);
1046   offset++;
1047   switch(optcode) {
1048
1049   case TN_EOF:
1050     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1051                         "Command: End of File");
1052     break;
1053
1054   case TN_SUSP:
1055     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1056                         "Command: Suspend Current Process");
1057     break;
1058
1059   case TN_ABORT:
1060     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1061                         "Command: Abort Process");
1062     break;
1063
1064   case TN_EOR:
1065     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1066                         "Command: End of Record");
1067     break;
1068
1069   case TN_SE:
1070     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1071                         "Command: Suboption End");
1072     break;
1073
1074   case TN_NOP:
1075     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1076                         "Command: No Operation");
1077     break;
1078
1079   case TN_DM:
1080     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1081                         "Command: Data Mark");
1082     break;
1083
1084   case TN_BRK:
1085     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1086                         "Command: Break");
1087     break;
1088
1089   case TN_IP:
1090     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1091                         "Command: Interrupt Process");
1092     break;
1093
1094   case TN_AO:
1095     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1096                         "Command: Abort Output");
1097     break;
1098
1099   case TN_AYT:
1100     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1101                         "Command: Are You There?");
1102     break;
1103
1104   case TN_EC:
1105     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1106                         "Command: Escape Character");
1107     break;
1108
1109   case TN_EL:
1110     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1111                         "Command: Erase Line");
1112     break;
1113
1114   case TN_GA:
1115     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1116                         "Command: Go Ahead");
1117     break;
1118
1119   case TN_SB:
1120     offset = telnet_sub_option(telnet_tree, tvb, start_offset);
1121     break;
1122
1123   case TN_WILL:
1124     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1125                                         "Will");
1126     break;
1127
1128   case TN_WONT:
1129     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1130                                         "Won't");
1131     break;
1132
1133   case TN_DO:
1134     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1135                                         "Do");
1136     break;
1137
1138   case TN_DONT:
1139     offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
1140                                         "Don't");
1141     break;
1142
1143   default:
1144     proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
1145                         "Command: Unknown (0x%02x)", optcode);
1146     break;
1147   }
1148
1149   return offset;
1150 }
1151
1152 static void
1153 telnet_add_text(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
1154 {
1155   gint next_offset;
1156   int linelen;
1157   guint8 c;
1158   gboolean last_char_was_cr;
1159
1160   while (len != 0 && tvb_offset_exists(tvb, offset)) {
1161     /*
1162      * Find the end of the line.
1163      */
1164     linelen = tvb_find_line_end(tvb, offset, len, &next_offset, FALSE);
1165     len -= next_offset - offset;        /* subtract out the line's characters */
1166
1167     /*
1168      * In Telnet, CR NUL is the way you send a CR by itself in the
1169      * default ASCII mode; don't treat CR by itself as a line ending,
1170      * treat only CR NUL, CR LF, or LF by itself as a line ending.
1171      */
1172     if (next_offset == offset + linelen + 1 && len >= 1) {
1173       /*
1174        * Well, we saw a one-character line ending, so either it's a CR
1175        * or an LF; we have at least two characters left, including the
1176        * CR.
1177        *
1178        * If the line ending is a CR, skip all subsequent CRs; at
1179        * least one capture appeared to have multiple CRs at the end of
1180        * a line.
1181        */
1182       if (tvb_get_guint8(tvb, offset + linelen) == '\r') {
1183         last_char_was_cr = TRUE;
1184         while (len != 0 && tvb_offset_exists(tvb, next_offset)) {
1185           c = tvb_get_guint8(tvb, next_offset);
1186           next_offset++;        /* skip over that character */
1187           len--;
1188           if (c == '\n' || (c == '\0' && last_char_was_cr)) {
1189             /*
1190              * LF is a line ending, whether preceded by CR or not.
1191              * NUL is a line ending if preceded by CR.
1192              */
1193             break;
1194           }
1195           last_char_was_cr = (c == '\r');
1196         }
1197       }
1198     }
1199
1200     /*
1201      * Now compute the length of the line *including* the end-of-line
1202      * indication, if any; we display it all.
1203      */
1204     linelen = next_offset - offset;
1205
1206     proto_tree_add_text(tree, tvb, offset, linelen,
1207                         "Data: %s",
1208                         tvb_format_text(tvb, offset, linelen));
1209     offset = next_offset;
1210   }
1211 }
1212
1213 static void
1214 dissect_telnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
1215 {
1216         proto_tree      *telnet_tree, *ti;
1217
1218         if (check_col(pinfo->cinfo, COL_PROTOCOL))
1219                 col_set_str(pinfo->cinfo, COL_PROTOCOL, "TELNET");
1220
1221         if (check_col(pinfo->cinfo, COL_INFO))
1222                 col_add_fstr(pinfo->cinfo, COL_INFO, "Telnet Data ...");
1223
1224         if (tree) {
1225           gint offset = 0;
1226           guint len;
1227           int data_len;
1228           gint iac_offset;
1229
1230           ti = proto_tree_add_item(tree, proto_telnet, tvb, offset, -1, FALSE);
1231           telnet_tree = proto_item_add_subtree(ti, ett_telnet);
1232
1233           /*
1234            * Scan through the buffer looking for an IAC byte.
1235            */
1236           while ((len = tvb_length_remaining(tvb, offset)) > 0) {
1237             iac_offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
1238             if (iac_offset != -1) {
1239               /*
1240                * We found an IAC byte.
1241                * If there's any data before it, add that data to the
1242                * tree, a line at a time.
1243                */
1244               data_len = iac_offset - offset;
1245               if (data_len > 0)
1246                 telnet_add_text(telnet_tree, tvb, offset, data_len);
1247
1248               /*
1249                * Now interpret the command.
1250                */
1251               offset = telnet_command(telnet_tree, tvb, iac_offset);
1252             }
1253             else {
1254               /*
1255                * We found no IAC byte, so what remains in the buffer
1256                * is the last of the data in the packet.
1257                * Add it to the tree, a line at a time, and then quit.
1258                */
1259               telnet_add_text(telnet_tree, tvb, offset, len);
1260               break;
1261             }
1262           }
1263         }
1264 }
1265
1266 void
1267 proto_register_telnet(void)
1268 {
1269 /*        static hf_register_info hf[] = {
1270                 { &variable,
1271                 { "Name",           "telnet.abbreviation", TYPE, VALS_POINTER }},
1272         };*/
1273         static gint *ett[] = {
1274                 &ett_telnet,
1275                 &ett_telnet_subopt,
1276                 &ett_status_subopt,
1277                 &ett_rcte_subopt,
1278                 &ett_olw_subopt,
1279                 &ett_ops_subopt,
1280                 &ett_crdisp_subopt,
1281                 &ett_htstops_subopt,
1282                 &ett_htdisp_subopt,
1283                 &ett_ffdisp_subopt,
1284                 &ett_vtstops_subopt,
1285                 &ett_vtdisp_subopt,
1286                 &ett_lfdisp_subopt,
1287                 &ett_extasc_subopt,
1288                 &ett_bytemacro_subopt,
1289                 &ett_det_subopt,
1290                 &ett_supdupout_subopt,
1291                 &ett_sendloc_subopt,
1292                 &ett_termtype_subopt,
1293                 &ett_tacacsui_subopt,
1294                 &ett_outmark_subopt,
1295                 &ett_tlocnum_subopt,
1296                 &ett_tn3270reg_subopt,
1297                 &ett_x3pad_subopt,
1298                 &ett_naws_subopt,
1299                 &ett_tspeed_subopt,
1300                 &ett_rfc_subopt,
1301                 &ett_linemode_subopt,
1302                 &ett_xdpyloc_subopt,
1303                 &ett_env_subopt,
1304                 &ett_auth_subopt,
1305                 &ett_enc_subopt,
1306                 &ett_newenv_subopt,
1307                 &ett_tn3270e_subopt,
1308                 &ett_xauth_subopt,
1309                 &ett_charset_subopt,
1310                 &ett_rsp_subopt,
1311                 &ett_comport_subopt,
1312         };
1313
1314         proto_telnet = proto_register_protocol("Telnet", "TELNET", "telnet");
1315  /*       proto_register_field_array(proto_telnet, hf, array_length(hf));*/
1316         proto_register_subtree_array(ett, array_length(ett));
1317 }
1318
1319 void
1320 proto_reg_handoff_telnet(void)
1321 {
1322         dissector_handle_t telnet_handle;
1323
1324         telnet_handle = create_dissector_handle(dissect_telnet, proto_telnet);
1325         dissector_add("tcp.port", TCP_PORT_TELNET, telnet_handle);
1326 }