/* Package door: a golang implementation of a BBS door for linux that support door32.sys. import ( "door" ) int main() { d = door.Door{} d.Init() // Process commandline switches, initialize door, detect screen size. d.Write("Welcome to my awesome door, written in "+door.ColorText("BLINK BOLD WHITE")+"golang"+door.Reset+"."+door.CRNL) d.Write("Press a key...") d.Key() d.Write(door.CRNL) } */ package door import ( "bufio" "flag" "fmt" "log" "os" "path/filepath" "strconv" "strings" "syscall" "time" ) const CRNL = "\r\n" // BBS Line Ending const Clrscr = "\x1b[0m\x1b[2J\x1b[H" // Clear screen, home cursor const HideCursor = "\x1b[?25l" // Hide Cursor const ShowCursor = "\x1b[?25h" // Show Cursor var Reset string = Color(0) // ANSI Color Reset var Unicode bool // Unicode support detected var CP437 bool // CP437 support detected var Full_CP437 bool // Full CP437 support detected (handles control codes properly) var Height int // Screen height detected var Width int // Screen width detected var Inactivity int64 = 120 // Inactivity timeout /* door32.sys: 0 Line 1 : Comm type (0=local, 1=serial, 2=telnet) 0 Line 2 : Comm or socket handle 38400 Line 3 : Baud rate Mystic 1.07 Line 4 : BBSID (software name and version) 1 Line 5 : User record position (1-based) James Coyle Line 6 : User's real name g00r00 Line 7 : User's handle/alias 255 Line 8 : User's security level 58 Line 9 : User's time left (in minutes) 1 Line 10: Emulation *See Below 1 Line 11: Current node number */ type DropfileConfig struct { Comm_type int Comm_handle int Baudrate int BBSID string User_number int Real_name string Handle string Security_level int Time_left int Emulation int Node int } type Door struct { Config DropfileConfig READFD int WRITEFD int Disconnected bool TimeOut time.Time // Fixed point in time, when time expires StartTime time.Time Pushback FIFOBuffer } // Return the amount of time left as time.Duration func (d *Door) TimeLeft() time.Duration { return d.TimeOut.Sub(time.Now()) } func (d *Door) TimeUsed() time.Duration { return time.Now().Sub(d.StartTime) } // Read the BBS door file. We only support door32.sys. func (d *Door) ReadDropfile(filename string) { file, err := os.Open(filename) if err != nil { panic(fmt.Sprintf("Open(%s): %s\n", filename, err)) // os.Exit(2) } defer file.Close() var lines []string // read line by line // The scanner handles DOS and linux file endings. scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() lines = append(lines, line) } d.Config.Comm_type, err = strconv.Atoi(lines[0]) if err != nil { log.Panicf("Door32 Comm Type (expected integer): %s\n", err) } d.Config.Comm_handle, err = strconv.Atoi(lines[1]) if err != nil { log.Panicf("Door32 Comm Handle (expected integer): %s\n", err) } d.Config.Baudrate, err = strconv.Atoi(lines[2]) if err != nil { log.Panicf("Door32 Baudrate (expected integer): %s\n", err) } d.Config.BBSID = lines[3] d.Config.User_number, err = strconv.Atoi(lines[4]) if err != nil { log.Panicf("Door32 User Number (expected integer): %s\n", err) } d.Config.Real_name = lines[5] d.Config.Handle = lines[6] d.Config.Security_level, err = strconv.Atoi(lines[7]) if err != nil { log.Panicf("Door32 Security Level (expected integer): %s\n", err) } d.Config.Time_left, err = strconv.Atoi(lines[8]) if err != nil { log.Panicf("Door32 Time Left (expected integer): %s\n", err) } d.Config.Emulation, err = strconv.Atoi(lines[9]) if err != nil { log.Panicf("Door32 Emulation (expected integer): %s\n", err) } d.Config.Node, err = strconv.Atoi(lines[10]) if err != nil { log.Panicf("Door32 Node Number (expected integer): %s\n", err) } d.READFD = d.Config.Comm_handle d.WRITEFD = d.Config.Comm_handle // Calculate the time when time expires. d.StartTime = time.Now() d.TimeOut = time.Now().Add(time.Duration(d.Config.Time_left) * time.Minute) } // Detect client terminal capabilities, Unicode, CP437, Full_CP437, // screen Height and Width. func (d *Door) detect() { d.Write("\x1b[0;30;40m\x1b[2J\x1b[H") // black on black, clrscr, go home d.Write("\x03\x04\x1b[6n") // hearts and diamonds does CP437 work? d.Write(CRNL + "\u2615\x1b[6n") // hot beverage d.Write("\x1b[999C\x1b[999B\x1b[6n" + Reset + "\x1b[2J\x1b[H") // goto end of screen + cursor pos // time.Sleep(50 * time.Millisecond) time.Sleep(250 * time.Millisecond) // read everything // telnet term isn't in RAW mode, so keys are buffer until var results string = "" for { r := d.getch() if r < 0 { // Timeout or Disconnect break } results += string(byte(r)) } if len(results) > 0 { output := strings.Replace(results, "\x1b", "^[", -1) fmt.Println("DETECT:", output) } else { // local telnet echos the reply :() fmt.Println("DETECT: Nothing received.") return } if ((strings.Index(results, "1;1R") != -1) || (strings.Index(results, "1;3R") != -1)) && ((strings.Index(results, "2:2R") != -1) || (strings.Index(results, "2;3R") != -1)) { Unicode = true } else { Unicode = false CP437 = true } if strings.Index(results, "1;3R") != -1 { Full_CP437 = true } // get screen size var err error pos := strings.LastIndex(results, "\x1b") if pos != -1 { pos++ if results[pos] == '[' { pos++ results = results[pos:] pos = strings.Index(results, ";") if pos != -1 { height := results[:pos] Height, err = strconv.Atoi(height) pos++ results = results[pos:] pos = strings.Index(results, "R") if pos != -1 { width := results[:pos] Width, err = strconv.Atoi(width) fmt.Printf("Width: %s, %d, %v\n", results, Width, err) } } else { Height = 0 Width = 0 } } } fmt.Printf("Unicode %v Screen: %d X %d\n", Unicode, Width, Height) } // Initialize door framework. Parse commandline, read dropfile, // detect terminal capabilities. func (d *Door) Init(doorname string) { var dropfile string d.Pushback = NewFIFOBuffer(5) // Get path to binary, and chdir to it. binaryPath, _ := os.Executable() binaryPath = filepath.Dir(binaryPath) _ = os.Chdir(binaryPath) flag.StringVar(&dropfile, "d", "", "Path to dropfile") flag.Parse() if len(dropfile) == 0 { flag.PrintDefaults() os.Exit(2) } // fmt.Printf("Loading: %s\n", dropfile) d.ReadDropfile(dropfile) // doorname - node #? logfilename := fmt.Sprintf("%s-%d.log", doorname, d.Config.Node) logf, err := os.OpenFile(logfilename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) if err != nil { log.Panicf("Error creating log file %s: %v", logfilename, err) } log.SetOutput(logf) log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) log.Printf("Loading dropfile %s\n", dropfile) log.Printf("BBS %s, User %s / Handle %s / File %d\n", d.Config.BBSID, d.Config.Real_name, d.Config.Handle, d.Config.Comm_handle) // Init Windows Reader Channel readerChannel = make(chan byte) go Reader(syscall.Handle(d.Config.Comm_handle)) d.detect() if Unicode { BOXES = BOXES_UNICODE BARS = BARS_UNICODE } else { BOXES = BOXES_CP437 BARS = BARS_CP437 } } func Goto(x int, y int) string { return fmt.Sprintf("\x1b[%d;%dH", y, x) }