bot.go 4.7 KB

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