package main import ( "net" "sync" "time" "github.com/racingmars/go3270" ) // Telnet Command Constants (RFC 854 / RFC 885) const ( IAC byte = 0xFF DONT byte = 0xFE DO byte = 0xFD WONT byte = 0xFC WILL byte = 0xFB SB byte = 0xFA GA byte = 0xF9 NOP byte = 0xF1 SE byte = 0xF0 EOR byte = 0xEF OPT_BINARY byte = 0x00 OPT_SUPPRESS_GA byte = 0x03 OPT_TIME_MARK byte = 0x06 OPT_TERMINAL_TYPE byte = 0x18 OPT_EOR byte = 0x19 OPT_TN3270E byte = 0x28 // Terminal-Type Subnegotiation Codes (RFC 1091) TTYPE_IS byte = 0x00 TTYPE_SEND byte = 0x01 // TN3270E Subnegotiation Command Codes (RFC 2355) TN3270E_DEVICE_TYPE byte = 0x02 TN3270E_FUNCTIONS byte = 0x03 TN3270E_IS byte = 0x04 TN3270E_REJECT byte = 0x06 TN3270E_REQUEST byte = 0x07 TN3270E_SEND byte = 0x08 // Data Types in TN3270E Header TN3270E_DATA_3270 byte = 0x00 TN3270E_DATA_RESP byte = 0x02 TN3270E_DATA_BIND byte = 0x03 TN3270E_DATA_UNBIND byte = 0x04 TN3270E_DATA_SSCP_LU byte = 0x07 // Functions & Header Flags in RFC 2355 TN3270E_FUNC_BIND_IMAGE byte = 0x00 TN3270E_FUNC_RESPONSES byte = 0x02 TN3270E_RESP_ALWAYS byte = 0x02 TN3270E_UNBIND_NORMAL byte = 0x01 TN3270E_MAX_SEQ uint16 = 0x7FFF // 3270 Command Codes CMD_WRITE byte = 0xF1 CMD_WSF byte = 0xF3 CMD_ERASE_WRITE byte = 0xF5 CMD_ERASE_WRITE_SNA byte = 0x05 CMD_ERASE_WRITE_ALT byte = 0x7E CMD_ERASE_WRITE_ALT_SNA byte = 0x0D CMD_ERASE_ALL_UNPROTECTED byte = 0x6F // 3270 Structured Field & Query Reply Constants AID_QUERY_REPLY byte = 0x88 SF_READ_PARTITION byte = 0x01 SF_RP_QUERY byte = 0xFF SF_QUERY_REPLY byte = 0x81 QR_USABLE_AREA byte = 0x81 QR_COLOR byte = 0x86 QR_HIGHLIGHTING byte = 0x87 QR_IMPLICIT_PARTITION byte = 0xA6 QR_NULL byte = 0xFF // 3270 Extended Color & Highlighting Attribute Constants ATTR_COLOR_BLUE byte = 0xF1 ATTR_COLOR_RED byte = 0xF2 ATTR_COLOR_PINK byte = 0xF3 ATTR_COLOR_GREEN byte = 0xF4 ATTR_COLOR_TURQUOISE byte = 0xF5 ATTR_COLOR_YELLOW byte = 0xF6 ATTR_COLOR_WHITE byte = 0xF7 ATTR_HL_BLINK byte = 0xF1 ATTR_HL_REVERSE byte = 0xF2 ATTR_HL_UNDERSCORE byte = 0xF4 ) type LoginCredentials struct { Username string Password string SessionKey string } type AuthResult struct { OK bool Reason string IsAdmin bool } type TargetSystem struct { Name string `toml:"name"` Description string `toml:"description"` HostPort string `toml:"host_port"` TLS bool `toml:"tls"` InsecureSkipVerify bool `toml:"insecure_skip_verify"` } type ServerConfig struct { BindAddress string `toml:"bind_address"` Codepage string `toml:"codepage"` TLS bool `toml:"tls"` CertFile string `toml:"cert_file"` KeyFile string `toml:"key_file"` Public bool `toml:"public"` AutoHoldDiscardLimit int `toml:"auto_hold_discard_limit"` AutoHoldBurstLimit int `toml:"auto_hold_burst_limit"` } type LoginConfig struct { Title string `toml:"title"` Subtitle string `toml:"subtitle"` PromptUsername string `toml:"prompt_username"` PromptPassword string `toml:"prompt_password"` Instructions string `toml:"instructions"` } type MenuConfig struct { Title string `toml:"title"` PromptSelect string `toml:"prompt_select"` Footer string `toml:"footer"` } type UserConfig struct { Username string `toml:"username"` Hash string `toml:"hash"` Password string `toml:"password"` Admin bool `toml:"admin"` } type Config struct { Public bool `toml:"public"` Server ServerConfig `toml:"server"` Login LoginConfig `toml:"login"` Menu MenuConfig `toml:"menu"` Systems []TargetSystem `toml:"systems"` Users []UserConfig `toml:"users"` } const ( defaultAutoHoldDiscardLimit = 5 autoHoldDiscardWindow = 5 * time.Second defaultAutoHoldBurstLimit = 20 autoHoldBurstWindow = 1 * time.Second ) type SessionSlot struct { System TargetSystem Upstream net.Conn LastScreen []byte ScreenChanged bool IsTN3270E bool IsBound bool SeqNumber uint16 IsHeld bool MaxBufferSize int DiscardCount int DiscardWindow time.Time WriteBurstCount int WriteBurstWindow time.Time AutoHoldDiscardLimit int AutoHoldBurstLimit int Mu sync.Mutex } func (s *SessionSlot) resetHoldAndLoopCountersLocked() { s.IsHeld = false s.ScreenChanged = false s.DiscardCount = 0 s.DiscardWindow = time.Time{} s.WriteBurstCount = 0 s.WriteBurstWindow = time.Time{} } type slotRenderInfo struct { status string color go3270.Color hasNotice bool } type UserMasterSession struct { Username string SessionKey string DisplayTag string TerminalType string DevInfo go3270.DevInfo ClientConn net.Conn ActiveSlotIdx int Slots []*SessionSlot IsDisconnected bool MenuPage int IsAdmin bool Mu sync.Mutex } func (u *UserConfig) GetHash() string { if u.Hash != "" { return u.Hash } return u.Password } func (s *UserMasterSession) Identity() (username, displayTag string) { s.Mu.Lock() defer s.Mu.Unlock() return s.Username, s.DisplayTag } func (s *UserMasterSession) Codepage() go3270.Codepage { s.Mu.Lock() defer s.Mu.Unlock() return s.codepageLocked() } func (s *UserMasterSession) codepageLocked() go3270.Codepage { if s.DevInfo != nil { return s.DevInfo.Codepage() } return nil } func (s *UserMasterSession) CountActiveSlots() int { s.Mu.Lock() defer s.Mu.Unlock() return s.countActiveSlotsLocked() } func (s *UserMasterSession) countActiveSlotsLocked() int { count := 0 for _, slot := range s.Slots { if slot != nil { count++ } } return count } func (s *UserMasterSession) GetMatrix() (rows, cols, maxBuf int) { s.Mu.Lock() defer s.Mu.Unlock() return GetTerminalMatrix(s.TerminalType, s.DevInfo) }