4

Using Indy10 - DelphiXE. How do I tell the telnet server I am connecting to with TIdTelnet to not echo the commands I send towards the telnet server?

This is my current attempt (which doesn't work), I try to send the sequence IAC DO SUPPRESS_LOCAL_ECHO on each telnet write but I do believe this has to be done in the negotiating phase?

uses
  TIdTelnet,

...
procedure TIOTelnetConnection.SendSupressEcho;

var
  Resp: TIdBytes;

begin
 SetLength(Resp, 3);
 Resp[0] := TNC_IAC;
 Resp[1] := TNC_DO;
 Resp[2] := TNO_SUPLOCALECHO;
 FIOConnection.IOHandler.Write(Resp);
end;

procedure TIOTelnetConnection.Write(Str: AnsiString);
begin
 SendSupressEcho;
 if Str <> '' then
  FIOConnection.IOHandler.Write(Str);
end;

looking at the TIdTelnet source I see the Negotiate procedure, but it is protected, how can I override it's behaviour?

whosrdaddy
  • 11,720
  • 4
  • 50
  • 99

3 Answers3

5

If the server sends an IAC WILL ECHO to you, TIdTelnet is hard-coded to send an IAC DO ECHO response, and then triggers an OnTelnetCommand(tncNoLocalEcho) event to tell you not to locally echo what you send.

If the server sends an IAC WONT ECHO to you, TIdTelnet is hard-coded to send an IAC DONT ECHO response, and then triggers an OnTelnetCommand(tncLocalEcho) event to tell you to locally echo what you send.

If the server sends an IAC DO ECHO to you, TIdTelnet is hard-coded to send an IAC WILL ECHO response, and then triggers an OnTelnetCommand(tncEcho) event to tell you to echo back whatever you receive.

If the server sends an IAC DONT ECHO to you, TIdTelnet is hard-coded to send an IAC WONT ECHO response, and then triggers an OnTelnetCommand(tncLocalEcho) event to tell you to echo back whatever you receive.

So, if you don't want the server to echo back to you, you can send an IAC DONT ECHO command to the server, not an IAC DO SUPLOCALECHO command. The server will then reply with either IAC WONT ECHO or IAC WILL ECHO accordingly (which apparently TIdTelnet will then reply to, but the server will not respond back again since its ECHO current state does not change, avoiding an endless response loop).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks, I found it a bit confusing but indeed IAC DONT ECHO is the sequence I was looking for. Any chance you change this into a property in later revisions of Indy? – whosrdaddy Jun 20 '13 at 16:48
  • `TIdTelnet` is very barebones, so I am sure there is a lot that can be done to make it better in a future version. – Remy Lebeau Jun 20 '13 at 21:23
0

@Remy Lebeau: As I understand it, you're saying: If the server sends an IAC DO ECHO to you, TIdTelnet is hard-coded to send an IAC WILL ECHO response, and then triggers an OnTelnetCommand( tncEcho ) event to tell you to echo back whatever you receive.

That makes sense and agrees with what the RFC857 sets out to achieve...

However, in the code we have:

procedure TIdTelnet.Negotiate;
...
      TNC_DONT:
        begin
          b := IOHandler.ReadByte;
          case b of
            TNO_ECHO:
              begin
                DoTelnetCommand(tncEcho);
                //DoStatus('ECHO');    {Do not Localize}
                Reply := TNC_WONT;
              end;
            else
              Reply := TNC_WONT;
          end;

and

  TNC_DO:
    begin
      b := IOHandler.ReadByte;
      case b of
        TNO_ECHO:
          begin
            Reply := TNC_WILL;
            DoTelnetCommand(tncLocalEcho);
          end;

Surely this code is not correct? (in version 10.6.0.497 of Indy)

I believe this would make more sense:

 procedure TIdTelnet.Negotiate;
 ...
      TNC_DONT:
         begin
           b := IOHandler.ReadByte;
           case b of
             TNO_ECHO:
               begin
               // Agree not to echo back everything received from server 
               // (This being the default - you shouldn't echo unless asked to)
                  Reply := TNC_WONT;            
               // Therefore only print locally what is sent to server
               // (Again: this is the default behavior without negotiation) 
                  DoTelnetCommand(tncLocalEcho);
               end;
             else
               Reply := TNC_WONT;
           end;

and

   TNC_DO:
     begin
       b := IOHandler.ReadByte;
       case b of
         TNO_ECHO:
           begin
           // Agree to echo back everything received from server 
              Reply := TNC_WILL;
              DoTelnetCommand(tncEcho); 
           // Therefore you may still have to locally print what you send
           // (i.e. local echo is usually still implicit in this)
           end;   

In other words, I believe the code is presently swapped from what it should be - which is that the server sending DO ECHO should beget the tncEcho token - which is what you said in the above quote!

How has this bug survived this long? (probably because most Telnet servers don't bother with RFC857 echo negotiation any more)

Unfortunately the only way I see to "compensate" for this bug at the moment is to create a copy of the IDTelnet.pas file; link that to your project in the Project Manager; and then make the corrections to that copy as outlined above.

Alex T
  • 375
  • 3
  • 8
0

To tackle the original question: "How to supress echo with TIdTelnet?", I would suggest the following steps:

  1. Add a TIdConnectionIntercept to your form and hook it into your TIdTelnet component via the Intercept property using the Object Inspector.

  2. Produce the event handler for the TIdConnectionIntercept OnSend event.

  3. Use this to detect when its var ABuffer: TIdBytes parameter gets the response to the server's negotiation command regarding echoing. Note that buffer WILL have EXACTLY and ONLY all three bytes of the response in it due to the way the response is stuffed and sent by TIdTelnet.Negotiate

  4. Alter the middle byte as desired (i.e. to enforce a change from WILL to WONT or DO to DONT to suit what you are trying to enforce).

Job's a "good-un"

Remember of course that this technique injects the change AFTER TidTelnet OnTelnetCommand is called, so you will have to alter anything you set up in that handler to suite your modified response.

Alex T
  • 375
  • 3
  • 8
  • Unfortunately, I've since discovered that forcing a DONT in a client will cause a SERVER written in Delphi XE4 to raise an assertion exception in the IOHandler! This appears to be because the hard-coding in TidTelnet produces an additional response: i.e. we get the sequence WILL -> DONT -> WONT -> DONT (and that last DONT should not happen). The unexpected 3-byte command can cause the server (when written with Unicode support enabled) to detect a "missing byte" and raise an assertion exception. – Alex T Aug 21 '13 at 16:57
  • So to summarize: (a) TidTelnet will answer once too often in the WILL-DONT-WONT sequence and (b) TidTelnetServer may have a bug relating to receiving unexpected odd-number byte sequences. – Alex T Aug 21 '13 at 16:57
  • It might be because I had to use result := AContext.Connection.IoHandler.WaitFor(chr(TNC_IAC),False,False,Indy8BitEncoding,Timeout); in the server code, since there was no TidByte overload for "WaitFor". This omission of an overload may be at the heart of the problem - though I still don't think its very good for unexpected incoming data to crash a server and cause it to disconnect! – Alex T Aug 21 '13 at 17:04
  • Same thing happens when injecting WONT: i.e. we get a sequence of DO-WONT-DONT-WONT, where the last WONT raises an assertion exception in the server. Turning off assertions stops the crash, but the server will then receive two unexpected characters (the last 2 bytes of the 3-byte WONT) mixed in with its normal text input. So it looks to me that unicode support and Telnet have still not been worked out properly yet. – Alex T Aug 21 '13 at 17:16
  • For now, I've solved the problem by detecting the unnecessary re-transmission of the 3-byte WONT & DONT responses in the TidConnectionIntercept OnSend handler and setting each to three nulls (0,0,0). This stops the server from raising the assertion error because it doesn't try to interpret an unexpected command. Sending three nulls is a pretty safe (albeit annoying) thing to do. Unfortunately it appears you can't use OnSend to just delete the buffer. – Alex T Aug 21 '13 at 17:32