123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- { lTelnet CopyRight (C) 2004-2008 Ales Katona
- This library is Free software; you can rediStribute it and/or modify it
- under the terms of the GNU Library General Public License as published by
- the Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- This program is diStributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
- for more details.
- You should have received a Copy of the GNU Library General Public License
- along with This library; if not, Write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- This license has been modified. See File LICENSE for more inFormation.
- Should you find these sources withOut a LICENSE File, please contact
- me at [email protected]
- }
- unit lTelnet;
- {$mode objfpc}{$H+}
- //{$define debug}
- interface
- uses
- Classes, SysUtils, lNet, lControlStack;
-
- const
- // Telnet printer signals
- TS_NUL = #0;
- TS_ECHO = #1;
- TS_SGA = #3; // Surpass go-ahead
- TS_BEL = #7;
- TS_BS = #8;
- TS_HT = #9;
- TS_LF = #10;
- TS_VT = #11;
- TS_FF = #12;
- TS_CR = #13;
- // Telnet control signals
- TS_NAWS = #31;
- TS_DATA_MARK = #128;
- TS_BREAK = #129;
- TS_HYI = #133; // Hide Your Input
- // Data types codes
- TS_STDTELNET = #160;
- TS_TRANSPARENT = #161;
- TS_EBCDIC = #162;
- // Control bytes
- TS_SE = #240;
- TS_NOP = #241;
- TS_GA = #249; // go ahead currently ignored(full duplex)
- TS_SB = #250;
- TS_WILL = #251;
- TS_WONT = #252;
- TS_DO = #253;
- TS_DONT = #254;
- // Mother of all power
- TS_IAC = #255;
-
- type
- TLTelnetClient = class;
- TLTelnetControlChars = set of Char;
- TLHowEnum = (TE_WILL = 251, TE_WONT, TE_DO, TE_DONW);
- TLSubcommandCallback= function(command: char; const parameters, defaultResponse: string): string;
- TLSubcommandEntry= record
- callback: TLSubcommandCallback;
- defaultResponse: string;
- requiredParams: integer
- end;
- TLSubcommandArray= array[#$00..#$ff] of TLSubcommandEntry;
- EInsufficientSubcommandParameters= class(Exception);
- { TLTelnet }
- TLTelnet = class(TLComponent, ILDirect)
- protected
- FStack: TLControlStack;
- FConnection: TLTcp;
- FPossible: TLTelnetControlChars;
- FActiveOpts: TLTelnetControlChars;
- FOutput: TMemoryStream;
- FOperation: Char;
- FCommandCharIndex: Byte;
- FOnReceive: TLSocketEvent;
- FOnConnect: TLSocketEvent;
- FOnDisconnect: TLSocketEvent;
- FOnError: TLSocketErrorEvent;
- FCommandArgs: string[3];
- FOrders: TLTelnetControlChars;
- FBuffer: array of Char;
- FBufferIndex: Integer;
- FBufferEnd: Integer;
- FSubcommandCallbacks: TLSubcommandArray;
- procedure InflateBuffer;
- function AddToBuffer(const aStr: string): Boolean; inline;
-
- function Question(const Command: Char; const Value: Boolean): Char;
-
- function GetConnected: Boolean;
-
- function GetTimeout: Integer;
- procedure SetTimeout(const Value: Integer);
- function GetSocketClass: TLSocketClass;
- procedure SetSocketClass(Value: TLSocketClass);
- function GetSession: TLSession;
- procedure SetSesssion(const AValue: TLSession);
- procedure SetCreator(AValue: TLComponent); override;
- procedure StackFull;
- procedure DoubleIAC(var s: string);
- function TelnetParse(const msg: string): Integer;
- function React(const Operation, Command: Char): boolean; virtual; abstract;
- procedure SendCommand(const Command: Char; const Value: Boolean); virtual; abstract;
- procedure OnCs(aSocket: TLSocket);
- public
- constructor Create(aOwner: TComponent); override;
- destructor Destroy; override;
-
- function Get(out aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; virtual; abstract;
- function GetMessage(out msg: string; aSocket: TLSocket = nil): Integer; virtual; abstract;
-
- function Send(const aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; virtual; abstract;
- function SendMessage(const msg: string; aSocket: TLSocket = nil): Integer; virtual; abstract;
-
- function OptionIsSet(const Option: Char): Boolean;
- function RegisterOption(const aOption: Char; const aCommand: Boolean): Boolean;
- procedure SetOption(const Option: Char);
- procedure UnSetOption(const Option: Char);
- function RegisterSubcommand(aOption: char; callback: TLSubcommandCallback;
- const defaultResponse: string= ''; requiredParams: integer= 0): boolean;
- procedure Disconnect(const Forced: Boolean = True); override;
-
- procedure SendCommand(const aCommand: Char; const How: TLHowEnum); virtual;
- public
- property Output: TMemoryStream read FOutput;
- property Connected: Boolean read GetConnected;
- property Timeout: Integer read GetTimeout write SetTimeout;
- property OnReceive: TLSocketEvent read FOnReceive write FOnReceive;
- property OnDisconnect: TLSocketEvent read FOnDisconnect write FOnDisconnect;
- property OnConnect: TLSocketEvent read FOnConnect write FOnConnect;
- property OnError: TLSocketErrorEvent read FOnError write FOnError;
- property Connection: TLTCP read FConnection;
- property SocketClass: TLSocketClass read GetSocketClass write SetSocketClass;
- property Session: TLSession read GetSession write SetSesssion;
- end;
- { TLTelnetClient }
- TLTelnetClient = class(TLTelnet, ILClient)
- protected
- FLocalEcho: Boolean;
- procedure OnEr(const msg: string; aSocket: TLSocket);
- procedure OnDs(aSocket: TLSocket);
- procedure OnRe(aSocket: TLSocket);
- procedure OnCo(aSocket: TLSocket);
- function React(const Operation, Command: Char): boolean; override;
-
- procedure SendCommand(const Command: Char; const Value: Boolean); override;
- public
- constructor Create(aOwner: TComponent); override;
-
- function Connect(const anAddress: string; const aPort: Word): Boolean;
- function Connect: Boolean;
-
- function Get(out aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; override;
- function GetMessage(out msg: string; aSocket: TLSocket = nil): Integer; override;
-
- function Send(const aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; override;
- function SendMessage(const msg: string; aSocket: TLSocket = nil): Integer; override;
-
- procedure CallAction; override;
- public
- property LocalEcho: Boolean read FLocalEcho write FLocalEcho;
- end;
- function LTelnetSubcommandCallback(command: char; const parameters, defaultResponse: string): string;
- implementation
- uses
- Math;
- const subcommandEndLength= 2;
- var
- zz: Char;
- TNames: array[Char] of string;
- //*******************************TLTelnetClient********************************
- constructor TLTelnet.Create(aOwner: TComponent);
- begin
- inherited Create(aOwner);
-
- FConnection := TLTCP.Create(nil);
- FConnection.Creator := Self;
- FConnection.OnCanSend := @OnCs;
-
- FOutput := TMemoryStream.Create;
- FCommandCharIndex := 0;
- FStack := TLControlStack.Create;
- FStack.OnFull := @StackFull;
- end;
- destructor TLTelnet.Destroy;
- begin
- Disconnect(True);
- FOutput.Free;
- FConnection.Free;
- FStack.Free;
- inherited Destroy;
- end;
- function TLTelnet.GetConnected: Boolean;
- begin
- Result := FConnection.Connected;
- end;
- function TLTelnet.GetSession: TLSession;
- begin
- Result := FConnection.Session;
- end;
- procedure TLTelnet.SetSesssion(const AValue: TLSession);
- begin
- FConnection.Session := aValue;
- end;
- procedure TLTelnet.SetCreator(AValue: TLComponent);
- begin
- inherited SetCreator(AValue);
- FConnection.Creator := aValue;
- end;
- procedure TLTelnet.InflateBuffer;
- var
- n: Integer;
- begin
- n := Max(Length(FBuffer), 25);
- SetLength(FBuffer, n * 10);
- end;
- function TLTelnet.AddToBuffer(const aStr: string): Boolean; inline;
- begin
- Result := False;
-
- while Length(aStr) + FBufferEnd > Length(FBuffer) do
- InflateBuffer;
-
- Move(aStr[1], FBuffer[FBufferEnd], Length(aStr));
- Inc(FBufferEnd, Length(aStr));
- end;
- function TLTelnet.Question(const Command: Char; const Value: Boolean): Char;
- begin
- Result := TS_NOP;
- if Value then begin
- if Command in FOrders then
- Result := TS_DO
- else
- Result := TS_WILL;
- end else begin
- if Command in FOrders then
- Result := TS_DONT
- else
- Result := TS_WONT;
- end;
- end;
- function TLTelnet.GetSocketClass: TLSocketClass;
- begin
- Result := FConnection.SocketClass;
- end;
- function TLTelnet.GetTimeout: Integer;
- begin
- Result := FConnection.Timeout;
- end;
- procedure TLTelnet.SetSocketClass(Value: TLSocketClass);
- begin
- FConnection.SocketClass := Value;
- end;
- procedure TLTelnet.SetTimeout(const Value: Integer);
- begin
- FConnection.Timeout := Value;
- end;
- procedure TLTelnet.StackFull;
- begin
- {$ifdef debug}
- Writeln('**STACKFULL**');
- {$endif}
- if FStack[1] = TS_IAC then
- begin
- FOutput.WriteByte(Byte(FStack[1]));
- FOutput.WriteByte(Byte(FStack[2]));
- FStack.Clear
- end else
- if React(FStack[1], FStack[2]) then
- FStack.Clear
- end;
- procedure TLTelnet.DoubleIAC(var s: string);
- var
- i: Longint;
- begin
- i := 0;
- if Length(s) > 0 then
- while i < Length(s) do begin
- Inc(i);
- if s[i] = TS_IAC then begin
- Insert(TS_IAC, s, i);
- Inc(i, 2);
- end;
- end;
- end;
- function TLTelnet.TelnetParse(const msg: string): Integer;
- var
- i: Longint;
- begin
- Result := 0;
- for i := 1 to Length(msg) do
- if (FStack.ItemIndex > 0) or (msg[i] = TS_IAC) then begin
- if msg[i] = TS_GA then
- FStack.Clear
- else
- FStack.Push(msg[i])
- end else begin
- FOutput.WriteByte(Byte(msg[i]));
- Inc(Result);
- end;
- end;
- procedure TLTelnet.OnCs(aSocket: TLSocket);
- var
- n: Integer;
- begin
- n := 1;
- while (n > 0) and (FBufferIndex < FBufferEnd) do begin
- n := FConnection.Send(FBuffer[FBufferIndex], FBufferEnd - FBufferIndex);
- if n > 0 then
- Inc(FBufferIndex, n);
- end;
-
- if FBufferEnd - FBufferIndex < FBufferIndex then begin // if we can move the "right" side of the buffer back to the left
- Move(FBuffer[FBufferIndex], FBuffer[0], FBufferEnd - FBufferIndex);
- FBufferEnd := FBufferEnd - FBufferIndex;
- FBufferIndex := 0;
- end;
- end;
- function TLTelnet.OptionIsSet(const Option: Char): Boolean;
- begin
- Result := False;
- Result := Option in FActiveOpts;
- end;
- function TLTelnet.RegisterOption(const aOption: Char;
- const aCommand: Boolean): Boolean;
- begin
- Result := False;
- if not (aOption in FPossible) then begin
- FPossible := FPossible + [aOption];
- if aCommand then
- FOrders := FOrders + [aOption];
- Result := True;
- end;
- end;
- procedure TLTelnet.SetOption(const Option: Char);
- begin
- if Option in FPossible then
- SendCommand(Option, True);
- end;
- procedure TLTelnet.UnSetOption(const Option: Char);
- begin
- if Option in FPossible then
- SendCommand(Option, False);
- end;
- (* If already set, the callback can be reverted to nil but it can't be changed *)
- (* in a single step. The default response, if specified, is used by the *)
- (* LTelnetSubcommandCallback() function and is available to others; the *)
- (* callback will not be invoked until there is at least the indicated number of *)
- (* parameter bytes available. *)
- //
- function TLTelnet.RegisterSubcommand(aOption: char; callback: TLSubcommandCallback;
- const defaultResponse: string= ''; requiredParams: integer= 0): boolean;
- begin
- result := (not Assigned(FSubcommandCallbacks[aOption].callback)) or (@callback = nil);
- if result then begin
- FSubcommandCallbacks[aOption].callback := callback;
- FSubcommandCallbacks[aOption].defaultResponse := defaultResponse;
- Inc(requiredParams, subcommandEndLength);
- if requiredParams < 0 then (* Assume -subcommandEndLength is a *)
- requiredParams := 0; (* valid parameter. *)
- FSubcommandCallbacks[aOption].requiredParams := requiredParams;
- end
- end { TLTelnet.RegisterSubcommand } ;
- procedure TLTelnet.Disconnect(const Forced: Boolean = True);
- begin
- FConnection.Disconnect(Forced);
- end;
- procedure TLTelnet.SendCommand(const aCommand: Char; const How: TLHowEnum);
- begin
- {$ifdef debug}
- Writeln('**SENT** ', TNames[Char(How)], ' ', TNames[aCommand]);
- {$endif}
- AddToBuffer(TS_IAC + Char(How) + aCommand);
- OnCs(nil);
- end;
- //****************************TLTelnetClient*****************************
- constructor TLTelnetClient.Create(aOwner: TComponent);
- begin
- inherited Create(aOwner);
- FConnection.OnError := @OnEr;
- FConnection.OnDisconnect := @OnDs;
- FConnection.OnReceive := @OnRe;
- FConnection.OnConnect := @OnCo;
- FPossible := [TS_ECHO, TS_HYI, TS_SGA];
- FActiveOpts := [];
- FOrders := [];
- end;
- procedure TLTelnetClient.OnEr(const msg: string; aSocket: TLSocket);
- begin
- if Assigned(FOnError) then
- FOnError(msg, aSocket)
- else
- FOutput.Write(Pointer(msg)^, Length(msg));
- end;
- procedure TLTelnetClient.OnDs(aSocket: TLSocket);
- begin
- if Assigned(FOnDisconnect) then
- FOnDisconnect(aSocket);
- end;
- procedure TLTelnetClient.OnRe(aSocket: TLSocket);
- var
- s: string;
- begin
- if aSocket.GetMessage(s) > 0 then
- if (TelnetParse(s) > 0) and Assigned(FOnReceive) then
- FOnReceive(aSocket);
- end;
- procedure TLTelnetClient.OnCo(aSocket: TLSocket);
- begin
- if Assigned(FOnConnect) then
- FOnConnect(aSocket);
- end;
- function TLTelnetClient.React(const Operation, Command: Char): boolean;
- procedure Accept(const Operation, Command: Char);
- begin
- FActiveOpts := FActiveOpts + [Command];
- {$ifdef debug}
- Writeln('**SENT** ', TNames[Operation], ' ', TNames[Command]);
- {$endif}
- AddToBuffer(TS_IAC + Operation + Command);
- OnCs(nil);
- end;
-
- procedure Refuse(const Operation, Command: Char);
- begin
- FActiveOpts := FActiveOpts - [Command];
- {$ifdef debug}
- Writeln('**SENT** ', TNames[Operation], ' ', TNames[Command]);
- {$endif}
- AddToBuffer(TS_IAC + Operation + Command);
- OnCs(nil);
- end;
- (* Retrieve the parameters from the current instance, and pass them explicitly *)
- (* to the callback. Return false if there are insufficient parameters on the *)
- (* stack. *)
- //
- function subcommand(command: char): boolean;
- var parameters, response: string;
- i: integer;
- begin
- FStack.AllowInflation := true; (* We might need more than the standard *)
- if FStack.ItemIndex > 65536 then (* command, but protect against parse *)
- {%H- 6018 } exit(true); (* failure which could be a DoS attack. *)
- i := FStack.ItemIndex - TL_CSLENGTH; (* Number of parameter bytes available.*)
- if i < FSubcommandCallbacks[command].requiredParams then
- exit(false); (* Early insufficient-parameters decision *)
- result := true;
- if FStack.ItemIndex > TL_CSLENGTH then begin
- SetLength(parameters, FStack.ItemIndex - TL_CSLENGTH );
- Move(FStack[3], parameters[1], FStack.ItemIndex - TL_CSLENGTH );
- if (Length(parameters) >= 2) and (parameters[Length(parameters)] = TS_IAC) and
- (parameters[Length(parameters) - 1] <> TS_IAC) then
- exit(false); (* Special case: need at least one more *)
- i := 1;
- while i <= Length(parameters) - 1 do (* Undouble IACs *)
- if (parameters[i] = TS_IAC) and (parameters[i + 1] = TS_IAC) then
- Delete(parameters, i, 1)
- else
- Inc(i)
- end else
- parameters := '';
- if Length(parameters) < FSubcommandCallbacks[command].requiredParams then
- exit(false); (* Insufficient params after IAC undouble *)
- if (FSubcommandCallbacks[command].requiredParams >= subcommandEndLength) and
- (Length(parameters) >= subcommandEndLength) then
- SetLength(parameters, Length(parameters) - subcommandEndLength);
- try
- response := FSubcommandCallbacks[command].callback(command, parameters,
- FSubcommandCallbacks[command].defaultResponse)
- except
- on e: EInsufficientSubcommandParameters do
- Exit(false) (* Late insufficient-parameters decision *)
- else
- Raise (* Application-specific error *)
- end;
- DoubleIAC(response);
- AddToBuffer(TS_IAC + TS_SB + command + response + TS_IAC + TS_SE);
- OnCs(nil)
- end { subcommand } ;
- begin
- result := true; (* Stack will normally be cleared *)
- {$ifdef debug}
- Writeln('**GOT** ', TNames[Operation], ' ', TNames[Command]);
- {$endif}
- case Operation of
- TS_DO : if Command in FPossible then Accept(TS_WILL, Command)
- else Refuse(TS_WONT, Command);
-
- TS_DONT : if Command in FPossible then Refuse(TS_WONT, Command);
-
- TS_WILL : if Command in FPossible then FActiveOpts := FActiveOpts + [Command]
- else Refuse(TS_DONT, Command);
-
- TS_WONT : if Command in FPossible then FActiveOpts := FActiveOpts - [Command];
- TS_SB : if not Assigned(FSubcommandCallbacks[command].callback) then
- refuse(TS_WONT, command)
- else
- result := subcommand(command)
- (* In the final case above, the stack will not be cleared if sufficient *)
- (* parameters to keep the subcommand happy have not yet been parsed out of the *)
- (* message. *)
- end;
- end;
- procedure TLTelnetClient.SendCommand(const Command: Char; const Value: Boolean);
- begin
- if Connected then begin
- {$ifdef debug}
- Writeln('**SENT** ', TNames[Question(Command, Value)], ' ', TNames[Command]);
- {$endif}
- case Question(Command, Value) of
- TS_WILL : FActiveOpts := FActiveOpts + [Command];
- end;
- AddToBuffer(TS_IAC + Question(Command, Value) + Command);
- OnCs(nil);
- end;
- end;
- function TLTelnetClient.Connect(const anAddress: string; const aPort: Word): Boolean;
- begin
- Result := FConnection.Connect(anAddress, aPort);
- end;
- function TLTelnetClient.Connect: Boolean;
- begin
- Result := FConnection.Connect(FHost, FPort);
- end;
- function TLTelnetClient.Get(out aData; const aSize: Integer; aSocket: TLSocket): Integer;
- begin
- Result := FOutput.Read(aData {%H- 5058 } , aSize);
- if FOutput.Position = FOutput.Size then
- FOutput.Clear;
- end;
- function TLTelnetClient.GetMessage(out msg: string; aSocket: TLSocket): Integer;
- begin
- Result := 0;
- msg := '';
- if FOutput.Size > 0 then begin
- FOutput.Position := 0;
- SetLength(msg, FOutput.Size);
- Result := FOutput.Read(PChar(msg)^, Length(msg));
- FOutput.Clear;
- end;
- end;
- function TLTelnetClient.Send(const aData; const aSize: Integer;
- aSocket: TLSocket): Integer;
- var
- Tmp: string;
- begin
- {$ifdef debug}
- Writeln('**SEND START** ');
- {$endif}
- Result := 0;
- if aSize > 0 then begin
- SetLength(Tmp, aSize);
- Move(aData, PChar(Tmp)^, aSize);
- DoubleIAC(Tmp);
- if LocalEcho and (not OptionIsSet(TS_ECHO)) and (not OptionIsSet(TS_HYI)) then
- FOutput.Write(PChar(Tmp)^, Length(Tmp));
-
- AddToBuffer(Tmp);
- OnCs(nil);
-
- Result := aSize;
- end;
- {$ifdef debug}
- Writeln('**SEND END** ');
- {$endif}
- end;
- function TLTelnetClient.SendMessage(const msg: string; aSocket: TLSocket
- ): Integer;
- begin
- Result := Send(PChar(msg)^, Length(msg));
- end;
- procedure TLTelnetClient.CallAction;
- begin
- FConnection.CallAction;
- end;
- (* This is a default callback for use with the RegisterSubcommand() method. It *)
- (* may be used where the result is unchanging, for example in order to return *)
- (* the terminal type. *)
- //
- function LTelnetSubcommandCallback(command: char; const parameters, defaultResponse: string): string;
- begin
- result := defaultResponse
- end { LTelnetSubcommandCallback } ;
- initialization
- for zz := #0 to #255 do
- TNames[zz] := IntToStr(Ord(zz));
- TNames[#1] := 'TS_ECHO';
- TNames[#133] := 'TS_HYI';
- TNames[#251] := 'TS_WILL';
- TNames[#252] := 'TS_WONT';
- TNames[#253] := 'TS_DO';
- TNames[#254] := 'TS_DONT';
- end.
|