-
Notifications
You must be signed in to change notification settings - Fork 0
/
dline.go
245 lines (212 loc) · 5.67 KB
/
dline.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
"fmt"
"net"
"sync"
"time"
"encoding/json"
"github.com/tidwall/buntdb"
)
const (
keyDlineEntry = "bans.dline %s"
)
// IPRestrictTime contains the expiration info about the given IP.
type IPRestrictTime struct {
// Duration is how long this block lasts for.
Duration time.Duration `json:"duration"`
// Expires is when this block expires.
Expires time.Time `json:"expires"`
}
// 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.
Reason string `json:"reason"`
// OperReason is an oper ban reason.
OperReason string `json:"oper_reason"`
// OperName is the oper who set the ban.
OperName string `json:"oper_name"`
// Time holds details about the duration, if it exists.
Time *IPRestrictTime `json:"time"`
}
// 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
}
// 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 {
sync.RWMutex // tier 1
// addresses that are dlined
addresses map[string]*dLineAddr
// networks that are dlined
networks map[string]*dLineNet
}
// NewDLineManager returns a new DLineManager.
func NewDLineManager() *DLineManager {
var dm DLineManager
dm.addresses = make(map[string]*dLineAddr)
dm.networks = make(map[string]*dLineNet)
return &dm
}
// AllBans returns all bans (for use with APIs, etc).
func (dm *DLineManager) AllBans() map[string]IPBanInfo {
allb := make(map[string]IPBanInfo)
dm.RLock()
defer dm.RUnlock()
for name, info := range dm.addresses {
allb[name] = info.Info
}
for name, info := range dm.networks {
allb[name] = info.Info
}
return allb
}
// AddNetwork adds a network to the blocked list.
func (dm *DLineManager) AddNetwork(network net.IPNet, length *IPRestrictTime, reason, operReason, operName string) {
netString := network.String()
dln := dLineNet{
Network: network,
Info: IPBanInfo{
Time: length,
Reason: reason,
OperReason: operReason,
OperName: operName,
},
}
dm.Lock()
dm.networks[netString] = &dln
dm.Unlock()
}
// RemoveNetwork removes a network from the blocked list.
func (dm *DLineManager) RemoveNetwork(network net.IPNet) {
netString := network.String()
dm.Lock()
delete(dm.networks, netString)
dm.Unlock()
}
// AddIP adds an IP address to the blocked list.
func (dm *DLineManager) AddIP(addr net.IP, length *IPRestrictTime, reason, operReason, operName string) {
addrString := addr.String()
dla := dLineAddr{
Address: addr,
Info: IPBanInfo{
Time: length,
Reason: reason,
OperReason: operReason,
OperName: operName,
},
}
dm.Lock()
dm.addresses[addrString] = &dla
dm.Unlock()
}
// RemoveIP removes an IP from the blocked list.
func (dm *DLineManager) RemoveIP(addr net.IP) {
addrString := addr.String()
dm.Lock()
delete(dm.addresses, addrString)
dm.Unlock()
}
// 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()
dm.RLock()
addrInfo := dm.addresses[addrString]
dm.RUnlock()
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 {
return true, &addrInfo.Info
}
}
// check networks
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)
}
}
}()
}
}()
dm.RLock()
defer dm.RUnlock()
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
}
}
// no matches!
return false, nil
}
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 {
// get address name
key = key[len("bans.dline "):]
// 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)
// set opername if it isn't already set
if info.OperName == "" {
info.OperName = s.name
}
// add to the server
if hostNet == nil {
s.dlines.AddIP(hostAddr, info.Time, info.Reason, info.OperReason, info.OperName)
} else {
s.dlines.AddNetwork(*hostNet, info.Time, info.Reason, info.OperReason, info.OperName)
}
return true // true to continue I guess?
})
return nil
})
}