Playing with sockets

I’m writing a little expect-like piece of code and trying to test it
by connecting to a telnet port.

Not working at all for me.

Code is shown below.

And here’s the result:

[hal@dhcppc3]$ ruby repro.rb
"\377\375\030\377\375\037\377\375#\377\375’\377\375$"
repro.rb:14:in `wait’: Timed out waiting for login: (RuntimeError)
from repro.rb:37

What’s up??

Thanks,
Hal

require "socket"
class Talk
def initialize(input,wait=nil)
@in, @out, @wait = input, input, wait
@data = ""
end

def wait(str)
pos = nil
tmp = ""
until pos = @data.index(str)
unless IO.select([@in],nil,nil,@wait)
p @data
raise "Timed out waiting for #{str}"
end
begin
@data << tmp = @in.getc
rescue EOFError, TypeError
raise "EOF…"
end
end
end

def send(str)
unless IO.select(nil, [@out], nil, @wait)
raise "Timed out writing #{str}"
end
@out.syswrite(str)
end
end

sock = TCPSocket.open(“hypermetrics.com”,“telnet”)
sock.sync = true

t = Talk.new(sock,10)
t.wait "login:"
t.send "bob\n"
t.wait "Password:"
t.send "foobar\n"
t.wait “Login incorrect”

sock.close

Hal Fulton wrote:

I’m writing a little expect-like piece of code and trying to test it
by connecting to a telnet port.

Not working at all for me.

I made the obvious changes to make it connect to the SMTP daemon on my
machine and it works fine on Linux with Ruby 1.8.1 (2004-02-06).

Nothing is ever discarded from @data, and you search from the beginning
each time, so you’ll never find the second occurence of any string, but
that’s not the problem you’re having here.

Are you sure your telnet daemon is behaving? That trash string found at
the beginning is not right.

Steve

I know this isn’t the solution to your actual problem, but have you
seen the Telnet module? It looks very similar to what you’re trying to
do:

(adapted from the source of the telnet module–untested)

hal = Net::Telnet::new({“Host” => “hypermetrics.com”,
“Timeout” => 10,
“Prompt” => /[$%#>] \z/n})
hal.login(“bob”, “foobar”)
hal.waitfor({“Match” => /Login incorrect/})
puts “Fin”

···

On 23/4/2004, at 7:41 PM, Hal Fulton wrote:

I’m writing a little expect-like piece of code and trying to test it
by connecting to a telnet port.

While I know your question is already answered, I just wanted to point
out that what you experienced here is tty negotiation. Expect
encapsulates all processes in a pty so that you could run the telnet
program or anything else and have it work as one would want.

Dan

···

On Apr 23, 2004, at 19:41, Hal Fulton wrote:

And here’s the result:

[hal@dhcppc3]$ ruby repro.rb
“\377\375\030\377\375\037\377\375#\377\375’\377\375$”
repro.rb:14:in `wait’: Timed out waiting for login: (RuntimeError)
from repro.rb:37

What’s up??

Thanks,
Hal

Steven Jenkins wrote:

Hal Fulton wrote:

I’m writing a little expect-like piece of code and trying to test it
by connecting to a telnet port.

Not working at all for me.

I made the obvious changes to make it connect to the SMTP daemon on my
machine and it works fine on Linux with Ruby 1.8.1 (2004-02-06).

Nothing is ever discarded from @data, and you search from the beginning
each time, so you’ll never find the second occurence of any string, but
that’s not the problem you’re having here.

That’s a significant bug, thank you.

Are you sure your telnet daemon is behaving? That trash string found at
the beginning is not right.

Apparently the server is trying to negotiate with the client.

I tried to answer properly, to no avail.

Then I tried to use Net::Telnet to do it and then pretend to be an
ordinary socket. May work later, but not yet.

Hmm. How hard would it be to write a little program (say, a tiny
text adventure) to which I could connect via telnet? I’ve never
written anything like a telnet server, but it doesn’t seem hard
if we skip the “real” functionality of it. Am I wrong?

Thanks,
Hal

Chad Fowler wrote:

I’m writing a little expect-like piece of code and trying to test it
by connecting to a telnet port.

I know this isn’t the solution to your actual problem, but have you seen
the Telnet module? It looks very similar to what you’re trying to do:

I’m familiar with that.

I wonder: How hard is it to write a server to which one would connect
with telnet?

Hal

···

On 23/4/2004, at 7:41 PM, Hal Fulton wrote:

Dan Janowski wrote:

While I know your question is already answered, I just wanted to point
out that what you experienced here is tty negotiation. Expect
encapsulates all processes in a pty so that you could run the telnet
program or anything else and have it work as one would want.

Yes, I found a doc about that (or had it pointed out to me).

I couldn’t figure out how to answer successfully, though. It seemed
trivial, but didn’t work for me.

Then I thought: Maybe I can get Net::Telnet to do it for me, and then
I’ll let the connection serve as the socket. That didn’t work for me
either.

For now, I’m just opening sockets onto my little customized server –
a text adventure that currently has only five rooms. :slight_smile:

Hal

Here is something I ripped out of some code I am working on.
require ‘socket’
require ‘thread’

PORT=4033

io = Proc.new{ TCPServer.new(‘localhost’, PORT).accept }
#io = Proc.new { IO.new(1) }

mutex = Mutex.new

threads =
cmd_buffer =
threads << Thread.new do
cmd = “”
puts “Started thread” if $DEBUG

call the proc object that creates the io object

f = io.call
while ! cmd.match(/^quit|q/i)
f.print “HA>”
f.flush
cmd = f.readline.chomp
next if cmd.empty?
puts “Got command #{cmd}” if $DEBUG
(f.close; next) if cmd.match(/^(quit|q)$/i)
# lock access to cmd_buffer since another thread will
# be reading it
mutex.synchronize {cmd_buffer << cmd }
end
end

threads.each { |t| t.join }

END

On the server:

···

===========
$ ruby -d test
Started thread

On the client:

$ telnet ital.jasonandvivian.net 4033
Trying 192.168.168.20…
Connected to ital.
Escape character is ‘^]’.

testing
this is a test
quit
Connection closed by foreign host.

On the server:

Got command testing
Got command this is a test
Got command quit

I wonder: How hard is it to write a server to which one would connect
with telnet?

Hal

Hal Fulton wrote:

I wonder: How hard is it to write a server to which one would connect
with telnet?

To answer my own question: Frightfully easy.

 require 'socket'
 port = 8023
 server = TCPServer.new('localhost', port)
 session = server.accept
 while cmd = session.gets.chomp
   break if cmd =~ /quit/
   session.print "You said: '#{cmd}'\n"
 end
 session.print "Bye!"
 session.close

It’s nice when things just work.

Hal

Writing your own server is much easier and also more secure, not that
clear text is anything to brag about.

I had to write an expect program (I’m a long time TCL programmer) the
other day and was thinking about how nice it would be to have
ExpectRuby. It is pretty great to run sub-processes in a pty.

Dan

···

On Apr 27, 2004, at 01:02, Hal Fulton wrote:

Dan Janowski wrote:

While I know your question is already answered, I just wanted to
point out that what you experienced here is tty negotiation. Expect
encapsulates all processes in a pty so that you could run the telnet
program or anything else and have it work as one would want.

Yes, I found a doc about that (or had it pointed out to me).

I couldn’t figure out how to answer successfully, though. It seemed
trivial, but didn’t work for me.

Then I thought: Maybe I can get Net::Telnet to do it for me, and then
I’ll let the connection serve as the socket. That didn’t work for me
either.

For now, I’m just opening sockets onto my little customized server –
a text adventure that currently has only five rooms. :slight_smile:

Jason Wold wrote:

Here is something I ripped out of some code I am working on.

Very cool, Jason, thanks.

/me saves code.

It’s funny how much this has in common with the little
snippet I wrote five minutes ago, down to the ‘quit’
command.

Thanks much.

I’m thinking of actually writing a little miniature
text adventure – partly for testing my code, partly
just for the Hal of it.

Cheers,
Hal

Hal Fulton wrote:

Hal Fulton wrote:

I wonder: How hard is it to write a server to which one would connect
with telnet?

To answer my own question: Frightfully easy.

require 'socket'
port = 8023
server = TCPServer.new('localhost', port)
session = server.accept
while cmd = session.gets.chomp
  break if cmd =~ /quit/
  session.print "You said: '#{cmd}'\n"
end
session.print "Bye!"
session.close

It’s nice when things just work.

And if you want to handle multiple requests, it’s easy to thread this
server. See sample/tsvr.rb in the ruby distribution.

this?
http://rubyforge.org/projects/rexpect
(you may need to force the author to relrease it :slight_smile:

···

il Tue, 27 Apr 2004 14:24:08 +0900, Dan Janowski danj@3skel.com ha scritto::

I had to write an expect program (I’m a long time TCL programmer) the
other day and was thinking about how nice it would be to have
ExpectRuby. It is pretty great to run sub-processes in a pty.

Very cool, Jason, thanks.

You might find this interesting too. That was actually part of my
attempt to generalize IO for taking commands from multiple sources.
It is still not generalized enough (if FIFO checks) but work is still
in process. This code starts a thread listening on three different
sources. It currently only works on *nix but I hope to generalize the
FIFO listener to also work on windows:

# start servers to listen for commands on FIFO, TCP PORT, and STDIO
# FIXME make listen OS independant, use windows named pipe on windows
def listen(*listeners)

  # if no arguments then  listen on STDIN
  listeners = ["STDIO"] if listeners.size == 0

  #capitalize list of listeners
  listeners.map!{ |l| l.upcase }

  mutex = Mutex.new

  #restart all threads if we aren't running the threads we think we are
  #FIXME
  #restart(@threads.keys) if @threads.values.sort != (Thread.list
  • [Thread.main]).sort

    # TODO turn mkfifo into OS independant method that returns
    

correct IO object
mkfifo "#{FIFO_CONTROL}" unless test(?p, FIFO_CONTROL)

  # set an IO object for each listener to be called later
  io = {}
  io["FIFO"] = Proc.new {
                 File.open(FIFO_CONTROL, File::NONBLOCK | File::RDONLY ) }
  io["STDIO"] = Proc.new { IO.new(1) }
  io["TCP"] = Proc.new{ TCPServer.new('localhost', PORT).accept }

  writeable=%w(STDIO TCP)

  # thread to aggregate cmd_buffer and process it
···

  Thread.new do
    puts "Started Command Buffer processing thread" if $DEBUG
    cmd=""
    # run until we get a quit command
    #
    while ! cmd.match(/^(quit|q)$/i)
      # join buffers and zero them out
      mutex.synchronize do
        VALID_LISTENERS.each do |vl|
          @cmd_buffer["MAIN"] += @cmd_buffer[vl]
          @cmd_buffer[vl] = []
        end
      end

      # step through aggregate buffer, clean it, and run it
      while @cmd_buffer["MAIN"].size > 0
        puts "CMD BUFFER: " + @cmd_buffer["MAIN"].join(":")  if $DEBUG
        mutex.synchronize do
          cmd=@cmd_buffer["MAIN"].shift
        end
        # next if string is characters or numbers
        #FIXME (how do you internationalize untainting?)
        next if cmd.match(/[^A-Za-z0-9 ]/)
        cmd.untaint
        run_cmd(cmd)
      end
      sleep 1
    end
  end


  listeners.sort.each do |listener|
    # next unless we have a handler for this type
    next unless VALID_LISTENERS.include?(listener)
    # do we already have a thread listening?
    unless @threads[listener].respond_to?("alive") &&
           @threads[listener].alive?
      @threads[listener] = Thread.new do
        cmd = ""
        puts "Started #{listener} thread" if $DEBUG
        # call the proc object that creates the io object
        f = io[listener].call
        while ! cmd.match(/^quit|q/i)
          # Have to poll FIFO's and will get eof if nothing there
          (sleep(1);next) if listener == "FIFO" and f.eof
          # FIFO's are read only in server thread
          (f.print "HA>";f.flush) if writeable.include?(listener)
          cmd = f.readline.chomp
          next if cmd.empty?
          puts "Got command #{cmd} from #{listener}" if $DEBUG
          (f.close; next) if cmd.match(/^(quit|q)$/i)
          mutex.synchronize { @cmd_buffer[listener] << cmd }
        end
      end
    end
  end

  # prompt user or give focus to STDIO thread if it exists
  #
  if @threads["STDIO"].nil?
    puts "Listening on " + listeners.join(" and ")
    puts "Enter quit to exit."
    until gets.match(/^(quit|q)$/)
    end
  else
    @threads["STDIO"].join
  end
end

Cheers,
Hal

Yup. Looks like it (it is mostly there). Hadn’t caught that pty was
added in 1.8.

Hal, see if this thing works. Even if it needs some clean up, it is
pretty far along.

Dan

···

On Apr 27, 2004, at 02:39, gabriele renzi wrote:

il Tue, 27 Apr 2004 14:24:08 +0900, Dan Janowski danj@3skel.com ha
scritto::

I had to write an expect program (I’m a long time TCL programmer) the
other day and was thinking about how nice it would be to have
ExpectRuby. It is pretty great to run sub-processes in a pty.

this?
http://rubyforge.org/projects/rexpect
(you may need to force the author to relrease it :slight_smile:

Dan Janowski wrote:

Yup. Looks like it (it is mostly there). Hadn’t caught that pty was
added in 1.8.

Hal, see if this thing works. Even if it needs some clean up, it is
pretty far along.

Any suggestions you’d have would be greatly appreciated. I’ve added
some things that made it work better for me, but unfortunately Forces
Beyond My Control™ forced me to switch first to Python, then to
Python on Windows and therefore PTY objects weren’t really an option.
Instead, I had to use PySerial, and roll my own ‘expect’ function around
that.

Sigh

Oh well, at least it’s a break from embedded C.

Ben