Telnet protocolo and Memo

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

Telnet protocolo and Memo

Postby mark_c » Thu Feb 15, 2018 1:33 pm

Hello,
this is a part of my program that uses the telnet protocol to send commands to a unix server.
The problem with this program is the Memo, which also shows non-printable characters. I thought of a loop for the purpose of cleaning up the text received from the socket but, my idea is that I can lose some characters during socket acquisition. Another problem is that the characters \ n and \ r present in the data coming from the socket are not recognized by the Memo and not displayed correctly.
Code: Select all
AnsiString MyBuffer; // declared public for all functions

void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
      TCustomWinSocket *Socket)
{
   int BytesReceived;
 
    int Size = Socket->ReceiveLength();
   
    if (Size > 0)
    {
        char *Buffer = new char[Size];
        if ((BytesReceived = Socket->ReceiveBuf(Buffer, Size) > 0))
         MyBuffer += AnsiString(Buffer, ByteRecived);
        delete[] Buffer;
    }
   
    if (strstr(MyBuffer, "ÿþ%ÿý") )   
      Socket1->SendText("ÿü%ÿûÿû");
      
    if (strstr(MyBuffer, "ÿýÿýÿúÿð") )   
      Socket1->SendText("ÿûÿú?ANSIÿð");      
   
   int a=1;
   for(int i=1; i < MyBuffer.Length();i++)
      if(MyBuffer.substring(i,1) == "\n" || MyBuffer.substring(i,1) == "\r")
      {
         MyBuffer.substring(i,1) = "";                  
         Memo1->Lines->Add(MyBuffer.substring(a,i));
         a=i;
      }
   MyBuffer="";
   
}
mark_c
BCBJ Veteran
BCBJ Veteran
 
Posts: 99
Joined: Thu Jun 21, 2012 1:13 am

Re: Telnet protocolo and Memo

Postby HsiaLin » Thu Feb 15, 2018 2:08 pm

Have you tried setting the Memo font to terminal to see how that works.
HsiaLin
BCBJ Master
BCBJ Master
 
Posts: 297
Joined: Sun Jul 08, 2007 6:29 pm

Re: Telnet protocolo and Memo

Postby mark_c » Thu Feb 15, 2018 2:48 pm

there is not this setting in the old bcb6
mark_c
BCBJ Veteran
BCBJ Veteran
 
Posts: 99
Joined: Thu Jun 21, 2012 1:13 am

Re: Telnet protocolo and Memo

Postby rlebeau » Thu Feb 15, 2018 11:44 pm

mark_c wrote:this is a part of my program that uses the telnet protocol to send commands to a unix server.
The problem with this program is the Memo, which also shows non-printable characters. I thought of a loop for the purpose of cleaning up the text received from the socket but, my idea is that I can lose some characters during socket acquisition. Another problem is that the characters \ n and \ r present in the data coming from the socket are not recognized by the Memo and not displayed correctly.


One, your code doesn't even compile, since you have some typos in it. For instance, your use of MyBuffer.substring() is all wrong. And you can't pass an AnsiString itself to the C library's strstr() function. strstr() expects 'const char*' pointers as input, so you would have to pass the pointer returned by the AnsiString::c_str() method instead. But then, why are you using strstr() at all, instead of using the AnsiString::Pos() method, or the RTL's System::Pos/Ex() function?

Two, your code is not even remotely close to being an accurate Telnet client. It is not implementing the Telnet protocol correctly at all, let alone handling individual Telnet commands the right way.

Three, when dealing with Telnet non-command data, I suggest you use the TMemo's SelText property to insert text into the UI, instead of using the Lines->Add() method. Not all Telnet servers send line-based data, especially when prompting for user input data. Using the SelText property will give you more of a terminal experience, by inserting exactly what is given to you, as it is being given to you.

With that said, I would suggest something more like this instead (untested, but you should get the idea):

Code: Select all
#include <StrUtils.hpp>

enum SeqType { seqNone, seqCommand, seqData };

...

__published:
    TClientSocket *ClientSocket1;
    void __fastcall ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket);
    void __fastcall ClientSocket1Write(TObject *Sender, TCustomWinSocket *Socket);
    void __fastcall ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket);
    ...
private:
    AnsiString InBuffer;
    AnsiString OutBuffer;
    SeqType __fastcall NextSequenceFromBuffer(AnsiString &Sequence);
    int __fastcal SendNow(TCustomWinSocket *Socket, const AnsiString &Data);
    void __fastcal Send(TCustomWinSocket *Socket, const AnsiString &Data);

...

void __fastcall TForm1::ClientSocket1Connect(TObject *Sender, TCustomWinSocket *Socket)
{
    InBuffer = "":
    OutBuffer = "";
}

SeqType __fastcall TForm1::NextSequenceFromBuffer(AnsiString &Sequence)
{
    Sequence = "";

    if( InBuffer.IsEmpty() )
        return seqNone; // more data needed...

    // look for starting IAC of a command, ignoring IAC IAC for escaped 0xFF data bytes...
    int Offset = PosEx("\xFF", InBuffer, 1); // IAC
    while( (Offset != 0) &&
           ((Offset+1) <= InBuffer.Length()) &&
           (InBuffer[Offset+1] == '\xFF') ) // IAC
    {
        Offset = PosEx("\xFF", InBuffer, Offset+2);
    }

    if( Offset == 0 ) // was a command found?
        Offset = InBuffer.Length() + 1; // no, return whole buffer

    if( Offset > 1 )
    {
        // non-command data before a command...

        int len = Offset-1;
        // check if last character is a CR, need more data to decide what to do with it...
        if( InBuffer[len] == '\r' ) --len;

        if( len == 0 )
            return seqNone;

        // return the data, replacing IAC IAC with 0xFF, CR<nul> with CR, etc...
        static const TReplaceFlags flags = TReplaceFlags() << rfReplaceAll;
        Sequence = InBuffer.SubString(1, len);
        Sequence = StringReplace(Sequence, "\xFF\xFF", "\xFF", flags);
        Sequence = StringReplace(Sequence, AnsiString("\r\0", 2), "\r", flags);

        // remove data from buffer...
        InBuffer.Delete(1, len);

        return seqData;
    }

    if( InBuffer.Length() < 2 )
        return seqNone; // more data needed...

    switch( InBuffer[2] ) // command code
    {
        case '\xFA': // sub-negotiation
        {
            if( InBuffer.Length() < 5 )
                return seqNone; // more data needed...

            // look for closing IAC SE after parameter data...
            Offset = PosEx("\xFF\xF0", InBuffer, 4);
            if( Offset == 0 )
                return seqNone; // more data needed...

            // return option code and parameter data...
            Sequence = InBuffer.SubString(2, Offset-2);

            // remove whole command from buffer...
            InBuffer.Delete(1, Offset+1);

            break;
        }

        case '\xFB': // WILL
        case '\xFC': // WONT
        case '\xFD': // DO
        case '\xFE': // DONT
        {
            if( InBuffer.Length() < 3 )
                return seqNone;

            // return command code and option code...
            Sequence = InBuffer.SubString(2, 2);

            // remove whole command from buffer...
            InBuffer.Delete(1, 3);

            break;
        }

        default:
        {
            // return command code...
            Sequence = InBuffer.SubString(2, 1);

            // remove whole command from buffer...
            InBuffer.Delete(1, 2);

            break;
        }
    }
       
    return seqCommand;
}

int __fastcal TForm1::SendNow(TCustomWinSocket *Socket, const AnsiString &Data)
{
    const char *ptr = Data.c_str();
    int len = Data.Length();
    int total = 0;

    while( len > 0 )
    {
        int BytesSent = Socket->SendBuf(ptr, len);
        if( BytesSent < 1 )
            break;

        ptr += BytesSent;
        len -= BytesSent;
    }

    return total;
}

void __fastcal TForm1::Send(TCustomWinSocket *Socket, const AnsiString &Data)
{
    int NumSent = 0;

    if( OutBuffer.IsEmpty() )
    {
        // no earlier data is pending, send new data now...
        NumSent = SendNow(Socket, Data);
        if( NumSent == Data.Length() )
            return;
    }

    // buffer whatever wasn't sent, try again later...
    if( NumSent > 0 )
        OutBuffer += Data.SubString(1+NumSent, MaxInt);
    else
        OutBuffer += Data;
}

void __fastcall TForm1::ClientSocket1Write(TObject *Sender, TCustomWinSocket *Socket)
{
    if( !OutBuffer.IsEmpty() )
    {
        int NumSent = SendNow(Socket, OutBuffer);
        if( NumSent > 0 )
            OutBuffer.Delete(1, NumSent);
    }
}

void __fastcall TForm1::ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket)
{
    // check for new data...
    int Size = Socket->ReceiveLength();
    if( Size < 1 )
        return;

    // read new data and append to buffer...
    {
    AnsiString Buffer;
    Buffer.SetLength(Size);

    int BytesReceived = Socket->ReceiveBuf(&Buffer[1], Size);
    if( BytesReceived < 1 )
        return;

    Buffer.SetLength(BytesReceived);
    InBuffer += Buffer;
    }

    // process buffer...
    AnsiString sequence;
    SeqType type;

    while( (type = NextSequenceFromBuffer(sequence)) != seqNone )
    {
        if( type == seqData )
        {
            Memo1->SelStart = Memo1->GetTextLen();
            Memo1->SelLength = 0;
            Memo1->SelText = sequence;
            continue;
        }

        switch( sequence[1] )
        {
            case '\xFA': // sub-negotiation
            {
                case opCode = sequence[2];

                switch( opCode )
                {
                    case '\x18': // TERMINAL-TYPE
                    {
                        if( sequence[3] == 0x01 ) // SEND
                            Send(Socket, AnsiString("\xFF\xFA\x18\x00ANSI\xFF\xF0", 10)); // IAC SB TERMINAL-TYPE IS 'ANSI' IAC SE
                        break;
                    }

                    // handle other negotiated options as needed...
                }

                break;
            }

            case '\xFB': // WILL
            {
                char opCode = sequence[2];
                // server WILL do the specified option...
                break;
            }

            case '\xFC': // WONT
            {
                char opCode = sequence[2];
                // server WONT do the specified option...
                break;
            }

            case '\xFD': // DO
            {
                char opCode = sequence[2];

                switch( opCode )
                {
                    // options you WILL do...

                    case '\x18': // TERMINAL-TYPE
                    {
                        Send(Socket, "\xFF\xFB\x18"); // IAC WILL TERMINAL-TYPE
                        Send(Socket, "\xFF\xFB\x1F"); // IAC WILL NAWS
                        break;
                    }

                    case '\x1F': // NAWS
                    {
                        Send(Socket, "\xFF\xFA\x1F" + ... + "\xFF\xF0"); // send window width and height...
                        break;
                    }

                    // handle other options as needed...

                    default:
                        // everything else, you WONT do...
                        Send(Socket, "\xFF\xFC" + AnsiString(opCode));
                        break;
                }

                break;
            }

            case '\xFE': // DONT
            {
                // you WONT do the specified option...

                char opCode = token[2];

                switch( opCode )
                {
                    ...
                }

                Send(Socket, "\xFF\xFC" + AnsiString(opCode));
                break;
            }

            // handle other commands as needed...
        }
    }
}
Last edited by rlebeau on Fri Feb 16, 2018 3:19 pm, edited 1 time in total.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1504
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Telnet protocolo and Memo

Postby mark_c » Fri Feb 16, 2018 12:11 am

sorry, I wrote that code without testing it

Code: Select all

AnsiString MyBuffer;

void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
      TCustomWinSocket *Socket)
{

        unsigned char resp1[] = {0xff, 0xfc, 0x25, 0xff, 0xfb, 0x18, 0xff, 0xfb, 0x1f};
        unsigned char resp2[] = {0xff, 0xfa, 0x1f, 0x00, 0x50, 0x00, 0x19, 0xff, 0xf0 ,0xff, 0xfb, 0x18, 0xff, 0xfa, 0x18, 0x00, 0x41, 0x4e, 0x53, 0x49, 0xff, 0xf0};


    int BytesReceived;
 
    int Size = Socket->ReceiveLength();
   
    if (Size > 0)
    {
        char *Buffer = new char[Size];
        if ((BytesReceived = Socket->ReceiveBuf(Buffer, Size) > 0))
         MyBuffer += AnsiString(Buffer, BytesReceived);
        delete[] Buffer;
    }
   
    if ( strstr(MyBuffer.c_str(), "ÿþ%ÿý") )
                ClientSocket1->Socket->SendBuf(resp1, sizeof(resp1));
     
    if (strstr(MyBuffer.c_str(), "ÿýÿýÿúÿð") )
      ClientSocket1->Socket->SendBuf(resp2, sizeof(resp2));

   ...........
   
   int a=1;
   for(int i=1; i < MyBuffer.Length();i++)
      if(MyBuffer.SubString(i,1) == "\n" || MyBuffer.SubString(i,1) == "\r")
      {
         MyBuffer.SubString(i,1) = "";
         Memo1->Lines->Add(MyBuffer.SubString(a,i));
         a=i;
      }
   MyBuffer="";
mark_c
BCBJ Veteran
BCBJ Veteran
 
Posts: 99
Joined: Thu Jun 21, 2012 1:13 am

Re: Telnet protocolo and Memo

Postby rlebeau » Fri Feb 16, 2018 3:26 pm

mark_c wrote:sorry, I wrote that code without testing it


That is still not the right way to handle the Telnet protocol. Your use of strstr() to scan your buffer for Telnet command sequences is all wrong. Not only are you not handling Telnet command sequences on a per-command basis like you need to, but you are not even handling the possibility that your buffer may contain partial data at any given time, so you can mistakenly send Telnet commands to the Memo instead of replying to them. That is why I re-designed your code in my earlier example to scan the buffer for completed Telnet sequences, leaving incomplete sequences in the buffer, replying to only completed Telnet commands, and sending only non-command data to the Memo (and also to add the OnWrite event handler for buffering outbound sends, since you are clearly using the TClientSocket in non-blocking mode).
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1504
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Telnet protocolo and Memo

Postby mark_c » Sat Feb 17, 2018 5:50 am

you are right. My code does not understand if arrives only one byte, zero or complete message. In your opinion, if I add a control like this, can I understand if the message is complete and then treat it?

Code: Select all
AnsiString MyBuffer; // declared public for all functions
......................
......................
   
   if(Size == BytesReceived)
   {
      process MyBuffer....
      ..........
   }
}
mark_c
BCBJ Veteran
BCBJ Veteran
 
Posts: 99
Joined: Thu Jun 21, 2012 1:13 am

Re: Telnet protocolo and Memo

Postby denville » Mon Feb 19, 2018 12:15 pm

Remy, just a note to say thank you for this ditty. Having several times had to log incoming data to a memo type display it simply did not occur to me to use SelText in the manner you illustrate. Thanks again.

Denville.
denville
BCBJ Veteran
BCBJ Veteran
 
Posts: 52
Joined: Sat Mar 06, 2010 4:40 am

Re: Telnet protocolo and Memo

Postby mark_c » Tue Feb 20, 2018 8:47 am

Remy, in its implementation, has thought of a continuous streaming of data. I, instead, I have thought, to blocks of data of pre-established dimensions and I think this is wrong.
mark_c
BCBJ Veteran
BCBJ Veteran
 
Posts: 99
Joined: Thu Jun 21, 2012 1:13 am

Re: Telnet protocolo and Memo

Postby rlebeau » Tue Feb 20, 2018 12:00 pm

mark_c wrote:you are right. My code does not understand if arrives only one byte, zero or complete message. In your opinion, if I add a control like this, can I understand if the message is complete and then treat it?


No. TCP has no concept of message boundaries, so you MUST pay attention to the semantics of the protocol so you can detect where one message ends and the next begins. That means actually parsing the data, and only processing data that is complete.

mark_c wrote:Remy, in its implementation, has thought of a continuous streaming of data. I, instead, I have thought, to blocks of data of pre-established dimensions and I think this is wrong.


Yes, it is wrong. The Telnet protocol has structure to it. You have to code for that structure, so you are going to fail to process it correctly.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1504
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Telnet protocolo and Memo

Postby rlebeau » Tue Feb 20, 2018 12:02 pm

denville wrote:Remy, just a note to say thank you for this ditty. Having several times had to log incoming data to a memo type display it simply did not occur to me to use SelText in the manner you illustrate. Thanks again.


You are welcome. And if you want to be really clever about it, you should use the EM_SETSEL message directly, instead of using the TMemo.SelStart and TMemo.SelLength properties individually (the latter in particular sends EM_GETSEL, EM_SETSEL, and EM_SCROLLCARET messages, two of which you can avoid).
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1504
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA


Return to Technical

Who is online

Users browsing this forum: No registered users and 13 guests

cron