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.

ergo.go 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2014-2015 Edmund Huber
  3. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  4. // released under the MIT license
  5. package main
  6. import (
  7. "bufio"
  8. "encoding/json"
  9. "fmt"
  10. "log"
  11. "os"
  12. "strings"
  13. "syscall"
  14. "golang.org/x/crypto/bcrypt"
  15. "golang.org/x/crypto/ssh/terminal"
  16. "github.com/docopt/docopt-go"
  17. "github.com/ergochat/ergo/irc"
  18. "github.com/ergochat/ergo/irc/logger"
  19. "github.com/ergochat/ergo/irc/mkcerts"
  20. "github.com/ergochat/ergo/irc/passwd"
  21. )
  22. // set via linker flags, either by make or by goreleaser:
  23. var commit = "" // git hash
  24. var version = "" // tagged version
  25. // get a password from stdin from the user
  26. func getPassword() string {
  27. fd := int(os.Stdin.Fd())
  28. if terminal.IsTerminal(fd) {
  29. bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
  30. if err != nil {
  31. log.Fatal("Error reading password:", err.Error())
  32. }
  33. return string(bytePassword)
  34. }
  35. reader := bufio.NewReader(os.Stdin)
  36. text, _ := reader.ReadString('\n')
  37. return strings.TrimSpace(text)
  38. }
  39. func fileDoesNotExist(file string) bool {
  40. if _, err := os.Stat(file); os.IsNotExist(err) {
  41. return true
  42. }
  43. return false
  44. }
  45. // implements the `ergo mkcerts` command
  46. func doMkcerts(configFile string, quiet bool) {
  47. config, err := irc.LoadRawConfig(configFile)
  48. if err != nil {
  49. log.Fatal(err)
  50. }
  51. if !quiet {
  52. log.Println("making self-signed certificates")
  53. }
  54. certToKey := make(map[string]string)
  55. for name, conf := range config.Server.Listeners {
  56. if conf.TLS.Cert == "" {
  57. continue
  58. }
  59. existingKey, ok := certToKey[conf.TLS.Cert]
  60. if ok {
  61. if existingKey == conf.TLS.Key {
  62. continue
  63. } else {
  64. log.Fatal("Conflicting TLS key files for ", conf.TLS.Cert)
  65. }
  66. }
  67. if !quiet {
  68. log.Printf(" making cert for %s listener\n", name)
  69. }
  70. host := config.Server.Name
  71. cert, key := conf.TLS.Cert, conf.TLS.Key
  72. if !(fileDoesNotExist(cert) && fileDoesNotExist(key)) {
  73. log.Fatalf("Preexisting TLS cert and/or key files: %s %s", cert, key)
  74. }
  75. err := mkcerts.CreateCert("Ergo", host, cert, key)
  76. if err == nil {
  77. if !quiet {
  78. log.Printf(" Certificate created at %s : %s\n", cert, key)
  79. }
  80. certToKey[cert] = key
  81. } else {
  82. log.Fatal(" Could not create certificate:", err.Error())
  83. }
  84. }
  85. }
  86. func doCheckPasswd() (returnCode int) {
  87. reader := bufio.NewReader(os.Stdin)
  88. text, err := reader.ReadBytes('\n')
  89. if err != nil {
  90. log.Fatal(err)
  91. }
  92. var hashAndPassword [2]string
  93. err = json.Unmarshal(text, &hashAndPassword)
  94. if err != nil {
  95. log.Fatal(err)
  96. }
  97. if passwd.CompareHashAndPassword([]byte(hashAndPassword[0]), []byte(hashAndPassword[1])) != nil {
  98. return 1
  99. }
  100. return 0
  101. }
  102. func main() {
  103. irc.SetVersionString(version, commit)
  104. usage := `ergo.
  105. Usage:
  106. ergo initdb [--conf <filename>] [--quiet]
  107. ergo upgradedb [--conf <filename>] [--quiet]
  108. ergo importdb <database.json> [--conf <filename>] [--quiet]
  109. ergo genpasswd [--conf <filename>] [--quiet]
  110. ergo mkcerts [--conf <filename>] [--quiet]
  111. ergo checkpasswd
  112. ergo run [--conf <filename>] [--quiet] [--smoke]
  113. ergo -h | --help
  114. ergo --version
  115. Options:
  116. --conf <filename> Configuration file to use [default: ircd.yaml].
  117. --quiet Don't show startup/shutdown lines.
  118. -h --help Show this screen.
  119. --version Show version.`
  120. arguments, _ := docopt.ParseArgs(usage, nil, irc.Ver)
  121. // don't require a config file for genpasswd
  122. if arguments["genpasswd"].(bool) {
  123. var password string
  124. fd := int(os.Stdin.Fd())
  125. if terminal.IsTerminal(fd) {
  126. fmt.Print("Enter Password: ")
  127. password = getPassword()
  128. fmt.Print("\n")
  129. fmt.Print("Reenter Password: ")
  130. confirm := getPassword()
  131. fmt.Print("\n")
  132. if confirm != password {
  133. log.Fatal("passwords do not match")
  134. }
  135. } else {
  136. password = getPassword()
  137. }
  138. if err := irc.ValidatePassphrase(password); err != nil {
  139. log.Printf("WARNING: this password contains characters that may cause problems with your IRC client software.\n")
  140. log.Printf("We strongly recommend choosing a different password.\n")
  141. }
  142. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
  143. if err != nil {
  144. log.Fatal("encoding error:", err.Error())
  145. }
  146. fmt.Print(string(hash))
  147. if terminal.IsTerminal(fd) {
  148. fmt.Println()
  149. }
  150. return
  151. } else if arguments["mkcerts"].(bool) {
  152. doMkcerts(arguments["--conf"].(string), arguments["--quiet"].(bool))
  153. return
  154. } else if arguments["checkpasswd"].(bool) {
  155. os.Exit(doCheckPasswd())
  156. }
  157. configfile := arguments["--conf"].(string)
  158. config, err := irc.LoadConfig(configfile)
  159. if err != nil {
  160. _, isCertError := err.(*irc.CertKeyError)
  161. if !(isCertError && arguments["mkcerts"].(bool)) {
  162. log.Fatal("Config file did not load successfully: ", err.Error())
  163. }
  164. }
  165. logman, err := logger.NewManager(config.Logging)
  166. if err != nil {
  167. log.Fatal("Logger did not load successfully:", err.Error())
  168. }
  169. if arguments["initdb"].(bool) {
  170. err = irc.InitDB(config.Datastore.Path)
  171. if err != nil {
  172. log.Fatal("Error while initializing db:", err.Error())
  173. }
  174. if !arguments["--quiet"].(bool) {
  175. log.Println("database initialized: ", config.Datastore.Path)
  176. }
  177. } else if arguments["upgradedb"].(bool) {
  178. err = irc.UpgradeDB(config)
  179. if err != nil {
  180. log.Fatal("Error while upgrading db:", err.Error())
  181. }
  182. if !arguments["--quiet"].(bool) {
  183. log.Println("database upgraded: ", config.Datastore.Path)
  184. }
  185. } else if arguments["importdb"].(bool) {
  186. err = irc.ImportDB(config, arguments["<database.json>"].(string))
  187. if err != nil {
  188. log.Fatal("Error while importing db:", err.Error())
  189. }
  190. } else if arguments["run"].(bool) {
  191. if !arguments["--quiet"].(bool) {
  192. logman.Info("server", fmt.Sprintf("%s starting", irc.Ver))
  193. }
  194. // warning if running a non-final version
  195. if strings.Contains(irc.Ver, "unreleased") {
  196. logman.Warning("server", "You are currently running an unreleased beta version of Ergo that may be unstable and could corrupt your database.\nIf you are running a production network, please download the latest build from https://ergo.chat/downloads.html and run that instead.")
  197. }
  198. server, err := irc.NewServer(config, logman)
  199. if err != nil {
  200. logman.Error("server", fmt.Sprintf("Could not load server: %s", err.Error()))
  201. os.Exit(1)
  202. }
  203. if !arguments["--smoke"].(bool) {
  204. server.Run()
  205. }
  206. }
  207. }