2017-03-27 14:15:02 +02:00
|
|
|
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
2016-11-04 03:42:58 +01:00
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package irc
|
|
|
|
|
2016-11-04 12:14:52 +01:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2017-09-29 08:07:09 +02:00
|
|
|
"sync"
|
2016-11-04 12:14:52 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"github.com/tidwall/buntdb"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
keyDlineEntry = "bans.dline %s"
|
|
|
|
)
|
|
|
|
|
2016-11-04 03:42:58 +01:00
|
|
|
// IPRestrictTime contains the expiration info about the given IP.
|
|
|
|
type IPRestrictTime struct {
|
2016-11-04 12:14:52 +01:00
|
|
|
// Duration is how long this block lasts for.
|
2017-03-07 10:55:14 +01:00
|
|
|
Duration time.Duration `json:"duration"`
|
2016-11-04 03:42:58 +01:00
|
|
|
// Expires is when this block expires.
|
2017-03-07 10:55:14 +01:00
|
|
|
Expires time.Time `json:"expires"`
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsExpired returns true if the time has expired.
|
|
|
|
func (iptime *IPRestrictTime) IsExpired() bool {
|
|
|
|
return iptime.Expires.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
// IPBanInfo holds info about an IP/net ban.
|
|
|
|
type IPBanInfo struct {
|
|
|
|
// Reason is the ban reason.
|
2017-03-07 10:55:14 +01:00
|
|
|
Reason string `json:"reason"`
|
2016-11-04 03:42:58 +01:00
|
|
|
// OperReason is an oper ban reason.
|
2016-11-04 12:14:52 +01:00
|
|
|
OperReason string `json:"oper_reason"`
|
2017-11-19 01:27:40 +01:00
|
|
|
// OperName is the oper who set the ban.
|
|
|
|
OperName string `json:"oper_name"`
|
2016-11-04 03:42:58 +01:00
|
|
|
// Time holds details about the duration, if it exists.
|
2017-03-07 10:55:14 +01:00
|
|
|
Time *IPRestrictTime `json:"time"`
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
|
2017-09-11 09:13:53 +02:00
|
|
|
// BanMessage returns the ban message.
|
|
|
|
func (info IPBanInfo) BanMessage(message string) string {
|
|
|
|
message = fmt.Sprintf(message, info.Reason)
|
|
|
|
if info.Time != nil {
|
|
|
|
message += fmt.Sprintf(" [%s]", info.Time.Duration.String())
|
|
|
|
}
|
|
|
|
return message
|
|
|
|
}
|
|
|
|
|
2016-11-04 03:42:58 +01:00
|
|
|
// dLineAddr contains the address itself and expiration time for a given network.
|
|
|
|
type dLineAddr struct {
|
|
|
|
// Address is the address that is blocked.
|
|
|
|
Address net.IP
|
|
|
|
// Info contains information on the ban.
|
|
|
|
Info IPBanInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
// dLineNet contains the net itself and expiration time for a given network.
|
|
|
|
type dLineNet struct {
|
|
|
|
// Network is the network that is blocked.
|
|
|
|
Network net.IPNet
|
|
|
|
// Info contains information on the ban.
|
|
|
|
Info IPBanInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
// DLineManager manages and dlines.
|
|
|
|
type DLineManager struct {
|
2017-11-22 10:41:11 +01:00
|
|
|
sync.RWMutex // tier 1
|
2016-11-04 03:42:58 +01:00
|
|
|
// addresses that are dlined
|
|
|
|
addresses map[string]*dLineAddr
|
|
|
|
// networks that are dlined
|
|
|
|
networks map[string]*dLineNet
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewDLineManager returns a new DLineManager.
|
|
|
|
func NewDLineManager() *DLineManager {
|
2016-11-04 12:14:52 +01:00
|
|
|
var dm DLineManager
|
|
|
|
dm.addresses = make(map[string]*dLineAddr)
|
|
|
|
dm.networks = make(map[string]*dLineNet)
|
2016-11-04 03:42:58 +01:00
|
|
|
return &dm
|
|
|
|
}
|
|
|
|
|
2016-11-06 02:05:29 +01:00
|
|
|
// AllBans returns all bans (for use with APIs, etc).
|
|
|
|
func (dm *DLineManager) AllBans() map[string]IPBanInfo {
|
|
|
|
allb := make(map[string]IPBanInfo)
|
|
|
|
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.RLock()
|
|
|
|
defer dm.RUnlock()
|
|
|
|
|
2016-11-06 02:05:29 +01:00
|
|
|
for name, info := range dm.addresses {
|
|
|
|
allb[name] = info.Info
|
|
|
|
}
|
|
|
|
for name, info := range dm.networks {
|
|
|
|
allb[name] = info.Info
|
|
|
|
}
|
|
|
|
|
|
|
|
return allb
|
|
|
|
}
|
|
|
|
|
2016-11-04 03:42:58 +01:00
|
|
|
// AddNetwork adds a network to the blocked list.
|
2017-11-19 01:27:40 +01:00
|
|
|
func (dm *DLineManager) AddNetwork(network net.IPNet, length *IPRestrictTime, reason, operReason, operName string) {
|
2016-11-04 03:42:58 +01:00
|
|
|
netString := network.String()
|
|
|
|
dln := dLineNet{
|
|
|
|
Network: network,
|
|
|
|
Info: IPBanInfo{
|
|
|
|
Time: length,
|
2016-11-04 12:14:52 +01:00
|
|
|
Reason: reason,
|
|
|
|
OperReason: operReason,
|
2017-11-19 01:27:40 +01:00
|
|
|
OperName: operName,
|
2016-11-04 03:42:58 +01:00
|
|
|
},
|
|
|
|
}
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Lock()
|
2016-11-04 03:42:58 +01:00
|
|
|
dm.networks[netString] = &dln
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Unlock()
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveNetwork removes a network from the blocked list.
|
|
|
|
func (dm *DLineManager) RemoveNetwork(network net.IPNet) {
|
|
|
|
netString := network.String()
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Lock()
|
2016-11-04 03:42:58 +01:00
|
|
|
delete(dm.networks, netString)
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Unlock()
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddIP adds an IP address to the blocked list.
|
2017-11-19 01:27:40 +01:00
|
|
|
func (dm *DLineManager) AddIP(addr net.IP, length *IPRestrictTime, reason, operReason, operName string) {
|
2016-11-04 03:42:58 +01:00
|
|
|
addrString := addr.String()
|
|
|
|
dla := dLineAddr{
|
|
|
|
Address: addr,
|
|
|
|
Info: IPBanInfo{
|
|
|
|
Time: length,
|
2016-11-04 12:14:52 +01:00
|
|
|
Reason: reason,
|
|
|
|
OperReason: operReason,
|
2017-11-19 01:27:40 +01:00
|
|
|
OperName: operName,
|
2016-11-04 03:42:58 +01:00
|
|
|
},
|
|
|
|
}
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Lock()
|
2016-11-04 03:42:58 +01:00
|
|
|
dm.addresses[addrString] = &dla
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Unlock()
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveIP removes an IP from the blocked list.
|
|
|
|
func (dm *DLineManager) RemoveIP(addr net.IP) {
|
|
|
|
addrString := addr.String()
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Lock()
|
2016-11-04 03:42:58 +01:00
|
|
|
delete(dm.addresses, addrString)
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.Unlock()
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
|
|
|
|
func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info *IPBanInfo) {
|
|
|
|
// check IP addr
|
|
|
|
addrString := addr.String()
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.RLock()
|
2016-11-04 03:42:58 +01:00
|
|
|
addrInfo := dm.addresses[addrString]
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.RUnlock()
|
|
|
|
|
2016-11-04 03:42:58 +01:00
|
|
|
if addrInfo != nil {
|
|
|
|
if addrInfo.Info.Time != nil {
|
|
|
|
if addrInfo.Info.Time.IsExpired() {
|
|
|
|
// ban on IP has expired, remove it from our blocked list
|
|
|
|
dm.RemoveIP(addr)
|
|
|
|
} else {
|
|
|
|
return true, &addrInfo.Info
|
|
|
|
}
|
|
|
|
} else {
|
2016-11-04 13:15:14 +01:00
|
|
|
return true, &addrInfo.Info
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check networks
|
2017-09-29 08:07:09 +02:00
|
|
|
doCleanup := false
|
|
|
|
defer func() {
|
|
|
|
if doCleanup {
|
|
|
|
go func() {
|
|
|
|
dm.Lock()
|
|
|
|
defer dm.Unlock()
|
|
|
|
for key, netInfo := range dm.networks {
|
|
|
|
if netInfo.Info.Time.IsExpired() {
|
|
|
|
delete(dm.networks, key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
2017-09-29 08:07:09 +02:00
|
|
|
}()
|
2016-11-04 03:42:58 +01:00
|
|
|
|
2017-09-29 08:07:09 +02:00
|
|
|
dm.RLock()
|
|
|
|
defer dm.RUnlock()
|
2016-11-04 03:42:58 +01:00
|
|
|
|
2017-09-29 08:07:09 +02:00
|
|
|
for _, netInfo := range dm.networks {
|
|
|
|
if netInfo.Info.Time != nil && netInfo.Info.Time.IsExpired() {
|
|
|
|
// expired ban, ignore and clean up later
|
|
|
|
doCleanup = true
|
|
|
|
} else if netInfo.Network.Contains(addr) {
|
|
|
|
return true, &netInfo.Info
|
|
|
|
}
|
2016-11-04 03:42:58 +01:00
|
|
|
}
|
|
|
|
// no matches!
|
|
|
|
return false, nil
|
|
|
|
}
|
2016-11-04 12:14:52 +01:00
|
|
|
|
|
|
|
func (s *Server) loadDLines() {
|
|
|
|
s.dlines = NewDLineManager()
|
|
|
|
|
|
|
|
// load from datastore
|
|
|
|
s.store.View(func(tx *buntdb.Tx) error {
|
|
|
|
//TODO(dan): We could make this safer
|
|
|
|
tx.AscendKeys("bans.dline *", func(key, value string) bool {
|
2016-11-04 13:15:14 +01:00
|
|
|
// get address name
|
|
|
|
key = key[len("bans.dline "):]
|
|
|
|
|
2016-11-04 12:14:52 +01:00
|
|
|
// load addr/net
|
|
|
|
var hostAddr net.IP
|
|
|
|
var hostNet *net.IPNet
|
|
|
|
_, hostNet, err := net.ParseCIDR(key)
|
|
|
|
if err != nil {
|
|
|
|
hostAddr = net.ParseIP(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// load ban info
|
|
|
|
var info IPBanInfo
|
|
|
|
json.Unmarshal([]byte(value), &info)
|
|
|
|
|
2017-11-19 01:27:40 +01:00
|
|
|
// set opername if it isn't already set
|
|
|
|
if info.OperName == "" {
|
|
|
|
info.OperName = s.name
|
|
|
|
}
|
|
|
|
|
2016-11-04 12:14:52 +01:00
|
|
|
// add to the server
|
|
|
|
if hostNet == nil {
|
2017-11-19 01:27:40 +01:00
|
|
|
s.dlines.AddIP(hostAddr, info.Time, info.Reason, info.OperReason, info.OperName)
|
2016-11-04 12:14:52 +01:00
|
|
|
} else {
|
2017-11-19 01:27:40 +01:00
|
|
|
s.dlines.AddNetwork(*hostNet, info.Time, info.Reason, info.OperReason, info.OperName)
|
2016-11-04 12:14:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true // true to continue I guess?
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|