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.

kline.go 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "strings"
  8. "time"
  9. "github.com/DanielOaks/girc-go/ircmatch"
  10. "github.com/DanielOaks/girc-go/ircmsg"
  11. "github.com/tidwall/buntdb"
  12. )
  13. const (
  14. keyKlineEntry = "bans.kline %s"
  15. )
  16. // KLineInfo contains the address itself and expiration time for a given network.
  17. type KLineInfo struct {
  18. // Mask that is blocked.
  19. Mask string
  20. // Matcher, to facilitate fast matching.
  21. Matcher ircmatch.Matcher
  22. // Info contains information on the ban.
  23. Info IPBanInfo
  24. }
  25. // KLineManager manages and klines.
  26. type KLineManager struct {
  27. // kline'd entries
  28. entries map[string]*KLineInfo
  29. }
  30. // NewKLineManager returns a new KLineManager.
  31. func NewKLineManager() *KLineManager {
  32. var km KLineManager
  33. km.entries = make(map[string]*KLineInfo)
  34. return &km
  35. }
  36. // AllBans returns all bans (for use with APIs, etc).
  37. func (km *KLineManager) AllBans() map[string]IPBanInfo {
  38. allb := make(map[string]IPBanInfo)
  39. for name, info := range km.entries {
  40. allb[name] = info.Info
  41. }
  42. return allb
  43. }
  44. // AddMask adds to the blocked list.
  45. func (km *KLineManager) AddMask(mask string, length *IPRestrictTime, reason string, operReason string) {
  46. kln := KLineInfo{
  47. Mask: mask,
  48. Matcher: ircmatch.MakeMatch(mask),
  49. Info: IPBanInfo{
  50. Time: length,
  51. Reason: reason,
  52. OperReason: operReason,
  53. },
  54. }
  55. km.entries[mask] = &kln
  56. }
  57. // RemoveMask removes a mask from the blocked list.
  58. func (km *KLineManager) RemoveMask(mask string) {
  59. delete(km.entries, mask)
  60. }
  61. // CheckMasks returns whether or not the hostmask(s) are banned, and how long they are banned for.
  62. func (km *KLineManager) CheckMasks(masks ...string) (isBanned bool, info *IPBanInfo) {
  63. // check networks
  64. var masksToRemove []string
  65. for _, entryInfo := range km.entries {
  66. var matches bool
  67. for _, mask := range masks {
  68. if entryInfo.Matcher.Match(mask) {
  69. matches = true
  70. break
  71. }
  72. }
  73. if !matches {
  74. continue
  75. }
  76. if entryInfo.Info.Time != nil {
  77. if entryInfo.Info.Time.IsExpired() {
  78. // ban on network has expired, remove it from our blocked list
  79. masksToRemove = append(masksToRemove, entryInfo.Mask)
  80. } else {
  81. return true, &entryInfo.Info
  82. }
  83. } else {
  84. return true, &entryInfo.Info
  85. }
  86. }
  87. // remove expired networks
  88. for _, expiredMask := range masksToRemove {
  89. km.RemoveMask(expiredMask)
  90. }
  91. // no matches!
  92. return false, nil
  93. }
  94. // KLINE [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]]
  95. func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  96. // check oper permissions
  97. if !client.class.Capabilities["oper:local_ban"] {
  98. client.Send(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, "Insufficient oper privs")
  99. return false
  100. }
  101. currentArg := 0
  102. // when setting a ban that covers the oper's current connection, we require them to say
  103. // "KLINE MYSELF" so that we're sure they really mean it.
  104. var klineMyself bool
  105. if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "myself" {
  106. klineMyself = true
  107. currentArg++
  108. }
  109. // duration
  110. duration, err := time.ParseDuration(msg.Params[currentArg])
  111. durationIsUsed := err == nil
  112. if durationIsUsed {
  113. currentArg++
  114. }
  115. // get mask
  116. if len(msg.Params) < currentArg+1 {
  117. client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
  118. return false
  119. }
  120. mask := strings.ToLower(msg.Params[currentArg])
  121. currentArg++
  122. // check mask
  123. if !strings.Contains(mask, "!") && !strings.Contains(mask, "@") {
  124. mask = mask + "!*@*"
  125. } else if !strings.Contains(mask, "@") {
  126. mask = mask + "@*"
  127. }
  128. matcher := ircmatch.MakeMatch(mask)
  129. for _, clientMask := range client.AllNickmasks() {
  130. if !klineMyself && matcher.Match(clientMask) {
  131. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>")
  132. return false
  133. }
  134. }
  135. // check remote
  136. if len(msg.Params) > currentArg && msg.Params[currentArg] == "ON" {
  137. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "Remote servers not yet supported")
  138. return false
  139. }
  140. // get comment(s)
  141. reason := "No reason given"
  142. operReason := "No reason given"
  143. if len(msg.Params) > currentArg {
  144. tempReason := strings.TrimSpace(msg.Params[currentArg])
  145. if len(tempReason) > 0 && tempReason != "|" {
  146. tempReasons := strings.SplitN(tempReason, "|", 2)
  147. if tempReasons[0] != "" {
  148. reason = tempReasons[0]
  149. }
  150. if len(tempReasons) > 1 && tempReasons[1] != "" {
  151. operReason = tempReasons[1]
  152. } else {
  153. operReason = reason
  154. }
  155. }
  156. }
  157. // assemble ban info
  158. var banTime *IPRestrictTime
  159. if durationIsUsed {
  160. banTime = &IPRestrictTime{
  161. Duration: duration,
  162. Expires: time.Now().Add(duration),
  163. }
  164. }
  165. info := IPBanInfo{
  166. Reason: reason,
  167. OperReason: operReason,
  168. Time: banTime,
  169. }
  170. // save in datastore
  171. err = server.store.Update(func(tx *buntdb.Tx) error {
  172. klineKey := fmt.Sprintf(keyKlineEntry, mask)
  173. // assemble json from ban info
  174. b, err := json.Marshal(info)
  175. if err != nil {
  176. return err
  177. }
  178. tx.Set(klineKey, string(b), nil)
  179. return nil
  180. })
  181. server.klines.AddMask(mask, banTime, reason, operReason)
  182. if durationIsUsed {
  183. client.Notice(fmt.Sprintf("Added temporary (%s) K-Line for %s", duration.String(), mask))
  184. } else {
  185. client.Notice(fmt.Sprintf("Added K-Line for %s", mask))
  186. }
  187. return false
  188. }
  189. func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  190. // check oper permissions
  191. if !client.class.Capabilities["oper:local_unban"] {
  192. client.Send(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, "Insufficient oper privs")
  193. return false
  194. }
  195. // get host
  196. mask := msg.Params[0]
  197. if !strings.Contains(mask, "!") && !strings.Contains(mask, "@") {
  198. mask = mask + "!*@*"
  199. } else if !strings.Contains(mask, "@") {
  200. mask = mask + "@*"
  201. }
  202. // save in datastore
  203. err := server.store.Update(func(tx *buntdb.Tx) error {
  204. klineKey := fmt.Sprintf(keyKlineEntry, mask)
  205. // check if it exists or not
  206. val, err := tx.Get(klineKey)
  207. if val == "" {
  208. return errNoExistingBan
  209. } else if err != nil {
  210. return err
  211. }
  212. tx.Delete(klineKey)
  213. return nil
  214. })
  215. if err != nil {
  216. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, fmt.Sprintf("Could not remove ban [%s]", err.Error()))
  217. return false
  218. }
  219. server.klines.RemoveMask(mask)
  220. client.Notice(fmt.Sprintf("Removed K-Line for %s", mask))
  221. return false
  222. }
  223. func (s *Server) loadKLines() {
  224. s.klines = NewKLineManager()
  225. // load from datastore
  226. s.store.View(func(tx *buntdb.Tx) error {
  227. //TODO(dan): We could make this safer
  228. tx.AscendKeys("bans.kline *", func(key, value string) bool {
  229. // get address name
  230. key = key[len("bans.kline "):]
  231. mask := key
  232. // load ban info
  233. var info IPBanInfo
  234. json.Unmarshal([]byte(value), &info)
  235. // add to the server
  236. s.klines.AddMask(mask, info.Time, info.Reason, info.OperReason)
  237. return true // true to continue I guess?
  238. })
  239. return nil
  240. })
  241. }