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.

histserv.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "bufio"
  6. "fmt"
  7. "os"
  8. "strconv"
  9. "time"
  10. "github.com/ergochat/ergo/irc/history"
  11. "github.com/ergochat/ergo/irc/modes"
  12. "github.com/ergochat/ergo/irc/utils"
  13. )
  14. type CanDelete uint
  15. const (
  16. canDeleteAny CanDelete = iota // User is allowed to delete any message (for a given channel/PM)
  17. canDeleteSelf // User is allowed to delete their own messages (ditto)
  18. canDeleteNone // User is not allowed to delete any message (ditto)
  19. )
  20. const (
  21. histservHelp = `HistServ provides commands related to history.`
  22. )
  23. func histservEnabled(config *Config) bool {
  24. return config.History.Enabled
  25. }
  26. func historyComplianceEnabled(config *Config) bool {
  27. return config.History.Enabled && config.History.Persistent.Enabled && config.History.Retention.EnableAccountIndexing
  28. }
  29. var (
  30. histservCommands = map[string]*serviceCommand{
  31. "forget": {
  32. handler: histservForgetHandler,
  33. help: `Syntax: $bFORGET <account>$b
  34. FORGET deletes all history messages sent by an account.`,
  35. helpShort: `$bFORGET$b deletes all history messages sent by an account.`,
  36. capabs: []string{"history"},
  37. enabled: histservEnabled,
  38. minParams: 1,
  39. maxParams: 1,
  40. },
  41. "delete": {
  42. handler: histservDeleteHandler,
  43. help: `Syntax: $bDELETE <target> <msgid>$b
  44. DELETE deletes an individual message by its msgid. The target is the channel
  45. name. The msgid is the ID as can be found in the tags of that message.`,
  46. helpShort: `$bDELETE$b deletes an individual message by its target and msgid.`,
  47. enabled: histservEnabled,
  48. minParams: 2,
  49. maxParams: 2,
  50. },
  51. "export": {
  52. handler: histservExportHandler,
  53. help: `Syntax: $bEXPORT <account>$b
  54. EXPORT exports all messages sent by an account as JSON. This can be used at
  55. the request of the account holder.`,
  56. helpShort: `$bEXPORT$b exports all messages sent by an account as JSON.`,
  57. enabled: historyComplianceEnabled,
  58. capabs: []string{"history"},
  59. minParams: 1,
  60. maxParams: 1,
  61. },
  62. "play": {
  63. handler: histservPlayHandler,
  64. help: `Syntax: $bPLAY <target> [limit]$b
  65. PLAY plays back history messages, rendering them into direct messages from
  66. HistServ. 'target' is a channel name or nickname to query, and 'limit'
  67. is a message count or a time duration. Note that message playback may be
  68. incomplete or degraded, relative to direct playback from /HISTORY or
  69. CHATHISTORY.`,
  70. helpShort: `$bPLAY$b plays back history messages.`,
  71. enabled: histservEnabled,
  72. minParams: 1,
  73. maxParams: 2,
  74. },
  75. }
  76. )
  77. func histservForgetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  78. accountName := server.accounts.AccountToAccountName(params[0])
  79. if accountName == "" {
  80. service.Notice(rb, client.t("Could not look up account name, proceeding anyway"))
  81. accountName = params[0]
  82. }
  83. server.ForgetHistory(accountName)
  84. service.Notice(rb, fmt.Sprintf(client.t("Enqueued account %s for message deletion"), accountName))
  85. }
  86. // Returns:
  87. //
  88. // 1. `canDeleteAny` if the client allowed to delete other users' messages from the target, ie.:
  89. // - the client is a channel operator, or
  90. // - the client is an operator with "history" capability
  91. //
  92. // 2. `canDeleteSelf` if the client is allowed to delete their own messages from the target
  93. // 3. `canDeleteNone` otherwise
  94. func deletionPolicy(server *Server, client *Client, target string) CanDelete {
  95. isOper := client.HasRoleCapabs("history")
  96. if isOper {
  97. return canDeleteAny
  98. } else {
  99. if server.Config().History.Retention.AllowIndividualDelete {
  100. channel := server.channels.Get(target)
  101. if channel != nil && channel.ClientIsAtLeast(client, modes.Operator) {
  102. return canDeleteAny
  103. } else {
  104. return canDeleteSelf
  105. }
  106. } else {
  107. return canDeleteNone
  108. }
  109. }
  110. }
  111. func histservDeleteHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  112. target, msgid := params[0], params[1] // Fix #1881 2 params are required
  113. canDelete := deletionPolicy(server, client, target)
  114. accountName := "*"
  115. if canDelete == canDeleteNone {
  116. service.Notice(rb, client.t("Insufficient privileges"))
  117. return
  118. } else if canDelete == canDeleteSelf {
  119. accountName = client.AccountName()
  120. if accountName == "*" {
  121. service.Notice(rb, client.t("Insufficient privileges"))
  122. return
  123. }
  124. }
  125. err := server.DeleteMessage(target, msgid, accountName)
  126. if err == nil {
  127. service.Notice(rb, client.t("Successfully deleted message"))
  128. } else {
  129. isOper := client.HasRoleCapabs("history")
  130. if isOper {
  131. service.Notice(rb, fmt.Sprintf(client.t("Error deleting message: %v"), err))
  132. } else {
  133. service.Notice(rb, client.t("Could not delete message"))
  134. }
  135. }
  136. }
  137. func histservExportHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  138. cfAccount, err := CasefoldName(params[0])
  139. if err != nil {
  140. service.Notice(rb, client.t("Invalid account name"))
  141. return
  142. }
  143. config := server.Config()
  144. // don't include the account name in the filename because of escaping concerns
  145. filename := fmt.Sprintf("%s-%s.json", utils.GenerateSecretToken(), time.Now().UTC().Format(IRCv3TimestampFormat))
  146. pathname := config.getOutputPath(filename)
  147. outfile, err := os.Create(pathname)
  148. if err != nil {
  149. service.Notice(rb, fmt.Sprintf(client.t("Error opening export file: %v"), err))
  150. } else {
  151. service.Notice(rb, fmt.Sprintf(client.t("Started exporting data for account %[1]s to file %[2]s"), cfAccount, filename))
  152. }
  153. go histservExportAndNotify(service, server, cfAccount, outfile, filename, client.Nick())
  154. }
  155. func histservExportAndNotify(service *ircService, server *Server, cfAccount string, outfile *os.File, filename, alertNick string) {
  156. defer server.HandlePanic()
  157. defer outfile.Close()
  158. writer := bufio.NewWriter(outfile)
  159. defer writer.Flush()
  160. server.historyDB.Export(cfAccount, writer)
  161. client := server.clients.Get(alertNick)
  162. if client != nil && client.HasRoleCapabs("history") {
  163. client.Send(nil, service.prefix, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Data export for %[1]s completed and written to %[2]s"), cfAccount, filename))
  164. }
  165. }
  166. func histservPlayHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  167. items, _, err := easySelectHistory(server, client, params)
  168. if err != nil {
  169. service.Notice(rb, client.t("Could not retrieve history"))
  170. return
  171. }
  172. playMessage := func(timestamp time.Time, nick, message string) {
  173. service.Notice(rb, fmt.Sprintf("%s <%s> %s", timestamp.Format("15:04:05"), NUHToNick(nick), message))
  174. }
  175. for _, item := range items {
  176. // TODO: support a few more of these, maybe JOIN/PART/QUIT
  177. if item.Type != history.Privmsg && item.Type != history.Notice {
  178. continue
  179. }
  180. if len(item.Message.Split) == 0 {
  181. playMessage(item.Message.Time, item.Nick, item.Message.Message)
  182. } else {
  183. for _, pair := range item.Message.Split {
  184. playMessage(item.Message.Time, item.Nick, pair.Message)
  185. }
  186. }
  187. }
  188. service.Notice(rb, client.t("End of history playback"))
  189. }
  190. // handles parameter parsing and history queries for /HISTORY and /HISTSERV PLAY
  191. func easySelectHistory(server *Server, client *Client, params []string) (items []history.Item, channel *Channel, err error) {
  192. channel, sequence, err := server.GetHistorySequence(nil, client, params[0])
  193. if sequence == nil || err != nil {
  194. return nil, nil, errNoSuchChannel
  195. }
  196. var duration time.Duration
  197. maxChathistoryLimit := server.Config().History.ChathistoryMax
  198. limit := 100
  199. if maxChathistoryLimit < limit {
  200. limit = maxChathistoryLimit
  201. }
  202. if len(params) > 1 {
  203. providedLimit, err := strconv.Atoi(params[1])
  204. if err == nil && providedLimit != 0 {
  205. limit = providedLimit
  206. if maxChathistoryLimit < limit {
  207. limit = maxChathistoryLimit
  208. }
  209. } else if err != nil {
  210. duration, err = time.ParseDuration(params[1])
  211. if err == nil {
  212. limit = maxChathistoryLimit
  213. }
  214. }
  215. }
  216. if duration == 0 {
  217. items, err = sequence.Between(history.Selector{}, history.Selector{}, limit)
  218. } else {
  219. now := time.Now().UTC()
  220. start := history.Selector{Time: now}
  221. end := history.Selector{Time: now.Add(-duration)}
  222. items, err = sequence.Between(start, end, limit)
  223. }
  224. return
  225. }