Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

dline.go 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "net"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/oragono/oragono/irc/flatip"
  12. "github.com/oragono/oragono/irc/utils"
  13. "github.com/tidwall/buntdb"
  14. )
  15. const (
  16. keyDlineEntry = "bans.dlinev2 %s"
  17. )
  18. // IPBanInfo holds info about an IP/net ban.
  19. type IPBanInfo struct {
  20. // Reason is the ban reason.
  21. Reason string `json:"reason"`
  22. // OperReason is an oper ban reason.
  23. OperReason string `json:"oper_reason"`
  24. // OperName is the oper who set the ban.
  25. OperName string `json:"oper_name"`
  26. // time of ban creation
  27. TimeCreated time.Time
  28. // duration of the ban; 0 means "permanent"
  29. Duration time.Duration
  30. }
  31. func (info IPBanInfo) timeLeft() time.Duration {
  32. return time.Until(info.TimeCreated.Add(info.Duration))
  33. }
  34. func (info IPBanInfo) TimeLeft() string {
  35. if info.Duration == 0 {
  36. return "indefinite"
  37. } else {
  38. return info.timeLeft().Truncate(time.Second).String()
  39. }
  40. }
  41. // BanMessage returns the ban message.
  42. func (info IPBanInfo) BanMessage(message string) string {
  43. message = fmt.Sprintf(message, info.Reason)
  44. if info.Duration != 0 {
  45. message += fmt.Sprintf(" [%s]", info.TimeLeft())
  46. }
  47. return message
  48. }
  49. // DLineManager manages and dlines.
  50. type DLineManager struct {
  51. sync.RWMutex // tier 1
  52. persistenceMutex sync.Mutex // tier 2
  53. // networks that are dlined:
  54. networks map[flatip.IPNet]IPBanInfo
  55. // this keeps track of expiration timers for temporary bans
  56. expirationTimers map[flatip.IPNet]*time.Timer
  57. server *Server
  58. }
  59. // NewDLineManager returns a new DLineManager.
  60. func NewDLineManager(server *Server) *DLineManager {
  61. var dm DLineManager
  62. dm.networks = make(map[flatip.IPNet]IPBanInfo)
  63. dm.expirationTimers = make(map[flatip.IPNet]*time.Timer)
  64. dm.server = server
  65. dm.loadFromDatastore()
  66. return &dm
  67. }
  68. // AllBans returns all bans (for use with APIs, etc).
  69. func (dm *DLineManager) AllBans() map[string]IPBanInfo {
  70. allb := make(map[string]IPBanInfo)
  71. dm.RLock()
  72. defer dm.RUnlock()
  73. for key, info := range dm.networks {
  74. allb[key.String()] = info
  75. }
  76. return allb
  77. }
  78. // AddNetwork adds a network to the blocked list.
  79. func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, reason, operReason, operName string) error {
  80. dm.persistenceMutex.Lock()
  81. defer dm.persistenceMutex.Unlock()
  82. // assemble ban info
  83. info := IPBanInfo{
  84. Reason: reason,
  85. OperReason: operReason,
  86. OperName: operName,
  87. TimeCreated: time.Now().UTC(),
  88. Duration: duration,
  89. }
  90. id := dm.addNetworkInternal(network, info)
  91. return dm.persistDline(id, info)
  92. }
  93. func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (id flatip.IPNet) {
  94. flatnet := flatip.FromNetIPNet(network)
  95. id = flatnet
  96. var timeLeft time.Duration
  97. if info.Duration != 0 {
  98. timeLeft = info.timeLeft()
  99. if timeLeft <= 0 {
  100. return
  101. }
  102. }
  103. dm.Lock()
  104. defer dm.Unlock()
  105. dm.networks[flatnet] = info
  106. dm.cancelTimer(flatnet)
  107. if info.Duration == 0 {
  108. return
  109. }
  110. // set up new expiration timer
  111. timeCreated := info.TimeCreated
  112. processExpiration := func() {
  113. dm.Lock()
  114. defer dm.Unlock()
  115. banInfo, ok := dm.networks[flatnet]
  116. if ok && banInfo.TimeCreated.Equal(timeCreated) {
  117. delete(dm.networks, flatnet)
  118. // TODO(slingamn) here's where we'd remove it from the radix tree
  119. delete(dm.expirationTimers, flatnet)
  120. }
  121. }
  122. dm.expirationTimers[flatnet] = time.AfterFunc(timeLeft, processExpiration)
  123. return
  124. }
  125. func (dm *DLineManager) cancelTimer(flatnet flatip.IPNet) {
  126. oldTimer := dm.expirationTimers[flatnet]
  127. if oldTimer != nil {
  128. oldTimer.Stop()
  129. delete(dm.expirationTimers, flatnet)
  130. }
  131. }
  132. func (dm *DLineManager) persistDline(id flatip.IPNet, info IPBanInfo) error {
  133. // save in datastore
  134. dlineKey := fmt.Sprintf(keyDlineEntry, id.String())
  135. // assemble json from ban info
  136. b, err := json.Marshal(info)
  137. if err != nil {
  138. dm.server.logger.Error("internal", "couldn't marshal d-line", err.Error())
  139. return err
  140. }
  141. bstr := string(b)
  142. var setOptions *buntdb.SetOptions
  143. if info.Duration != 0 {
  144. setOptions = &buntdb.SetOptions{Expires: true, TTL: info.Duration}
  145. }
  146. err = dm.server.store.Update(func(tx *buntdb.Tx) error {
  147. _, _, err := tx.Set(dlineKey, bstr, setOptions)
  148. return err
  149. })
  150. if err != nil {
  151. dm.server.logger.Error("internal", "couldn't store d-line", err.Error())
  152. }
  153. return err
  154. }
  155. func (dm *DLineManager) unpersistDline(id flatip.IPNet) error {
  156. dlineKey := fmt.Sprintf(keyDlineEntry, id.String())
  157. return dm.server.store.Update(func(tx *buntdb.Tx) error {
  158. _, err := tx.Delete(dlineKey)
  159. return err
  160. })
  161. }
  162. // RemoveNetwork removes a network from the blocked list.
  163. func (dm *DLineManager) RemoveNetwork(network net.IPNet) error {
  164. dm.persistenceMutex.Lock()
  165. defer dm.persistenceMutex.Unlock()
  166. id := flatip.FromNetIPNet(network)
  167. present := func() bool {
  168. dm.Lock()
  169. defer dm.Unlock()
  170. _, ok := dm.networks[id]
  171. delete(dm.networks, id)
  172. dm.cancelTimer(id)
  173. return ok
  174. }()
  175. if !present {
  176. return errNoExistingBan
  177. }
  178. return dm.unpersistDline(id)
  179. }
  180. // AddIP adds an IP address to the blocked list.
  181. func (dm *DLineManager) AddIP(addr net.IP, duration time.Duration, reason, operReason, operName string) error {
  182. return dm.AddNetwork(utils.NormalizeIPToNet(addr), duration, reason, operReason, operName)
  183. }
  184. // RemoveIP removes an IP address from the blocked list.
  185. func (dm *DLineManager) RemoveIP(addr net.IP) error {
  186. return dm.RemoveNetwork(utils.NormalizeIPToNet(addr))
  187. }
  188. // CheckIP returns whether or not an IP address was banned, and how long it is banned for.
  189. func (dm *DLineManager) CheckIP(addr flatip.IP) (isBanned bool, info IPBanInfo) {
  190. if addr.IsLoopback() {
  191. return // #671
  192. }
  193. dm.RLock()
  194. defer dm.RUnlock()
  195. // check networks
  196. // TODO(slingamn) use a radix tree as the data plane for this
  197. for flatnet, info := range dm.networks {
  198. if flatnet.Contains(addr) {
  199. return true, info
  200. }
  201. }
  202. // no matches!
  203. return
  204. }
  205. func (dm *DLineManager) loadFromDatastore() {
  206. dlinePrefix := fmt.Sprintf(keyDlineEntry, "")
  207. dm.server.store.View(func(tx *buntdb.Tx) error {
  208. tx.AscendGreaterOrEqual("", dlinePrefix, func(key, value string) bool {
  209. if !strings.HasPrefix(key, dlinePrefix) {
  210. return false
  211. }
  212. // get address name
  213. key = strings.TrimPrefix(key, dlinePrefix)
  214. // load addr/net
  215. hostNet, err := utils.NormalizedNetFromString(key)
  216. if err != nil {
  217. dm.server.logger.Error("internal", "bad dline cidr", err.Error())
  218. return true
  219. }
  220. // load ban info
  221. var info IPBanInfo
  222. err = json.Unmarshal([]byte(value), &info)
  223. if err != nil {
  224. dm.server.logger.Error("internal", "bad dline data", err.Error())
  225. return true
  226. }
  227. // set opername if it isn't already set
  228. if info.OperName == "" {
  229. info.OperName = dm.server.name
  230. }
  231. // add to the server
  232. dm.addNetworkInternal(hostNet, info)
  233. return true
  234. })
  235. return nil
  236. })
  237. }
  238. func (s *Server) loadDLines() {
  239. s.dlines = NewDLineManager(s)
  240. }