Browse Source

A telnet option debugger

dhasenan 2 years ago
parent
commit
1cbaa053dd
4 changed files with 237 additions and 0 deletions
  1. 8
    0
      .gitignore
  2. 15
    0
      dub.json
  3. 10
    0
      dub.selections.json
  4. 204
    0
      source/app.d

+ 8
- 0
.gitignore View File

@@ -0,0 +1,8 @@
1
+.dub
2
+docs.json
3
+__dummy.html
4
+*.o
5
+*.obj
6
+__test__*__
7
+/telnetdebugger
8
+*.log

+ 15
- 0
dub.json View File

@@ -0,0 +1,15 @@
1
+{
2
+	"name": "telnetdebugger",
3
+	"authors": [
4
+		"Christopher Wright"
5
+	],
6
+	"description": "Telnet debugging server",
7
+	"copyright": "Copyright © 2017, Christopher Wright",
8
+	"license": "MIT",
9
+	"dependencies":
10
+	{
11
+		"vibe-d": "~>0.7.30",
12
+		"openssl": "*"
13
+	},
14
+	"versions": ["VibeDefaultMain"]
15
+}

+ 10
- 0
dub.selections.json View File

@@ -0,0 +1,10 @@
1
+{
2
+	"fileVersion": 1,
3
+	"versions": {
4
+		"libasync": "0.7.9",
5
+		"libevent": "2.0.2+2.0.16",
6
+		"memutils": "0.4.8",
7
+		"openssl": "1.1.5+1.0.1g",
8
+		"vibe-d": "0.7.30"
9
+	}
10
+}

+ 204
- 0
source/app.d View File

@@ -0,0 +1,204 @@
1
+module telnetdebugger;
2
+
3
+import std.stdio;
4
+import vibe.d;
5
+import core.time;
6
+import std.datetime;
7
+import std.format;
8
+import std.experimental.logger;
9
+
10
+struct TelnetOption
11
+{
12
+    ubyte code;
13
+    string name;
14
+}
15
+
16
+enum options =
17
+[
18
+    TelnetOption(0,  "Transmit Binary"),
19
+    TelnetOption(1,  "Echo"),
20
+    TelnetOption(2,  "Reconnection"),
21
+    TelnetOption(3,  "Suppress Go Ahead"),
22
+    TelnetOption(4,  "Approx Message Size Negotiation"),
23
+    TelnetOption(5,  "Status"),
24
+    TelnetOption(6,  "Timing Mark"),
25
+    TelnetOption(7,  "Remote Controlled Trans and Echo"),
26
+    TelnetOption(8,  "Output Line Width"),
27
+    TelnetOption(9,  "Output Page Size"),
28
+    TelnetOption(10, "Negotiate About Output Carriage-Return Disposition"),
29
+    TelnetOption(11, "Negotiate About Output Horizontal Tabstops"),
30
+    TelnetOption(12, "NAOHTD, Negotiate About Output Horizontal Tab Disposition"),
31
+    TelnetOption(13, "Negotiate About Output Formfeed Disposition"),
32
+    TelnetOption(14, "Negotiate About Vertical Tabstops"),
33
+    TelnetOption(15, "Negotiate About Output Vertcial Tab Disposition"),
34
+    TelnetOption(16, "Negotiate About Output Linefeed Disposition"),
35
+    TelnetOption(17, "Extended ASCII"),
36
+    TelnetOption(18, "Logout"),
37
+    TelnetOption(19, "Byte Macro"),
38
+    TelnetOption(20, "Data Entry Terminal"),
39
+    TelnetOption(21, "SUPDUP"),
40
+    TelnetOption(22, "SUPDUP Output"),
41
+    TelnetOption(23, "Send Location"),
42
+    TelnetOption(24, "Terminal Type"),
43
+    TelnetOption(25, "End of Record"),
44
+    TelnetOption(26, "TACACS User Identification"),
45
+    TelnetOption(27, "Output Marking"),
46
+    TelnetOption(28, "TTYLOC, Terminal Location Number"),
47
+    TelnetOption(29, "Telnet 3270 Regime"),
48
+    TelnetOption(30, "X.3 PAD"),
49
+    TelnetOption(31, "NAWS, Negotiate About Window Size"),
50
+    TelnetOption(32, "Terminal Speed"),
51
+    TelnetOption(33, "Remote Flow Control"),
52
+    TelnetOption(34, "Linemode"),
53
+    TelnetOption(35, "X Display Location"),
54
+    TelnetOption(36, "Environment"),
55
+    TelnetOption(37, "Authentication"),
56
+    TelnetOption(38, "Encryption Option"),
57
+    TelnetOption(39, "New Environment"),
58
+    TelnetOption(40, "TN3270E"),
59
+    TelnetOption(41, "XAUTH"),
60
+    TelnetOption(42, "CHARSET"),
61
+    TelnetOption(43, "RSP, Telnet Remote Serial Port"),
62
+    TelnetOption(44, "Com Port Control"),
63
+    TelnetOption(45, "Telnet Suppress Local Echo"),
64
+    TelnetOption(46, "Telnet Start TLS"),
65
+    TelnetOption(47, "KERMIT"),
66
+    TelnetOption(48, "SEND-URL"),
67
+    TelnetOption(49, "FORWARD_X"),
68
+    TelnetOption(90, "MSP (MUD Sound Protocol)"),
69
+    TelnetOption(91, "MXP (MUD eXtension Protocol)"),
70
+    TelnetOption(200, "ATCP (Achaea proprietary protocol)"),
71
+    TelnetOption(201, "GMCP"),
72
+];
73
+
74
+__gshared MultiLogger log;
75
+shared static this()
76
+{
77
+    log = new MultiLogger();
78
+    log.insertLogger("stderr", sharedLog);
79
+    log.insertLogger("file", new std.experimental.logger.FileLogger("telnet.log"));
80
+    size_t id = 0;
81
+    listenTCP(2323, (conn)
82
+    {
83
+        id++;
84
+        log.infof("new connection: %s", id);
85
+        conn.write("Telnet client tester!\r\n");
86
+        conn.write("Beginning tests for telnet options.\r\n");
87
+        ubyte[3] enable;
88
+        enable[0] = 255;  // IAC
89
+        enable[1] = 251;  // WILL
90
+        ubyte[3] buf;
91
+        foreach (opt; options)
92
+        {
93
+            enable[2] = opt.code;
94
+            conn.write(enable.idup);
95
+            conn.write("Asked about %s.\r\n".format(opt.name));
96
+            if (conn.dataAvailableForRead)
97
+            {
98
+                considerResponses(conn, id, buf);
99
+            }
100
+        }
101
+        auto now = Clock.currTime;
102
+        auto bound = now + dur!("seconds")(2 * options.length);
103
+        conn.write("Waiting for more responses...\r\n");
104
+        while (Clock.currTime < bound)
105
+        {
106
+            if (!considerResponses(conn, id, buf)) break;
107
+        }
108
+        conn.write("Timed out. We're done, folks.\r\n");
109
+    });
110
+}
111
+
112
+bool considerResponses(scope TCPConnection conn, size_t id, scope ubyte[3] buf)
113
+{
114
+    auto resp = checkTelnetResponse(conn, buf);
115
+    with (Response.Type) final switch (resp.type)
116
+    {
117
+        case TIMEOUT:
118
+            return false;
119
+        case ENABLED:
120
+        case REFUSED:
121
+            bool found = false;
122
+            string en = resp.type == ENABLED ? "Enabled" : "Refused";
123
+            string name = resp.code.codeToName();
124
+            conn.write("%s %s.\r\n".format(en, name));
125
+            if (resp.type == ENABLED)
126
+            {
127
+                log.infof("%s accepts option %s", id, name);
128
+            }
129
+            break;
130
+        case GARBAGE:
131
+            conn.write("Garbage: IAC %s.\r\n".format(resp.code));
132
+            break;
133
+        case SUBCOMMAND:
134
+            conn.write("Subcommand for %s.\r\n".format(resp.code.codeToName));
135
+            break;
136
+    }
137
+    return true;
138
+}
139
+
140
+string codeToName(ubyte code)
141
+{
142
+    foreach (opt; options)
143
+    {
144
+        if (opt.code == code)
145
+        {
146
+            return opt.name;
147
+        }
148
+    }
149
+    return "%s [unknown]".format(code);
150
+}
151
+
152
+struct Response
153
+{
154
+    enum Type {TIMEOUT, ENABLED, REFUSED, SUBCOMMAND, GARBAGE}
155
+    Type type;
156
+    ubyte code;
157
+}
158
+
159
+Response checkTelnetResponse(scope TCPConnection conn, scope ubyte[3] buf)
160
+{
161
+    enum timeout = dur!("seconds")(2);
162
+    ubyte[512] data;
163
+    auto now = Clock.currTime;
164
+    auto bound = now + timeout;
165
+    while (bound > Clock.currTime)
166
+    {
167
+        if (!conn.waitForData(timeout))
168
+        {
169
+            return Response(Response.Type.TIMEOUT, 0);
170
+        }
171
+        while (conn.dataAvailableForRead)
172
+        {
173
+            ubyte[1] b;
174
+            conn.read(b);
175
+            buf[0] = buf[1];
176
+            buf[1] = buf[2];
177
+            buf[2] = b[0];
178
+            if (buf[0] == 255)      // IAC
179
+            {
180
+                switch (buf[1])
181
+                {
182
+                    case 255:
183
+                        buf[0] = 0;
184
+                        buf[1] = 0;
185
+                        break;
186
+                    case 250:
187
+                        return Response(Response.Type.SUBCOMMAND, buf[2]);
188
+                    case 251:
189
+                    case 253:
190
+                        return Response(Response.Type.ENABLED, buf[2]);
191
+                    case 252:
192
+                    case 254:
193
+                        return Response(Response.Type.REFUSED, buf[2]);
194
+                    case 240:
195
+                        // subcommand end
196
+                        break;
197
+                    default:
198
+                        return Response(Response.Type.GARBAGE, buf[1]);
199
+                }
200
+            }
201
+        }
202
+    }
203
+    return Response(Response.Type.TIMEOUT, 0);
204
+}