/* 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" "os" "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 type LIFOBuffer struct { data []int index int } func (b *LIFOBuffer) Empty() bool { return b.index == 0 } func (b *LIFOBuffer) Push(value int) { if b.index+1 > len(b.data) { b.data = append(b.data, value) b.index++ } else { b.data[b.index] = value b.index++ } } func (b *LIFOBuffer) Pop() int { if b.index == 0 { panic("Attempting to Pop from empty LIFOBuffer.") } b.index-- return b.data[b.index] } /* 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 LIFOBuffer } // 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]) d.Config.Comm_handle, err = strconv.Atoi(lines[1]) d.Config.Baudrate, err = strconv.Atoi(lines[2]) d.Config.BBSID = lines[3] d.Config.User_number, err = strconv.Atoi(lines[4]) d.Config.Real_name = lines[5] d.Config.Handle = lines[6] d.Config.Security_level, err = strconv.Atoi(lines[7]) d.Config.Time_left, err = strconv.Atoi(lines[8]) d.Config.Emulation, err = strconv.Atoi(lines[9]) d.Config.Node, err = strconv.Atoi(lines[10]) 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 == -1 { 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() { var dropfile string 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) fmt.Printf("BBS %s, User %s / Handle %s / File %d\n", d.Config.BBSID, d.Config.Real_name, d.Config.Handle, d.Config.Comm_handle) 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) }