package main import ( "bufio" "fmt" "io/ioutil" "net/http" "os" "os/exec" "strings" "time" "github.com/unixvoid/glogger" "golang.org/x/crypto/sha3" "gopkg.in/gcfg.v1" "gopkg.in/redis.v5" ) type Config struct { NethackLauncher struct { Loglevel string ServerDisplay string NethackVersion string RecordLocation string ReclistLocation string BootstrapDelay time.Duration } Redis struct { Host string Password string } } var ( config = Config{} ) func main() { // read conf file readConf() // init config file and logger initLogger() // init redis connection redisClient, redisErr := initRedisConnection() if redisErr != nil { glogger.Debug.Printf("redis connection cannot be made, trying again in %s second(s)\n", config.NethackLauncher.BootstrapDelay*time.Second) time.Sleep(config.NethackLauncher.BootstrapDelay * time.Second) redisClient, redisErr = initRedisConnection() if redisErr != nil { glogger.Error.Println("redis connection cannot be made, exiting.") panic(redisErr) } } else { glogger.Debug.Println("connection to redis succeeded.") } screenFunction := printWelcomeScreen(redisClient) fmt.Printf("screen %s recieved\n", screenFunction) } func readConf() { // init config file err := gcfg.ReadFileInto(&config, "config.gcfg") if err != nil { panic(fmt.Sprintf("Could not load config.gcfg, error: %s\n", err)) } } func initLogger() { // init logger if config.NethackLauncher.Loglevel == "debug" { glogger.LogInit(os.Stdout, os.Stdout, os.Stdout, os.Stderr) } else if config.NethackLauncher.Loglevel == "cluster" { glogger.LogInit(os.Stdout, os.Stdout, ioutil.Discard, os.Stderr) } else if config.NethackLauncher.Loglevel == "info" { glogger.LogInit(os.Stdout, ioutil.Discard, ioutil.Discard, os.Stderr) } else { glogger.LogInit(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr) } } func initRedisConnection() (*redis.Client, error) { // init redis connection redisClient := redis.NewClient(&redis.Options{ Addr: config.Redis.Host, Password: config.Redis.Password, DB: 0, }) _, redisErr := redisClient.Ping().Result() return redisClient, redisErr } func checkFiles() { // make sure record file exists if _, err := os.Stat(config.NethackLauncher.RecordLocation); os.IsNotExist(err) { glogger.Info.Printf("record file not found in %s\n", config.NethackLauncher.RecordLocation) fmt.Printf("%s\n", err) os.Exit(1) } // make sure reclist bin exists if _, err := os.Stat(config.NethackLauncher.ReclistLocation); os.IsNotExist(err) { glogger.Info.Printf("reclist binary not found in %s\n", config.NethackLauncher.ReclistLocation) fmt.Printf("%s\n", err) os.Exit(1) } } func clearScreen() { cmd := exec.Command("clear") cmd.Stdout = os.Stdout cmd.Run() } func printWelcomeScreen(redisClient *redis.Client) string { clearScreen() fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) println("") println(" Not logged in.") println("") println(" l) Login") println(" r) Register new user") println(" w) Watch games in progress") println(" q) Quit") println("") fmt.Printf(">> ") // disable input buffering exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() // do not display entered characters on the screen exec.Command("stty", "-F", "/dev/tty", "-echo").Run() var b []byte = make([]byte, 1) for { os.Stdin.Read(b) switch string(b) { case "l": // restart display exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() clearScreen() printLoginScreen(redisClient) case "r": // restart display exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() clearScreen() printRegisterScreen(redisClient) case "w": clearScreen() return ("w") case "q": clearScreen() os.Exit(0) default: } } } func printUserScreen(redisClient *redis.Client, username string) string { clearScreen() fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) println("") fmt.Printf(" Logged in as: %s\n", username) println("") println(" l) Logout") println(" w) Watch games in progress") fmt.Printf(" p) Play NetHack %s\n", config.NethackLauncher.NethackVersion) println(" q) Quit") println("") fmt.Printf(">> ") // disable input buffering exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() // do not display entered characters on the screen exec.Command("stty", "-F", "/dev/tty", "-echo").Run() var b []byte = make([]byte, 1) for { os.Stdin.Read(b) switch string(b) { case "l": // restart display exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() clearScreen() printWelcomeScreen(redisClient) case "p": // restart display exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() clearScreen() nh := exec.Command("nethack", "-u", username) nh.Stdout = os.Stdout nh.Stdin = os.Stdin nh.Stderr = os.Stderr nh.Run() printUserScreen(redisClient, username) case "q": clearScreen() os.Exit(0) default: } } } func printLoginScreen(redisClient *redis.Client) { fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) println("") println(" Please enter your username. (blank entry aborts)") println("") fmt.Printf(">> ") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { if scanner.Text() == "" { printWelcomeScreen(redisClient) } // check redis for user username := scanner.Text() storedHash, err := redisClient.Get(fmt.Sprintf("user:%s", username)).Result() if err != nil { // user does not exist fmt.Printf(" There was a problem with your last entry.\n>> ") } else { // get password from user and compare // turn off echo display exec.Command("stty", "-F", "/dev/tty", "-echo").Run() noPass := true for noPass { fmt.Printf("\n Please enter your password (blank entry aborts).\n>> ") reader := bufio.NewReader(os.Stdin) typedAuth, _ := reader.ReadString('\n') typedAuth = strings.Replace(typedAuth, "\n", "", -1) if typedAuth == "" { // exit to main menu printWelcomeScreen(redisClient) } // get hash of typedAuth typedHash := sha3.Sum512([]byte(typedAuth)) if fmt.Sprintf("%x", typedHash) == storedHash { // user authed printUserScreen(redisClient, username) noPass = false } else { fmt.Printf("\n There was a problem with your last entry.") } } } } if err := scanner.Err(); err != nil { fmt.Printf("%s\n", err) } } func printRegisterScreen(redisClient *redis.Client) { // TODO : configure password restriction checking fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) println("") println(" Welcome new user. Please enter a username") println(" Only characters and numbers are allowed, with no spaces.") println(" 20 characters max. (blank entry aborts)") println("") fmt.Printf(">> ") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { if scanner.Text() == "" { printWelcomeScreen(redisClient) } // check redis for user username := scanner.Text() _, err := redisClient.Get(fmt.Sprintf("user:%s", username)).Result() if err != redis.Nil { // user already exists fmt.Printf("There was a problem with your last entry.\n>> ") } else { // set up user // turn off echo display exec.Command("stty", "-F", "/dev/tty", "-echo").Run() reader := bufio.NewReader(os.Stdin) noPass := true sec := "" for noPass { // pull pass the first time fmt.Printf(" Please enter your password (blank entry aborts).\n>> ") sec0, _ := reader.ReadString('\n') sec0 = strings.Replace(sec0, "\n", "", -1) if sec0 == "" { printWelcomeScreen(redisClient) } // pull pass the second time fmt.Printf("\n Please enter your password again.\n>> ") sec1, _ := reader.ReadString('\n') sec1 = strings.Replace(sec1, "\n", "", -1) // make sure passwords match if sec0 == sec1 { sec = sec0 noPass = false } else { fmt.Println("Lets try that again.") } } // set user in redis secHash := sha3.Sum512([]byte(sec)) redisClient.Set(fmt.Sprintf("user:%s", username), fmt.Sprintf("%x", secHash), 0).Err() printUserScreen(redisClient, username) } } if err := scanner.Err(); err != nil { fmt.Printf("%s\n", err) } } func gethighscore(w http.ResponseWriter, r *http.Request) { // run script output, err := exec.Command(config.NethackLauncher.ReclistLocation, "-f", config.NethackLauncher.RecordLocation).CombinedOutput() if err != nil { fmt.Printf("%s\n", err) } fmt.Fprintf(w, "%s\n", output) }