source: PinConnection/CommTelnet.pas

Last change on this file was 575, checked in by chronos, 5 months ago
  • Modified: Remove U prefix from unit names of PinConnection package.
File size: 14.5 KB
Line 
1unit CommTelnet;
2
3interface
4
5uses
6 Classes, SysUtils, CommPin, SpecializedList, DateUtils, Dialogs,
7 BinarySerializer;
8
9type
10 TCommTelnet = class;
11
12 TTelnetCode = (tcEOR = 239, tcSE = 240, tcNOP = 241, tcDATA_MARK = 242,
13 tcBREAK = 243, tcIP = 244, tcAO = 245, tcAYT = 246, tcEC = 247,
14 tcEL = 248, tcGA = 249, tcSB = 250, tcWILL = 251, tcWONT = 252,
15 tcDO = 253, tcDONT = 254, tcIAC = 255);
16
17 TTelnetCommand = (tmTransmitBinary = 0, tmEcho = 1, tmReconnection = 2,
18 tmSupressGoAhead = 3, tmApproxMessageSizeNegotiation = 4,
19 tmStatus = 5, tmTimingMark = 6,
20 tmRemoteControlledTransAndEcho = 7, tmOutputLineWidth = 8, tmOutputPageSize = 9,
21 tmOutputCarriageReturnDisposition = 10, tmOutputHorizontalTabStops = 11,
22 tmOutputHorizontalTabDisposition = 12, tmOutputFormfeedDisposition = 13,
23 tmOutputVerticalTabstops = 14, tmOutputVerticalTabDisposition = 15,
24 tmOutputLinefeedDisposition = 16, tmExtendedASCII = 17, tmLogout = 18,
25 tmByteMacro = 19, tmDataEntryTerminal = 20,
26 tmSUPDUP = 21, tmSUPDUPOutput = 22, tmSendLocation = 23, tmTerminalType = 24,
27 tmEndOfRecord = 25, tmTACACSUserIdentification = 26, tmOutputMarking = 27,
28 tmTerminalLocationNumber = 28, tmTelnet3270Regime = 29, tmX_3PAD = 30,
29 tmNegotiateAboutWindowSize = 31, tmTerminalSpeed = 32,
30 tmRemoteFlowControl = 33, tmLineMode = 34,
31 tmXDisplayLocation = 35, tmEnvironmentOption = 36,
32 tmAuthentication = 37, tmEncryptionOption = 38, tmNewEnvironmentOption = 39,
33 tmTN3270E = 40, tmXauth = 41,
34 tmCharset = 42, tmRSP = 43, tmComPortControlOption = 44,
35 tmTelnetSuppressLocalEcho = 45, tmTelnetStartTLS = 46,
36 rmKERMIT = 47, tmSendURL = 48, tmForwardX = 49);
37
38 TTelnetState = (tsNormal, tsIAC, tsSB, tsSB_IAC, tsOption);
39
40 TTelnetOptionEvent = procedure (Sender: TCommTelnet; Data: TStream) of object;
41
42 TTelnetType = (ttClient, ttServer);
43
44 { TTelnetOption }
45
46 TTelnetOption = class
47 private
48 FActive: Boolean;
49 FOnRequest: TTelnetOptionEvent;
50 protected
51 procedure SetActive(AValue: Boolean); virtual;
52 public
53 Telnet: TCommTelnet;
54 Code: TTelnetCommand;
55 ServerChecked: Boolean;
56 SupportedByServer: Boolean;
57 procedure Assign(Source: TTelnetOption); virtual;
58 function CheckOption: Boolean;
59 procedure SendCommand(Request, Response: TListByte);
60 property OnRequest: TTelnetOptionEvent read FOnRequest write FOnRequest;
61 property Active: Boolean read FActive write SetActive;
62 end;
63
64 TTelnetPortType = (ptClient, ptServer);
65
66 { TCommTelnet }
67
68 TCommTelnet = class(TCommNode)
69 private
70 FResponses: TListObject; // TListObject<TListByte>
71 FActive: Boolean;
72 FState: TTelnetState;
73 FCommandData: TBinarySerializer;
74 procedure TelnetDataReceive(Sender: TCommPin; Stream: TListByte);
75 procedure RawDataReceive(Sender: TCommPin; Stream: TListByte);
76 procedure ReadResponse(Response: TListByte);
77 function ResponseCount: Integer;
78 protected
79 procedure SetActive(const AValue: Boolean); override;
80 procedure AssignTo(Dest: TPersistent); override;
81 public
82 Options: TListObject;
83 TelnetPin: TCommPin;
84 RawPin: TCommPin;
85 Timeout: TDateTime;
86 PortType: TTelnetPortType;
87 ErrorCount: Integer;
88 OptionsNegotationEnable: Boolean;
89 procedure Register(Option: TTelnetOption);
90 procedure Unregister(Option: TTelnetOption);
91 function CheckOption(OptionCode: TTelnetCommand): Boolean;
92 function SearchOption(OptionCode: TTelnetCommand): TTelnetOption;
93 procedure SendSubCommand(OptionCode: TTelnetCommand; Request, Response: TListByte);
94 procedure SendCommand(Code: TTelnetCode; Request, Response: TListByte);
95 procedure Purge;
96 constructor Create(AOwner: TComponent); override;
97 destructor Destroy; override;
98 property Active: Boolean read FActive write SetActive;
99 end;
100
101
102implementation
103
104resourcestring
105 SUnknownState = 'Unknown state';
106 SWrongResponseOption = 'Wrong response option';
107 SWrongResponseCode = 'Wrong response code';
108 SWrongResponse = 'Wrong response';
109 SOptionNotFound = 'Option not found';
110 STimeout = 'Telnet command timout';
111
112{ TTelnetOption }
113
114procedure TTelnetOption.SetActive(AValue: Boolean);
115begin
116 if FActive = AValue then Exit;
117 FActive := AValue;
118end;
119
120procedure TTelnetOption.Assign(Source: TTelnetOption);
121begin
122 SupportedByServer := Source.SupportedByServer;
123 ServerChecked := Source.ServerChecked;
124 Code := Source.Code;
125 FOnRequest := Source.FOnRequest;
126 Active := Source.FActive;
127end;
128
129function TTelnetOption.CheckOption: Boolean;
130var
131 RequestData: TBinarySerializer;
132 ResponseData: TBinarySerializer;
133begin
134 if not ServerChecked then
135 try
136 RequestData := TBinarySerializer.Create;
137 RequestData.List := TListByte.Create;
138 RequestData.OwnsList := True;
139 ResponseData := TBinarySerializer.Create;
140 ResponseData.List := TListByte.Create;
141 ResponseData.OwnsList := True;
142
143 RequestData.WriteByte(Byte(Code));
144 Telnet.SendCommand(tcDo, RequestData.List, ResponseData.List);
145 if ResponseData.List[0] = Byte(tcWILL) then SupportedByServer := True
146 else if ResponseData.List[0] = Byte(tcWONT) then SupportedByServer := False
147 else raise Exception.Create(SWrongResponse);
148 ServerChecked := True;
149 finally
150 RequestData.Free;
151 RequestData.Free;
152 end;
153 Result := SupportedByServer;
154end;
155
156procedure TTelnetOption.SendCommand(Request, Response: TListByte);
157var
158 RequestData: TBinarySerializer;
159 ResponseData: TBinarySerializer;
160begin
161 if Telnet.OptionsNegotationEnable then CheckOption;
162 try
163 RequestData := TBinarySerializer.Create;
164 RequestData.List := TListByte.Create;
165 RequestData.OwnsList := True;
166 ResponseData := TBinarySerializer.Create;
167 ResponseData.List := TListByte.Create;
168 ResponseData.OwnsList := True;
169
170 RequestData.WriteByte(Byte(Code));
171 RequestData.WriteList(Request, 0, Request.Count);
172 if Assigned(Response) then begin
173 Telnet.SendCommand(tcSB, RequestData.List, ResponseData.List);
174 if ResponseData.List[0] <> Byte(Code) then
175 raise Exception.Create(SWrongResponseOption);
176 ResponseData.List.Delete(0);
177 Response.Assign(ResponseData.List);
178 end else Telnet.SendCommand(tcSB, RequestData.List, nil);
179 finally
180 RequestData.Free;
181 ResponseData.Free;
182 end;
183end;
184
185{ TCommTelnet }
186
187procedure TCommTelnet.TelnetDataReceive(Sender: TCommPin; Stream: TListByte);
188var
189 Data: Byte;
190 RawData: TBinarySerializer;
191 I: Integer;
192begin
193 try
194 RawData := TBinarySerializer.Create;
195 RawData.List := TListByte.Create;
196 RawData.OwnsList := True;
197 for I := 0 to Stream.Count - 1 do begin
198 Data := Stream[I];
199 if Data = Byte(tcIAC) then begin
200 RawData.WriteByte(Byte(tcIAC));
201 RawData.WriteByte(Byte(tcIAC));
202 end else RawData.WriteByte(Data);
203 end;
204 RawPin.Send(RawData.List);
205 finally
206 RawData.Free;
207 end;
208end;
209
210procedure TCommTelnet.SetActive(const AValue: Boolean);
211var
212 I: Integer;
213begin
214 if FActive = AValue then Exit;
215 FActive := AValue;
216 for I := 0 to Options.Count - 1 do
217 with TTelnetOption(Options[I]) do begin
218 if (not ServerChecked) and OptionsNegotationEnable then CheckOption;
219 Active := AValue;
220 end;
221end;
222
223procedure TCommTelnet.RawDataReceive(Sender: TCommPin; Stream: TListByte);
224var
225 Data: Byte;
226 RawData: TBinarySerializer;
227 I: Integer;
228begin
229 try
230 RawData := TBinarySerializer.Create;
231 RawData.List := TListByte.Create;
232 RawData.OwnsList := True;
233
234 for I := 0 to Stream.Count - 1 do begin
235 Data := Stream[I];
236 if FState = tsNormal then begin
237 if Data = Byte(tcIAC) then begin
238 FCommandData.Clear;
239 FState := tsIAC;
240 end else RawData.WriteByte(Data);
241 end else
242 if FState = tsIAC then begin
243 if Data = Byte(tcIAC) then begin
244 RawData.WriteByte(Data);
245 FState := tsNormal;
246 end else
247 if Data = Byte(tcSB) then begin
248 // Subnegotation
249 FCommandData.WriteByte(Data);
250 FState := tsSB;
251 end else
252 if (Data = Byte(tcWILL)) or (Data = Byte(tcDONT)) or (Data = Byte(tcWONT)) or (Data = Byte(tcDO))
253 then begin
254 // Three byte negotation commands
255 FCommandData.WriteByte(Data);
256 FState := tsOption;
257 end else
258 if (Data = Byte(tcAYT)) or (Data = Byte(tcNOP)) or (Data = Byte(tcGA)) or
259 (Data = Byte(tcEL)) or (Data = Byte(tcEC)) or (Data = Byte(tcAO)) or
260 (Data = Byte(tcIP)) or (Data = Byte(tcBREAK)) or (Data = Byte(tcDATA_MARK)) or
261 (Data = Byte(tcEOR)) then begin
262 // Two byte commands
263 FCommandData.WriteByte(Data);
264 FResponses.AddNew(TListByte.Create);
265 TListByte(FResponses.Last).Assign(FCommandData.List); FState := tsNormal;
266 end else
267 FState := tsNormal;
268 end else
269 if FState = tsSB then begin
270 // Data inside subnegotation
271 if Data = Byte(tcIAC) then FState := tsSB_IAC
272 else FCommandData.WriteByte(Data);
273 end else
274 if FState = tsSB_IAC then begin
275 // End of subnegotation data
276 if Data = Byte(tcSE) then begin
277 FResponses.AddNew(TListByte.Create);
278 TListByte(FResponses.Last).Assign(FCommandData.List);
279 FState := tsNormal;
280 end else begin
281 Inc(ErrorCount);
282 FState := tsNormal;
283 end;
284 end else
285 if FState = tsOption then begin
286 // Third byte of negotation
287 FCommandData.WriteByte(Data);
288 FResponses.AddNew(TListByte.Create);
289 TListByte(FResponses.Last).Assign(FCommandData.List);
290 FState := tsNormal;
291 end else raise Exception.Create(SUnknownState);
292 end;
293 TelnetPin.Send(RawData.List);
294 finally
295 RawData.Free;
296 end;
297end;
298
299procedure TCommTelnet.ReadResponse(Response: TListByte);
300var
301 TimeStart: TDateTime;
302 ElapsedTime: TDateTime;
303begin
304 TimeStart := Now;
305 repeat
306 ElapsedTime := Now - TimeStart;
307 until (ElapsedTime > Timeout) or (ResponseCount > 0);
308 if ElapsedTime > Timeout then
309 raise Exception.Create(STimeout);
310 Response.Assign(TListByte(FResponses.First));
311 FResponses.Delete(0);
312end;
313
314function TCommTelnet.ResponseCount: Integer;
315begin
316 Result := FResponses.Count;
317end;
318
319procedure TCommTelnet.AssignTo(Dest: TPersistent);
320var
321 I: Integer;
322begin
323 if Dest is TCommTelnet then begin
324 TCommTelnet(Dest).Timeout := Timeout;
325 TCommTelnet(Dest).PortType := PortType;
326 TCommTelnet(Dest).ErrorCount := ErrorCount;
327 TCommTelnet(Dest).OptionsNegotationEnable := OptionsNegotationEnable;
328 for I := 0 to Options.Count - 1 do begin
329 TTelnetOption(TCommTelnet(Dest).Options[I]).Assign(TTelnetOption(Options[I]));
330 TTelnetOption(TCommTelnet(Dest).Options[I]).Telnet := TCommTelnet(Dest);
331 end;
332 end else inherited;
333end;
334
335procedure TCommTelnet.Register(Option: TTelnetOption);
336begin
337 Option.Telnet := Self;
338 Options.Add(Option);
339end;
340
341procedure TCommTelnet.Unregister(Option: TTelnetOption);
342begin
343 Options.Remove(Option);
344end;
345
346function TCommTelnet.CheckOption(OptionCode: TTelnetCommand): Boolean;
347var
348 Option: TTelnetOption;
349begin
350 Option := SearchOption(OptionCode);
351 if Assigned(Option) then Result := Option.CheckOption
352 else raise Exception.Create(SOptionNotFound);
353end;
354
355function TCommTelnet.SearchOption(OptionCode: TTelnetCommand): TTelnetOption;
356var
357 I: Integer;
358begin
359 I := 0;
360 while (I < Options.Count) and (TTelnetOption(Options[I]).Code <> OptionCode) do
361 Inc(I);
362 if I < Options.Count then Result := TTelnetOption(Options[I])
363 else Result := nil;
364end;
365
366procedure TCommTelnet.SendSubCommand(OptionCode: TTelnetCommand; Request,
367 Response: TListByte);
368var
369 Option: TTelnetOption;
370begin
371 Option := SearchOption(OptionCode);
372 if Assigned(Option) then Option.SendCommand(Request, Response)
373 else raise Exception.Create(SOptionNotFound);
374end;
375
376function ByteToStr(Value: Byte): string;
377begin
378 Result := IntToStr(Value);
379end;
380
381procedure TCommTelnet.SendCommand(Code: TTelnetCode; Request,
382 Response: TListByte);
383var
384 Data: TBinarySerializer;
385 LastIAC: Boolean;
386 I: Integer;
387begin
388 try
389 Data := TBinarySerializer.Create;
390 Data.List := TListByte.Create;
391 Data.OwnsList := True;
392 Data.WriteByte(Byte(tcIAC));
393 Data.WriteByte(Byte(Code));
394 for I := 0 to Request.Count - 1 do begin
395 if Request[I] = Byte(tcIAC) then Data.WriteByte(Byte(tcIAC));
396 Data.WriteByte(Request[I]);
397 end;
398 if Code = tcSB then begin
399 Data.WriteByte(Byte(tcIAC));
400 Data.WriteByte(Byte(tcSE));
401 end;
402 RawPin.Send(Data.List);
403 if Assigned(Response) then begin
404 ReadResponse(Response);
405 if Response[0] <> Byte(Code) then
406 raise Exception.Create(SWrongResponseCode);
407 Response.Delete(0);
408 if Code = tcSB then begin
409 if (Response[Response.Count - 2] <> Byte(tcIAC)) or
410 (Response[Response.Count - 1] <> Byte(tcSE)) then begin
411 ShowMessage(Response.Implode(' ', ByteToStr));
412 ReadResponse(Response);
413 ShowMessage(Response.Implode(' ', ByteToStr));
414 raise Exception.Create(SWrongResponse);
415 end;
416 Response.DeleteItems(Response.Count - 2, 2);
417 end;
418 // Remove IAC escape character from data
419 I := 0;
420 LastIAC := False;
421 while (I < Response.Count) do begin
422 if Response[I] = Byte(tcIAC) then begin
423 if not LastIAC then LastIAC := True
424 else begin
425 LastIAC := False;
426 Response.Delete(I);
427 Dec(I);
428 end;
429 end;
430 Inc(I);
431 end;
432 end;
433 finally
434 Data.Free;
435 end;
436end;
437
438procedure TCommTelnet.Purge;
439begin
440 FState := tsNormal;
441 FResponses.Clear;
442end;
443
444constructor TCommTelnet.Create(AOwner: TComponent);
445begin
446 inherited;
447 FResponses := TListObject.Create;
448 FCommandData := TBinarySerializer.Create;
449 FCommandData.List := TListByte.Create;
450 FCommandData.OwnsList := True;
451 TelnetPin := TCommPin.Create;
452 TelnetPin.OnReceive := TelnetDataReceive;
453 TelnetPin.Node := Self;
454 RawPin := TCommPin.Create;
455 RawPin.OnReceive := RawDataReceive;
456 RawPin.Node := Self;
457 Options := TListObject.Create;
458 Options.OwnsObjects := False;
459 Timeout := 2 * OneSecond;
460end;
461
462destructor TCommTelnet.Destroy;
463begin
464 FreeAndNil(FCommandData);
465 FreeAndNil(Options);
466 FreeAndNil(TelnetPin);
467 FreeAndNil(RawPin);
468 FreeAndNil(FResponses);
469 inherited;
470end;
471
472end.
473
Note: See TracBrowser for help on using the repository browser.