bot.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. package main
  4. import (
  5. "crypto/tls"
  6. "crypto/x509"
  7. "fmt"
  8. "io/ioutil"
  9. "log"
  10. "github.com/monkeybird/autimaat/app"
  11. "github.com/monkeybird/autimaat/app/logger"
  12. "github.com/monkeybird/autimaat/app/proc"
  13. "github.com/monkeybird/autimaat/irc"
  14. "github.com/monkeybird/autimaat/irc/proto"
  15. "github.com/monkeybird/autimaat/plugins"
  16. _ "github.com/monkeybird/autimaat/plugins/action"
  17. _ "github.com/monkeybird/autimaat/plugins/admin"
  18. _ "github.com/monkeybird/autimaat/plugins/alarm"
  19. _ "github.com/monkeybird/autimaat/plugins/dictionary"
  20. _ "github.com/monkeybird/autimaat/plugins/stats"
  21. _ "github.com/monkeybird/autimaat/plugins/url"
  22. _ "github.com/monkeybird/autimaat/plugins/weather"
  23. )
  24. // Bot defines state for a single IRC bot.
  25. type Bot struct {
  26. profile irc.Profile
  27. client *Client
  28. }
  29. // Run creates a new connection to the server and begins processing
  30. // incoming messages and OS signals. This call will not return for as long
  31. // as the connection is active.
  32. func Run(p irc.Profile) error {
  33. // Initialize the log and ensure it is properly stopped when we are done.
  34. logger.Init("logs")
  35. defer logger.Shutdown()
  36. log.Printf("[bot] Running %s version %d.%d.%s",
  37. app.Name, app.VersionMajor, app.VersionMinor, app.VersionRevision)
  38. defer log.Println("[bot] Shutting down")
  39. // Initialize plugins.
  40. plugins.Load(p)
  41. defer plugins.Unload(p)
  42. // Create te bot, open the connection and spin up the client's read loop
  43. // in a separate goroutine.
  44. var bot Bot
  45. bot.profile = p
  46. bot.client = NewClient(bot.payloadHandler)
  47. return bot.run()
  48. }
  49. // run opens a new connection, or inherits an existing one and then begins
  50. // the client's message poll routine.
  51. func (b *Bot) run() error {
  52. // Initialize the connection.
  53. err := b.open()
  54. if err != nil {
  55. return err
  56. }
  57. // Spin up the connection's read loop.
  58. go func() {
  59. log.Println("[bot] Entering data loop...")
  60. err := b.client.Run()
  61. if err != nil {
  62. log.Println(err)
  63. }
  64. // Break out of the Wait() call below.
  65. proc.Kill()
  66. }()
  67. // Wait for external signals. Either to cleanly shut the bot down,
  68. // or to initiate the forking process.
  69. fd, _ := b.client.File()
  70. proc.Wait(b.profile.ForkArgs(), fd)
  71. return b.client.Close()
  72. }
  73. // payloadHandler handles incoming server messages.
  74. func (b *Bot) payloadHandler(payload []byte) {
  75. var r irc.Request
  76. // Try to parse the payload into a request.
  77. if !parseRequest(&r, payload) {
  78. return
  79. }
  80. // If Target points to the bot's own name, then this message came from
  81. // a user as a PM. Change the Target to the sender's name, so any replies
  82. // we create, end up at the right destination. In any other case, the
  83. // target is set to the channel name from whence the message came.
  84. if b.profile.IsNick(r.Target) {
  85. r.Target = r.SenderName
  86. }
  87. // Run the appropriate handler for housekeeping.
  88. switch r.Type {
  89. case "ERROR":
  90. log.Println("[bot] Network error:", r.Data)
  91. return
  92. case "PING":
  93. proto.Pong(b.client, r.Data)
  94. return
  95. }
  96. // Notify plugins of message.
  97. plugins.Dispatch(b.client, &r)
  98. // Log request if applicable.
  99. if b.profile.Logging() {
  100. log.Println("[>]", r.String())
  101. }
  102. }
  103. // open either establishes a new connection or inherits an existing one
  104. // from a parent process.
  105. func (b *Bot) open() error {
  106. var config *tls.Config
  107. p := b.profile
  108. // Create TLS configuration, if applicable.
  109. if len(p.TLSCert()) > 0 && len(p.TLSKey()) > 0 {
  110. cert, err := tls.LoadX509KeyPair(p.TLSCert(), p.TLSKey())
  111. if err != nil {
  112. return err
  113. }
  114. config = &tls.Config{
  115. Certificates: []tls.Certificate{cert},
  116. PreferServerCipherSuites: true,
  117. InsecureSkipVerify: false,
  118. }
  119. // Should we replace the client's root CA pool?
  120. if len(p.CAPemData()) > 0 {
  121. config.RootCAs = x509.NewCertPool()
  122. data, err := ioutil.ReadFile(p.CAPemData())
  123. if err != nil {
  124. return err
  125. }
  126. if !config.RootCAs.AppendCertsFromPEM(data) {
  127. return fmt.Errorf("AppendCertsFromPEM: failed to add certificates in %s",
  128. p.CAPemData())
  129. }
  130. }
  131. }
  132. files := proc.InheritedFiles()
  133. // Are we a fork? Then we should inherit an existing connection.
  134. if len(files) > 0 {
  135. log.Println("[bot] Inherit connection to:", p.Address())
  136. err := b.client.OpenFd(files[0], config)
  137. if err != nil {
  138. return err
  139. }
  140. // We're done inheriting. Kill the parent process.
  141. proc.KillParent()
  142. return nil
  143. }
  144. log.Println("[bot] Opening new connection to:", p.Address())
  145. // Fresh session - create a new connection.
  146. err := b.client.Open(p.Address(), config)
  147. if err != nil {
  148. return err
  149. }
  150. // Perform initial handshake.
  151. proto.Pass(b.client, p.ConnectionPassword())
  152. proto.User(b.client, p.Nickname(), "8", p.Nickname())
  153. proto.Nick(b.client, p.Nickname(), p.NickservPassword())
  154. return nil
  155. }