From 9ad2ba0bbeca95224742aaa6860a2615ffb68a27 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Fri, 25 Dec 2015 22:13:49 +0100 Subject: telnet control interface now uses spreadspace/telgo diff --git a/Makefile b/Makefile index e3e0383..0cd6738 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ getlibs: $(GOCMD) get "github.com/ziutek/mymysql/godrv" $(GOCMD) get "github.com/golang-basic/go-curl" $(GOCMD) get "github.com/satori/go.uuid" + $(GOCMD) get "github.com/spreadspace/telgo" # $(GOCMD) get "github.com/gorilla/websocket" vet: export GOPATH=$(curdir) diff --git a/src/helsinki.at/rhimport/importer.go b/src/helsinki.at/rhimport/importer.go index 3ee94cd..7ce4033 100644 --- a/src/helsinki.at/rhimport/importer.go +++ b/src/helsinki.at/rhimport/importer.go @@ -119,7 +119,7 @@ func (ctx *ImportContext) SanityCheck() error { return err } if !ismusic { - return fmt.Errorf("supplied GroupName is not a music pool") + return fmt.Errorf("supplied GroupName '%s' is not a music pool", ctx.GroupName) } if ctx.Cart != 0 || ctx.Cut != 0 { return fmt.Errorf("Cart and Cut must not be supplied when importing into a music group") diff --git a/src/helsinki.at/rhimport/session_store.go b/src/helsinki.at/rhimport/session_store.go index 505bd75..2aabc44 100644 --- a/src/helsinki.at/rhimport/session_store.go +++ b/src/helsinki.at/rhimport/session_store.go @@ -79,6 +79,7 @@ type SessionStore struct { } func (self *SessionStore) new(ctx *ImportContext) (resp newSessionResponse) { + // TODO: for untrusted interfaces we need to check Username and PassWord!!!! b := uuid.NewV4().Bytes() resp.id = strings.ToLower(strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=")) if _, exists := self.store[ctx.UserName]; !exists { diff --git a/src/helsinki.at/rhimportd/ctrlTelnet.go b/src/helsinki.at/rhimportd/ctrlTelnet.go index 55701cd..f578f54 100644 --- a/src/helsinki.at/rhimportd/ctrlTelnet.go +++ b/src/helsinki.at/rhimportd/ctrlTelnet.go @@ -25,558 +25,268 @@ package main import ( - "bufio" - "bytes" "fmt" + "github.com/spreadspace/telgo" "helsinki.at/rhimport" - "net" "net/http" "strconv" "strings" ) -const ( - prompt = "> " - EOT = byte(4) - IP = byte(244) - WILL = byte(251) - WONT = byte(252) - DO = byte(253) - DONT = byte(254) - IAC = byte(255) -) - -type TelnetClient struct { - conn net.Conn - scanner *bufio.Scanner - writer *bufio.Writer - conf *rhimport.Config - rddb *rhimport.RdDbChan - ctx *rhimport.ImportContext - iacout chan []byte -} - -func (c *TelnetClient) write_string(text string) { - defer c.writer.Flush() - - data := []byte(text) - for { - idx := bytes.IndexByte(data, IAC) - if idx >= 0 { - c.writer.Write(data[:idx+1]) - c.writer.WriteByte(IAC) - data = data[idx+1:] - } else { - c.writer.Write(data) - return - } - } -} - -func (c *TelnetClient) say(format string, a ...interface{}) { - c.write_string(fmt.Sprintf(format, a...) + "\n") +func handle_cmd_quit(c *telgo.TelnetClient, args []string, conf *rhimport.Config, rddb *rhimport.RdDbChan) bool { + return true } -func (c *TelnetClient) handle_cmd_help(args []string) { +func handle_cmd_help(c *telgo.TelnetClient, args []string, conf *rhimport.Config, rddb *rhimport.RdDbChan) bool { switch len(args) { - case 1: - switch args[0] { + case 2: + switch args[1] { case "quit": - c.say("usage: quit") - c.say(" terminates the client connection. You may also use Ctrl-D to do this.") - return + c.Sayln("usage: quit") + c.Sayln(" terminates the client connection. You may also use Ctrl-D to do this.") + return false case "help": - c.say("usage: help [ ]") - c.say(" prints command overview or detailed info to .") - return + c.Sayln("usage: help [ ]") + c.Sayln(" prints command overview or detailed info to .") + return false case "set": - c.say("usage: set ") - c.say(" this sets the import parameter to .") - c.say("") - c.say(" available parameters:") - c.say(" UserName string username to use for rdxport interface") - c.say(" Password string password to use for rdxport interface") - c.say(" SourceUri string uri to the file to import") - c.say(" ShowId uint the RHRD show id to import to") - c.say(" ClearShowCarts bool clear all show-carts before importing?") - c.say(" GroupName string name of music-pool group to import to") - c.say(" Cart uint cart to import to") - c.say(" ClearCart bool remove/add cart before import") - c.say(" Cut uint cut to import to") - c.say(" Channels uint number of audio channels (default: %v)", c.conf.ImportParamDefaults.Channels) - c.say(" NormalizationLevel int normalization level in dB (default: %v)", c.conf.ImportParamDefaults.NormalizationLevel) - c.say(" AutotrimLevel int autotrim level in dB (default: %v)", c.conf.ImportParamDefaults.AutotrimLevel) - c.say(" UseMetaData bool extract meta data from file (default: %v)", c.conf.ImportParamDefaults.UseMetaData) - c.say("") - c.say(" UserName, Password and SourceUri are mandatory parameters.") - c.say("") - c.say(" If ShowId is supplied GroupName, Channels, NomalizationLevel, AutorimLevel,") - c.say(" UseMetaData and Cut will be ignored. The values from the shows' dropbox will") - c.say(" be used instead. Cart may be specified but must point to an empty cart within") - c.say(" that show. If ClearCut is true the specified cart will get deleted before") - c.say(" importing. If Cart is 0 the next free cart in the show will be used. Show") - c.say(" carts will always be imported into cut 1.") - c.say("") - c.say(" If GroupName is supplied Channels, NomalizationLevel, AutorimLevel,") - c.say(" UseMetaData, Cut, Cart and ClearCart will be ignored. The values from") - c.say(" the music pools' dropbox will be used instead. The file will always be") - c.say(" imported into cut 1 of the first free cart within the music pool.") - c.say("") - c.say(" If ShowId and GroupName are omitted a Cart must be specified. Cut may be") - c.say(" supplied in which case both cart and cut must already exist. The import will") - c.say(" then replace the contents of the current data stored in Cart/Cut. If only Cart") - c.say(" and no Cut is supplied and ClearCut is false the file will either get imported") - c.say(" into the next cut of an existing cart or the cart will be created and the file") - c.say(" will be imported into cut 1 of this cart.") - c.say("") - c.say(" In case of an error carts/cuts which might got created will be removed. Carts") - c.say(" which got deleted because of ClearShowCarts or ClearCart are however gone for") - c.say(" good.") - return + c.Sayln("usage: set ") + c.Sayln(" this sets the import parameter to .") + c.Sayln("") + c.Sayln(" available parameters:") + c.Sayln(" UserName string username to use for rdxport interface") + c.Sayln(" Password string password to use for rdxport interface") + c.Sayln(" SourceUri string uri to the file to import") + c.Sayln(" ShowId uint the RHRD show id to import to") + c.Sayln(" ClearShowCarts bool clear all show-carts before importing?") + c.Sayln(" GroupName string name of music-pool group to import to") + c.Sayln(" Cart uint cart to import to") + c.Sayln(" ClearCart bool remove/add cart before import") + c.Sayln(" Cut uint cut to import to") + c.Sayln(" Channels uint number of audio channels (default: %v)", conf.ImportParamDefaults.Channels) + c.Sayln(" NormalizationLevel int normalization level in dB (default: %v)", conf.ImportParamDefaults.NormalizationLevel) + c.Sayln(" AutotrimLevel int autotrim level in dB (default: %v)", conf.ImportParamDefaults.AutotrimLevel) + c.Sayln(" UseMetaData bool extract meta data from file (default: %v)", conf.ImportParamDefaults.UseMetaData) + c.Sayln("") + c.Sayln(" UserName, Password and SourceUri are mandatory parameters.") + c.Sayln("") + c.Sayln(" If ShowId is supplied GroupName, Channels, NomalizationLevel, AutorimLevel,") + c.Sayln(" UseMetaData and Cut will be ignored. The values from the shows' dropbox will") + c.Sayln(" be used instead. Cart may be specified but must point to an empty cart within") + c.Sayln(" that show. If ClearCut is true the specified cart will get deleted before") + c.Sayln(" importing. If Cart is 0 the next free cart in the show will be used. Show") + c.Sayln(" carts will always be imported into cut 1.") + c.Sayln("") + c.Sayln(" If GroupName is supplied Channels, NomalizationLevel, AutorimLevel,") + c.Sayln(" UseMetaData, Cut, Cart and ClearCart will be ignored. The values from") + c.Sayln(" the music pools' dropbox will be used instead. The file will always be") + c.Sayln(" imported into cut 1 of the first free cart within the music pool.") + c.Sayln("") + c.Sayln(" If ShowId and GroupName are omitted a Cart must be specified. Cut may be") + c.Sayln(" supplied in which case both cart and cut must already exist. The import will") + c.Sayln(" then replace the contents of the current data stored in Cart/Cut. If only Cart") + c.Sayln(" and no Cut is supplied and ClearCut is false the file will either get imported") + c.Sayln(" into the next cut of an existing cart or the cart will be created and the file") + c.Sayln(" will be imported into cut 1 of this cart.") + c.Sayln("") + c.Sayln(" In case of an error carts/cuts which might got created will be removed. Carts") + c.Sayln(" which got deleted because of ClearShowCarts or ClearCart are however gone for") + c.Sayln(" good.") + return false case "show": - c.say("usage: show") - c.say(" this prints the current values of all import parameters.") - return + c.Sayln("usage: show") + c.Sayln(" this prints the current values of all import parameters.") + return false case "reset": - c.say("usage: reset") - c.say(" this resets all import parameters to default values.") - return + c.Sayln("usage: reset") + c.Sayln(" this resets all import parameters to default values.") + return false case "run": - c.say("usage: run") - c.say(" this starts the fetch/import process according to the current") - c.say(" import parameters.") - return + c.Sayln("usage: run") + c.Sayln(" this starts the fetch/import process according to the current") + c.Sayln(" import parameters.") + return false } fallthrough default: - c.say("usage: [ [ ] ... ]") - c.say(" available commands:") - c.say(" quit close connection (or use Ctrl-D)") - c.say(" help [ ] print this, or help for specific command") - c.say(" set sets parameter on current import context") - c.say(" show shows current import context") - c.say(" reset resets current import context") - c.say(" run runs fetch/import using current import context") - } + c.Sayln("usage: [ [ ] ... ]") + c.Sayln(" available commands:") + c.Sayln(" quit close connection (or use Ctrl-D)") + c.Sayln(" help [ ] print this, or help for specific command") + c.Sayln(" set sets parameter on current import context") + c.Sayln(" show shows current import context") + c.Sayln(" reset resets current import context") + c.Sayln(" run runs fetch/import using current import context") + } + return false } -func (c *TelnetClient) handle_cmd_set_string(param *string, val string) { - if val == "\"\"" || val == "''" { - *param = "" - } else { - *param = val - } -} - -func (c *TelnetClient) handle_cmd_set_int(param *int, val string) { +func handle_cmd_set_int(c *telgo.TelnetClient, param *int, val string) { if vint, err := strconv.ParseInt(val, 10, 32); err != nil { - c.say("invalid value (must be an integer)") + c.Sayln("invalid value (must be an integer)") } else { *param = int(vint) } } -func (c *TelnetClient) handle_cmd_set_uint(param *uint, val string) { +func handle_cmd_set_uint(c *telgo.TelnetClient, param *uint, val string) { if vuint, err := strconv.ParseUint(val, 10, 32); err != nil { - c.say("invalid value (must be a positive integer)") + c.Sayln("invalid value (must be a positive integer)") } else { *param = uint(vuint) } } -func (c *TelnetClient) handle_cmd_set_bool(param *bool, val string) { +func handle_cmd_set_bool(c *telgo.TelnetClient, param *bool, val string) { if vbool, err := strconv.ParseBool(val); err != nil { - c.say("invalid value (must be true or false)") + c.Sayln("invalid value (must be true or false)") } else { *param = vbool } } -func (c *TelnetClient) handle_cmd_set(args []string) { - if len(args) != 2 { - c.say("wrong number of arguments") - return +func handle_cmd_set(c *telgo.TelnetClient, args []string, conf *rhimport.Config, rddb *rhimport.RdDbChan) bool { + if len(args) != 3 { + c.Sayln("wrong number of arguments") + return false } - if c.ctx == nil { - c.ctx = rhimport.NewImportContext(c.conf, c.rddb, "") - c.ctx.Trusted = false + + var ctx *rhimport.ImportContext + if c.UserData == nil { + c.UserData = rhimport.NewImportContext(conf, rddb, "") + ctx = c.UserData.(*rhimport.ImportContext) + ctx.Trusted = false + } else { + ctx = c.UserData.(*rhimport.ImportContext) } - switch strings.ToLower(args[0]) { + switch strings.ToLower(args[1]) { case "username": - c.handle_cmd_set_string(&c.ctx.UserName, args[1]) + ctx.UserName = args[2] case "password": - c.handle_cmd_set_string(&c.ctx.Password, args[1]) + ctx.Password = args[2] case "sourceuri": - c.handle_cmd_set_string(&c.ctx.SourceUri, args[1]) + ctx.SourceUri = args[2] case "showid": - c.handle_cmd_set_uint(&c.ctx.ShowId, args[1]) + handle_cmd_set_uint(c, &ctx.ShowId, args[2]) case "clearshowcarts": - c.handle_cmd_set_bool(&c.ctx.ClearShowCarts, args[1]) + handle_cmd_set_bool(c, &ctx.ClearShowCarts, args[2]) case "groupname": - c.handle_cmd_set_string(&c.ctx.GroupName, args[1]) + ctx.GroupName = args[2] case "cart": - c.handle_cmd_set_uint(&c.ctx.Cart, args[1]) + handle_cmd_set_uint(c, &ctx.Cart, args[2]) case "clearcart": - c.handle_cmd_set_bool(&c.ctx.ClearCart, args[1]) + handle_cmd_set_bool(c, &ctx.ClearCart, args[2]) case "cut": - c.handle_cmd_set_uint(&c.ctx.Cut, args[1]) + handle_cmd_set_uint(c, &ctx.Cut, args[2]) case "channels": - c.handle_cmd_set_uint(&c.ctx.Channels, args[1]) + handle_cmd_set_uint(c, &ctx.Channels, args[2]) case "normalizationlevel": - c.handle_cmd_set_int(&c.ctx.NormalizationLevel, args[1]) + handle_cmd_set_int(c, &ctx.NormalizationLevel, args[2]) case "autotrimlevel": - c.handle_cmd_set_int(&c.ctx.AutotrimLevel, args[1]) + handle_cmd_set_int(c, &ctx.AutotrimLevel, args[2]) case "usemetadata": - c.handle_cmd_set_bool(&c.ctx.UseMetaData, args[1]) + handle_cmd_set_bool(c, &ctx.UseMetaData, args[2]) default: - c.say("unknown parameter, use 'help set' for a list of available parameters") + c.Sayln("unknown parameter, use 'help set' for a list of available parameters") } + return false } -func (c *TelnetClient) handle_cmd_reset(args []string) { - if len(args) > 0 { - c.say("too many arguments") - return +func handle_cmd_reset(c *telgo.TelnetClient, args []string, conf *rhimport.Config, rddb *rhimport.RdDbChan) bool { + if len(args) > 1 { + c.Sayln("too many arguments") + return false } - c.ctx = nil + + c.UserData = nil + return false } -func (c *TelnetClient) handle_cmd_show(args []string) { - if len(args) > 0 { - c.say("too many arguments") - return - } - if c.ctx != nil { - c.say(" UserName: %v", c.ctx.UserName) - c.say(" Password: %v", c.ctx.Password) - c.say(" SourceUri: %v", c.ctx.SourceUri) - c.say(" ShowId: %v", c.ctx.ShowId) - c.say(" ClearShowCarts: %v", c.ctx.ClearShowCarts) - c.say(" GroupName: %v", c.ctx.GroupName) - c.say(" Cart: %v", c.ctx.Cart) - c.say(" ClearCart: %v", c.ctx.ClearCart) - c.say(" Cut: %v", c.ctx.Cut) - c.say(" Channels: %v", c.ctx.Channels) - c.say(" NormalizationLevel: %v", c.ctx.NormalizationLevel) - c.say(" AutotrimLevel: %v", c.ctx.AutotrimLevel) - c.say(" UseMetaData: %v", c.ctx.UseMetaData) +func handle_cmd_show(c *telgo.TelnetClient, args []string, conf *rhimport.Config, rddb *rhimport.RdDbChan) bool { + if len(args) > 1 { + c.Sayln("too many arguments") + return false + } + + if c.UserData != nil { + ctx := c.UserData.(*rhimport.ImportContext) + c.Sayln(" UserName: %q", ctx.UserName) + c.Sayln(" Password: %q", ctx.Password) + c.Sayln(" SourceUri: %q", ctx.SourceUri) + c.Sayln(" ShowId: %v", ctx.ShowId) + c.Sayln(" ClearShowCarts: %v", ctx.ClearShowCarts) + c.Sayln(" GroupName: %q", ctx.GroupName) + c.Sayln(" Cart: %v", ctx.Cart) + c.Sayln(" ClearCart: %v", ctx.ClearCart) + c.Sayln(" Cut: %v", ctx.Cut) + c.Sayln(" Channels: %v", ctx.Channels) + c.Sayln(" NormalizationLevel: %v", ctx.NormalizationLevel) + c.Sayln(" AutotrimLevel: %v", ctx.AutotrimLevel) + c.Sayln(" UseMetaData: %v", ctx.UseMetaData) } else { - c.say("context is empty") + c.Sayln("context is empty") } + return false } func telnet_progress_callback(step int, step_name string, progress float64, userdata interface{}) bool { - out := userdata.(chan<- string) - out <- fmt.Sprintf("%s: %3.2f%%\r", step_name, progress*100) + c := userdata.(*telgo.TelnetClient) + c.Say("%s: %3.2f%%\r", step_name, progress*100) return true } -func telnet_cmd_run(ctx rhimport.ImportContext, out chan<- string) { - defer close(out) - - out <- fmt.Sprintf("fetching file from '%s'\n", ctx.SourceUri) - if res, err := rhimport.FetchFile(&ctx); err != nil { - out <- fmt.Sprintf("fetch file error: %s\n", err) - return - } else if res.ResponseCode != http.StatusOK { - out <- fmt.Sprintf("fetch file error: %s\n", res.ErrorString) - return +func handle_cmd_run(c *telgo.TelnetClient, args []string, conf *rhimport.Config, rddb *rhimport.RdDbChan) bool { + if c.UserData == nil { + c.Sayln("context is empty please set at least one option") + return false } - - out <- fmt.Sprintf("\nimporting file '%s'\n", ctx.SourceFile) - if res, err := rhimport.ImportFile(&ctx); err != nil { - out <- fmt.Sprintf("\nimport file error: %s\n", err) - return - } else { - if res.ResponseCode == http.StatusOK { - out <- fmt.Sprintf("\nFile got succesfully imported into Cart/Cut %d/%d\n", res.Cart, res.Cut) - } else { - out <- fmt.Sprintf("\nFileimport has failed (Cart/Cut %d/%d): %s\n", res.Cart, res.Cut, res.ErrorString) - } - } -} - -func (c *TelnetClient) handle_cmd_run(args []string, cancel <-chan bool) { - if c.ctx == nil { - c.say("context is empty please set at least one option") - return - } - - if err := c.ctx.SanityCheck(); err != nil { - c.say("sanity check for import context returned: %s", err) - return - } - select { - case <-cancel: // consume potentially pending cancel request - default: + ctx := c.UserData.(*rhimport.ImportContext) + if err := ctx.SanityCheck(); err != nil { + c.Sayln("sanity check for import context returned: %s", err) + return false } - stdout := make(chan string) - c.ctx.ProgressCallBack = telnet_progress_callback - c.ctx.ProgressCallBackData = (chan<- string)(stdout) - c.ctx.Cancel = cancel - go telnet_cmd_run(*c.ctx, stdout) - for str := range stdout { - c.write_string(str) - } -} + ctx.ProgressCallBack = telnet_progress_callback + ctx.ProgressCallBackData = c + ctx.Cancel = c.Cancel -func (c *TelnetClient) handle_cmd(cmdstr string, done chan<- bool, cancel <-chan bool) { - cmdslice := strings.Fields(cmdstr) - if len(cmdslice) == 0 || cmdslice[0] == "" { - done <- false - return + c.Sayln("fetching file from '%s'", ctx.SourceUri) + if res, err := rhimport.FetchFile(ctx); err != nil { + c.Sayln("fetch file error: %s", err) + return false + } else if res.ResponseCode != http.StatusOK { + c.Sayln("fetch file error: %s", res.ErrorString) + return false } - cmd := cmdslice[0] - args := cmdslice[1:] - if cmd == "quit" { - done <- true - return - } else if cmd == "help" { - c.handle_cmd_help(args) - } else if cmd == "set" { - c.handle_cmd_set(args) - } else if cmd == "reset" { - c.handle_cmd_reset(args) - } else if cmd == "show" { - c.handle_cmd_show(args) - } else if cmd == "run" { - c.handle_cmd_run(args, cancel) + c.Sayln("") + c.Sayln("importing file '%s'", ctx.SourceFile) + if res, err := rhimport.ImportFile(ctx); err != nil { + c.Sayln("") + c.Sayln("import file error: %s", err) } else { - c.say("unknown command '%s'", cmd) - } - done <- false -} - -func handleIac(iac []byte, iacout chan<- []byte) { - switch iac[1] { - case WILL, WONT: // Don't accept any proposed options - iac[1] = DONT - case DO, DONT: - iac[1] = WONT - case IP: - // pass this through to client.handle which will cancel the process - default: - rhdl.Printf("ignoring unimplemented telnet command: %X", iac[1]) - return - } - iacout <- iac -} - -func dropCR(data []byte) []byte { - if len(data) > 0 && data[len(data)-1] == '\r' { - return data[0 : len(data)-1] - } - return data -} - -func dropIAC(data []byte) []byte { - var token []byte - iiac := 0 - for { - niiac := bytes.IndexByte(data[iiac:], IAC) - if niiac >= 0 { - token = append(token, data[iiac:iiac+niiac]...) - iiac += niiac - if (len(data) - iiac) < 2 { - return token - } - switch data[iiac+1] { - case DONT, DO, WONT, WILL: - if (len(data) - iiac) < 3 { - return token - } - iiac += 3 - case IAC: - token = append(token, IAC) - fallthrough - default: - iiac += 2 - } - } else { - token = append(token, data[iiac:]...) - break - } - } - return token -} - -func compare_idx(a, b int) int { - if a < 0 { - a = int(^uint(0) >> 1) - } - if b < 0 { - b = int(^uint(0) >> 1) - } - return a - b -} - -func ScanLinesTelnet(data []byte, atEOF bool, iacout chan<- []byte, lastiiac *int) (advance int, token []byte, err error) { - if atEOF && len(data) == 0 { - return 0, nil, nil - } - - inl := bytes.IndexByte(data, '\n') // index of first newline character - ieot := bytes.IndexByte(data, EOT) // index of first End of Transmission - - iiac := *lastiiac - for { - niiac := bytes.IndexByte(data[iiac:], IAC) // index of first/next telnet IAC - if niiac >= 0 { - iiac += niiac - } else { - iiac = niiac - } - - if inl >= 0 && compare_idx(inl, ieot) < 0 && compare_idx(inl, iiac) < 0 { - *lastiiac = 0 - return inl + 1, dropCR(dropIAC(data[0:inl])), nil // found a complete line - } - if ieot >= 0 && compare_idx(ieot, iiac) < 0 { - *lastiiac = 0 - return ieot + 1, data[ieot : ieot+1], nil // found a EOT (aka Ctrl-D) - } - if iiac >= 0 { - l := 2 - if (len(data) - iiac) < 2 { - return 0, nil, nil // data does not yet contain the telnet command code -> need more data - } - switch data[iiac+1] { - case DONT, DO, WONT, WILL: - if (len(data) - iiac) < 3 { - return 0, nil, nil // this is a 3-byte command and data does not yet contain the option code -> need more data - } - l = 3 - case IAC: - iiac += 2 - continue - } - handleIac(data[iiac:iiac+l], iacout) - iiac += l - *lastiiac = iiac + c.Sayln("") + if res.ResponseCode == http.StatusOK { + c.Sayln("File got succesfully imported into Cart/Cut %d/%d", res.Cart, res.Cut) } else { - break - } - } - if atEOF { - return len(data), dropCR(data), nil // allow last line to have no new line - } - return 0, nil, nil // we have found none of the escape codes -> need more data -} - -func (c *TelnetClient) recv(in chan<- string) { - defer close(in) - - for c.scanner.Scan() { - b := c.scanner.Bytes() - if len(b) > 0 && b[0] == EOT { - rhdl.Printf("telnet-ctrl(%s): Ctrl-D received, closing", c.conn.RemoteAddr()) - return - } - in <- string(b) - } - if err := c.scanner.Err(); err != nil { - rhdl.Printf("telnet-ctrl(%s): recv() error: %s", c.conn.RemoteAddr(), err) - } else { - rhdl.Printf("telnet-ctrl(%s): Connection closed by foreign host", c.conn.RemoteAddr()) - } -} - -func (c *TelnetClient) handle() { - defer c.conn.Close() - - in := make(chan string) - go c.recv(in) - - done := make(chan bool) - cancel := make(chan bool, 1) - defer func() { // make sure to cancel possible running job when closing connection - select { - case cancel <- true: - default: - } - }() - - var cmd_backlog []string - busy := false - c.write_string(prompt) - for { - select { - case cmd, ok := <-in: - if !ok { - return - } - if len(cmd) > 0 { - if !busy { - go c.handle_cmd(cmd, done, cancel) - busy = true - } else { - cmd_backlog = append(cmd_backlog, cmd) - } - } else { - c.write_string(prompt) - } - case exit := <-done: - if exit { - return - } - c.write_string(prompt) - if len(cmd_backlog) > 0 { - go c.handle_cmd(cmd_backlog[0], done, cancel) - cmd_backlog = cmd_backlog[1:] - } else { - busy = false - } - case iac := <-c.iacout: - if iac[1] == IP { - select { - case cancel <- true: - default: // process got canceled already - } - } else { - c.writer.Write(iac) - c.writer.Flush() - } + c.Sayln("Fileimport has failed (Cart/Cut %d/%d): %s", res.Cart, res.Cut, res.ErrorString) } } -} - -func newTelnetClient(conn net.Conn, conf *rhimport.Config, rddb *rhimport.RdDbChan) (c *TelnetClient) { - rhl.Println("telnet-ctrl: new client from:", conn.RemoteAddr()) - c = &TelnetClient{} - c.conn = conn - c.scanner = bufio.NewScanner(conn) - c.writer = bufio.NewWriter(conn) - c.conf = conf - c.rddb = rddb - c.ctx = nil - // the telnet split function needs some closures to handle OOB telnet commands - c.iacout = make(chan []byte) - lastiiac := 0 - c.scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { - return ScanLinesTelnet(data, atEOF, c.iacout, &lastiiac) - }) - return c + return false } func StartControlTelnet(addr_s string, conf *rhimport.Config, rddb *rhimport.RdDbChan) { - rhl.Println("telnet-ctrl: listening on", addr_s) + cmdlist := make(telgo.TelgoCmdList) + cmdlist["quit"] = func(c *telgo.TelnetClient, args []string) bool { return handle_cmd_quit(c, args, conf, rddb) } + cmdlist["help"] = func(c *telgo.TelnetClient, args []string) bool { return handle_cmd_help(c, args, conf, rddb) } + cmdlist["set"] = func(c *telgo.TelnetClient, args []string) bool { return handle_cmd_set(c, args, conf, rddb) } + cmdlist["reset"] = func(c *telgo.TelnetClient, args []string) bool { return handle_cmd_reset(c, args, conf, rddb) } + cmdlist["show"] = func(c *telgo.TelnetClient, args []string) bool { return handle_cmd_show(c, args, conf, rddb) } + cmdlist["run"] = func(c *telgo.TelnetClient, args []string) bool { return handle_cmd_run(c, args, conf, rddb) } - server, err := net.Listen("tcp", addr_s) - if err != nil { - rhl.Println("telnet-ctrl: Listen() Error:", err) - return - } - - for { - conn, err := server.Accept() - if err != nil { - rhl.Println("telnet-ctrl: Accept() Error:", err) - return - } - - c := newTelnetClient(conn, conf, rddb) - go c.handle() + rhl.Println("telnet-ctrl: listening on", addr_s) + s := telgo.NewTelnetServer(addr_s, "rhimportd> ", cmdlist, nil) + if err := s.Run(); err != nil { + fmt.Printf("telnet server returned: %s", err) } } -- cgit v0.10.2