Simple telnet server for write.as http://nerds.write.as
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
5.2 KiB

  1. package main
  2. import (
  3. "bytes"
  4. "database/sql"
  5. "flag"
  6. "fmt"
  7. _ "github.com/go-sql-driver/mysql"
  8. "io/ioutil"
  9. "net"
  10. "os"
  11. "os/exec"
  12. "github.com/writeas/nerds/store"
  13. )
  14. var (
  15. banner []byte
  16. outDir string
  17. staticDir string
  18. debugging bool
  19. rsyncHost string
  20. db *sql.DB
  21. )
  22. const (
  23. colBlue = "\033[0;34m"
  24. colGreen = "\033[0;32m"
  25. colBGreen = "\033[1;32m"
  26. colCyan = "\033[0;36m"
  27. colBRed = "\033[1;31m"
  28. colBold = "\033[1;37m"
  29. noCol = "\033[0m"
  30. hr = "————————————————————————————————————————————————————————————————————————————————"
  31. )
  32. func main() {
  33. // Get any arguments
  34. outDirPtr := flag.String("o", "/var/write", "Directory where text files will be stored.")
  35. staticDirPtr := flag.String("s", "./static", "Directory where required static files exist.")
  36. rsyncHostPtr := flag.String("h", "", "Hostname of the server to rsync saved files to.")
  37. portPtr := flag.Int("p", 2323, "Port to listen on.")
  38. debugPtr := flag.Bool("debug", false, "Enables garrulous debug logging.")
  39. flag.Parse()
  40. outDir = *outDirPtr
  41. staticDir = *staticDirPtr
  42. rsyncHost = *rsyncHostPtr
  43. debugging = *debugPtr
  44. fmt.Print("\nCONFIG:\n")
  45. fmt.Printf("Output directory : %s\n", outDir)
  46. fmt.Printf("Static directory : %s\n", staticDir)
  47. fmt.Printf("rsync host : %s\n", rsyncHost)
  48. fmt.Printf("Debugging enabled : %t\n\n", debugging)
  49. fmt.Print("Initializing...")
  50. var err error
  51. banner, err = ioutil.ReadFile(staticDir + "/banner.txt")
  52. if err != nil {
  53. fmt.Println(err)
  54. }
  55. fmt.Println("DONE")
  56. // Connect to database
  57. dbUser := os.Getenv("WA_USER")
  58. dbPassword := os.Getenv("WA_PASSWORD")
  59. dbName := os.Getenv("WA_DB")
  60. dbHost := os.Getenv("WA_HOST")
  61. if outDir == "" && (dbUser == "" || dbPassword == "" || dbName == "") {
  62. // Ensure parameters needed for storage (file or database) are given
  63. fmt.Println("Database user, password, or database name not set.")
  64. return
  65. }
  66. if outDir == "" {
  67. fmt.Print("Connecting to database...")
  68. db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8mb4", dbUser, dbPassword, dbHost, dbName))
  69. if err != nil {
  70. fmt.Printf("\n%s\n", err)
  71. return
  72. }
  73. defer db.Close()
  74. fmt.Println("CONNECTED")
  75. }
  76. ln, err := net.Listen("tcp", fmt.Sprintf(":%d", *portPtr))
  77. if err != nil {
  78. panic(err)
  79. }
  80. fmt.Printf("Listening on localhost:%d\n", *portPtr)
  81. for {
  82. conn, err := ln.Accept()
  83. if err != nil {
  84. fmt.Println(err)
  85. continue
  86. }
  87. go handleConnection(conn)
  88. }
  89. }
  90. func output(c net.Conn, m string) bool {
  91. _, err := c.Write([]byte(m))
  92. if err != nil {
  93. c.Close()
  94. return false
  95. }
  96. return true
  97. }
  98. func outputBytes(c net.Conn, m []byte) bool {
  99. _, err := c.Write(m)
  100. if err != nil {
  101. c.Close()
  102. return false
  103. }
  104. return true
  105. }
  106. func handleConnection(c net.Conn) {
  107. outputBytes(c, banner)
  108. output(c, fmt.Sprintf("\n%sWelcome to write.as!%s\n", colBGreen, noCol))
  109. output(c, fmt.Sprintf("Our telnet server is open source! https://github.com/writeas/nerds\nOptionally post with %scURL%s: http://cmd.write.as\nOr use our cross-platform %scommand-line client%s: https://write.as/cli.html\nWe won't judge if you crawl back to the GUI: https://write.as/apps\n\n", colBold, noCol, colBold, noCol))
  110. waitForEnter(c)
  111. c.Close()
  112. fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
  113. }
  114. func waitForEnter(c net.Conn) {
  115. b := make([]byte, 4)
  116. output(c, fmt.Sprintf("%sPress Enter to continue...%s\n", colBRed, noCol))
  117. for {
  118. n, err := c.Read(b)
  119. if debugging {
  120. fmt.Print(b[0:n])
  121. fmt.Printf("\n%d: %s\n", n, b[0:n])
  122. }
  123. if bytes.IndexRune(b[0:n], '\n') > -1 {
  124. break
  125. }
  126. if err != nil || n == 0 {
  127. c.Close()
  128. break
  129. }
  130. }
  131. output(c, fmt.Sprintf("Enter anything you like.\nPress %sCtrl-D%s to publish and quit.\n%s\n", colBold, noCol, hr))
  132. readInput(c)
  133. }
  134. func checkExit(b []byte, n int) bool {
  135. return n > 0 && bytes.IndexRune(b[0:n], '\n') == -1
  136. }
  137. func readInput(c net.Conn) {
  138. defer c.Close()
  139. b := make([]byte, 4096)
  140. var post bytes.Buffer
  141. for {
  142. n, err := c.Read(b)
  143. post.Write(b[0:n])
  144. if debugging {
  145. fmt.Print(b[0:n])
  146. fmt.Printf("\n%d: %s\n", n, b[0:n])
  147. }
  148. if checkExit(b, n) {
  149. friendlyId := store.GenerateFriendlyRandomString(store.FriendlyIdLen)
  150. editToken := store.Generate62RandomString(32)
  151. if outDir == "" {
  152. _, err := db.Exec("INSERT INTO posts (id, content, modify_token, text_appearance) VALUES (?, ?, ?, 'mono')", friendlyId, post.Bytes(), editToken)
  153. if err != nil {
  154. fmt.Printf("There was an error saving: %s\n", err)
  155. output(c, "Something went terribly wrong, sorry. Try again later?\n\n")
  156. break
  157. }
  158. output(c, fmt.Sprintf("\n%s\nPosted! View at %shttps://write.as/%s%s", hr, colBlue, friendlyId, noCol))
  159. } else {
  160. output(c, fmt.Sprintf("\n%s\nPosted to %shttp://nerds.write.as/%s%s", hr, colBlue, friendlyId, noCol))
  161. if rsyncHost != "" {
  162. output(c, "\nPosting to secure site...")
  163. exec.Command("rsync", "-ptgou", outDir+"/"+friendlyId, rsyncHost+":").Run()
  164. output(c, fmt.Sprintf("\nPosted! View at %shttps://write.as/%s%s", colBlue, friendlyId, noCol))
  165. }
  166. }
  167. output(c, "\nSee you later.\n\n")
  168. break
  169. }
  170. if err != nil || n == 0 {
  171. break
  172. }
  173. }
  174. }