Sunday, 19 July 2020

Nodemcu esp8266 telnet client

The example net example in the nodemcu docs works ok talking to a busybox telnetd, but when trying to talk to an old Linux NetKit (0.17)  in.telnetd running on a bifferboard it chokes on the telnet option negotiation.
The man page for the sever does say
"Because of bugs in the original 4.2 BSD telnet(1), telnetd performs some dubious protocol exchanges" 

So, wading thru RFCs and wireshark captures to fake up the telnet options results in this :

-- tn.lua =====================================

-- TELNET OPTIONS
NUL = string.char(0)
ECHO = string.char(1)
SGA = string.char(3)
ESC = string.char(27)
FLOWCONTROL = string.char(33)
SUB = string.char(250)
WILL = string.char(251)   --fb
WONT = string.char(252)   --fc
DO = string.char(253)     --fd
DONT = string.char(254)   --fe
IAC = string.char(255)    --ff
AYT = string.char(246)  -- ARE YOU THERE
SE = string.char(240)  -- End of subnegotiation
GA = string.char(249) -- GO AHEAD
BRK = string.char(243)  -- BRK
IP = string.char(244) -- INTERUPT PROCESS
DM = string.char(244)  -- DATA MARK

optionsSent = nil 
buffer = ""
hexprint = function(s)
 for z=1,#s  do
    uart.write(0, string.format("/x%x - %d ",string.byte(s,z),string.byte(s,z)))
 end
end


function conff = function(s,...)  return s:format(...) end

telnetOptions =  IAC .. DO .. SGA ..
                 IAC .. WILL .. string.char(24)..
                 IAC .. WILL .. string.char(31) ..
                 IAC .. WILL .. string.char(32) ..
                 IAC .. WONT .. FLOWCONTROL ..
                 IAC .. WILL .. string.char(34) ..
                 IAC .. WILL .. string.char(39) ..
                 IAC .. DO .. string.char(05) ..
                 IAC .. WILL .. string.char(35)
   
--subptions
telnetSubOptions = IAC .. SUB .. string.char(31) .. NUL .. string.char(80) .. NUL .. string.char(24) .. IAC .. SE ..
IAC .. SUB .. string.char(32) .. NUL .. "115200,115200" .. IAC .. SE  ..
IAC .. SUB .. string.char(35) .. NUL .. "ESP8266" .. IAC .. SE .. 
IAC .. SUB .. string.char(39) .. NUL .. "ESP8266" .. IAC .. SE ..
IAC .. SUB .. string.char(24) .. NUL .. "ansi" .. IAC .. SE
      
if IP and LOGIN and PASSWORD then
conn=net.createConnection(net.TCP, false)

conn:on("connection", function(sck, c)
 if DEBUG then print ("*** Connection ***")  end
 optionsSent = 1
 conn:send(telnetOptions)
 if DEBUG then print "** Telnet options SENT ***"  end
end )

conn:on("sent",function(sck,c)
 -- print "**** SENT ***"
 if optionsSent == 1 then
   if DEBUG then print "** Telnet sub options SENT ***" end
   conn:send(telnetSubOptions)
   optionsSent = optionsSent + 1
   end
 end )

conn:on("receive", function(sck, c)
 if DEBUG then print ("*** Receiving ***") end
 

 if string.find(c, IAC .. DO .. string.char(24)) then
  print("DO TERMINAL")
-- conn:send(conff("%c%c%c",IAC,DO,24))
 end
 

 if string.find(c, IAC .. DO .. string.char(32)) then
  print("DO TSPEED")
-- conn:send(conff("%c%c%c",IAC,DO,32))
 end
 

 if string.find(c, IAC .. DO .. string.char(35)) then
   print("DO XDISPLOC")
-- conn:send(conff("%c%c%c",IAC,DO,32))
 end
 

 if string.find(c, IAC .. DO .. string.char(39)) then
  print("DO ENVIRON")
-- conn:send(conff("%c%c%c",IAC,DO,39))
-- conn:send(conff("%c%c%c",IAC,DO,03))
 end
 

  if string.find(c, IAC .. DO .. ECHO ) then 
   conn:send(IAC .. WONT .. ECHO) -- WONT DO ECHO
   print "rec do echo, send wont"
 end   
 

  if string.find(c, IAC .. WILL .. ECHO ) then
  conn:send(IAC .. DO .. ECHO) -- WONT DO ECHO
  print "rec will echo, sending do"
 end  
 

  if string.find(c, IAC .. SUB .. FLOWCONTROL .. NUL ) then 
    print ("REMOTE FLOW CONTROL OFF")
 end  
 

  if string.find(c, IAC .. SUB .. FLOWCONTROL .. string.char(01) ) then 
    print ("REMOTE FLOW CONTROL ON")
    tmr.delay(100)
 end  
 

 if  string.find(c, "gin:") then conn:send(LOGIN .. "\n") end

 if string.find(c, "word:") then conn:send(PASSWORD .. "\n") end

  uart.write(0,c)

end )

 uart.on("data", 0 ,
   function(data)
    if buffer == "" then
      buffer = data
    else
      buffer = buffer .. data
    end 


     if string.find(buffer,"+++q")  then
       uart.on("data") -- unregister callback function
       conn:close()
     end
    
     if string.find(buffer,"+++c") then
        buffer = ""
        conn:connect(23,HOSTIP)
     end
    
    if #buffer > 20 then buffer = "" end
    if string.find(buffer,"\r")  then buffer = "" end
 
      
   if string.find(buffer,ESC ) then
     if DEBUG then hexprint ( buffer  ) end
     if string.find(buffer,ESC .. ESC ) then
       conn:send( ESC )
       buffer = ""
     end          

    -- CURSOR KEYS
    CURSORKEY =  ESC .. "%[%u"
      if string.find(buffer,CURSORKEY ) then
        if DEBUG then hexprint ( buffer  ) end
        conn:send( string.match(buffer,CURSORKEY ))
        buffer = ""
      end          
  
    else
        conn:send( data )
    end
 end, 0)

 print "+++c to connect , +++q to disconnect"

 else
 print ("define HOSTIP, LOGIN and PASSWORD and try again")
 end

--=======================================================


this works for the old server, but doesn't work on a busybox telnetd or a newer telnetd (GNU inetutils) 1.9.2. it needs a proper state machine to do negotiation.
 
And you need to push escape twice to send escape, it is a work in progress.