Procházet zdrojové kódy

Hammering out telnet protocol implementation

Beoran před 8 roky
rodič
revize
423f64ace9

+ 4 - 0
src/woe/server/ask.go

@@ -0,0 +1,4 @@
+package server
+
+/* This file contains dialog helpers fort the client. */
+

+ 67 - 8
src/woe/server/client.go

@@ -8,18 +8,37 @@ import (
     // "errors"
     // "io"
     "github.com/beoran/woe/monolog"
+    "github.com/beoran/woe/telnet"
 )
 
+/* Specific properties of a client. */
+type ClientInfo struct {
+    w           int
+    h           int
+    mtts        int
+    naws        bool
+    compress2   bool
+    mssp        bool
+    zmp         bool
+    msp         bool
+    msdp        bool
+    mxp         bool
+    ttype       bool
+    terminals []string
+    terminal    string
+}
+
 type Client struct {
     server * Server
     id       int
     conn     net.Conn
-    telnet   interface{}
     alive    bool
     timeout  int
     datachan chan []byte
     errchan  chan error
     timechan chan time.Time 
+    telnet * telnet.Telnet
+    info     ClientInfo
 }
 
 
@@ -27,7 +46,9 @@ func NewClient(server * Server, id int, conn net.Conn) * Client {
     datachan := make (chan []byte, 1024)
     errchan  := make (chan error, 1)
     timechan := make (chan time.Time, 32)
-    return &Client{server, id, conn, nil, true, -1, datachan, errchan, timechan}
+    telnet   := telnet.New()
+    info     := ClientInfo{-1, -1, 0, false, false, false, false, false, false, false, false, nil, "none"}
+    return &Client{server, id, conn, true, -1, datachan, errchan, timechan, telnet, info}
 }
 
 func (me * Client) Close() {
@@ -40,20 +61,34 @@ func (me * Client) Close() {
 func (me * Client) ServeRead() {
     for (me.alive) { 
         buffer  := make([]byte, 1024, 1024)
-        _ , err := me.conn.Read(buffer);
+        read , err := me.conn.Read(buffer);
         if err != nil {
             me.errchan <- err
             return
         }
-        me.datachan <- buffer
+        // reply will be stored in me.telnet.Events
+        me.telnet.ProcessBytes(buffer[:read])
+    }
+}
+
+/* Goroutine that sends any data that must be sent through the Telnet protocol 
+ * (that any data, really) other to the connected client.
+ */
+ func (me * Client) ServeWrite() {
+     for (me.alive) {
+        select {
+            case data := <- me.telnet.ToClient:
+            monolog.Debug("Will send to client: %v", data)
+            me.conn.Write(data)
+        }
     }
 }
 
 
-func (me * Client) TryRead(millis int) (data [] byte, timeout bool, close bool) {
+func (me * Client) TryReadEvent(millis int) (event telnet.Event, timeout bool, close bool) {
     select {
-        case data := <- me.datachan:
-            return data, false, false
+        case event := <- me.telnet.Events:
+            return event, false, false
                        
         case err  := <- me.errchan:
             monolog.Info("Connection closed: %s\n", err)
@@ -65,15 +100,39 @@ func (me * Client) TryRead(millis int) (data [] byte, timeout bool, close bool)
     }
 }
 
+func (me * Client) TryRead(millis int) (data []byte, timeout bool, close bool) {
+    
+    for (me.alive) { 
+        event, timeout, close := me.TryReadEvent(millis)
+        if event == nil && (timeout || close) {
+            return nil, timeout, close
+        }
+        switch event := event.(type) {
+            case * telnet.DataEvent:
+                monolog.Debug("Telnet data event %T : %d.", event, len(event.Data))
+                return event.Data, false, false
+            default:
+                monolog.Info("Ignoring telnet event %T : %v for now.", event, event)
+        }
+    }
+    
+    return nil, false, true
+}
+
 
 func (me * Client) Serve() (err error) {
     // buffer := make([]byte, 1024, 1024)
+    go me.ServeWrite()
     go me.ServeRead()
+    me.SetupTelnet()
+    
     for (me.alive) {
+        
+        
         data, _, _ := me.TryRead(3000)
         
         if data == nil {
-            me.conn.Write([]byte("Too late!\r\n"))
+           // me.telnet.TelnetPrintf("Too late!\r\n")
         } else {
             me.server.Broadcast(string(data))
         }

+ 382 - 0
src/woe/server/setuptelnet.go

@@ -0,0 +1,382 @@
+package server
+
+import "github.com/beoran/woe/monolog"
+import t "github.com/beoran/woe/telnet"
+import "github.com/beoran/woe/telnet"
+import "strings"
+import "strconv"
+
+
+
+/* This file contains telnet setup helpers for the client. */
+
+
+// generic negotiation
+
+func (me * Client) SetupNegotiate(millis int, command byte, option byte, yes_event telnet.EventType, no_event telnet.EventType) (bool, telnet.Event) {
+    me.telnet.TelnetSendNegotiate(command, option)
+    tev, timeout, close := me.TryReadEvent(millis)
+
+    if tev == nil || timeout || close {
+        monolog.Info("Timeout or close in TryReadEvent")
+        return false, nil
+    }
+    
+    if telnet.IsEventType(tev, no_event) {
+        monolog.Info("Negative event no_event %v %v", tev, no_event)
+        return false, tev
+    }
+    
+    if !telnet.IsEventType(tev, yes_event) {
+        monolog.Info("Unexpected event yes_event %v %v", tev, yes_event)
+        return false, tev
+    }
+    
+    return true, tev
+}
+
+  
+// Negotiate COMPRESS2 support
+func (me * Client) SetupCompress2() telnet.Event {
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_WILL, t.TELNET_TELOPT_COMPRESS2, t.TELNET_DO_EVENT, t.TELNET_DONT_EVENT)
+    if (!ok) {
+        return tev
+    } 
+     
+    me.telnet.TelnetBeginCompress2()
+    monolog.Info("Client #{@id} started COMPRESS2 compression")
+    me.info.compress2 = true
+    return tev
+}
+
+
+// Negotiate NAWS (window size) support
+func (me * Client) SetupNAWS() telnet.Event {  
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_DO, t.TELNET_TELOPT_NAWS, t.TELNET_WILL_EVENT, t.TELNET_WONT_EVENT)
+    if (!ok) {
+        return tev
+    } 
+    
+    tev2, _, _ := me.TryReadEvent(1000)
+    if (tev2 == nil) || (!telnet.IsEventType(tev2, t.TELNET_NAWS_EVENT)) {
+        return tev2
+    }
+    
+    nawsevent := tev.(telnet.NAWSEvent)
+    me.info.w = nawsevent.W
+    me.info.h = nawsevent.H
+    monolog.Info("Client %d window size #{%d}x#{%d}", me.id, me.info.w, me.info.h) 
+    me.info.naws     = true
+    return nil
+}
+ 
+func (me * Client) SetupMSSP() telnet.Event {
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_WILL, t.TELNET_TELOPT_MSSP, t.TELNET_DO_EVENT, t.TELNET_DONT_EVENT)
+    if (!ok) {
+        return tev
+    } 
+    me.telnet.TelnetSendMSSP(MSSP)
+    monolog.Info("Client %d accepts MSSP", me.id) 
+    me.info.mssp = true
+    return nil
+}
+ 
+// Check for MXP (html-like) support (but don't implement it yet)
+func (me * Client) SetupMXP() telnet.Event { 
+
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_DO, t.TELNET_TELOPT_MXP, t.TELNET_WILL_EVENT, t.TELNET_WONT_EVENT)
+    if (!ok) {
+        return tev
+    } 
+    monolog.Info("Client %d accepts MXP", me.id) 
+    me.info.mxp = true
+    return nil
+}
+
+// Check for MSP (sound) support (but don't implement it yet)
+func (me * Client) SetupMSP() telnet.Event { 
+
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_DO, t.TELNET_TELOPT_MSP, t.TELNET_WILL_EVENT, t.TELNET_WONT_EVENT)
+    if (!ok) {
+        return tev
+    } 
+    monolog.Info("Client %d accepts MSP", me.id) 
+    me.info.msp = true
+    return nil
+}
+
+// Check for MSDP (two way MSSP) support (but don't implement it yet)
+func (me * Client) SetupMSDP() telnet.Event { 
+
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_WILL, t.TELNET_TELOPT_MSDP, t.TELNET_DO_EVENT, t.TELNET_DONT_EVENT)
+    if (!ok) {
+        return tev
+    } 
+    monolog.Info("Client %d accepts MSDP", me.id) 
+    me.info.msdp = true
+    return nil
+}
+
+func (me * Client) HasTerminal(name string) bool {
+    for index := range me.info.terminals {
+        return me.info.terminals[index] == name
+    }
+    return false
+}
+
+
+
+
+// Negotiate MTTS/TTYPE (TERMINAL TYPE) support
+func (me * Client)  SetupTType() telnet.Event {
+    me.info.terminals = nil
+    ok, tev := me.SetupNegotiate(1000, t.TELNET_DO, t.TELNET_TELOPT_TTYPE, t.TELNET_WILL_EVENT, t.TELNET_WONT_EVENT)
+    if (!ok) {
+        return tev
+    }
+        
+    var last string = "none"
+    var now  string = ""
+    
+    for last != now {
+        last = now
+        me.telnet.TelnetTTypeSend()
+        var tev2 telnet.Event = nil
+        // Some clients (like KildClient, but not TinTin or telnet), 
+        // insist on spamming useless NUL characters
+        // here... So we have to retry a few times to get a ttype_is
+        // throwing away any undesirable junk in between.
+        for index := 0 ; index < 3 ; index++ {
+            tev2, _, _ := me.TryReadEvent(1000)
+        
+            if tev2 != nil && telnet.IsEventType(tev2, t.TELNET_TTYPE_EVENT) {
+                break
+            }
+        }
+        
+        if tev2 == nil || !telnet.IsEventType(tev2, t.TELNET_TTYPE_EVENT) {
+            return tev2
+        }
+        
+        ttypeevent := tev.(*telnet.TTypeEvent)
+        now = ttypeevent.Name
+        if (!me.HasTerminal(now)) {
+            me.info.terminals = append(me.info.terminals, now)
+        }
+        me.info.terminal = now
+    }
+    
+    monolog.Info("Client %d supports terminals %v", me.id, me.info.terminals)
+    //  MTTS support
+    for i := range me.info.terminals {
+        term := me.info.terminals[i]
+        if strings.HasPrefix(term, "MTTS ") {
+            // it's an mtts terminal
+            strnum := strings.TrimPrefix(term, "MTTS ")
+            num, err := strconv.Atoi(strnum)
+            if err != nil {
+                me.info.mtts = num
+                monolog.Info("Client %d supports mtts %d", me.id, me.info.mtts)                
+            }
+        }
+    }
+    me.info.ttype = true
+    return nil
+}
+
+func (me * Client) SetupTelnet() {
+    for {
+      tev, _, _ := me.TryReadEvent(500)
+      if tev != nil {
+        monolog.Info("Client %d telnet setup received: %v", me.id, tev)
+      } else {
+        monolog.Info("Client %d no telnet setup received", me.id)
+        break
+      }
+    }
+    me.SetupMSSP()
+    // me.SetupCompress2
+    me.SetupNAWS()
+    me.SetupTType()
+    me.SetupMXP()
+    me.SetupMSP()
+    me.SetupMSDP()
+    // color_test
+}
+
+
+/*  
+  # Switches to "password" mode.
+  def password_mode
+    # The server sends "IAC WILL ECHO", meaning "I, the server, will do any 
+    # echoing from now on." The client should acknowledge this with an IAC DO 
+    # ECHO, and then stop putting echoed text in the input buffer. 
+    # It should also do whatever is appropriate for password entry to the input 
+    # box thing - for example, it might * it out. Text entered in server-echoes 
+    # mode should also not be placed any command history.
+    # don't use the Q state machne for echos
+    @telnet.telnet_send_bytes(TELNET_IAC, TELNET_WILL, TELNET_TELOPT_ECHO)
+    tev = wait_for_input(0.1)
+    return tev if tev && tev.type != :do
+    return nil
+  end
+
+  # Switches to "normal, or non-password mode.
+  def normal_mode
+    # When the server wants the client to start local echoing again, it sends 
+    # "IAC WONT ECHO" - the client must respond to this with "IAC DONT ECHO".
+    # Again don't use Q state machine.   
+    @telnet.telnet_send_bytes(TELNET_IAC, TELNET_WONT, TELNET_TELOPT_ECHO)
+    tev = wait_for_input(0.1)
+    return tev if tev && tev.type != :dont
+    return nil
+  end
+  
+  def color_test
+    self.write("\e[1mBold\e[0m\r\n")
+    self.write("\e[3mItalic\e[0m\r\n")
+    self.write("\e[4mUnderline\e[0m\r\n")
+    30.upto(37) do | fg |
+      self.write("\e[#{fg}mForeground Color #{fg}\e[0m\r\n")
+      self.write("\e[1;#{fg}mBold Foreground Color #{fg}\e[0m\r\n")
+    end  
+    40.upto(47) do | bg |
+      self.write("\e[#{bg}mBackground Color #{bg}\e[0m\r\n")
+      self.write("\e[1;#{bg}mBold Background Color #{bg}\e[0m\r\n")
+    end    
+  end
+  
+  def setup_telnet
+    loop do
+      tev = wait_for_input(0.5)
+      if tev
+        p "setup_telnet", tev
+      else
+        p "no telnet setup received..."
+        break
+      end
+    end
+    setup_mssp
+    setup_compress2
+    setup_naws
+    setup_ttype
+    setup_mxp
+    setup_msp
+    setup_msdp
+    # color_test
+    
+    
+    #p "mssp ev #{tev}"
+    # @telnet.telnet_send_negotiate(TELNET_WILL, TELNET_TELOPT_MSSP)        
+    # tev = wait_for_input(0.5)
+    # p "mssp ev #{tev}"
+    
+    # @telnet.telnet_ttype_send
+    
+    
+  end
+ 
+  LOGIN_RE = /\A[A-Za-z][A-Za-z0-9]*\Z/
+  
+  def ask_something(prompt, re, nomatch_prompt, noecho=false)
+    something = nil
+    
+    if noecho
+      password_mode
+    end
+
+    while  something.nil? || something.empty? 
+      write("#{prompt}:")
+      something = wait_for_command
+      if something
+          something.chomp!
+        if re && something !~ re
+          write("\r\n#{nomatch_prompt}\r\n")
+          something = nil
+        end
+      end
+    end
+    
+    if noecho
+      normal_mode
+    end
+    
+    something.chomp!
+    return something
+  end
+  
+  
+  
+  def ask_login
+    return ask_something("Login", LOGIN_RE, "Login must consist of a letter followed by letters or numbers.")
+  end
+
+  EMAIL_RE = /@/
+
+  def ask_email
+    return ask_something("E-mail", EMAIL_RE, "Email must have at least an @ in there somewhere.")
+  end
+
+
+  def ask_password(prompt = "Password")
+    return ask_something(prompt, nil, "", true) 
+  end
+  
+  def handle_command
+    order = wait_for_command
+    case order
+    when "/quit"
+      write("Byebye!\r\n")
+      @busy = false
+    else
+      @server.broadcast("#{@account.id} said #{order}\r\n")
+    end
+  end
+  
+  def existing_account_dialog
+    pass  = ask_password
+    return false unless pass
+    unless @account.challenge?(pass)
+      printf("Password not correct!\n")
+      return false
+    end
+    return true
+  end
+  
+  def new_account_dialog(login)
+    while !@account 
+      printf("\nWelcome, %s! Creating new account...\n", login)
+      pass1  = ask_password
+      return false unless pass1
+      pass2 = ask_password("Repeat Password")
+      return false unless pass2
+      if pass1 != pass2
+        printf("\nPasswords do not match! Please try again!\n")
+        next
+      end
+      email = ask_email
+      return false unless email
+      @account = Woe::Account.new(:id => login, :email => email )
+      @account.password   = pass1
+      @account.woe_points = 7
+      unless @account.save_one
+        printf("\nFailed to save your account! Please contact a WOE administrator!\n")
+        return false
+      end
+      printf("\nSaved your account.\n")
+      return true
+    end
+  end
+  
+  def account_dialog
+    login  = ask_login
+    return false unless login
+    @account = Account.fetch(login)
+    if @account
+      return existing_account_dialog
+    else
+      return new_account_dialog(login)
+    end
+  end
+ 
+*/

+ 3 - 0
src/woe/telnet/rfc1143.go

@@ -0,0 +1,3 @@
+package telnet
+
+// RFC 1143 is not implemented yet.

+ 578 - 474
src/woe/telnet/telnet.go

@@ -1,27 +1,32 @@
 package telnet
 
-import "bytes"
-import "compression/zlib"
+// import "bytes"
+import "io"
+import "strings"
+import "fmt"
+import "compress/zlib"
 import "github.com/beoran/woe/monolog"
 
+
 // This Telnet struct implements a subset of the Telnet protocol.
 
 // Telnet states
 type TelnetState int
 
 const (
-    data_state TelnetState  = iota,
-    iac_state               = iota,
-    will_state              = iota,
-    wont_state              = iota,
-    do_state                = iota,
+    data_state TelnetState  = iota
+    iac_state               = iota
+    will_state              = iota
+    wont_state              = iota
+    do_state                = iota
     dont_state              = iota
-    sb_state                = iota,
-    sb_data_state           = iota,
+    sb_state                = iota
+    sb_data_state           = iota
     sb_data_iac_state       = iota
 )
 
 
+
 // Telnet event types
 
 type Event interface {
@@ -50,7 +55,7 @@ type TTypeEvent struct {
 func (me TTypeEvent) isEvent() {}
 
 
-type SubnegotioateEvent struct {
+type SubnegotiateEvent struct {
     Telopt    byte
     Buffer [] byte
 }
@@ -65,16 +70,23 @@ type IACEvent struct {
 func (me IACEvent) isEvent() {}
 
 
-type CompressionEvent struct {
+type CompressEvent struct {
     Compress  bool
 }
 
-func (me CompressionEvent) isEvent() {}
+func (me CompressEvent) isEvent() {}
+
+
+//Storage for environment values
+type Environment struct {
+    Type byte
+    Value string
+}
 
 
 type EnvironmentEvent struct {
     Telopt    byte
-    Vars      map[string] string
+    Vars      [] Environment
 }
 
 func (me EnvironmentEvent) isEvent() {}
@@ -89,14 +101,99 @@ func (me MSSPEvent) isEvent() {}
 
 
 type ZMPEvent struct {
-    Telopt    byte
-    Vars      map[string] string
+    Vars      []string
 }
 
 func (me ZMPEvent) isEvent() {}
 
+type WillEvent struct {
+    Telopt byte
+}
+
+func (me WillEvent) isEvent() {}
+
+type WontEvent struct {
+    Telopt byte
+}
+
+func (me WontEvent) isEvent() {}
+
+
+type DoEvent struct {
+    Telopt byte
+}
+
+func (me DoEvent) isEvent() {}
+
+type DontEvent struct {
+    Telopt byte
+}
+
+func (me DontEvent) isEvent() {}
+
+
+// Telnet event type constants
+type EventType int
+
+const (
+    TELNET_DATA_EVENT           EventType =  iota
+    TELNET_NAWS_EVENT           EventType =  iota
+    TELNET_TTYPE_EVENT          EventType =  iota
+    TELNET_SUBNEGOTIATE_EVENT   EventType =  iota
+    TELNET_IAC_EVENT            EventType =  iota
+    TELNET_COMPRESS_EVENT       EventType =  iota
+    TELNET_ENVIRONMENT_EVENT    EventType =  iota
+    TELNET_MSSP_EVENT           EventType =  iota
+    TELNET_ZMP_EVENT            EventType =  iota
+    TELNET_WILL_EVENT           EventType =  iota
+    TELNET_WONT_EVENT           EventType =  iota
+    TELNET_DO_EVENT             EventType =  iota
+    TELNET_DONT_EVENT           EventType =  iota
+    TELNET_UNKNOWN_EVENT        EventType =  iota
+)
+
+
+/* Returns the numerical event type of an event. Useful for direct comparison. */
+func EventTypeOf(event Event) EventType {
+    switch event.(type) {
+        case DataEvent:
+            return TELNET_DATA_EVENT
+        case NAWSEvent:
+            return TELNET_NAWS_EVENT
+        case TTypeEvent:
+            return TELNET_TTYPE_EVENT
+        case SubnegotiateEvent:
+            return TELNET_SUBNEGOTIATE_EVENT
+        case IACEvent:
+            return TELNET_IAC_EVENT
+        case CompressEvent:
+            return TELNET_COMPRESS_EVENT
+        case EnvironmentEvent:
+            return TELNET_ENVIRONMENT_EVENT
+        case MSSPEvent:
+            return TELNET_MSSP_EVENT
+        case ZMPEvent:
+            return TELNET_ZMP_EVENT
+        case WillEvent:
+            return TELNET_WILL_EVENT
+        case WontEvent:
+            return TELNET_WONT_EVENT
+        case DoEvent:
+            return TELNET_DO_EVENT
+        case DontEvent:
+            return TELNET_DONT_EVENT
+        default:
+            return TELNET_UNKNOWN_EVENT
+    }
+}
+
+// Returns true if the event is of the given type, or false if not
+func IsEventType(event Event, typ EventType) bool {
+    return EventTypeOf(event) == typ;
+}
+
 
-type EventChannel chan[Event]
+type EventChannel chan(Event)
 
 
 type Telopt struct {
@@ -106,28 +203,29 @@ type Telopt struct {
 }
     
 type Telnet struct { 
-  Events            EventChannel  
+  Events            EventChannel
+  ToClient          chan([]byte)
   telopts map[byte] Telopt 
   state             TelnetState 
   compress          bool
-  zwriter           Writer
-  zreader           Reader
+  zwriter           zlib.Writer
+  zreader           io.ReadCloser
   buffer          []byte
   sb_telopt         byte
 }
 
-func New() telnet * Telnet {
+func New() (telnet * Telnet) {
+    
     events     := make(EventChannel, 64)
+    toclient   := make(chan([]byte), 64)
     telopts    := make (map[byte] Telopt)
     state      := data_state
     compress   := false
-    zwriter    := nil
-    zreader    := nil
-    buffer     := make([]byte, 1024, 1024)
-    sb_telopt  := 0
-    telnet      = &Telnet { events, telopts, state, compress, 
-        zwriter, zreader, buffer, sb_telopt
-    }
+    var zwriter zlib.Writer
+    var zreader io.ReadCloser
+    var buffer []byte = nil
+    sb_telopt  := byte(0)
+    telnet      = &Telnet { events, toclient, telopts, state, compress, zwriter, zreader, buffer, sb_telopt }
     return telnet
 }
 
@@ -146,13 +244,13 @@ func (me * Telnet) Close() {
 }
 
 // Filters raw text, only compressing it if needed. 
-func (me * Telnet) FilterRaw(in []byte, out chan []byte) {
+func (me * Telnet) SendRaw(in []byte) {
     // XXX Handle compression here later
-    out <- in
+    me.ToClient <- in
 } 
 
 // Filters text, escaping IAC bytes. 
-func (me * Telnet) FilterRaw(in []byte, out chan []byte) {
+func (me * Telnet) SendEscaped(in []byte) {
     buffer := make([]byte, len(in) * 2, len(in) * 2) 
     outdex := 0
     /* Double IAC characters to escape them. */
@@ -162,492 +260,498 @@ func (me * Telnet) FilterRaw(in []byte, out chan []byte) {
             buffer[outdex] = TELNET_IAC; 
             outdex++;    
         }
-        buffer[outdex] = TELNET_IAC;
+        buffer[outdex] = now;
         outdex++;
     }
-    out <- buffer
+    me.SendRaw(buffer)
 } 
 
 // Send negotiation bytes
-func (me * Telnet) SendNegotiate(cmd byte, telopt byte, out chan []byte) {
+func (me * Telnet) SendNegotiate(cmd byte, telopt byte) {
     buffer      := make([]byte, 3)
     buffer[0]    = TELNET_IAC
     buffer[1]    = cmd
     buffer[2]    = telopt
-    me.FilterRaw(buffer, out)
-}    
+    me.SendRaw(buffer)
+}
+
+func (me * Telnet) SendEvent(event Event) {
+    me.Events <- event
+}
    
 // Parse a subnegotiation buffer for a naws event
-func (me * Telnet) SubnegotiateNAWS(buffer []byte,  )
+func (me * Telnet) SubnegotiateNAWS(buffer []byte) {
     // Some clients, like Gnome-Mud can't even get this right. Grrr!
-    // XXx continue here
-    if buffer.nil? || buffer.empty? || buffer.size != 4
-      monolog.Info("Bad NAWS negotiation: #{buffer}")
-      return nil
-    end
-    arr   = buffer.bytes.to_a
-    w     = (arr[0] << 8) + arr[1]
-    h     = (arr[2] << 8) + arr[3]
-    send_event(:naws, w, h)
-  end
-  
+    if buffer == nil || len(buffer) != 4 {
+      monolog.Warning("Bad NAWS negotiation: #{buffer}")
+      return
+    }
+    var w int   = (int(buffer[0]) << 8) + int(buffer[1])
+    var h int   = (int(buffer[2]) << 8) + int(buffer[3])
+    me.SendEvent(&NAWSEvent{w, h})
+}
 
-  # Storage for environment values
-  class Environment 
-    attr_accessor :type
-    attr_accessor :value
-    
-    def initialize(type, value)
-      me.type   = type
-      me.value  = value
-    end
-  end
-
-
-  # process an ENVIRON/NEW-ENVIRON subnegotiation buffer
-  def subnegotiate_environ(buffer)
-    vars  = []
-    cmd   = ""
-    arr   = buffer.bytes.to_a
-    fb    = arr.first  
-    # first byte must be a valid command 
-    if fb != TELNET_ENVIRON_SEND && fb != TELNET_ENVIRON_IS && fb != TELNET_ENVIRON_INFO
-      log_error("telopt environment subneg command not valid")
-      return 0
-    end
+// process an ENVIRON/NEW-ENVIRON subnegotiation buffer
+func (me * Telnet) SubnegotiateEnviron(buffer []byte) {
+    var vars []Environment
+    var cmd []byte
+    fb   := buffer[0]  
+    // First byte must be a valid command 
+    if fb != TELNET_ENVIRON_SEND && fb != TELNET_ENVIRON_IS && fb != TELNET_ENVIRON_INFO {
+      monolog.Warning("telopt environment subneg command not valid")
+    }
     
-    cmd << fb    
+    cmd = append(cmd, fb)   
     
-    if (buffer.size == 1) 
-      send_event(:environment, fb, vars)
-      return false
-    end
+    if len(buffer) == 1 { 
+      me.SendEvent(&EnvironmentEvent{fb, vars})
+      return
+    }
         
-    # Second byte must be VAR or USERVAR, if present
-    sb = arr[1]
-    if sb != TELNET_ENVIRON_VAR && fb != TELNET_ENVIRON_USEVAR
-      log_error("telopt environment subneg missing variable type")
-      return false
-    end
-    
-    # ensure last byte is not an escape byte (makes parsing later easier) 
-    lb = arr.last
-    if lb == TELNET_ENVIRON_ESC
-      log_error("telopt environment subneg ends with ESC")
-      return false
-    end
-
-    var    = nil
-    index  = 1
-    escape = false
+    // Second byte must be VAR or USERVAR, if present
+    sb := buffer[1]
+    if sb != TELNET_ENVIRON_VAR && fb != TELNET_ENVIRON_USERVAR {
+      monolog.Warning("telopt environment subneg missing variable type")
+      return
+    }
     
-    arr.shift
+    // ensure last byte is not an escape byte (makes parsing later easier) 
+    lb := buffer[len(buffer) - 1]
+    if lb == TELNET_ENVIRON_ESC {
+      monolog.Warning("telopt environment subneg ends with ESC")
+      return
+    }
+
+/* XXX : not implemented yet
+    var variable * Environment = nil
+    index           := 1
+    escape          := false
     
-    arr.each do | c | 
-      case c
-      when TELNET_ENVIRON_VAR
-      when TELNET_ENVIRON_VALUE
-      when TELNET_ENVIRON_USERVAR
-        if escape
-          escape = false
-          var.value << c
-        elsif var
-          vars << var
-          var = Environment.new(c, "")
-        else
-          var = Environment.new(c, "")        
-        end
-      when TELNET_ENVIRON_ESC
+    for index := 1 ; index < len(buffer) ; index++ {
+      c := buffer[index]  
+      switch c {
+        case TELNET_ENVIRON_VAR: 
+            fallthrough
+        case TELNET_ENVIRON_VALUE:
+            fallthrough
+        case TELNET_ENVIRON_USERVAR:
+            if escape {
+                escape = false
+                variable.Value  = append(variable.Value, c)
+            } else if (variable != nil) {
+                vars            = append(vars, variable)
+                variable        = new(Environment)
+                variable.Type   = c
+            } else {
+                variable        = new(Environment)
+                variable.Type   = c
+            }
+      case TELNET_ENVIRON_ESC:
         escape = true
-      else
-        var.value << c  
-      end # case
-    end # each
-    
-    send_event(:environment, fb, vars)    
-    return false
-  end
+      default:
+        variable.Value = append(variable.Value, c)
+      }
+    }
+    // Finally send event
+    me.SendEvent(&EnvironmentEvent{fb, vars})
+*/
+}
 
 
+const (
+    MSTATE_NONE = 0
+    MSTATE_VAR  = 1
+    MSTATE_VAL  = 2
+)
 
-# process an MSSP subnegotiation buffer
-def subnegotiate_mssp(buffer)
-  telnet_event_t ev;
-  struct telnet_environ_t *values;
-  char *var = 0;
-  char *c, *last, *out;
-  size_t i, count;
-  unsigned char next_type;
-  
-  if buffer.size < 1
-    return 0
-  end
-  
-  arr   = buffer.bytes.to_a
-  fb    = arr.first  
-  # first byte must be a valid command
-  if fb != TELNET_MSSSP_VAR
-    log_error("telopt MSSP subneg data not valid")
-    return false
-  end
+// process an MSSP subnegotiation buffer
+func (me * Telnet) SubnegotiateMSSP(buffer []byte) {
+    if len(buffer) < 1 {
+        return
+    }
   
-  vars    = {}
-  var     = ""
-  val     = ""
-  mstate  = :var
-  while index <  arr.size
-    c     = arr[index]
-    case c
-    when TELNET_MSSP_VAR
-      mstate = :var
-      if mstate == :val
-        vars[var] = val
-        var = ""
-        val = ""
-      end      
-    when TELNET_MSSP_VAL
-      mstate = :val
-    else
-      if mstate == :var
-        var << c  
-      elsif mstate == :val
-        val << c  
-      end      
-    end # case
-    index += 1
-  end # while
+    fb    := buffer[0]  
+    // first byte must be a valid command
+    if fb != TELNET_MSSP_VAR {
+        monolog.Warning("telopt MSSP subneg data not valid")
+        return
+    }
   
-  send_event(:mssp, vars)
-  return false
-end
+    variables := make(map[string] string)
+    var variable []byte
+    var value []byte
+    mstate := MSTATE_NONE
+    
+    for index := 0 ; index <  len(buffer) ; index ++ {
+        c     := buffer[index]
+        
+        switch c {
+            case TELNET_MSSP_VAR:
+            mstate = MSTATE_VAR
+            if mstate == MSTATE_VAR {
+                variables[string(variable)] = string(value)
+                variable = nil
+                value    = nil
+            }
+            case TELNET_MSSP_VAL:
+                mstate = MSTATE_VAL
+            default:
+                if mstate == MSTATE_VAL {
+                    variable = append(variable, c)
+                } else {
+                    value = append(value, c)
+                }  
+        }
+    }
+    me.SendEvent(&MSSPEvent{fb, variables})
+}
 
 
-# parse ZMP command subnegotiation buffers 
-def subnegotiate_zmp(buffer)
-  args = []
-  arg  = ""
-  
-  buffer.each_byte do |b|  
-    if b == 0
-      args << arg
-      arg = ""
-    else
-      arg << byte
-    end
-  end
-  send_event(:zmp, vars)
-  return false
-end
-
-# parse TERMINAL-TYPE command subnegotiation buffers
-def subnegotiate_ttype(buffer)
-  # make sure request is not empty
-  if buffer.size == 0
-    log_error("Incomplete TERMINAL-TYPE request");
-    return 0
-  end
+// Parse ZMP command subnegotiation buffers 
+func (me * Telnet) SubnegotiateZMP(buffer []byte) {
+  var vars []string
+  var variable []byte
+  var b byte
+  for index := 0 ; index < len(buffer) ; index++ {
+      b = buffer[index]
+      if b == 0 {
+        vars     = append(vars, string(variable))
+        variable = nil
+      } else {
+        variable = append(variable, b)
+      }  
+  }
+  me.SendEvent(&ZMPEvent{vars})
+}
+
+// parse TERMINAL-TYPE command subnegotiation buffers
+func (me * Telnet) SubnegotiateTType(buffer []byte) {
+  // make sure request is not empty
+  if len(buffer) == 0 {
+    monolog.Warning("Incomplete TERMINAL-TYPE request");
+    return 
+  }
   
-  arr   = buffer.bytes
-  fb    = arr.first
-  term  = nil 
+  fb    := buffer[0]
+  if fb != TELNET_TTYPE_IS || fb != TELNET_TTYPE_SEND {
+    monolog.Warning("TERMINAL-TYPE request has invalid type")
+    return
+  }
   
-  if fb == TELNET_TTYPE_IS
-    term = buffer[1, buffer.size]
-    send_event(:ttype_is, term)
-  elsif fb == TELNET_TTYPE_SEND
-    term = buffer[1, buffer.size]
-    send_event(:ttype_send, term)
-  else
-    log_error("TERMINAL-TYPE request has invalid type")
+  term := string(buffer[1:])
+  me.SendEvent(&TTypeEvent{fb, term})
+}
+
+
+// process a subnegotiation buffer; returns true if the current buffer
+// must be aborted and reprocessed due to COMPRESS2 being activated
+func (me * Telnet) DoSubnegotiate(buffer []byte) bool {
+    switch me.sb_telopt {
+        case TELNET_TELOPT_COMPRESS2:
+        // received COMPRESS2 begin marker, setup our zlib box and
+        // start handling the compressed stream if it's not already.
+        me.compress = true
+        me.SendEvent(&CompressEvent{me.compress})
+        return true
+        // specially handled subnegotiation telopt types
+        case TELNET_TELOPT_TTYPE:
+            me.SubnegotiateTType(buffer)
+        case TELNET_TELOPT_ENVIRON:
+            me.SubnegotiateEnviron(buffer)
+        case TELNET_TELOPT_NEW_ENVIRON:
+            me.SubnegotiateEnviron(buffer)
+        case TELNET_TELOPT_MSSP:
+            me.SubnegotiateMSSP(buffer)
+        case TELNET_TELOPT_NAWS:
+            me.SubnegotiateNAWS(buffer)
+        case TELNET_TELOPT_ZMP:
+            me.SubnegotiateZMP(buffer)
+        default:    
+            // Send catch all subnegotiation event
+            me.SendEvent(&SubnegotiateEvent{me.sb_telopt, buffer})
+    }
     return false
-  end
-  return false
-end
+}
 
+func (me * Telnet) DoNegotiate(state TelnetState, telopt byte) bool {
+    switch me.state {
+        case will_state:
+            me.SendEvent(&WillEvent{telopt})
+        case wont_state:
+            me.SendEvent(&WontEvent{telopt})
+        case do_state:
+            me.SendEvent(&DoEvent{telopt})
+        case dont_state:
+            me.SendEvent(&DontEvent{telopt})
+        default:
+            monolog.Warning("State not vvalid in  telnet negotiation.")
+    }
+    me.state = data_state
+    return false
+}
 
-# process a subnegotiation buffer; returns true if the current buffer
-# must be aborted and reprocessed due to COMPRESS2 being activated
+// Send the current buffer as a DataEvent if it's not empty
+// Also empties the buffer if it wasn't emmpty
+func (me * Telnet) maybeSendDataEventAndEmptyBuffer() {
+    if (me.buffer != nil) && (len(me.buffer) > 0) {
+        me.SendEvent(&DataEvent{me.buffer})
+        me.buffer = nil
+    }
+}
 
-def do_subnegotiate(buffer)
-  case me.sb_telopt
-  when TELNET_TELOPT_COMPRESS2
-    # received COMPRESS2 begin marker, setup our zlib box and
-    # start handling the compressed stream if it's not already.
-    me.compress = true
-    send_event(:compress, me.compress)
-    return true
-  # specially handled subnegotiation telopt types
-  when TELNET_TELOPT_ZMP
-    return subnegotiate_zmp(buffer)
-  when TELNET_TELOPT_TTYPE
-    return subnegotiate_ttype(buffer)
-  when TELNET_TELOPT_ENVIRON  
-    return subnegotiate_environ(buffer)
-  when TELNET_TELOPT_NEW_ENVIRON
-    return subnegotiate_environ(buffer)
-  when TELNET_TELOPT_MSSP
-    return subnegotiate_mssp(buffer)
-  when TELNET_TELOPT_NAWS
-    return subnegotiate_naws(buffer)
-  else
-    send_event(:subnegotiate, me.sb_telopt, buffer)
+// Append a byte to the data buffer
+// Also empties the buffer if it wasn't emmpty
+func (me * Telnet) appendByte(bin byte) {
+    monolog.Debug("Appending to telnet buffer: %d %d", len(me.buffer), cap(me.buffer))
+    me.buffer = append(me.buffer, bin)
+}
+
+// Process a byte in the data state 
+func (me * Telnet) dataStateProcessByte(bin byte) bool {
+    if bin == TELNET_IAC {
+        // receive buffered bytes as data and go to IAC state if it's notempty
+        me.maybeSendDataEventAndEmptyBuffer()
+        me.state = iac_state
+    } else {
+        me.appendByte(bin)
+    }
     return false
-  end
-end
+}
 
+// Process a byte in the IAC state 
+func (me * Telnet) iacStateProcessByte(bin byte) bool {
+    switch bin {
+      // subnegotiation
+      case TELNET_SB:
+        me.state = sb_state
+      // negotiation commands
+      case TELNET_WILL:
+        me.state = will_state
+      case TELNET_WONT:
+        me.state = wont_state
+      case TELNET_DO:
+        me.state = do_state
+      case TELNET_DONT:
+        me.state = dont_state
+      // IAC escaping
+      case TELNET_IAC:
+        me.appendByte(TELNET_IAC)
+        me.maybeSendDataEventAndEmptyBuffer()
+        me.state = data_state
+      // some other command
+      default:
+        me.SendEvent(IACEvent { bin })
+        me.state = data_state
+    }
+    return false      
+}
 
-  
-  def process_byte(byte) 
-    # p "process_byte, #{me.state} #{byte}"
-    case me.state
-    # regular data
-    when :data
-      if byte == TELNET_IAC
-        # receive buffered bytes as data and go to IAC state if it's notempty
-        send_event(:data, me.buffer) unless me.buffer.empty?
-        me.buffer = ""
-        me.state = :iac
-      else
-        me.buffer << byte
-      end
-    # IAC received before
-    when :iac
-      case byte
-      # subnegotiation
-      when TELNET_SB
-        me.state = :sb
-      # negotiation commands
-      when TELNET_WILL
-        me.state = :will
-      when TELNET_WONT
-        me.state = :wont
-      when TELNET_DO
-        me.state = :do
-      when TELNET_DONT
-        me.state = :dont
-      # IAC escaping 
-      when TELNET_IAC
-        me.buffer << TELNET_IAC.chr
-        send_event(:data, me.buffer) unless me.buffer.empty?
-        me.buffer = ""
-        me.state = :data
-      # some other command
-      else
-        send_event(:iac, byte)
-        me.state = :data
-      end
-
-    # negotiation received before
-    when :will, :wont, :do, :dont
-      do_negotiate(byte)
-      me.state = :data
-    # subnegotiation started, determine option to subnegotiate
-    when :sb
-      me.sb_telopt = byte
-      me.state     = :sb_data
-    # subnegotiation data, buffer bytes until the end request 
-    when :sb_data
-      # IAC command in subnegotiation -- either IAC SE or IAC IAC
-      if (byte == TELNET_IAC)
-        me.state = :sb_data_iac
-      elsif (me.sb_telopt == TELNET_TELOPT_COMPRESS && byte == TELNET_WILL)
-        # MCCPv1 defined an invalid subnegotiation sequence (IAC SB 85 WILL SE) 
-        # to start compression. Catch and discard this case, only support 
-        # MMCPv2.
-        me.state = data
-      else 
-        me.buffer << byte
-      end
-
-    # IAC received inside a subnegotiation
-    when :sb_data_iac
-      case byte
-        # end subnegotiation
-        when TELNET_SE
-          me.state = :data
-          # process subnegotiation
-          compress = do_subnegotiate(me.buffer)
-          # if compression was negotiated, the rest of the stream is compressed
-          # and processing it requires decompressing it. Return true to signal 
-          # this.
-          me.buffer = ""
-          return true if compress
-        # escaped IAC byte
-        when TELNET_IAC
-        # push IAC into buffer */
-          me.buffer << byte
-          me.state = :sb_data
-        # something else -- protocol error.  attempt to process
-        # content in subnegotiation buffer, then evaluate the
-        # given command as an IAC code.
-        else
-          log_error("Unexpected byte after IAC inside SB: %d", byte)
-          me.state = :iac
-          # subnegotiate with the buffer anyway, even though it's an error
-          compress = do_subnegotiate(me.buffer)
-          # if compression was negotiated, the rest of the stream is compressed
-          # and processing it requires decompressing it. Return true to signal 
-          # this.
-          me.buffer = ""
-          return true if compress
-        end
-    when :data  
-      # buffer any other bytes
-      me.buffer << byte
-    else 
-      # programing error, shouldn't happen
-      raise "Error in telet state machine!"
-    end
-    # return false to signal compression needn't start
+
+// Process a byte in the subnegotiation data state 
+func (me * Telnet) sbdataStateProcessByte(bin byte) bool {
+    // IAC command in subnegotiation -- either IAC SE or IAC IAC
+    if (bin == TELNET_IAC)  {
+        me.state = sb_data_iac_state
+    } else if me.sb_telopt == TELNET_TELOPT_COMPRESS &&  bin == TELNET_WILL {
+        // MCCPv1 defined an invalid subnegotiation sequence (IAC SB 85 WILL SE) 
+        // to start compression. Catch and discard this case, only support 
+        // MMCPv2.
+        me.state = data_state
+    } else {
+        me.appendByte(bin)
+    }
     return false
-  end
-  
-  def process_bytes(bytes)
-    # I have a feeling this way of handling strings isn't very efficient.. :p
-    arr = bytes.bytes.to_a
-    byte = arr.shift
-    while byte
-      compress = process_byte(byte)
-      if compress
-        # paper over this for a while... 
-        new_bytes = Zlib.inflate(arr.pack('c*')) rescue nil
-        if new_bytes
-          arr = new_bytes.bytes.to_a
-        end
-      end
-      byte = arr.shift    
-    end
-    send_event(:data, me.buffer) unless me.buffer.empty?
-    me.buffer = ""
-  end
-  
-  # Call this when the server receives data from the client
-  def telnet_receive(data)
-    # the COMPRESS2 protocol seems to be half-duplex in that only 
-    # the server's data stream is compressed (unless maybe if the client
-    # is asked to also compress with a DO command ?)
-    process_bytes(data)
-  end
-  
-  # Send a bytes array (raw) to the client
-  def telnet_send_bytes(*bytes)
-    s     = bytes.pack('C*')
-    send_raw(s)
-  end
+}
+
+// Process a byte in the IAC received when processing subnegotiation data state 
+func (me * Telnet) sbdataiacStateProcessByte(bin byte) bool {
+    switch bin { 
+        //end subnegotiation
+        case TELNET_SE:
+        me.state = data_state
+        // process subnegotiation
+        compress := me.DoSubnegotiate(me.buffer)
+        // if compression was negotiated, the rest of the stream is compressed
+        // and processing it requires decompressing it. Return true to signal 
+        // this.
+        me.buffer = nil
+        if compress {
+            return true 
+        }
+            
+        // escaped IAC byte
+        case TELNET_IAC:
+        // push IAC into buffer
+        me.appendByte(bin)
+        me.state = sb_data_state
+        // something else -- protocol error.  attempt to process
+        // content in subnegotiation buffer, then evaluate the
+        // given command as an IAC code.
+        default:
+        monolog.Warning("Unexpected byte after IAC inside SB: %d", bin)
+        me.state = iac_state
+        // subnegotiate with the buffer anyway, even though it's an error
+        compress := me.DoSubnegotiate(me.buffer)
+        // if compression was negotiated, the rest of the stream is compressed
+        // and processing it requires decompressing it. Return true to signal 
+        // this.
+        me.buffer = nil
+        if compress {
+            return true 
+        }
+    }
+    return false    
+}
+
+
+// Process a single byte received from the client 
+func (me * Telnet) ProcessByte(bin byte) bool {
+    monolog.Info("ProcessByte %d %d", bin, me.state)
+    switch me.state {
+    // regular data
+        case data_state:
+        return me.dataStateProcessByte(bin)
+    // IAC received before
+        case iac_state:
+        return me.iacStateProcessByte(bin)
+        case will_state, wont_state, do_state, dont_state:
+        return me.DoNegotiate(me.state, bin)
+        // subnegotiation started, determine option to subnegotiate
+        case sb_state:
+        me.sb_telopt = bin      
+        me.state     = sb_data_state
+        // subnegotiation data, buffer bytes until the end request 
+        case sb_data_state:
+        return me.sbdataStateProcessByte(bin)
+        // IAC received inside a subnegotiation
+        case sb_data_iac_state:
+        return me.sbdataiacStateProcessByte(bin)
+        default:
+            //  programing error, shouldn't happen
+            panic("Error in telnet state machine!")        
+    }
+    // return false to signal compression needn't start
+    return false
+}
+ 
+// Process multiple bytes received from the client
+func (me * Telnet) ProcessBytes(bytes []byte) {
+    for index := 0 ; index < len(bytes) ; {
+        bin := bytes[index]
+        compress := me.ProcessByte(bin)
+        if compress {
+            // paper over this for a while... 
+            // new_bytes = Zlib.inflate(arr.pack('c*')) rescue nil
+            // if new_bytes
+            //arr = new_bytes.bytes.to_a
+        }
+        index ++
+    }
+    me.maybeSendDataEventAndEmptyBuffer()
+}
+
   
-  # send an iac command 
-  def telnet_send_iac(cmd)
-    telnet_send_bytes(TELNET_IAC, cmd)
-  end
-
-  # send negotiation
-  def telnet_send_negotiate(cmd, telopt)
-    # get current option states
-    q = rfc1143_get(telopt)
-    unless q
-      rfc1143_set(telopt)
-      q = rfc1143_get(telopt)
-    end
-    
-    act, arg = nil, nil
-    case cmd
-      when TELNET_WILL
-        act, arg = q.send_will
-      when TELNET_WONT
-        act, arg = q.send_wont
-      when TELNET_DO
-        act, arg = q.send_do
-      when TELNET_DONT
-        act, arg = q.send_dont    
-    end
-        
-    return false unless act    
-    telnet_send_bytes(TELNET_IAC, act, telopt)
-  end
-        
+// Call this when the server receives data from the client
+func (me * Telnet) TelnetReceive(data []byte) {
+// the COMPRESS2 protocol seems to be half-duplex in that only 
+// the server's data stream is compressed (unless maybe if the client
+// is asked to also compress with a DO command ?)
+    me.ProcessBytes(data)
+}
 
-  # send non-command data (escapes IAC bytes)
-  def telnet_send(buffer)
-    send_escaped(buffer)
-  end
+// Send a bytes array (raw) to the client
+func (me * Telnet) TelnetSendBytes(bytes ...byte) {
+    me.SendRaw(bytes)
+}
+
+// Send an iac command 
+func (me * Telnet) TelnetSendIac(cmd byte) {
+    me.TelnetSendBytes(TELNET_IAC, cmd)
+}
+
+// Send negotiation. Currently rfc1143 is not implemented, so beware of 
+// server client loops. The simplest way to avoid those is to never answer any 
+// client requests, only send server requests.
+func (me * Telnet) TelnetSendNegotiate(cmd byte, telopt byte) {
+    me.TelnetSendBytes(TELNET_IAC, cmd, telopt)
+}
+        
+// Send non-command data (escapes IAC bytes)
+func (me * Telnet) TelnetSend(buffer []byte) {
+    me.SendEscaped(buffer)
+}
   
-  # send subnegotiation header
-  def telnet_begin_sb(telopt)
-    telnet_send_bytes(TELNET_IAC, TELNET_SB, telopt)
-  end
-
-  # send subnegotiation ending
-  def telnet_end_sb()
-    telnet_send_bytes(TELNET_IAC, TELNET_SE)
-  end
-
-
-  # send complete subnegotiation
-  def telnet_subnegotiation(telopt, buffer = nil)
-    telnet_send_bytes(TELNET_IAC, TELNET_SB, telopt)
-    telnet_send(buffer) if buffer;
-    telnet_send_bytes(TELNET_IAC, TELNET_SE)
-  end
+// send subnegotiation header
+func (me * Telnet) TelnetBeginSubnegotiation(telopt byte) {
+    me.TelnetSendBytes(TELNET_IAC, TELNET_SB, telopt)
+}
+
+
+// send subnegotiation ending
+func (me * Telnet) TelnetEndSubnegotiation() {
+    me.TelnetSendBytes(TELNET_IAC, TELNET_SE)
+}
+
+// Send complete subnegotiation
+func (me * Telnet) TelnetSubnegotiation(telopt byte, buffer []byte) {
+    me.TelnetBeginSubnegotiation(telopt)
+    if buffer != nil {
+        me.TelnetSend(buffer) 
+    }
+    me.TelnetEndSubnegotiation()
+}
   
-  # start compress2 compression
-  def telnet_begin_compress2() 
-    telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE);
+// Ask client to start accepting compress2 compression
+func (me * Telnet) TelnetBeginCompress2() {
+    me.TelnetSendBytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE);
     me.compress = true
-  end
-  
-  # send formatted data
-  def telnet_raw_printf(fmt, *args)
-    buf   = sprintf(fmt, *args)
-    telnet_send(buf)
-  end
-
-  CRLF  = "\r\n"
-  CRNUL = "\r\0"
-  
-  # send formatted data with \r and \n translation in addition to IAC IAC 
-  def telnet_printf(fmt, *args)
-    buf   = sprintf(fmt, *args)
-    buf.gsub!("\r", CRNUL)
-    buf.gsub!("\n", CRLF)
-    telnet_send(buf)
-  end
-
-  # begin NEW-ENVIRON subnegotation
-  def telnet_begin_newenviron(cmd)
-    telnet_begin_sb(TELNET_TELOPT_NEW_ENVIRON)
-    telnet_send_bytes(cmd)
-  end
-  
-  # send a NEW-ENVIRON value
-  def telnet_newenviron_value(type, value)
-    telnet_send_bytes(type)
-    telnet_send(string)
-  end
-  
-  # send TERMINAL-TYPE SEND command
-  def telnet_ttype_send() 
-    telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE)
-  end  
-  
-  # send TERMINAL-TYPE IS command 
-  def telnet_ttype_is(ttype)
-    telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS)
-    telnet_send(ttype)
-  end
+}
+
+// Send formatted data to the client
+func (me * Telnet) TelnetRawPrintf(format string, args ...interface{}) {
+    buf  := fmt.Sprintf(format, args...)
+    me.TelnetSend([]byte(buf))
+}
+
+const CRLF  = "\r\n"
+const CRNUL = "\r\000"
   
-  # send MSSP data
-  def telnet_send_mssp(mssp)
-    buf = ""
-    mssp.each do | key, val| 
-      buf << TELNET_MSSP_VAR.chr
-      buf << key
-      buf << TELNET_MSSP_VAL.chr
-      buf << val      
-    end
-    telnet_subnegotiation(TELNET_TELOPT_MSSP, buf)
-  end
-
-end
+// send formatted data with \r and \n translation in addition to IAC IAC 
+// escaping
+func (me * Telnet) TelnetPrintf(format string, args ...interface{}) {
+    buf  := fmt.Sprintf(format, args...)
+    buf   = strings.Replace(buf, "\r", CRNUL, -1)
+    buf   = strings.Replace(buf, "\n", CRLF , -1)
+    me.TelnetSend([]byte(buf))
+}
+
+// NEW-ENVIRON subnegotation
+func (me * Telnet) TelnetNewenviron(cmd []byte) {
+    me.TelnetSubnegotiation(TELNET_TELOPT_NEW_ENVIRON, cmd)
+}
+
+// send TERMINAL-TYPE SEND command
+func (me * Telnet)  TelnetTTypeSend() {
+    me.TelnetSendBytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE)
+}
+
+// send TERMINAL-TYPE IS command 
+func (me * Telnet)  TelnetTTypeIS(ttype string) {
+    me.TelnetSendBytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS)
+    me.TelnetSend([]byte(ttype))
+}
+
+// send MSSP data
+func (me * Telnet) TelnetSendMSSP(mssp map[string] string) {
+    var buf []byte 
+    for key, val := range mssp { 
+      buf = append(buf, TELNET_MSSP_VAR)
+      buf = append(buf, key...)
+      buf = append(buf, TELNET_MSSP_VAL)
+      buf = append(buf, val...)
+    }
+    me.TelnetSubnegotiation(TELNET_TELOPT_MSSP, buf)
+}
 
 
 

+ 65 - 57
woe.geany

@@ -5,7 +5,7 @@ strip_trailing_spaces=false
 replace_tabs=true
 
 [indentation]
-indent_width=2
+indent_width=4
 indent_type=0
 indent_hard_tab_width=8
 detect_indent=false
@@ -23,63 +23,71 @@ long_line_behaviour=1
 long_line_column=80
 
 [files]
-current_page=39
-FILE_NAME_0=11178;C;0;EUTF-8;0;1;0;%2Fusr%2Flocal%2Finclude%2Fmruby.h;0;2
-FILE_NAME_1=8011;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Futil%2Ftelnet-chatd.c;0;2
-FILE_NAME_2=697;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2FTupfile;0;2
+current_page=11
+FILE_NAME_0=11178;C;0;EUTF-8;0;1;0;%2Fusr%2Flocal%2Finclude%2Fmruby.h;0;4
+FILE_NAME_1=8011;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Futil%2Ftelnet-chatd.c;0;4
+FILE_NAME_2=697;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2FTupfile;0;4
 FILE_NAME_3=235;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmain.rb;0;2
-FILE_NAME_4=602;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsecurity.rb;0;2
+FILE_NAME_4=623;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fsecurity.rb;0;2
 FILE_NAME_5=1301;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fserdes.rb;0;2
-FILE_NAME_6=521;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Ftry.sitef;0;2
-FILE_NAME_7=143;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fureqma%2FREADME.md;0;2
-FILE_NAME_8=341;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fureqma%2Fureqma;0;2
-FILE_NAME_9=348;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Faccount.rb;0;2
-FILE_NAME_10=3275;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsitef.rb;0;2
-FILE_NAME_11=152;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmotd.rb;0;2
-FILE_NAME_12=35;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FD%2FDyon%2FDyon.account;0;2
-FILE_NAME_13=40;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FB%2FBeoran%2FBeoran.account;0;2
-FILE_NAME_14=29;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2Fa%2Faxl%2Faxl.account;0;2
-FILE_NAME_15=18;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Fmotd;0;2
-FILE_NAME_16=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fcharacter.rb;0;2
-FILE_NAME_17=137;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Flog.rb;0;2
-FILE_NAME_18=382;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fclient.rb;0;2
-FILE_NAME_19=245;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode.rb;0;2
-FILE_NAME_20=414;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fcharacter.rb;0;2
-FILE_NAME_21=669;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fsetup.rb;0;2
-FILE_NAME_22=6413;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Ftclient.rb;0;2
-FILE_NAME_23=2622;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Fconfig.yaml;0;2
-FILE_NAME_24=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fnormal.rb;0;2
-FILE_NAME_25=814;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_all_gitignores;0;2
-FILE_NAME_26=5662;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_gitignore;0;2
-FILE_NAME_27=1452;None;0;EUTF-8;0;1;0;%2Fusers%2Fnmbs_ebp%2Fr17%2F.gitignore;0;2
-FILE_NAME_28=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fmonolog.rb;0;2
-FILE_NAME_29=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fsitef.rb;0;2
-FILE_NAME_30=186;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2FREADME;0;2
-FILE_NAME_31=126;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Ftelnet%2Fcodes.rb;0;2
-FILE_NAME_32=2335;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fserdes.rb;0;2
-FILE_NAME_33=1;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_serdes.rb;0;2
-FILE_NAME_34=1389;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Flibtelnet.c;0;2
-FILE_NAME_35=11028;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Flibtelnet.h;0;2
-FILE_NAME_36=2505;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Fatw%2Fscrapeme%2Fatw_logs_scraper.rb;0;2
-FILE_NAME_37=252;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Faccount.rb;0;2
-FILE_NAME_38=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fansi.rb;0;2
-FILE_NAME_39=2196;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fclient.rb;0;2
-FILE_NAME_40=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fgserver.rb;0;2
-FILE_NAME_41=1696;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fserver.rb;0;2
-FILE_NAME_42=2724;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fcserver.rb;0;2
-FILE_NAME_43=923;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Ffserver.rb;0;2
-FILE_NAME_44=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_monolog.rb;0;2
-FILE_NAME_45=722;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Fwoe%2Ftest_settings.rb;0;2
-FILE_NAME_46=366;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fsettings.rb;0;2
-FILE_NAME_47=558;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_sitef.rb;0;2
-FILE_NAME_48=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_rfc1143.rb;0;2
-FILE_NAME_49=319;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_telnet.rb;0;2
-FILE_NAME_50=11112;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Ftelnet.rb;0;2
-FILE_NAME_51=6705;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Frfc1143.rb;0;2
-FILE_NAME_52=90;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Fwoe%2Ftest_server.rb;0;2
-FILE_NAME_53=11;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fatto%2Flib%2Fatto%2Frun.rb;0;2
-FILE_NAME_54=1597;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fatto%2Flib%2Fatto%2Ftest.rb;0;2
-FILE_NAME_55=147;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fbin%2Fwoe;0;2
+FILE_NAME_6=521;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Ftry.sitef;0;4
+FILE_NAME_7=348;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Faccount.rb;0;2
+FILE_NAME_8=99;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Fserver%2Fserver.go;0;4
+FILE_NAME_9=158;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Fserver%2Fclient.go;0;4
+FILE_NAME_10=2274;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Ftelnet%2Fcodes.go;0;4
+FILE_NAME_11=2718;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Ftelnet%2Ftelnet.go;0;4
+FILE_NAME_12=51;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Ftelnet%2Frfc1143.go;0;4
+FILE_NAME_13=317;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Fwoe.go;0;4
+FILE_NAME_14=1249;Go;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fwoe%2Fmonolog%2Fmonolog.go;0;4
+FILE_NAME_15=295;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fbin%2Ft_month_report;0;2
+FILE_NAME_16=3275;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsitef.rb;0;2
+FILE_NAME_17=152;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmotd.rb;0;2
+FILE_NAME_18=35;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FD%2FDyon%2FDyon.account;0;4
+FILE_NAME_19=40;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FB%2FBeoran%2FBeoran.account;0;4
+FILE_NAME_20=29;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2Fa%2Faxl%2Faxl.account;0;4
+FILE_NAME_21=18;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Fmotd;0;4
+FILE_NAME_22=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fcharacter.rb;0;2
+FILE_NAME_23=137;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Flog.rb;0;2
+FILE_NAME_24=382;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fclient.rb;0;2
+FILE_NAME_25=245;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode.rb;0;2
+FILE_NAME_26=414;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fcharacter.rb;0;2
+FILE_NAME_27=669;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fsetup.rb;0;2
+FILE_NAME_28=6413;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Ftclient.rb;0;2
+FILE_NAME_29=2622;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Fconfig.yaml;0;4
+FILE_NAME_30=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fnormal.rb;0;2
+FILE_NAME_31=814;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_all_gitignores;0;4
+FILE_NAME_32=5662;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_gitignore;0;4
+FILE_NAME_33=1452;None;0;EUTF-8;0;1;0;%2Fusers%2Fnmbs_ebp%2Fr17%2F.gitignore;0;4
+FILE_NAME_34=3659;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fmonolog.rb;0;2
+FILE_NAME_35=4541;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fsitef.rb;0;2
+FILE_NAME_36=186;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2FREADME;0;4
+FILE_NAME_37=3171;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Ftelnet%2Fcodes.rb;0;2
+FILE_NAME_38=1223;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fserdes.rb;0;2
+FILE_NAME_39=1;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_serdes.rb;0;2
+FILE_NAME_40=1389;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Flibtelnet.c;0;4
+FILE_NAME_41=11028;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Flibtelnet.h;0;4
+FILE_NAME_42=2505;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Fatw%2Fscrapeme%2Fatw_logs_scraper.rb;0;2
+FILE_NAME_43=319;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Faccount.rb;0;2
+FILE_NAME_44=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fansi.rb;0;2
+FILE_NAME_45=11942;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fclient.rb;0;2
+FILE_NAME_46=1187;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fbeing.rb;0;2
+FILE_NAME_47=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fcharacter.rb;0;2
+FILE_NAME_48=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fmobile.rb;0;2
+FILE_NAME_49=10;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fserver.rb;0;2
+FILE_NAME_50=2724;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fcserver.rb;0;2
+FILE_NAME_51=923;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Ffserver.rb;0;2
+FILE_NAME_52=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_monolog.rb;0;2
+FILE_NAME_53=722;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Fwoe%2Ftest_settings.rb;0;2
+FILE_NAME_54=366;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fsettings.rb;0;2
+FILE_NAME_55=558;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_sitef.rb;0;2
+FILE_NAME_56=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_rfc1143.rb;0;2
+FILE_NAME_57=319;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_telnet.rb;0;2
+FILE_NAME_58=3497;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Ftelnet.rb;0;2
+FILE_NAME_59=6705;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Frfc1143.rb;0;2
+FILE_NAME_60=90;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Fwoe%2Ftest_server.rb;0;2
+FILE_NAME_61=11;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fatto%2Flib%2Fatto%2Frun.rb;0;2
+FILE_NAME_62=1597;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fatto%2Flib%2Fatto%2Ftest.rb;0;2
+FILE_NAME_63=7;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fbin%2Fwoe;0;2
 
 [VTE]
 last_dir=/home/bjorn/src/woe
@@ -89,7 +97,7 @@ EX_00_LB=_Execute
 EX_00_CM=bin/woe
 EX_00_WD=%p
 NF_00_LB=_Tup
-NF_00_CM=tup
+NF_00_CM=GOROOT=/home/bjorn/opt/go GOPATH=/home/bjorn/src/woe/go PATH=$PATH:$GOROOT/bin go install github.com/beoran/woe
 NF_00_WD=%p
 NF_01_LB=Make Custom _Target
 NF_01_CM=make