door.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. package door
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "syscall"
  10. "time"
  11. )
  12. const CRNL = "\r\n"
  13. /*
  14. door32.sys:
  15. 0 Line 1 : Comm type (0=local, 1=serial, 2=telnet)
  16. 0 Line 2 : Comm or socket handle
  17. 38400 Line 3 : Baud rate
  18. Mystic 1.07 Line 4 : BBSID (software name and version)
  19. 1 Line 5 : User record position (1-based)
  20. James Coyle Line 6 : User's real name
  21. g00r00 Line 7 : User's handle/alias
  22. 255 Line 8 : User's security level
  23. 58 Line 9 : User's time left (in minutes)
  24. 1 Line 10: Emulation *See Below
  25. 1 Line 11: Current node number
  26. */
  27. var Reset string = Color(0)
  28. type DropfileConfig struct {
  29. comm_type int
  30. comm_handle int
  31. baudrate int
  32. BBSID string
  33. user_number int
  34. real_name string
  35. handle string
  36. security_level int
  37. time_left int
  38. emulation int
  39. node_number int
  40. }
  41. type Door struct {
  42. config DropfileConfig
  43. READFD int
  44. WRITEFD int
  45. TimeOut time.Time // Fixed point in time, when time expires
  46. }
  47. // Return the amount of time left as time.Duration
  48. func (d *Door) TimeLeft() time.Duration {
  49. return d.TimeOut.Sub(time.Now())
  50. }
  51. // Read the BBS door file. We only support door32.sys.
  52. func (d *Door) ReadDropfile(filename string) {
  53. file, err := os.Open(filename)
  54. if err != nil {
  55. fmt.Printf("Open(%s): %s\n", filename, err)
  56. os.Exit(2)
  57. }
  58. defer file.Close()
  59. var lines []string
  60. // read line by line
  61. // The scanner handles DOS and linux file endings.
  62. scanner := bufio.NewScanner(file)
  63. for scanner.Scan() {
  64. line := scanner.Text()
  65. lines = append(lines, line)
  66. }
  67. d.config.comm_type, err = strconv.Atoi(lines[0])
  68. d.config.comm_handle, err = strconv.Atoi(lines[1])
  69. d.config.baudrate, err = strconv.Atoi(lines[2])
  70. d.config.BBSID = lines[3]
  71. d.config.user_number, err = strconv.Atoi(lines[4])
  72. d.config.real_name = lines[5]
  73. d.config.handle = lines[6]
  74. d.config.security_level, err = strconv.Atoi(lines[7])
  75. d.config.time_left, err = strconv.Atoi(lines[8])
  76. d.config.emulation, err = strconv.Atoi(lines[9])
  77. d.config.node_number, err = strconv.Atoi(lines[10])
  78. d.READFD = d.config.comm_handle
  79. d.WRITEFD = d.config.comm_handle
  80. // Calculate the time when time expires.
  81. d.TimeOut = time.Now().Add(time.Duration(d.config.time_left) * time.Minute)
  82. }
  83. func (d *Door) HasKey() bool {
  84. var fdsetRead = syscall.FdSet{}
  85. clearAll(&fdsetRead)
  86. set(&fdsetRead, d.READFD)
  87. timeout := syscall.Timeval{Sec: 0, Usec: 1}
  88. v, _ := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
  89. if v == -1 {
  90. return false
  91. }
  92. if v == 0 {
  93. return false
  94. }
  95. return true
  96. }
  97. var Unicode bool // Unicode support detected
  98. var CP437 bool // CP437 support detected
  99. var Full_CP437 bool // Full CP437 support detected (handles control codes properly)
  100. var Height int // Screen height detected
  101. var Width int // Screen width detected
  102. // Detect client terminal capabilities, Unicode, CP437, Full_CP437,
  103. // screen Height and Width.
  104. func (d *Door) detect() {
  105. // if d.config.comm_handle == 0 {
  106. // d.Write("\377\375\042\377\373\001") // fix telnet client
  107. // }
  108. d.Write("\x1b[0;30;40m\x1b[2J\x1b[H") // black on black, clrscr, go home
  109. d.Write("\x03\x04\x1b[6n") // hearts and diamonds does CP437 work?
  110. d.Write(CRNL + "\u2615\x1b[6n")
  111. d.Write("\x1b[999C\x1b[999B\x1b[6n" + Reset + "\x1b[2J\x1b[H") // goto end of screen + cursor pos
  112. // time.Sleep(50 * time.Millisecond)
  113. time.Sleep(250 * time.Millisecond)
  114. // read everything
  115. // telnet term isn't in RAW mode, so keys are buffer until <CR>
  116. var results string
  117. if true { // d.HasKey() {
  118. buffer := make([]byte, 100)
  119. r, err := syscall.Read(d.READFD, buffer)
  120. results = string(buffer[:r])
  121. output := strings.Replace(results, "\x1b", "^[", -1)
  122. fmt.Println("DETECT:", r, err, output)
  123. } else {
  124. // local telnet echos the reply :()
  125. fmt.Println("DETECT: Nothing received.")
  126. return
  127. }
  128. if ((strings.Index(results, "1;1R") != -1) ||
  129. (strings.Index(results, "1;3R") != -1)) &&
  130. ((strings.Index(results, "2:2R") != -1) ||
  131. (strings.Index(results, "2;3R") != -1)) {
  132. Unicode = true
  133. } else {
  134. Unicode = false
  135. CP437 = true
  136. }
  137. if strings.Index(results, "1;3R") != -1 {
  138. Full_CP437 = true
  139. }
  140. // get screen size
  141. var err error
  142. pos := strings.LastIndex(results, "\x1b")
  143. if pos != -1 {
  144. pos++
  145. if results[pos] == '[' {
  146. pos++
  147. results = results[pos:]
  148. pos = strings.Index(results, ";")
  149. if pos != -1 {
  150. height := results[:pos]
  151. // Height, err = strconv.Atoi(results)
  152. Height, err = strconv.Atoi(height)
  153. pos++
  154. results = results[pos:]
  155. pos = strings.Index(results, "R")
  156. if pos != -1 {
  157. width := results[:pos]
  158. Width, err = strconv.Atoi(width)
  159. fmt.Printf("Width: %s, %d, %v\n", results, Width, err)
  160. }
  161. } else {
  162. Height = 0
  163. Width = 0
  164. }
  165. }
  166. }
  167. fmt.Printf("Unicode %v Screen: %d X %d\n", Unicode, Width, Height)
  168. }
  169. // Initialize door framework. Parse commandline, read dropfile,
  170. // detect terminal capabilities.
  171. func (d *Door) Init() {
  172. var dropfile string
  173. flag.StringVar(&dropfile, "d", "", "Path to dropfile")
  174. flag.Parse()
  175. if len(dropfile) == 0 {
  176. flag.PrintDefaults()
  177. os.Exit(2)
  178. }
  179. fmt.Printf("Loading: %s\n", dropfile)
  180. d.ReadDropfile(dropfile)
  181. 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)
  182. d.detect()
  183. }
  184. // Write string to client.
  185. func (d *Door) Write(output string) {
  186. buffer := []byte(output)
  187. n, err := syscall.Write(d.WRITEFD, buffer)
  188. if err != nil {
  189. fmt.Println("Write error/HANGUP?", n)
  190. }
  191. // No, this isn't it. The # of bytes in buffer == bytes written.
  192. if n != len(buffer) {
  193. fmt.Printf("Write fail: %d != %d\n", len(buffer), n)
  194. }
  195. }
  196. /*
  197. func write(output string, config *DropfileConfig) {
  198. buffer := []byte(output)
  199. n, err := syscall.Write(config.comm_handle, buffer)
  200. if err != nil {
  201. fmt.Println("Write error/HANGUP?", n)
  202. }
  203. }
  204. */
  205. // from: https://github.com/yubo/dea_ng
  206. // https://github.com/yubo/dea_ng/blob/master/go/src/directoryserver/streaming.go
  207. func set(fdSetPtr *syscall.FdSet, fd int) {
  208. (*fdSetPtr).Bits[fd/64] |= 1 << uint64(fd%64)
  209. }
  210. func isSet(fdSetPtr *syscall.FdSet, fd int) bool {
  211. return ((*fdSetPtr).Bits[fd/64] & (1 << uint64(fd%64))) != 0
  212. }
  213. func clearAll(fdSetPtr *syscall.FdSet) {
  214. for index, _ := range (*fdSetPtr).Bits {
  215. (*fdSetPtr).Bits[index] = 0
  216. }
  217. }
  218. func (d *Door) SleepKey(sleep int64) int {
  219. // var fdsetRead, fdsetWrite, fdsete syscall.FdSet
  220. var fdsetRead syscall.FdSet
  221. // fdsetWrite := syscall.FdSet
  222. clearAll(&fdsetRead)
  223. // clearAll(&fdsetWrite)
  224. // clearAll(&fdsete)
  225. set(&fdsetRead, d.READFD)
  226. // timeout := syscall.Timeval{Sec: 0, Usec: 100}
  227. timeout := syscall.Timeval{Sec: sleep, Usec: 0}
  228. v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
  229. if v == -1 {
  230. fmt.Println("-1 : ", err)
  231. // hangup ?!
  232. return -2
  233. }
  234. if v == 0 {
  235. // timeout
  236. return -1
  237. }
  238. // var buffer []byte -- 0 byte buffer. doh!
  239. buffer := make([]byte, 1)
  240. r, err := syscall.Read(d.READFD, buffer)
  241. if r != 1 {
  242. fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err)
  243. // hangup
  244. return -2
  245. }
  246. return int(buffer[0])
  247. }
  248. func (d *Door) Getch() int {
  249. // var fdsetRead, fdsetWrite, fdsete syscall.FdSet
  250. var fdsetRead syscall.FdSet
  251. // fdsetWrite := syscall.FdSet
  252. clearAll(&fdsetRead)
  253. // clearAll(&fdsetWrite)
  254. // clearAll(&fdsete)
  255. set(&fdsetRead, d.READFD)
  256. // timeout := syscall.Timeval{Sec: 0, Usec: 100}
  257. timeout := syscall.Timeval{Sec: 120, Usec: 0}
  258. v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
  259. if v == -1 {
  260. fmt.Println("-1 : ", err)
  261. // hangup ?!
  262. return -2
  263. }
  264. if v == 0 {
  265. // timeout
  266. return -1
  267. }
  268. // var buffer []byte -- 0 byte buffer. doh!
  269. buffer := make([]byte, 1)
  270. r, err := syscall.Read(d.READFD, buffer)
  271. if r != 1 {
  272. fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err)
  273. // hangup
  274. return -2
  275. }
  276. return int(buffer[0])
  277. }
  278. /*
  279. func sleep_key(config *DropfileConfig, secs int) int {
  280. // var fdsetRead, fdsetWrite, fdsete syscall.FdSet
  281. var fdsetRead = syscall.FdSet{}
  282. // fdsetWrite := syscall.FdSet
  283. clearAll(&fdsetRead)
  284. // clearAll(&fdsetWrite)
  285. // clearAll(&fdsete)
  286. set(&fdsetRead, config.comm_handle)
  287. timeout := syscall.Timeval{Sec: int64(secs), Usec: 0}
  288. // v, err := syscall.Select(config.comm_handle+1, &fdsetRead, &fdsetWrite, &fdsete, &timeout)
  289. v, err := syscall.Select(config.comm_handle+1, &fdsetRead, nil, nil, &timeout)
  290. fmt.Println("v:", v, "err:", err)
  291. if v == -1 {
  292. fmt.Println("-1 : ", err)
  293. // hangup ?!
  294. return -2
  295. }
  296. if v == 0 {
  297. // timeout
  298. return -1
  299. }
  300. // var buffer []byte
  301. buffer := make([]byte, 1)
  302. // var buffer [1]byte
  303. r, err := syscall.Read(config.comm_handle, buffer)
  304. if r != 1 {
  305. fmt.Printf("Read said ready, but didn't read a character %d %v ?\n", r, err)
  306. // hangup
  307. return -2
  308. }
  309. return int(buffer[0])
  310. }
  311. */
  312. /*
  313. func main() {
  314. fmt.Println("doorgo")
  315. var dropfile string
  316. flag.StringVar(&dropfile, "dropfile", "", "Dropfile to use")
  317. flag.Parse()
  318. if len(dropfile) == 0 {
  319. flag.PrintDefaults()
  320. os.Exit(2)
  321. }
  322. fmt.Printf("Loading: %s\n", dropfile)
  323. var config DropfileConfig
  324. read_dropfile(dropfile, &config)
  325. fmt.Printf("BBS %s, User %s / Handle %s\n", config.BBSID, config.real_name, config.handle)
  326. message := "Welcome BBS User!\n\r"
  327. // buffer := []byte(message)
  328. // n, err := syscall.Write(config.comm_handle, buffer)
  329. write(message, &config)
  330. write("Press a key...", &config)
  331. key := sleep_key(&config, 20)
  332. write("\n\r", &config)
  333. message = fmt.Sprintf("Key %d / %x\n\r", key, key)
  334. write(message, &config)
  335. write("\n\rReturning to BBS.\n\r", &config)
  336. fmt.Println("Done.")
  337. // fmt.Println(n, err)
  338. }
  339. */