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.

dline.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "errors"
  6. "fmt"
  7. "net"
  8. "sort"
  9. "time"
  10. "strings"
  11. "encoding/json"
  12. "github.com/goshuirc/irc-go/ircfmt"
  13. "github.com/goshuirc/irc-go/ircmsg"
  14. "github.com/oragono/oragono/irc/custime"
  15. "github.com/oragono/oragono/irc/sno"
  16. "github.com/tidwall/buntdb"
  17. )
  18. const (
  19. keyDlineEntry = "bans.dline %s"
  20. )
  21. var (
  22. errNoExistingBan = errors.New("Ban does not exist")
  23. )
  24. // IPRestrictTime contains the expiration info about the given IP.
  25. type IPRestrictTime struct {
  26. // Duration is how long this block lasts for.
  27. Duration time.Duration `json:"duration"`
  28. // Expires is when this block expires.
  29. Expires time.Time `json:"expires"`
  30. }
  31. // IsExpired returns true if the time has expired.
  32. func (iptime *IPRestrictTime) IsExpired() bool {
  33. return iptime.Expires.Before(time.Now())
  34. }
  35. // IPBanInfo holds info about an IP/net ban.
  36. type IPBanInfo struct {
  37. // Reason is the ban reason.
  38. Reason string `json:"reason"`
  39. // OperReason is an oper ban reason.
  40. OperReason string `json:"oper_reason"`
  41. // Time holds details about the duration, if it exists.
  42. Time *IPRestrictTime `json:"time"`
  43. }
  44. // dLineAddr contains the address itself and expiration time for a given network.
  45. type dLineAddr struct {
  46. // Address is the address that is blocked.
  47. Address net.IP
  48. // Info contains information on the ban.
  49. Info IPBanInfo
  50. }
  51. // dLineNet contains the net itself and expiration time for a given network.
  52. type dLineNet struct {
  53. // Network is the network that is blocked.
  54. Network net.IPNet
  55. // Info contains information on the ban.
  56. Info IPBanInfo
  57. }
  58. // DLineManager manages and dlines.
  59. type DLineManager struct {
  60. // addresses that are dlined
  61. addresses map[string]*dLineAddr
  62. // networks that are dlined
  63. networks map[string]*dLineNet
  64. }
  65. // NewDLineManager returns a new DLineManager.
  66. func NewDLineManager() *DLineManager {
  67. var dm DLineManager
  68. dm.addresses = make(map[string]*dLineAddr)
  69. dm.networks = make(map[string]*dLineNet)
  70. return &dm
  71. }
  72. // AllBans returns all bans (for use with APIs, etc).
  73. func (dm *DLineManager) AllBans() map[string]IPBanInfo {
  74. allb := make(map[string]IPBanInfo)
  75. for name, info := range dm.addresses {
  76. allb[name] = info.Info
  77. }
  78. for name, info := range dm.networks {
  79. allb[name] = info.Info
  80. }
  81. return allb
  82. }
  83. // AddNetwork adds a network to the blocked list.
  84. func (dm *DLineManager) AddNetwork(network net.IPNet, length *IPRestrictTime, reason string, operReason string) {
  85. netString := network.String()
  86. dln := dLineNet{
  87. Network: network,
  88. Info: IPBanInfo{
  89. Time: length,
  90. Reason: reason,
  91. OperReason: operReason,
  92. },
  93. }
  94. dm.networks[netString] = &dln
  95. }
  96. // RemoveNetwork removes a network from the blocked list.
  97. func (dm *DLineManager) RemoveNetwork(network net.IPNet) {
  98. netString := network.String()
  99. delete(dm.networks, netString)
  100. }
  101. // AddIP adds an IP address to the blocked list.
  102. func (dm *DLineManager) AddIP(addr net.IP, length *IPRestrictTime, reason string, operReason string) {
  103. addrString := addr.String()
  104. dla := dLineAddr{
  105. Address: addr,
  106. Info: IPBanInfo{
  107. Time: length,
  108. Reason: reason,
  109. OperReason: operReason,
  110. },
  111. }
  112. dm.addresses[addrString] = &dla
  113. }
  114. // RemoveIP removes an IP from the blocked list.
  115. func (dm *DLineManager) RemoveIP(addr net.IP) {
  116. addrString := addr.String()
  117. delete(dm.addresses, addrString)
  118. }
  119. // CheckIP returns whether or not an IP address was banned, and how long it is banned for.
  120. func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info *IPBanInfo) {
  121. // check IP addr
  122. addrString := addr.String()
  123. addrInfo := dm.addresses[addrString]
  124. if addrInfo != nil {
  125. if addrInfo.Info.Time != nil {
  126. if addrInfo.Info.Time.IsExpired() {
  127. // ban on IP has expired, remove it from our blocked list
  128. dm.RemoveIP(addr)
  129. } else {
  130. return true, &addrInfo.Info
  131. }
  132. } else {
  133. return true, &addrInfo.Info
  134. }
  135. }
  136. // check networks
  137. var netsToRemove []net.IPNet
  138. for _, netInfo := range dm.networks {
  139. if !netInfo.Network.Contains(addr) {
  140. continue
  141. }
  142. if netInfo.Info.Time != nil {
  143. if netInfo.Info.Time.IsExpired() {
  144. // ban on network has expired, remove it from our blocked list
  145. netsToRemove = append(netsToRemove, netInfo.Network)
  146. } else {
  147. return true, &addrInfo.Info
  148. }
  149. } else {
  150. return true, &addrInfo.Info
  151. }
  152. }
  153. // remove expired networks
  154. for _, expiredNet := range netsToRemove {
  155. dm.RemoveNetwork(expiredNet)
  156. }
  157. // no matches!
  158. return false, nil
  159. }
  160. // DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
  161. func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  162. // check oper permissions
  163. if !client.class.Capabilities["oper:local_ban"] {
  164. client.Send(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, "Insufficient oper privs")
  165. return false
  166. }
  167. currentArg := 0
  168. // when setting a ban, if they say "ANDKILL" we should also kill all users who match it
  169. var andKill bool
  170. if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "andkill" {
  171. andKill = true
  172. currentArg++
  173. }
  174. // when setting a ban that covers the oper's current connection, we require them to say
  175. // "DLINE MYSELF" so that we're sure they really mean it.
  176. var dlineMyself bool
  177. if len(msg.Params) > currentArg+1 && strings.ToLower(msg.Params[currentArg]) == "myself" {
  178. dlineMyself = true
  179. currentArg++
  180. }
  181. // duration
  182. duration, err := custime.ParseDuration(msg.Params[currentArg])
  183. durationIsUsed := err == nil
  184. if durationIsUsed {
  185. currentArg++
  186. }
  187. // get host
  188. if len(msg.Params) < currentArg+1 {
  189. client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, "Not enough parameters")
  190. return false
  191. }
  192. hostString := msg.Params[currentArg]
  193. currentArg++
  194. // check host
  195. var hostAddr net.IP
  196. var hostNet *net.IPNet
  197. _, hostNet, err = net.ParseCIDR(hostString)
  198. if err != nil {
  199. hostAddr = net.ParseIP(hostString)
  200. }
  201. if hostAddr == nil && hostNet == nil {
  202. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "Could not parse IP address or CIDR network")
  203. return false
  204. }
  205. if hostNet == nil {
  206. hostString = hostAddr.String()
  207. if !dlineMyself && hostAddr.Equal(client.IP()) {
  208. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "This ban matches you. To DLINE yourself, you must use the command: /DLINE MYSELF <arguments>")
  209. return false
  210. }
  211. } else {
  212. hostString = hostNet.String()
  213. if !dlineMyself && hostNet.Contains(client.IP()) {
  214. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "This ban matches you. To DLINE yourself, you must use the command: /DLINE MYSELF <arguments>")
  215. return false
  216. }
  217. }
  218. // check remote
  219. if len(msg.Params) > currentArg && msg.Params[currentArg] == "ON" {
  220. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "Remote servers not yet supported")
  221. return false
  222. }
  223. // get comment(s)
  224. reason := "No reason given"
  225. operReason := "No reason given"
  226. if len(msg.Params) > currentArg {
  227. tempReason := strings.TrimSpace(msg.Params[currentArg])
  228. if len(tempReason) > 0 && tempReason != "|" {
  229. tempReasons := strings.SplitN(tempReason, "|", 2)
  230. if tempReasons[0] != "" {
  231. reason = tempReasons[0]
  232. }
  233. if len(tempReasons) > 1 && tempReasons[1] != "" {
  234. operReason = tempReasons[1]
  235. } else {
  236. operReason = reason
  237. }
  238. }
  239. }
  240. // assemble ban info
  241. var banTime *IPRestrictTime
  242. if durationIsUsed {
  243. banTime = &IPRestrictTime{
  244. Duration: duration,
  245. Expires: time.Now().Add(duration),
  246. }
  247. }
  248. info := IPBanInfo{
  249. Reason: reason,
  250. OperReason: operReason,
  251. Time: banTime,
  252. }
  253. // save in datastore
  254. err = server.store.Update(func(tx *buntdb.Tx) error {
  255. dlineKey := fmt.Sprintf(keyDlineEntry, hostString)
  256. // assemble json from ban info
  257. b, err := json.Marshal(info)
  258. if err != nil {
  259. return err
  260. }
  261. tx.Set(dlineKey, string(b), nil)
  262. return nil
  263. })
  264. if err != nil {
  265. client.Notice(fmt.Sprintf("Could not successfully save new D-LINE: %s", err.Error()))
  266. return false
  267. }
  268. if hostNet == nil {
  269. server.dlines.AddIP(hostAddr, banTime, reason, operReason)
  270. } else {
  271. server.dlines.AddNetwork(*hostNet, banTime, reason, operReason)
  272. }
  273. var snoDescription string
  274. if durationIsUsed {
  275. client.Notice(fmt.Sprintf("Added temporary (%s) D-Line for %s", duration.String(), hostString))
  276. snoDescription = fmt.Sprintf(ircfmt.Unescape("%s$r added temporary (%s) D-Line for %s"), client.nick, duration.String(), hostString)
  277. } else {
  278. client.Notice(fmt.Sprintf("Added D-Line for %s", hostString))
  279. snoDescription = fmt.Sprintf(ircfmt.Unescape("%s$r added D-Line for %s"), client.nick, hostString)
  280. }
  281. server.snomasks.Send(sno.LocalXline, snoDescription)
  282. var killClient bool
  283. if andKill {
  284. var clientsToKill []*Client
  285. var killedClientNicks []string
  286. var toKill bool
  287. server.clients.ByNickMutex.RLock()
  288. for _, mcl := range server.clients.ByNick {
  289. if hostNet == nil {
  290. toKill = hostAddr.Equal(mcl.IP())
  291. } else {
  292. toKill = hostNet.Contains(mcl.IP())
  293. }
  294. if toKill {
  295. clientsToKill = append(clientsToKill, mcl)
  296. killedClientNicks = append(killedClientNicks, mcl.nick)
  297. }
  298. }
  299. server.clients.ByNickMutex.RUnlock()
  300. for _, mcl := range clientsToKill {
  301. mcl.exitedSnomaskSent = true
  302. mcl.Quit(fmt.Sprintf("You have been banned from this server (%s)", reason))
  303. if mcl == client {
  304. killClient = true
  305. } else {
  306. // if mcl == client, we kill them below
  307. mcl.destroy()
  308. }
  309. }
  310. // send snomask
  311. sort.Strings(killedClientNicks)
  312. server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s killed %d clients with a DLINE $c[grey][$r%s$c[grey]]"), client.nick, len(killedClientNicks), strings.Join(killedClientNicks, ", ")))
  313. }
  314. return killClient
  315. }
  316. func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
  317. // check oper permissions
  318. if !client.class.Capabilities["oper:local_unban"] {
  319. client.Send(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, "Insufficient oper privs")
  320. return false
  321. }
  322. // get host
  323. hostString := msg.Params[0]
  324. // check host
  325. var hostAddr net.IP
  326. var hostNet *net.IPNet
  327. _, hostNet, err := net.ParseCIDR(hostString)
  328. if err != nil {
  329. hostAddr = net.ParseIP(hostString)
  330. }
  331. if hostAddr == nil && hostNet == nil {
  332. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "Could not parse IP address or CIDR network")
  333. return false
  334. }
  335. if hostNet == nil {
  336. hostString = hostAddr.String()
  337. } else {
  338. hostString = hostNet.String()
  339. }
  340. // save in datastore
  341. err = server.store.Update(func(tx *buntdb.Tx) error {
  342. dlineKey := fmt.Sprintf(keyDlineEntry, hostString)
  343. // check if it exists or not
  344. val, err := tx.Get(dlineKey)
  345. if val == "" {
  346. return errNoExistingBan
  347. } else if err != nil {
  348. return err
  349. }
  350. tx.Delete(dlineKey)
  351. return nil
  352. })
  353. if err != nil {
  354. client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, fmt.Sprintf("Could not remove ban [%s]", err.Error()))
  355. return false
  356. }
  357. if hostNet == nil {
  358. server.dlines.RemoveIP(hostAddr)
  359. } else {
  360. server.dlines.RemoveNetwork(*hostNet)
  361. }
  362. client.Notice(fmt.Sprintf("Removed D-Line for %s", hostString))
  363. server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed D-Line for %s"), client.nick, hostString))
  364. return false
  365. }
  366. func (s *Server) loadDLines() {
  367. s.dlines = NewDLineManager()
  368. // load from datastore
  369. s.store.View(func(tx *buntdb.Tx) error {
  370. //TODO(dan): We could make this safer
  371. tx.AscendKeys("bans.dline *", func(key, value string) bool {
  372. // get address name
  373. key = key[len("bans.dline "):]
  374. // load addr/net
  375. var hostAddr net.IP
  376. var hostNet *net.IPNet
  377. _, hostNet, err := net.ParseCIDR(key)
  378. if err != nil {
  379. hostAddr = net.ParseIP(key)
  380. }
  381. // load ban info
  382. var info IPBanInfo
  383. json.Unmarshal([]byte(value), &info)
  384. // add to the server
  385. if hostNet == nil {
  386. s.dlines.AddIP(hostAddr, info.Time, info.Reason, info.OperReason)
  387. } else {
  388. s.dlines.AddNetwork(*hostNet, info.Time, info.Reason, info.OperReason)
  389. }
  390. return true // true to continue I guess?
  391. })
  392. return nil
  393. })
  394. }