From f0a810c330a42e73d15190941fba9cc4c56577b8 Mon Sep 17 00:00:00 2001 From: Sundog Date: Wed, 14 Apr 2021 05:51:34 -0700 Subject: [PATCH] first commit --- go.mod | 5 ++ main.go | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 go.mod create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..53c767c --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module versestudios.com/go-telnet-asyncio-test + +go 1.16 + +require github.com/xtaci/gaio v1.2.9 diff --git a/main.go b/main.go new file mode 100644 index 0000000..99fc749 --- /dev/null +++ b/main.go @@ -0,0 +1,260 @@ +package main + +import ( + "bufio" + "flag" + "io" + "log" + "net" + "os/exec" + "strconv" + "sync" + "time" + + "github.com/xtaci/gaio" +) + +func main() { + port := flag.Int("port", 3333, "Port to accept connections on.") + host := flag.String("host", "127.0.0.1", "Host or IP to bind to") + flag.Parse() + + w, err := gaio.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer w.Close() + + l, err := net.Listen("tcp", *host+":"+strconv.Itoa(*port)) + if err != nil { + log.Panicln(err) + } + log.Println("Listening to connections at '"+*host+"' on port", strconv.Itoa(*port)) + defer l.Close() + + for { + conn, err := l.Accept() + if err != nil { + log.Panicln(err) + } + + log.Println("new client", conn.RemoteAddr()) + + // set up io channel for this connection + chIO := make(chan gaio.OpResult) + + // submit the first async write IO request + err = (chIO, conn, welcomeHandler()) + if err != nil { + log.Printf("err sending welcomeHandler: %v\n", err) + return + } + // hand off channel to menuHandler + menuHandler(chIO, conn, *w) + } +} + +func menuHandler(chIO chan gaio.OpResult, c net.Conn, w gaio.Watcher) { + log.Printf("Connection received: %s", c.RemoteAddr()) + + // watcher.WaitIO goroutine + go func() { + for { + results, err := w.WaitIO() + if err != nil { + log.Println(err) + return + } + + for _, res := range results { + chIO <- res + } + } + }() + + // send welcome banner + if err := welcomeHandler(&c); err != nil { + log.Printf("error sending welcomeHandler on initial connect: %v", err) + } + + wg := new(sync.WaitGroup) + + wg.Add(1) + go func() { + defer wg.Done() + scanner := bufio.NewScanner(c) + scanner.Split(bufio.ScanLines) + + for scanner.Scan() { + line := scanner.Bytes() + + log.Printf("Menu received line: %v (%v)", line, string(line)) + switch string(line) { + case "welcome": + if err := welcomeHandler(&c); err != nil { + log.Printf("error sending welcomeHandler from cmd `welcome`: %v", err) + } + case "adventure": + // start the door and wait + log.Printf("starting door handler...") + if err := doorHandler(&c); err != nil { + log.Printf("error from cmd `adventure`: %v", err) + } + log.Printf("returning from door") + n, err := c.Write([]byte("\n\n> ")) + if err != nil { + log.Printf("error writing to connection: %v", err) + } else { + log.Printf("wrote %d byte prompt to connection", n) + } + case "exit": + if err := exitHandler(c); err != nil { + log.Printf("error sending exitHandler from cmd `exit`: %v", err) + } + default: + n, err := c.Write([]byte("huh?\n\n> ")) + if err != nil { + log.Printf("error writing to connection: %v", err) + } else { + log.Printf("wrote %d bytes to connection", n) + } + } + } + }() + + time.Sleep(time.Millisecond) + wg.Wait() + log.Printf("Goodbye %s!", c.RemoteAddr()) +} + +func sendClientText(c net.Conn, s string) error { + n, err := c.Write([]byte(s)) + if err != nil { // svr.ListenAndServe() + + log.Printf("error writing to connection: %v", err) + return err + } else { + log.Printf("wrote %d bytes to connection", n) + } + return nil +} + +func welcomeHandler(c *net.Conn) error { + const bannerText = "\x1b[2J\x1b[H\x1b[31m\x1b[J\r\n" + ` + + _ __ _ ` + "" + ` + | | / /__ / /________ ____ ___ ___` + "" + ` + | | /| / / _ \/ / ___/ __ \/ __ '__ \/ _ \` + "" + ` + | |/ |/ / __/ / /__/ /_/ / / / / / / __/` + "" + ` + |__/|__/\___/_/\___/\____/_/ /_/ /_/\___/` + "\r\n\x1b[33m" + ` + __` + "" + ` + / /_____ ` + "" + ` + / __/ __ \` + "" + ` + / /_/ /_/ /` + "" + ` + \__/\____/` + "\x1b[35m\r\n" + ` + _ __` + "" + ` + | | / /__ _____________` + "" + ` + | | / / _ \/ ___/ ___/ _ \` + "" + ` + | |/ / __/ / (__ ) __/` + "" + ` + |___/\___/_/ /____/\___/` + "\x1b[37m\r\n" + ` + _____ __ ___` + "" + ` + / ___// /___ ______/ (_)___ _____` + "" + ` + \__ \/ __/ / / / __ / / __ \/ ___/` + "" + ` + ___/ / /_/ /_/ / /_/ / / /_/ (__ ) ` + "" + ` + /____/\__/\__,_/\__,_/_/\____/____/ ` + "\x1b[32m\r\n\r\n\r\n" + ` + Help is available: type help` + "\r\n\r\n> " + err := sendClientText(*c, bannerText) + if err != nil { + log.Printf("error writing to connection: %v", err) + } + return nil +} + +func exitHandler(c net.Conn) error { + const exitMessage = "\x1b[2J\x1b[H\x1b[31m\x1b[J\r\n" + ` + + ___| | | + \___ \ _ \ _ \ | | _ \ | | __| __ \ _' | __| _ \ __| _ \\ \ \ / __ \ _ \ | | | + | __/ __/ | | ( | | | \__ \ | | ( | ( __/ ( ( |\ \ \ / | | ( | | |_| + _____/ \___|\___| \__, |\___/ \__,_| ) ____/ .__/ \__,_|\___|\___| \___|\___/ \_/\_/ _.__/ \___/ \__, |_) + ____/ / _| ____/ + + +` + err := sendClientText(c, exitMessage) + if err != nil { + log.Printf("error writing to connection: %v", err) + } + time.Sleep(500 * time.Millisecond) + + return c.Close() +} + +func doorHandler(c *net.Conn) error { + const bannerText = "\r\nCOLOSSAL CAVE\r\n\r\n" + err := sendClientText(*c, bannerText) + if err != nil { + log.Printf("error writing to connection: %v\n", err) + } + + cmd := exec.Command("/usr/games/adventure") + + stdout, _ := cmd.StdoutPipe() + stdin, _ := cmd.StdinPipe() + + cmd.Start() + Terminator := make(chan bool) + wg := new(sync.WaitGroup) + wg.Add(1) + go func(c net.Conn, Terminator *chan bool, wg *sync.WaitGroup) { + log.Println("starting stdin reader goroutine") + defer wg.Done() + Check: + for { + select { + case <-*Terminator: + break Check + default: + time.Sleep(time.Second) + inBuf := make([]byte, 80) + var l int + if l, err = c.Read(inBuf); err != nil { + if err == io.EOF { + log.Println("connection closed?") + break Check + } else { + log.Printf("error reading conn: %v\n", err) + break Check + } + } + if l > 0 { + log.Printf("writing %d bytes to stdin: %v\n", l, string(inBuf[:l])) + stdin.Write(inBuf[:l]) + } + } + } + log.Println("exiting stdin reader goroutine") + }(*c, &Terminator, wg) + + log.Println("starting stdout reader goroutine") + scanner := bufio.NewScanner(stdout) + scanner.Split(bufio.ScanLines) + + for scanner.Scan() { + m := scanner.Bytes() + (*c).Write(m) + (*c).Write([]byte{'\n'}) + log.Printf("wrote %d bytes to stdout: %v\n", len(m)+1, string(m)) + } + log.Println("exiting stdout reader goroutine") + Terminator <- true + + log.Println("waiting for wg") + wg.Wait() + log.Println("waiting for cmd") + exitErr := cmd.Wait() + if exitErr != nil { + log.Printf("cmd exited with err: %v\n", exitErr) + } + return nil +}