Przeglądaj źródła

Added door.sys, Putty HOME/END.

With tests (door.sys/Putty's HOME/END).
Move dropfile & tests to own file.
Steve Thielemann 2 lat temu
rodzic
commit
6fb8b4b7cc
7 zmienionych plików z 529 dodań i 222 usunięć
  1. 5 108
      door/door.go
  2. 5 0
      door/door_linux.go
  3. 23 108
      door/door_test.go
  4. 228 0
      door/dropfile.go
  5. 262 0
      door/dropfile_test.go
  6. 3 6
      door/menu_test.go
  7. 3 0
      door/tdfont.go

+ 5 - 108
door/door.go

@@ -20,14 +20,12 @@ detects screen size, and supports TheDraw Fonts.
 package door
 
 import (
-	"bufio"
 	"flag"
 	"fmt"
 	"log"
 	"os"
 	"path/filepath"
 	"runtime/debug"
-	"strconv"
 	"sync"
 	"time"
 
@@ -88,37 +86,6 @@ const (
 	AnyEvent MouseMode = 1003
 )
 
-/*
-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
-*/
-
-// Door32 information
-type DropfileConfig struct {
-	Comm_type      int    // Comm type (0 local, 2 telnet "linux fd")
-	Comm_handle    int    // Handle to use to talk to the user
-	Baudrate       int    // (not used)
-	BBSID          string // BBS Software name
-	User_number    int    // User number
-	Real_name      string // User's Real Name
-	Handle         string // User's Handle/Nick
-	Security_level int    // Security Level (if given)
-	Time_left      int    // Time Left (minutes)
-	Emulation      int    // (not used)
-	Node           int    // BBS Node number
-}
-
 type ReaderData struct {
 	R   rune
 	Ex  Extended
@@ -260,81 +227,6 @@ func (d *Door) Disconnect() bool {
 	return d.Disconnected // atomic.LoadInt32(&d.Disconnected) != 0
 }
 
-// Read the BBS door file.  We only support door32.sys.
-func (d *Door) ReadDropfile(filename string) {
-	var file *os.File
-	var err error
-
-	file, err = os.Open(filename)
-	if err != nil {
-		log.Panicf("Open(%s): %s\n", filename, err)
-	}
-
-	defer file.Close()
-
-	var lines []string
-	// read line by line
-	// The scanner handles DOS and linux file endings.
-
-	var scanner *bufio.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)
-	}
-
-	if d.Config.Comm_type == 0 {
-		d.READFD = 0
-		d.WRITEFD = 1
-		// RAW MODE
-		d.tio_default, _ = term.MakeRaw(d.READFD)
-	} else if d.Config.Comm_type == 2 {
-		d.READFD = d.Config.Comm_handle
-		d.WRITEFD = d.Config.Comm_handle
-	} else {
-		log.Panicf("Unsupported Comm type %d\n", d.Config.Comm_type)
-	}
-
-	d.StartTime = time.Now()
-	// Calculate when time expires.
-	d.TimeOut = time.Now().Add(time.Duration(d.Config.Time_left) * time.Minute)
-}
-
 func (d *Door) Detect() bool {
 	// detect is destructive ... make it non-destructive
 	// destructive: clears/trashes the screen.
@@ -449,6 +341,11 @@ func (d *Door) Init(doorname string) {
 
 	d.ReadDropfile(dropfile)
 
+	if d.Config.Comm_type == 0 {
+		// RAW MODE
+		d.tio_default, _ = term.MakeRaw(d.READFD)
+	}
+
 	// Logfile will be doorname - node #
 	var logfilename string = fmt.Sprintf("%s-%d.log", doorname, d.Config.Node)
 	var logf *os.File

+ 5 - 0
door/door_linux.go

@@ -1,5 +1,9 @@
 package door
 
+import (
+    "time"
+)
+
 func (d *Door) setupChannels() {
 	if d.ReaderCanClose {
 		// Yes, expect Reader and Writer to stop
@@ -10,5 +14,6 @@ func (d *Door) setupChannels() {
 	}
 
 	go Reader(d)
+    time.Sleep(time.Millisecond)
 	go Writer(d)
 }

+ 23 - 108
door/door_test.go

@@ -36,20 +36,8 @@ func TestGoto(t *testing.T) {
 	}
 }
 
-func TestReadDropfileFail(t *testing.T) {
-	d := Door{}
-
-	defer func() {
-		if r := recover(); r == nil {
-			t.Error("ReadDropfile did not panic on missing dropfile.")
-		}
-	}()
-
-	d.ReadDropfile("This_File_Will_Not_Be_Found")
-}
-
 func TestLogfileFailure(t *testing.T) {
-	tmpFile, err := os.CreateTemp("", "test-*")
+	tmpFile, err := os.CreateTemp("", "test-door32.sys-*")
 	if err != nil {
 		panic("Cannot create temporary file")
 	}
@@ -68,11 +56,9 @@ func TestLogfileFailure(t *testing.T) {
 	var fd int = 0
 
 	// Create door32.sys file
-	dfc := DropfileConfig{2, fd, 1800, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 0, 13}
+	dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 13}
 
-	_, err = tmpFile.WriteString(fmt.Sprintf("%d\n%d\n%d\n%s\n%d\n%s\n%s\n%d\n%d\n%d\n%d\n",
-		dfc.Comm_type, dfc.Comm_handle, dfc.Baudrate, dfc.BBSID, dfc.User_number, dfc.Real_name, dfc.Handle,
-		dfc.Security_level, dfc.Time_left, dfc.Emulation, dfc.Node))
+	err = CreateDoor32File(&dfc, tmpFile)
 	if err != nil {
 		t.Error("tmpFile.WriteString:", err)
 	}
@@ -88,88 +74,20 @@ func TestLogfileFailure(t *testing.T) {
 	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
 	// preset commandline args so door can init
 	os.Args = []string{"door", "-d", tmpFile.Name()}
-	d.Init("/badname-test")
 
-}
+	uid := os.Getuid()
 
-func TestReadDropFile(t *testing.T) {
-	tmpFile, err := os.CreateTemp("", "test-*")
-	if err != nil {
-		panic("Cannot create temporary file")
-	}
-
-	// Clean up the dropfile afterwards
-	defer os.Remove(tmpFile.Name())
-
-	dfc := DropfileConfig{2, 20, 1800, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 0, 12}
-
-	_, err = tmpFile.WriteString(fmt.Sprintf("%d\n%d\n%d\n%s\n%d\n%s\n%s\n%d\n%d\n%d\n%d\n",
-		dfc.Comm_type, dfc.Comm_handle, dfc.Baudrate, dfc.BBSID, dfc.User_number, dfc.Real_name, dfc.Handle,
-		dfc.Security_level, dfc.Time_left, dfc.Emulation, dfc.Node))
-	if err != nil {
-		t.Error("tmpFile.WriteString:", err)
-	}
-	err = tmpFile.Close()
-	if err != nil {
-		t.Error("tmpFile.Close:", err)
-	}
-
-	d := Door{}
-	d.ReadDropfile(tmpFile.Name())
-
-	if dfc.Comm_type != d.Config.Comm_type {
-		t.Errorf("Comm Type expected %#v, got %#v", dfc.Comm_type, d.Config.Comm_type)
-	}
-	if dfc.Comm_handle != d.Config.Comm_handle {
-		t.Errorf("Comm Handle expected %#v, got %#v", dfc.Comm_handle, d.Config.Comm_handle)
-	}
-	if dfc.BBSID != d.Config.BBSID {
-		t.Errorf("BBSID expected %#v, got %#v", dfc.BBSID, d.Config.BBSID)
-	}
-	if dfc.User_number != d.Config.User_number {
-		t.Errorf("User Number expected %#v, got %#v", dfc.User_number, d.Config.User_number)
-	}
-	if dfc.Real_name != d.Config.Real_name {
-		t.Errorf("Real Name expected %#v, got %#v", dfc.Real_name, d.Config.Real_name)
-	}
-	if dfc.Handle != d.Config.Handle {
-		t.Errorf("Handle expected %#v, got %#v", dfc.Handle, d.Config.Handle)
-	}
-	if dfc.Time_left != d.Config.Time_left {
-		t.Errorf("Time Left expected %#v, got %#v", dfc.Time_left, d.Config.Time_left)
-	}
-	if dfc.Node != d.Config.Node {
-		t.Errorf("Node expected %#v, got %#v", dfc.Node, d.Config.Node)
-	}
-	start := time.Now()
-	timeout := time.Now().Add(time.Duration(dfc.Time_left) * time.Minute)
-
-	// Verify the start time and timeout values have been set correctly.
-	startDelta := start.Sub(d.StartTime)
-	timeoutDelta := timeout.Sub(d.TimeOut)
-
-	left := d.TimeLeft()
-	used := d.TimeUsed()
-
-	if used.Seconds() > 1 {
-		t.Errorf("Time Used (from door) > 1 second: %#v", used)
-	}
-
-	time_left_seconds := dfc.Time_left * 60
-
-	if time_left_seconds-int(left.Seconds()) > 1 {
-		t.Errorf("Time Left differences > 1 second: test %#v door %#v", time_left_seconds, left)
-	}
-	if startDelta.Seconds() > 1 {
-		t.Errorf("Start Time differences: test %#v door %#v delta %#v", start, d.StartTime, startDelta)
-	}
-	if timeoutDelta.Seconds() > 1 {
-		t.Errorf("TimeOut differences: test %#v door %#v delta %#v", timeout, d.TimeOut, timeoutDelta)
+	if uid == 0 {
+		// Running as root.  Use invalid path here.
+		d.Init("/invalid-path/badpath-test")
+	} else {
+		// uid == -1 (windows), this should also fail there.
+		d.Init("/badname-test")
 	}
 }
 
 func TestDetectFail(t *testing.T) {
-	tmpFile, err := os.CreateTemp("", "test-*")
+	tmpFile, err := os.CreateTemp("", "test-door32.sys-*")
 	if err != nil {
 		panic("Cannot create temporary file")
 	}
@@ -190,11 +108,9 @@ func TestDetectFail(t *testing.T) {
 	defer close_fd(fd)
 
 	// Create door32.sys file
-	dfc := DropfileConfig{2, fd, 1800, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 0, 13}
+	dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 13}
 
-	_, err = tmpFile.WriteString(fmt.Sprintf("%d\n%d\n%d\n%s\n%d\n%s\n%s\n%d\n%d\n%d\n%d\n",
-		dfc.Comm_type, dfc.Comm_handle, dfc.Baudrate, dfc.BBSID, dfc.User_number, dfc.Real_name, dfc.Handle,
-		dfc.Security_level, dfc.Time_left, dfc.Emulation, dfc.Node))
+	err = CreateDoor32File(&dfc, tmpFile)
 	if err != nil {
 		t.Error("tmpFile.WriteString:", err)
 	}
@@ -281,16 +197,19 @@ func InputTests(t *testing.T, server net.Conn, d *Door, mode string) {
 		"\x00\x3b\x00\x3c\x00\x3d\x00\x3e": []Extended{F1, F2, F3, F4},
 		"\x00\x3f\x00\x40\x00\x41\x00\x42": []Extended{F5, F6, F7, F8},
 		"\x00\x43\x00\x44\x00\x52\x00\x53": []Extended{F9, F10, INSERT, DELETE},
+		"\x00\x45\x00\x46":                 []Extended{F11, F12},
 		"\x1b[A\x1b[B\x1b[C\x1b[D":         []Extended{UP_ARROW, DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW},
 		"\x1b[H\x1b[F\x1b[K\x1b[V\x1b[U":   []Extended{HOME, END, END, PAGE_UP, PAGE_DOWN},
 		"\x1b[5~\x1b[6~":                   []Extended{PAGE_UP, PAGE_DOWN},
 		"\x1b[@\x1b[2~\x1b[3~":             []Extended{INSERT, INSERT, DELETE},
+		"\x1b[1~\x1b[4~":                   []Extended{HOME, END},
 		"\x1bOP\x1bOQ\x1bOR\x1bOS":         []Extended{F1, F2, F3, F4},
 		"\x1b[15~\x1b[17~\x1b[18~\x1b[19~": []Extended{F5, F6, F7, F8},
 		"\x1b[20~\x1b[21~\x1b[23~\x1b[24~": []Extended{F9, F10, F11, F12},
 	}
 
-	keyWait := time.Duration(50 * time.Millisecond)
+	// 50ms causes 3 tries (internal is 200ms, use 210ms).
+	keyWait := time.Duration(210 * time.Millisecond)
 
 	// Verify input is empty
 	for {
@@ -564,7 +483,7 @@ func TestDoorCP437(t *testing.T) {
 	var tmpFile *os.File
 	var err error
 	// Dropfile
-	tmpFile, err = os.CreateTemp("", "test-*")
+	tmpFile, err = os.CreateTemp("", "test-door32.sys-*")
 	if err != nil {
 		panic("Cannot create temporary file")
 	}
@@ -591,11 +510,9 @@ func TestDoorCP437(t *testing.T) {
 
 	// Create door32.sys file
 	var node int = 10
-	dfc := DropfileConfig{2, fd, 1800, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 0, node}
+	dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, node}
 
-	_, err = tmpFile.WriteString(fmt.Sprintf("%d\n%d\n%d\n%s\n%d\n%s\n%s\n%d\n%d\n%d\n%d\n",
-		dfc.Comm_type, dfc.Comm_handle, dfc.Baudrate, dfc.BBSID, dfc.User_number, dfc.Real_name, dfc.Handle,
-		dfc.Security_level, dfc.Time_left, dfc.Emulation, dfc.Node))
+	err = CreateDoor32File(&dfc, tmpFile)
 	if err != nil {
 		t.Error("tmpFile.WriteString:", err)
 	}
@@ -703,7 +620,7 @@ func TestDoorUnicode(t *testing.T) {
 	var err error
 
 	// Dropfile
-	tmpFile, err = os.CreateTemp("", "test-*")
+	tmpFile, err = os.CreateTemp("", "test-door32.sys-*")
 	if err != nil {
 		panic("Cannot create temporary file")
 	}
@@ -730,11 +647,9 @@ func TestDoorUnicode(t *testing.T) {
 
 	// Create door32.sys file
 	var node int = 11
-	dfc := DropfileConfig{2, fd, 1800, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 0, node}
+	dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, node}
 
-	_, err = tmpFile.WriteString(fmt.Sprintf("%d\n%d\n%d\n%s\n%d\n%s\n%s\n%d\n%d\n%d\n%d\n",
-		dfc.Comm_type, dfc.Comm_handle, dfc.Baudrate, dfc.BBSID, dfc.User_number, dfc.Real_name, dfc.Handle,
-		dfc.Security_level, dfc.Time_left, dfc.Emulation, dfc.Node))
+	err = CreateDoor32File(&dfc, tmpFile)
 	if err != nil {
 		t.Error("tmpFile.WriteString:", err)
 	}

+ 228 - 0
door/dropfile.go

@@ -0,0 +1,228 @@
+package door
+
+import (
+	"bufio"
+	"log"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+)
+
+/*
+https://github.com/NuSkooler/ansi-bbs/blob/master/docs/dropfile_formats/door32_sys.txt
+
+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
+
+Emulation:
+ 0 = Ascii
+ 1 = Ansi
+ 2 = Avatar
+ 3 = RIP
+ 4 = Max Graphics
+
+http://wiki.synchro.net/ref:door.sys
+
+door.sys:
+
+Line #	Example 		Description 	Comment
+1 		COM1: 			Comm Port 		COM0: = LOCAL MODE
+2 		2400 			Baud (DCE) Rate	300 to 38400
+3 		8 				Parity 			7 or 8
+4 		1 				Node Number 	1 to 99
+5 		19200 			DTE Rate 		Was Y/N (LOCKED at 19200)
+6 		Y 				Screen Display 	Y=On N=Off
+7 		Y 				Printer Toggle 	Y=On N=Off
+8 		Y 				Page Bell 		Y=On N=Off
+9 		Y 				Caller Alarm 	Y=On N=Off
+10 		Rick Greer 		User Full Name
+11 		Lewisville, Tx. Calling From
+12 		214 221-7814 	Home Phone
+13 		214 221-7814 	Work/Data Phone
+14 		PASSWORD 		Password
+15 		110 			Security Level
+16 		1456 			Total Times On 	0 through 32767
+17 		03/14/88 		Last Date Called 	mm/dd/yy
+18 		7560 			Seconds Remaining 	THIS call
+19 		126 			Minutes Remaining 	THIS call
+20 		GR 				Graphics Mode 	GR=Graph, NG=Non-Graph, 7E=7,E Caller
+21 		23 				Page Length 	rows of text
+22 		Y 				User Mode 	Y = Expert, N = Novice
+23 		1234567 		Conferences/Forums Registered In 	ABCDEFG
+24 		7 				Conference Exited To DOOR From 	G
+25 		01/01/99 		User Expiration Date 	mm/dd/yy
+26 		1 				User File's Record Number 	1+
+27 		Y 				Default Protocol 	X, C, Y, G, I, N, Etc.
+28 		0 				Total Uploads
+29 		0 				Total Downloads
+30 		0 				Daily Download “K” Total
+31 		999999 			Daily Download Max. “K” Limit
+32 		10/22/88 		Caller's Birthdate 	mm/dd/yy
+33 		G:\GAP\MAIN 	Path to the MAIN directory 	where user file is
+34 		G:\GAP\GEN 		Path to the GEN directory
+35 		Michael 		Sysop's Name 	name BBS refers to Sysop as
+36 		Stud 			Alias name 	user's alias or handle
+37 		00:05 			Event time 	hh:mm
+38 		Y 				Error correcting connection 	Y/N
+39 		N 				ANSI supported & caller using NG mode 	Y/N
+40 		Y 				Use Record Locking 	Y/N
+41 		14 				BBS Default Color 	CGA color code (1-15)
+42 		10 				Time Credits in Minutes 	-32768 through 32767
+43 		07/07/90 		Last New File Scan Date 	mm/dd/yy
+44 		14:32 			Time of This Call 	hh:mm
+45 		07:30 			Time of Last Call 	hh:mm
+46 		6 				Maximum daily files available
+47 		3 				Files d/led so far today
+48 		23456 			Total “K” Bytes Uploaded
+49 		76329 			Total “K” Bytes Downloaded
+50 		A File Sucker 	User Comment
+51 		10 				Total Doors Opened
+52 		10283 			Total Messages Left 	0 through 32767
+*/
+
+// DropFile Information
+type DropfileConfig struct {
+	Comm_type      int    // Comm type (0 local, 2 telnet "linux fd")
+	Comm_handle    int    // Handle to use to talk to the user
+	BBSID          string // BBS Software name
+	User_number    int    // User number
+	Real_name      string // User's Real Name
+	Handle         string // User's Handle/Nick
+	Security_level int    // Security Level (if given)
+	Time_left      int    // Time Left (minutes)
+	Node           int    // BBS Node number
+}
+
+// Read the BBS door file.  We only support door32.sys.
+func (d *Door) ReadDropfile(filename string) {
+	var file *os.File
+	var err error
+	var base string = strings.ToLower(path.Base(filename))
+	var isDoor32, isDoorSys bool
+
+	if strings.Contains(base, "door.sys") {
+		isDoorSys = true
+	} else if strings.Contains(base, "door32.sys") {
+		isDoor32 = true
+	} else {
+		log.Panicf("Unknown dropfile format: %s\n", base)
+	}
+
+	file, err = os.Open(filename)
+	if err != nil {
+		log.Panicf("Open(%s): %s\n", filename, err)
+	}
+
+	defer file.Close()
+
+	var lines []string
+	// read line by line
+	// The scanner handles DOS and linux file endings.
+
+	var scanner *bufio.Scanner = bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		lines = append(lines, line)
+	}
+
+	if isDoor32 {
+		// door32.sys:
+		// 0                            Line 1 : Comm type (0=local, 1=serial, 2=telnet)
+		// 0                            Line 2 : Comm or socket handle
+		// 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 11: Current node number
+
+		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.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.Node, err = strconv.Atoi(lines[10])
+		if err != nil {
+			log.Panicf("Door32 Node Number (expected integer): %s\n", err)
+		}
+	} else if isDoorSys {
+		// Door.sys:
+		// Line 1:	COM1:			Comm Port, COM0: = LOCAL MODE
+		// Line 4:	1 				Node Number
+		// Line 10:	Rick Greer 		User Full Name
+		// Line 11:	Lewisville, Tx.	Calling From
+		// Line 15:	110				Security Level
+		// Line 18:	7560			Seconds Remaining
+		// Line 19:	126				Minutes Remaining
+		// Line 26:	1				User File's Record Number
+		// Line 35:	Michael			Sysop's Name
+		// Line 36:	Stud			Alias name
+		if lines[0] == "COM0:" {
+			d.Config.Comm_type = 0
+		}
+		d.Config.Node, err = strconv.Atoi(lines[3])
+		if err != nil {
+			log.Panicf("Door.sys Node Number (expected integer): %s\n", err)
+		}
+		d.Config.Real_name = lines[9]
+		d.Config.User_number, err = strconv.Atoi(lines[25])
+		if err != nil {
+			log.Panicf("Door.sys User Number (expected integer): %s\n", err)
+		}
+		d.Config.Handle = lines[35]
+		d.Config.Security_level, err = strconv.Atoi(lines[14])
+		if err != nil {
+			log.Panicf("Door.sys Security Level (expected integer): %s\n", err)
+		}
+		d.Config.Time_left, err = strconv.Atoi(lines[18])
+		if err != nil {
+			log.Panicf("Door.sys Time Left (expected integer): %s\n", err)
+		}
+	}
+
+	if d.Config.Comm_type == 0 {
+		d.READFD = 0
+		d.WRITEFD = 1
+	} else if d.Config.Comm_type == 2 {
+		d.READFD = d.Config.Comm_handle
+		d.WRITEFD = d.Config.Comm_handle
+	} else {
+		log.Panicf("Unsupported Comm type %d\n", d.Config.Comm_type)
+	}
+
+	d.StartTime = time.Now()
+	// Calculate when time expires.
+	d.TimeOut = time.Now().Add(time.Duration(d.Config.Time_left) * time.Minute)
+}

+ 262 - 0
door/dropfile_test.go

@@ -0,0 +1,262 @@
+package door
+
+import (
+	"os"
+	"strconv"
+	"testing"
+	"time"
+)
+
+// Create a valid door32.sys file from the config
+func CreateDoor32File(config *DropfileConfig, file *os.File) error {
+	var err error
+	// These are tests, but let's do it right!
+	var lines [11]string = [11]string{"0",
+		"0", "38400", "", "1", "<User Real Name>",
+		"<Handle>", "0", "0", "1", "1"}
+
+	lines[0] = strconv.Itoa(config.Comm_type)
+	lines[1] = strconv.Itoa(config.Comm_handle)
+	lines[3] = config.BBSID
+	lines[4] = strconv.Itoa(config.User_number)
+	lines[5] = config.Real_name
+	lines[6] = config.Handle
+	lines[7] = strconv.Itoa(config.Security_level)
+	lines[8] = strconv.Itoa(config.Time_left)
+	lines[10] = strconv.Itoa(config.Node)
+
+	// Write out the lines
+	for _, line := range lines {
+		_, err = file.WriteString(line + "\n")
+		if err != nil {
+			return err
+		}
+	}
+	return err
+}
+
+// Create a valid door.sys file from the config
+func CreateDoorSysFile(config *DropfileConfig, file *os.File) error {
+	var err error
+	// These are tests, but let's do this right.
+	var lines [52]string = [52]string{"0", "38400", "8", "0", "57600",
+		"Y", "Y", "Y", "Y", "<User Real Name>", "<User From>",
+		"XXX XXX-XXXX", "XXX XXX-XXXX", "PASSWORD", "0", "0",
+		"01/01/23", "0", "0", "GR", "23", "Y", "123ABC", "0",
+		"12/31/99", "0", "Z", "0", "0", "0", "0", "04/01/23",
+		"C:\\MAIN", "C:\\GEN", "<SysOp Name>", "<Handle>", "00:00",
+		"Y", "Y", "Y", "0", "1", "01/01/23", "00:00", "23:59",
+		"0", "0", "0", "0", "<Comment>", "0", "0"}
+
+	if config.Comm_type == 0 {
+		lines[0] = "COM0:"
+	} else {
+		lines[0] = "COM1:"
+	}
+
+	lines[3] = strconv.Itoa(config.Node)
+	lines[9] = config.Real_name
+	lines[14] = strconv.Itoa(config.Security_level)
+	lines[17] = strconv.Itoa(config.Time_left * 60)
+	lines[18] = strconv.Itoa(config.Time_left)
+	lines[25] = strconv.Itoa(config.User_number)
+	lines[35] = config.Handle
+
+	// Write out the lines
+	for _, line := range lines {
+		_, err = file.WriteString(line + "\n")
+		if err != nil {
+			return err
+		}
+	}
+
+	return err
+}
+
+func TestInvalidDropfileFail(t *testing.T) {
+	d := Door{}
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Error("ReadDropfile did not panic on missing dropfile.")
+		}
+	}()
+
+	d.ReadDropfile("dropfile_unknown.sys")
+}
+
+func TestMissingDropfileFail(t *testing.T) {
+	d := Door{}
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Error("ReadDropfile did not panic on missing dropfile.")
+		}
+	}()
+
+	d.ReadDropfile("MissingDropFile_door.sys")
+}
+
+func TestReadDoor32DropFile(t *testing.T) {
+	tmpFile, err := os.CreateTemp("", "test-door32.sys-*")
+	if err != nil {
+		panic("Cannot create temporary file")
+	}
+
+	// Clean up the dropfile afterwards
+	defer os.Remove(tmpFile.Name())
+
+	dfc := DropfileConfig{2, 20, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 12}
+
+	err = CreateDoor32File(&dfc, tmpFile)
+	if err != nil {
+		t.Error("tmpFile.WriteString:", err)
+	}
+	err = tmpFile.Close()
+	if err != nil {
+		t.Error("tmpFile.Close:", err)
+	}
+
+	/*
+		// Are my testing dropfiles correct?
+		tmpFile, err = os.Create("door32.sys")
+		CreateDoor32File(&dfc, tmpFile)
+		tmpFile.Close()
+	*/
+
+	d := Door{}
+	d.ReadDropfile(tmpFile.Name())
+
+	if dfc.Comm_type != d.Config.Comm_type {
+		t.Errorf("Comm Type expected %#v, got %#v", dfc.Comm_type, d.Config.Comm_type)
+	}
+	if dfc.Comm_handle != d.Config.Comm_handle {
+		t.Errorf("Comm Handle expected %#v, got %#v", dfc.Comm_handle, d.Config.Comm_handle)
+	}
+	if dfc.BBSID != d.Config.BBSID {
+		t.Errorf("BBSID expected %#v, got %#v", dfc.BBSID, d.Config.BBSID)
+	}
+	if dfc.User_number != d.Config.User_number {
+		t.Errorf("User Number expected %#v, got %#v", dfc.User_number, d.Config.User_number)
+	}
+	if dfc.Real_name != d.Config.Real_name {
+		t.Errorf("Real Name expected %#v, got %#v", dfc.Real_name, d.Config.Real_name)
+	}
+	if dfc.Handle != d.Config.Handle {
+		t.Errorf("Handle expected %#v, got %#v", dfc.Handle, d.Config.Handle)
+	}
+	if dfc.Time_left != d.Config.Time_left {
+		t.Errorf("Time Left expected %#v, got %#v", dfc.Time_left, d.Config.Time_left)
+	}
+	if dfc.Node != d.Config.Node {
+		t.Errorf("Node expected %#v, got %#v", dfc.Node, d.Config.Node)
+	}
+	start := time.Now()
+	timeout := time.Now().Add(time.Duration(dfc.Time_left) * time.Minute)
+
+	// Verify the start time and timeout values have been set correctly.
+	startDelta := start.Sub(d.StartTime)
+	timeoutDelta := timeout.Sub(d.TimeOut)
+
+	left := d.TimeLeft()
+	used := d.TimeUsed()
+
+	if used.Seconds() > 1 {
+		t.Errorf("Time Used (from door) > 1 second: %#v", used)
+	}
+
+	time_left_seconds := dfc.Time_left * 60
+
+	if time_left_seconds-int(left.Seconds()) > 1 {
+		t.Errorf("Time Left differences > 1 second: test %#v door %#v", time_left_seconds, left)
+	}
+	if startDelta.Seconds() > 1 {
+		t.Errorf("Start Time differences: test %#v door %#v delta %#v", start, d.StartTime, startDelta)
+	}
+	if timeoutDelta.Seconds() > 1 {
+		t.Errorf("TimeOut differences: test %#v door %#v delta %#v", timeout, d.TimeOut, timeoutDelta)
+	}
+}
+
+func TestReadDoorSysDropFile(t *testing.T) {
+	tmpFile, err := os.CreateTemp("", "test-door.sys-*")
+	if err != nil {
+		panic("Cannot create temporary file")
+	}
+
+	// Clean up the dropfile afterwards
+	defer os.Remove(tmpFile.Name())
+
+	dfc := DropfileConfig{2, 20, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 12}
+
+	err = CreateDoorSysFile(&dfc, tmpFile)
+	if err != nil {
+		t.Error("tmpFile.WriteString:", err)
+	}
+	err = tmpFile.Close()
+	if err != nil {
+		t.Error("tmpFile.Close:", err)
+	}
+
+	/*
+		// Are my testing dropfiles correct?
+		tmpFile, err = os.Create("door.sys")
+		CreateDoorSysFile(&dfc, tmpFile)
+		tmpFile.Close()
+	*/
+
+	d := Door{}
+	d.ReadDropfile(tmpFile.Name())
+
+	/*
+		if dfc.Comm_type != d.Config.Comm_type {
+			t.Errorf("Comm Type expected %#v, got %#v", dfc.Comm_type, d.Config.Comm_type)
+		}
+		if dfc.Comm_handle != d.Config.Comm_handle {
+			t.Errorf("Comm Handle expected %#v, got %#v", dfc.Comm_handle, d.Config.Comm_handle)
+		}
+		if dfc.BBSID != d.Config.BBSID {
+			t.Errorf("BBSID expected %#v, got %#v", dfc.BBSID, d.Config.BBSID)
+		}
+	*/
+	if dfc.User_number != d.Config.User_number {
+		t.Errorf("User Number expected %#v, got %#v", dfc.User_number, d.Config.User_number)
+	}
+	if dfc.Real_name != d.Config.Real_name {
+		t.Errorf("Real Name expected %#v, got %#v", dfc.Real_name, d.Config.Real_name)
+	}
+	if dfc.Handle != d.Config.Handle {
+		t.Errorf("Handle expected %#v, got %#v", dfc.Handle, d.Config.Handle)
+	}
+	if dfc.Time_left != d.Config.Time_left {
+		t.Errorf("Time Left expected %#v, got %#v", dfc.Time_left, d.Config.Time_left)
+	}
+	if dfc.Node != d.Config.Node {
+		t.Errorf("Node expected %#v, got %#v", dfc.Node, d.Config.Node)
+	}
+	start := time.Now()
+	timeout := time.Now().Add(time.Duration(dfc.Time_left) * time.Minute)
+
+	// Verify the start time and timeout values have been set correctly.
+	startDelta := start.Sub(d.StartTime)
+	timeoutDelta := timeout.Sub(d.TimeOut)
+
+	left := d.TimeLeft()
+	used := d.TimeUsed()
+
+	if used.Seconds() > 1 {
+		t.Errorf("Time Used (from door) > 1 second: %#v", used)
+	}
+
+	time_left_seconds := dfc.Time_left * 60
+
+	if time_left_seconds-int(left.Seconds()) > 1 {
+		t.Errorf("Time Left differences > 1 second: test %#v door %#v", time_left_seconds, left)
+	}
+	if startDelta.Seconds() > 1 {
+		t.Errorf("Start Time differences: test %#v door %#v delta %#v", start, d.StartTime, startDelta)
+	}
+	if timeoutDelta.Seconds() > 1 {
+		t.Errorf("TimeOut differences: test %#v door %#v delta %#v", timeout, d.TimeOut, timeoutDelta)
+	}
+}

+ 3 - 6
door/menu_test.go

@@ -50,7 +50,7 @@ func TestMenuSize(t *testing.T) {
 func TestMenuConnection(t *testing.T) {
 	var tmpFile *os.File
 	var err error
-	tmpFile, err = os.CreateTemp("", "test-*")
+	tmpFile, err = os.CreateTemp("", "test-door32.sys-*")
 	if err != nil {
 		panic("Cannot create temporary file")
 	}
@@ -80,11 +80,8 @@ func TestMenuConnection(t *testing.T) {
 
 	// Create door32.sys file
 	var node int = 8
-	var dfc DropfileConfig = DropfileConfig{2, fd, 1800, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 0, node}
-
-	_, err = tmpFile.WriteString(fmt.Sprintf("%d\n%d\n%d\n%s\n%d\n%s\n%s\n%d\n%d\n%d\n%d\n",
-		dfc.Comm_type, dfc.Comm_handle, dfc.Baudrate, dfc.BBSID, dfc.User_number, dfc.Real_name, dfc.Handle,
-		dfc.Security_level, dfc.Time_left, dfc.Emulation, dfc.Node))
+	var dfc DropfileConfig = DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, node}
+	err = CreateDoor32File(&dfc, tmpFile)
 	if err != nil {
 		t.Error("tmpFile.WriteString:", err)
 	}

+ 3 - 0
door/tdfont.go

@@ -7,6 +7,9 @@ import (
 	"strings"
 )
 
+// Not possible to do, without help from:
+// http://www.roysac.com/blog/2014/04/thedraw-fonts-file-tdf-specifications/
+
 type BlockFont struct {
 	Characters []int
 	Data       [][][]byte