Simple Telnet Connection / Scripting

Share your advanced PureBasic knowledge/code with the community.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Simple Telnet Connection / Scripting

Post by Xombie »

From my post here: http://www.purebasic.fr/english/viewtopic.php?t=31106

Please don't laugh too much at this because I never worked with the network library before and certainly not with telnet in it's raw form. Take this is an example for building your own library. I didn't take the time to clean this up since it works fine for me in my application (of which it is a pretty small but important part). I use it for scripting entry of information into our old CRM system. I've tested it with two different types of "scripts" and it works fine.

The code here is stripped down but hopefully gives some idea of how to do it.

TelnetConnect() is the main procedure that connects to a server and handles initial negotiation. You can make your own mini "scripts" in a similar way to the Telnet_DoSomething(). In my program I have a queue system that will handle different types of scripts in a thread without bothering the user. It outputs what it's doing to a separate window that the user can minimize while working on other parts of the program.

TelnetWaitUntilHas() is the procedure that actually does most of the work. It's ugly, big and can certainly be better but I don't have the time to fix it up at the moment. The Send parameter contains the keys or text you want to send. The Contains parameter is what you're expecting AFTER[\b] Send is sent to the server. AsRaw tells the procedure you're sending raw character codes. For example, to send F4 to the server I connect to, I need to send "chr(27)+[P" which are the characters "27, 91 and 80" so I would do...

Code: Select all

TelnetWaitUntilHas(connectionid, "27,91,80", "Main Menu", #True)
The code sends the F4 keystrokes as raw data and expects to be returned to the main menu. It will watch the text sent to the client until it gets those characters.

So, yeah. This all could be used as a basis for a much, much better library. Or, later (much later :cry: ), I could work something up as a small library.

Hope it helps!

Code: Select all

;-
EnableExplicit
;-
InitNetwork()
;-
Structure s_Telnet_Result
   ;
   Text.s
   ;
EndStructure
;-
Procedure.s TelnetCleanText(Value.s)
   ;
   Protected CaughtEscape.l
   ;
   Protected *HoldChar.Character
   ;
   Protected HoldString.s = Space(Len(Value))
   ;
   Protected *HoldOut.Character = @HoldString
   ;
   Protected IndexBegin.l, IndexEnd.l
   ;
   *HoldChar = @Value
   ;
   While *HoldChar\c
      ;
      If *HoldChar\c = 27
         ;
         CaughtEscape = #True
         ;
      ElseIf CaughtEscape And (*HoldChar\c = 'm' Or *HoldChar\c = 'H')
         ;
         CaughtEscape = #False
         ;
      Else
         ;
         If CaughtEscape = #False
            ;
            *HoldOut\c = *HoldChar\c
            ;
            *HoldOut + SizeOf(Character)
            ;
         EndIf
         ;
      EndIf
      ;
      *HoldChar + SizeOf(Character)
      ;
   Wend
   ;
   ProcedureReturn Left(HoldString, *HoldOut - @HoldString)
   ;
EndProcedure
Procedure TelnetSendRaw(iConnection.l, Codes.s)
   ;
   Protected iMax.l
   Protected iLoop.l
   ;
   Protected *Memory.l
   Protected *Position.Byte
   ;
   iMax = CountString(Codes, ",") + 1
   *Memory = AllocateMemory(iMax)
   *Position = *Memory
   For iLoop = 1 To iMax
      *Position\b = Val(StringField(Codes, iLoop, ","))
      *Position + 1
   Next iLoop
   SendNetworkData(iConnection, *Memory, iMax)
   FreeMemory(*Memory)
   ;
EndProcedure
Procedure.l TelnetWaitUntilHas(iConnection.l, Send.s, Contains.s, AsRaw.l = #False, Wait.l = 10000, *Result.s_Telnet_Result = #Null)
   ;
   Protected iMax.l
   Protected iLoop.l
   ;
   Protected HoldTime.q
   Protected HoldString.s
   Protected CaughtMatch.l
   Protected *HoldChar.Character
   ;
   Protected Result.l
   ;
   Protected *Memory.l
   Protected *Position.Byte
   ;
   Protected q.l, r.l
   ;
   Protected HoldStart.l
   ;
   Protected IsError.l
   ;
   Protected HoldLength.l
   ;
   If AsRaw
      ;
      iMax = CountString(Send, ",") + 1
      *Memory = AllocateMemory(iMax)
      *Position = *Memory
      For iLoop = 1 To iMax
         *Position\b = Val(StringField(Send, iLoop, ","))
         *Position + 1
      Next iLoop
      Debug "Sending: "+Send
      SendNetworkData(iConnection, *Memory, iMax)
      Debug "Done Sending..."
      FreeMemory(*Memory) : *Memory = 0
      ;
      If Contains = "" : CaughtMatch = #True : EndIf
      ;
   Else
      ;
      For iLoop = 1 To 255
         Send = ReplaceString(Send, "chr("+Str(iLoop)+")", Chr(iLoop))
      Next iLoop
      ;
      Debug "Sending: "+Send
      If Send <> "" : SendNetworkString(iConnection, Send) : EndIf
      Debug "Done Sending..."
      ;
   EndIf
   ;
   HoldTime = ElapsedMilliseconds()
   ;
   Repeat
      ;
      If NetworkClientEvent(iConnection) = #PB_NetworkEvent_Data
         ;
         If *Memory : FreeMemory(*Memory) : *Memory = 0 : EndIf
         *Memory = AllocateMemory(1000)
         HoldLength = ReceiveNetworkData(iConnection, *Memory, 1000)
         ;
         Debug TelnetCleanText(PeekS(*Memory, HoldLength))
         ;
         If HoldLength
            ;
            *HoldChar = *Memory
            While *HoldChar - *Memory < HoldLength
               If *HoldChar\c = 0 : *HoldChar\c = 1 : EndIf
               *HoldChar + SizeOf(Character)
            Wend
            ;
            HoldString = HoldString + TelnetCleanText(ReplaceString(PeekS(*Memory, HoldLength), Chr(1), ""))
            ;
            If Contains <> ""
               ;
               iMax = CountString(Contains, Chr(29)) + 1
               For iLoop = 1 To iMax
                  If FindString(HoldString, StringField(Contains, iLoop, Chr(29)), 1)
                     CaughtMatch = #True
                     If iLoop > 1 : IsError = #True : EndIf
                  EndIf
               Next iLoop
               ;
            Else
               ;CaughtMatch = #True
            EndIf
            ;
         EndIf
         ;
         If *Memory : FreeMemory(*Memory) : *Memory = 0 : EndIf
         ;
      EndIf
      ;
   Until CaughtMatch Or ElapsedMilliseconds() - HoldTime > Wait
   ;
   Debug "       Took: "+Str(ElapsedMilliseconds() - HoldTime)
   ;
   If *Result : *Result\Text = HoldString : EndIf
   ;
   If Contains = "" : CaughtMatch = #True : EndIf
   ;
   If IsError : CaughtMatch = #False : EndIf
   ; 
   ProcedureReturn CaughtMatch
   ;
EndProcedure
Procedure Telnet_DoSomething(iConnection.l)
   ;
   If TelnetWaitUntilHas(iConnection, "D", "A. Inventory Information")
      ;
      ;
   EndIf
   ;
EndProcedure
Procedure TelnetConnect()
   ;
   Protected *HoldOut.l
   Protected *Out.Byte
   ;
   Protected *HoldMemory.l
   ;
   Protected iLoop.l
   ;
   Protected CaughtMatch.l
   ;
   Protected GotPrompt.l, GotPassword.l
   ;
   Protected HasDBChoiceMenu.l, HasMainMenu.l
   ;
   Protected *HoldChar.Character
   ;
   Protected StartTime.q
   ;
   Protected HoldString.s
   ;
   Protected HoldValue.l
   ;
   Protected HoldThread
   ;
   Protected Result.l
   ;
   Protected IsCheckingTerminalType.l
   ;
   Protected iConnection.l
   ;
   Protected *Memory.l
   Protected HoldLength.l
   ;
   Protected HoldFlash.FLASHWINFO
   ;
   Protected HoldResult.s_Telnet_Result
   ;
   Protected lResult.l
   ;
   iConnection = OpenNetworkConnection("server_name", 23)
   ;
   If iConnection
      ;
      ;{ Negotiate
      Repeat
         ;
         Result = 0
         ;
         If *Memory : FreeMemory(*Memory) : *Memory = 0 : EndIf
         ;
         *Memory = AllocateMemory(1000)
         ;
         HoldLength = ReceiveNetworkData(iConnection, *Memory, 1000)
         ;
         If HoldLength = 0 : Break : EndIf
         ;
         *HoldMemory = *Memory
         ;
         ; Debug Str(gLength)+" characters."
         ; For iLoop = 0 To gLength - 1
            ; Debug Str(PeekB(*HoldMemory + iLoop) & $FF)
         ; Next iLoop
         ;
         While *HoldMemory - *Memory < HoldLength
            ;
            If PeekB(*HoldMemory) & $FF  = 255
               ; IAC code.
               HoldValue = PeekB(*HoldMemory + 1) & $FF 
               ;
               If HoldValue = 250
                  ;{ SB
                  If PeekB(*HoldMemory + 2) & $FF = 24 And PeekB(*HoldMemory + 3) & $FF = 1 And PeekB(*HoldMemory + 4) & $FF = 255 And PeekB(*HoldMemory + 5) & $FF = 240
                     ; IAC SB TERMINAL-TYPE SEND IAC SE
                     ; TelnetSendRawData("255,250,24,0,65,78,83,73,255,240")
                     TelnetSendRaw(iConnection, "255,250,24,0,86,84,49,48,48,255,240")
                     ; Send ANSI terminal type.
                     *HoldMemory + 6 
                     ;
                  EndIf
                  ;}
               ElseIf HoldValue = 251
                  ;{ Will
                  If PeekB(*HoldMemory + 2) & $FF = 1
                     ; Echo
                     TelnetSendRaw(iConnection, "255,254,1")
                     ; Send DONT echo.
                     *HoldMemory + 3
                     ;
                  ElseIf PeekB(*HoldMemory + 2) & $FF = 3
                     ; Echo
                     TelnetSendRaw(iConnection, "255,253,3")
                     ; Send DON suppress go ahead.
                     *HoldMemory + 3
                     ;
                  Else
                     ;
                     TelnetSendRaw(iConnection, "255,254,"+Str(PeekB(*HoldMemory+2) & $FF))
                     ; Send DONT to the message.
                     *HoldMemory + 3
                     ; 
                  EndIf
                  ;}
               ElseIf HoldValue = 252
                  ;{ Won't
                  If PeekB(*HoldMemory + 2) & $FF = 37
                     ; Athenticate
                     TelnetSendRaw(iConnection, "255,254,37")
                     ; Send DONT authenticate.
                     *HoldMemory + 3
                     ;
                  Else
                     ;
                     TelnetSendRaw(iConnection, "255,254,"+Str(PeekB(*HoldMemory+2) & $FF))
                     ; Send DONT to the message.
                     *HoldMemory + 3
                     ;
                  EndIf
                  ;}
               ElseIf HoldValue = 253
                  ;{ Do
                  If PeekB(*HoldMemory + 2) & $FF = 24
                     ; Terminal Type
                     If IsCheckingTerminalType = #False : TelnetSendRaw(iConnection, "255,251,24") : EndIf
                     ; If IsCheckingTerminalType = #False : TelnetSendRaw(iconnection, "255,252,24") : EndIf
                     ; Agree to send terminal type.  Don't send this out again if already sent.
                     *HoldMemory + 3
                     ;
                     IsCheckingTerminalType = #True
                     ;
                  ElseIf PeekB(*HoldMemory + 2) & $FF = 1
                     ; Allow echo
                     TelnetSendRaw(iConnection, "255,252,1")
                     ; Tell the server we WONT echo.
                     *HoldMemory + 3
                     ;
                  ElseIf PeekB(*HoldMemory + 2) & $FF = 3
                     ; SUPPRESS-GO-AHEAD, Suppress Go Ahead.
                     TelnetSendRaw(iConnection, "255,251,3")
                     ; Tell the server we WILL suppress go-ahead messages
                     *HoldMemory + 3
                     ;
                  ElseIf PeekB(*HoldMemory + 2) & $FF = 31
                     ; Negotiate about window size
                     TelnetSendRaw(iConnection, "255,252,31")
                     ; Tell the server we WONT negotiate about window size.
                     *HoldMemory + 3
                     ;
                  Else 
                     ;
                     TelnetSendRaw(iConnection, "255,252,"+Str(PeekB(*HoldMemory+2) & $FF))
                     ; Send WONT to the message.
                     *HoldMemory + 3
                     ;
                  EndIf
                  ;}
               ElseIf HoldValue = 254
                  ;{ Don't
                  If PeekB(*HoldMemory + 2) & $FF = 37
                     ; Athenticate
                     TelnetSendRaw(iConnection, "255,252,37")
                     ; Send WONT authenticate.
                     *HoldMemory + 3
                     ;
                  Else
                     ;
                     TelnetSendRaw(iConnection, "255,252,"+Str(PeekB(*HoldMemory+2) & $FF))
                     ; Send WONT to the message.
                     *HoldMemory + 3
                  EndIf
                  ;}
               EndIf
               ;
            Else
               ;
               If PeekB(*HoldMemory) & $FF  <= 0 : Break 2 : EndIf
               ;
               *HoldMemory + 1
               ;
            EndIf
            ;
         Wend
         ;
      ForEver
      ; 
      If *HoldOut : FreeMemory(*HoldOut) : *HoldOut = 0 : EndIf
      ;
      If *Memory : FreeMemory(*Memory) : *Memory = 0 : EndIf
      ;}
      ;
      If TelnetWaitUntilHas(iConnection, "", "login:")
         ;
         If TelnetWaitUntilHas(iConnection, "username_here" + Chr(13) + Chr(10), "Password:")
            ;
            lResult = TelnetWaitUntilHas(iConnection, "password_here" + Chr(13) + Chr(10), "Secur Menu"+Chr(29)+"You entered an invalid login name Or Password.", #False, 10000, @HoldResult)
            ;
            If lResult = 0
               ; Bad username or password.
               Debug "Bad username or password."
               ;
            Else
               ; Password is valid.
               If TelnetWaitUntilHas(iConnection, Chr(13)+Chr(10), "A. Overview")
                  ;
                  Telnet_DoSomething(iConnection)
                  ;- Do work here.  The user is at the main menu.
                  ;
                  If TelnetWaitUntilHas(iConnection, "27,91,80", "Do you want to Exit?", #True)
                     ;
                     Debug "Gracefully exited."
                     ;
                  EndIf
                  ;
               EndIf
               ;
            EndIf
            ;
         EndIf
         ; Session.Send chr(27) + "~[P"
         ; and then "Y" + enter
         ; and then Session.Send chr(27) + "~[P"
      EndIf
      ;
      CloseNetworkConnection(iConnection) : iConnection = 0
      ;
   EndIf
   ;
EndProcedure
;-
TelnetConnect()
;-
End
;-
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Post by Kwai chang caine »

Thanks for sharing, but i have an error, line 222 : "Structure no found'.
Is it normal ?
ImageThe happiness is a road...
Not a destination
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

Sorry, I left in some legacy code when I was trimming it down. Remove the...

Code: Select all

Protected HoldFlash.FLASHWINFO
... line from TelnetConnect(). I was using it to flash the queue window in my main program when all scripts were finished.

However, in case you need it, the structure is...

Code: Select all

Structure FLASHWINFO
   cbSize.l
   hwnd.l
   dwFlags.l
   uCount.l
   dwTimeout.l
EndStructure
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Post by Kwai chang caine »

Thanks, no error now 8)
ImageThe happiness is a road...
Not a destination
User avatar
Michael Vogel
Addict
Addict
Posts: 2666
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: Simple Telnet Connection / Scripting

Post by Michael Vogel »

I wrote a small management tool controling a lab by WMIC and SNMP which works fine (most of the time), but I would like to integrate a simple telnet client now.

I do not need full VT emulation (colors and so on), but there are still missing important things. Here's my fist approach which is a actually only a console app (so I don't have to deal with cursor left/right, backspace, delete, etc.) which is not able to do a complete login into the demo server...

Code: Select all

; Define

	Global Win=0
	Global Demo=1
	Global SetWinSize
	Global Connection

	#Mem=2048

	Structure Buffer
		byte.b[0]
	EndStructure

	#SB=			$FA
	#WILL=			$FB
	#WONT=		$FC
	#DO=			$FD
	#DONT=			$FE
	#CMD=			$FF
	#CMDECHO=		$01
	#WINSIZE=		$1F

	DataSection
		WinSizeInit:
		Data.b #CMD,#WILL,#WINSIZE
		WinSizeDefinition:
		Data.b #CMD,#SB,#WINSIZE, 0,100, 0,40, 255,240
	EndDataSection

; EndDefine
Procedure.s HexCode(*Memory.Buffer,Len)

	Protected r.s,s.s,t.s
	Protected c,i,n,strip

	strip=0

	#Gap="|"

	If Len>strip
		i=strip
		Len-strip

		While Len
			If n=16
				r+#Gap+s+"|"+t+"|"+#CRLF$
				s=""
				t=""
				n=0
			EndIf
			c=*Memory\byte[i]
			s+RSet(Hex((c)&$FF),2,"0")+" "
			If c>31 And c<127
				t+Chr(c)
			Else
				t+"."
			EndIf
			n+1
			If n=8
				t+" "
				s+" "
			EndIf

			i+1
			Len-1
		Wend

	EndIf

	ProcedureReturn r+#Gap+LSet(s,50," ")+"|"+LSet(t,18," ")+"|"

EndProcedure
Procedure PrintText(text.s)

	Protected i,len
	Protected s.s

	len=Len(text)
	While len
		Debug HexCode(@text,len)

		Select PeekA(@text)

		Case #CMD
			Select PeekA(@text+1)
			Case #WILL
				PokeA(@text+1,#DO)
				SendNetworkData(Connection,@text,3)
				Debug "YES WE CAN"
			Case #DO
				PokeA(@text+1,#WONT)
				SendNetworkData(Connection,@text,3)
				Debug "SICHER NICHT!"
			EndSelect

			If len>2
				len-3
				text=Mid(text,4)
			EndIf

		Default

			StartPosition = 1
			Repeat
				EndPosition = FindString(text, Chr(13), StartPosition)
				If EndPosition
					PrintN(Mid(text, StartPosition, EndPosition - StartPosition))
					StartPosition = EndPosition + 2
				Else
					len = Len(text) - StartPosition + 1
					If len > 0
						Print(Mid(text, StartPosition, len))
					EndIf
				EndIf
			Until EndPosition = 0

			len=0
		EndSelect

	Wend

EndProcedure
Procedure Receive(nil)

	mem = AllocateMemory(#MEM)

	Repeat
		Select NetworkClientEvent(Connection)
		Case #PB_NetworkEvent_Data
			size = ReceiveNetworkData(Connection,mem,#MEM)
			HexCode(mem,size)
			PrintText(PeekS(mem, size))
		Case #PB_NetworkEvent_None
			Delay(30)
		Case #PB_NetworkEvent_Disconnect
			PrintN("Connection closed. Press Enter.")
			quit=1
		EndSelect
	Until Quit

	FreeMemory(mem)

EndProcedure

If Win=0
	OpenConsole()
EndIf

If Demo Or CountProgramParameters()

	If InitNetwork()
		If Demo
			ip$="lab.sharontools.com"
			;ip$="127.0.0.1"
		Else
			ip$=ProgramParameter(0)
		EndIf

		Connection=OpenNetworkConnection(ip$,23,#PB_Network_TCP)
		If Connection
			Debug connection
			If SetWinSize
				SendNetworkData(Connection,?WinSizeInit,3)
				SendNetworkData(Connection,?WinSizeDefinition,9)
			EndIf
			ThreadID=CreateThread(@Receive(),#Null)
			Repeat
				input$ = Input()
				SendNetworkString(Connection, input$ + Chr(13) + Chr(10))
			Until Quit
			WaitThread(ThreadID)
			CloseNetworkConnection(Connection)

		Else
			PrintN("Telnet server "+ip$+" unreachable.")
		EndIf

	EndIf

Else
	PrintN("Telnet X client by Michael Vogel")
	PrintN("Server address or name needed")

EndIf
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Simple Telnet Connection / Scripting

Post by Kwai chang caine »

Thanks Michael for sharing, can be usefull a day 8)
ImageThe happiness is a road...
Not a destination
TassyJim
Enthusiast
Enthusiast
Posts: 151
Joined: Sun Jun 16, 2013 6:27 am
Location: Tasmania (Australia)

Re: Simple Telnet Connection / Scripting

Post by TassyJim »

Thanks Michael,
I had to add the ASCII flag to the peeks in the receive procedure.

Code: Select all

 PrintText(PeekS(mem, size, #PB_Ascii))
After that, it worked without any problems talking to my servers.

Jim
Post Reply