Bladeren bron

Merge pull request #329 from slingamn/dkline_refactor.4

refactor [dk]lines
tags/v1.0.0-rc1
Shivaram Lingamneni 5 jaren geleden
bovenliggende
commit
14ce8d850e
No account linked to committer's email address
7 gewijzigde bestanden met toevoegingen van 683 en 398 verwijderingen
  1. 119
    2
      irc/database.go
  2. 198
    139
      irc/dline.go
  3. 52
    190
      irc/handlers.go
  4. 149
    58
      irc/kline.go
  5. 4
    9
      irc/server.go
  6. 62
    0
      irc/utils/net.go
  7. 99
    0
      irc/utils/net_test.go

+ 119
- 2
irc/database.go Bestand weergeven

@@ -22,7 +22,7 @@ const (
22 22
 	// 'version' of the database schema
23 23
 	keySchemaVersion = "db.version"
24 24
 	// latest schema of the db
25
-	latestDbSchema = "3"
25
+	latestDbSchema = "4"
26 26
 )
27 27
 
28 28
 type SchemaChanger func(*Config, *buntdb.Tx) error
@@ -190,7 +190,7 @@ func UpgradeDB(config *Config) (err error) {
190 190
 	})
191 191
 
192 192
 	if err != nil {
193
-		log.Println("database upgrade failed and was rolled back")
193
+		log.Printf("database upgrade failed and was rolled back: %v\n", err)
194 194
 	}
195 195
 	return err
196 196
 }
@@ -278,6 +278,118 @@ func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
278 278
 	return nil
279 279
 }
280 280
 
281
+// 1. ban info format changed (from `legacyBanInfo` below to `IPBanInfo`)
282
+// 2. dlines against individual IPs are normalized into dlines against the appropriate /128 network
283
+func schemaChangeV3ToV4(config *Config, tx *buntdb.Tx) error {
284
+	type ipRestrictTime struct {
285
+		Duration time.Duration
286
+		Expires  time.Time
287
+	}
288
+	type legacyBanInfo struct {
289
+		Reason     string          `json:"reason"`
290
+		OperReason string          `json:"oper_reason"`
291
+		OperName   string          `json:"oper_name"`
292
+		Time       *ipRestrictTime `json:"time"`
293
+	}
294
+
295
+	now := time.Now()
296
+	legacyToNewInfo := func(old legacyBanInfo) (new_ IPBanInfo) {
297
+		new_.Reason = old.Reason
298
+		new_.OperReason = old.OperReason
299
+		new_.OperName = old.OperName
300
+
301
+		if old.Time == nil {
302
+			new_.TimeCreated = now
303
+			new_.Duration = 0
304
+		} else {
305
+			new_.TimeCreated = old.Time.Expires.Add(-1 * old.Time.Duration)
306
+			new_.Duration = old.Time.Duration
307
+		}
308
+		return
309
+	}
310
+
311
+	var keysToDelete []string
312
+
313
+	prefix := "bans.dline "
314
+	dlines := make(map[string]IPBanInfo)
315
+	tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
316
+		if !strings.HasPrefix(key, prefix) {
317
+			return false
318
+		}
319
+		keysToDelete = append(keysToDelete, key)
320
+
321
+		var lbinfo legacyBanInfo
322
+		id := strings.TrimPrefix(key, prefix)
323
+		err := json.Unmarshal([]byte(value), &lbinfo)
324
+		if err != nil {
325
+			log.Printf("error unmarshaling legacy dline: %v\n", err)
326
+			return true
327
+		}
328
+		// legacy keys can be either an IP or a CIDR
329
+		hostNet, err := utils.NormalizedNetFromString(id)
330
+		if err != nil {
331
+			log.Printf("error unmarshaling legacy dline network: %v\n", err)
332
+			return true
333
+		}
334
+		dlines[utils.NetToNormalizedString(hostNet)] = legacyToNewInfo(lbinfo)
335
+
336
+		return true
337
+	})
338
+
339
+	setOptions := func(info IPBanInfo) *buntdb.SetOptions {
340
+		if info.Duration == 0 {
341
+			return nil
342
+		}
343
+		ttl := info.TimeCreated.Add(info.Duration).Sub(now)
344
+		return &buntdb.SetOptions{Expires: true, TTL: ttl}
345
+	}
346
+
347
+	// store the new dlines
348
+	for id, info := range dlines {
349
+		b, err := json.Marshal(info)
350
+		if err != nil {
351
+			log.Printf("error marshaling migrated dline: %v\n", err)
352
+			continue
353
+		}
354
+		tx.Set(fmt.Sprintf("bans.dlinev2 %s", id), string(b), setOptions(info))
355
+	}
356
+
357
+	// same operations against klines
358
+	prefix = "bans.kline "
359
+	klines := make(map[string]IPBanInfo)
360
+	tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
361
+		if !strings.HasPrefix(key, prefix) {
362
+			return false
363
+		}
364
+		keysToDelete = append(keysToDelete, key)
365
+		mask := strings.TrimPrefix(key, prefix)
366
+		var lbinfo legacyBanInfo
367
+		err := json.Unmarshal([]byte(value), &lbinfo)
368
+		if err != nil {
369
+			log.Printf("error unmarshaling legacy kline: %v\n", err)
370
+			return true
371
+		}
372
+		klines[mask] = legacyToNewInfo(lbinfo)
373
+		return true
374
+	})
375
+
376
+	for mask, info := range klines {
377
+		b, err := json.Marshal(info)
378
+		if err != nil {
379
+			log.Printf("error marshaling migrated kline: %v\n", err)
380
+			continue
381
+		}
382
+		tx.Set(fmt.Sprintf("bans.klinev2 %s", mask), string(b), setOptions(info))
383
+	}
384
+
385
+	// clean up all the old entries
386
+	for _, key := range keysToDelete {
387
+		tx.Delete(key)
388
+	}
389
+
390
+	return nil
391
+}
392
+
281 393
 func init() {
282 394
 	allChanges := []SchemaChange{
283 395
 		{
@@ -290,6 +402,11 @@ func init() {
290 402
 			TargetVersion:  "3",
291 403
 			Changer:        schemaChangeV2ToV3,
292 404
 		},
405
+		{
406
+			InitialVersion: "3",
407
+			TargetVersion:  "4",
408
+			Changer:        schemaChangeV3ToV4,
409
+		},
293 410
 	}
294 411
 
295 412
 	// build the index

+ 198
- 139
irc/dline.go Bestand weergeven

@@ -4,33 +4,21 @@
4 4
 package irc
5 5
 
6 6
 import (
7
+	"encoding/json"
7 8
 	"fmt"
8 9
 	"net"
10
+	"strings"
9 11
 	"sync"
10 12
 	"time"
11 13
 
12
-	"encoding/json"
13
-
14
+	"github.com/oragono/oragono/irc/utils"
14 15
 	"github.com/tidwall/buntdb"
15 16
 )
16 17
 
17 18
 const (
18
-	keyDlineEntry = "bans.dline %s"
19
+	keyDlineEntry = "bans.dlinev2 %s"
19 20
 )
20 21
 
21
-// IPRestrictTime contains the expiration info about the given IP.
22
-type IPRestrictTime struct {
23
-	// Duration is how long this block lasts for.
24
-	Duration time.Duration `json:"duration"`
25
-	// Expires is when this block expires.
26
-	Expires time.Time `json:"expires"`
27
-}
28
-
29
-// IsExpired returns true if the time has expired.
30
-func (iptime *IPRestrictTime) IsExpired() bool {
31
-	return iptime.Expires.Before(time.Now())
32
-}
33
-
34 22
 // IPBanInfo holds info about an IP/net ban.
35 23
 type IPBanInfo struct {
36 24
 	// Reason is the ban reason.
@@ -39,30 +27,38 @@ type IPBanInfo struct {
39 27
 	OperReason string `json:"oper_reason"`
40 28
 	// OperName is the oper who set the ban.
41 29
 	OperName string `json:"oper_name"`
42
-	// Time holds details about the duration, if it exists.
43
-	Time *IPRestrictTime `json:"time"`
30
+	// time of ban creation
31
+	TimeCreated time.Time
32
+	// duration of the ban; 0 means "permanent"
33
+	Duration time.Duration
34
+}
35
+
36
+func (info IPBanInfo) timeLeft() time.Duration {
37
+	return info.TimeCreated.Add(info.Duration).Sub(time.Now())
38
+}
39
+
40
+func (info IPBanInfo) TimeLeft() string {
41
+	if info.Duration == 0 {
42
+		return "indefinite"
43
+	} else {
44
+		return info.timeLeft().Truncate(time.Second).String()
45
+	}
44 46
 }
45 47
 
46 48
 // BanMessage returns the ban message.
47 49
 func (info IPBanInfo) BanMessage(message string) string {
48 50
 	message = fmt.Sprintf(message, info.Reason)
49
-	if info.Time != nil {
50
-		message += fmt.Sprintf(" [%s]", info.Time.Duration.String())
51
+	if info.Duration != 0 {
52
+		message += fmt.Sprintf(" [%s]", info.TimeLeft())
51 53
 	}
52 54
 	return message
53 55
 }
54 56
 
55
-// dLineAddr contains the address itself and expiration time for a given network.
56
-type dLineAddr struct {
57
-	// Address is the address that is blocked.
58
-	Address net.IP
59
-	// Info contains information on the ban.
60
-	Info IPBanInfo
61
-}
62
-
63 57
 // dLineNet contains the net itself and expiration time for a given network.
64 58
 type dLineNet struct {
65 59
 	// Network is the network that is blocked.
60
+	// This is always an IPv6 CIDR; IPv4 CIDRs are translated with the 4-in-6 prefix,
61
+	// individual IPv4 and IPV6 addresses are translated to the relevant /128.
66 62
 	Network net.IPNet
67 63
 	// Info contains information on the ban.
68 64
 	Info IPBanInfo
@@ -70,18 +66,26 @@ type dLineNet struct {
70 66
 
71 67
 // DLineManager manages and dlines.
72 68
 type DLineManager struct {
73
-	sync.RWMutex // tier 1
74
-	// addresses that are dlined
75
-	addresses map[string]*dLineAddr
76
-	// networks that are dlined
77
-	networks map[string]*dLineNet
69
+	sync.RWMutex                // tier 1
70
+	persistenceMutex sync.Mutex // tier 2
71
+	// networks that are dlined:
72
+	// XXX: the keys of this map (which are also the database persistence keys)
73
+	// are the human-readable representations returned by NetToNormalizedString
74
+	networks map[string]dLineNet
75
+	// this keeps track of expiration timers for temporary bans
76
+	expirationTimers map[string]*time.Timer
77
+	server           *Server
78 78
 }
79 79
 
80 80
 // NewDLineManager returns a new DLineManager.
81
-func NewDLineManager() *DLineManager {
81
+func NewDLineManager(server *Server) *DLineManager {
82 82
 	var dm DLineManager
83
-	dm.addresses = make(map[string]*dLineAddr)
84
-	dm.networks = make(map[string]*dLineNet)
83
+	dm.networks = make(map[string]dLineNet)
84
+	dm.expirationTimers = make(map[string]*time.Timer)
85
+	dm.server = server
86
+
87
+	dm.loadFromDatastore()
88
+
85 89
 	return &dm
86 90
 }
87 91
 
@@ -92,154 +96,209 @@ func (dm *DLineManager) AllBans() map[string]IPBanInfo {
92 96
 	dm.RLock()
93 97
 	defer dm.RUnlock()
94 98
 
95
-	for name, info := range dm.addresses {
96
-		allb[name] = info.Info
97
-	}
98
-	for name, info := range dm.networks {
99
-		allb[name] = info.Info
99
+	// map keys are already the human-readable forms, just return a copy of the map
100
+	for key, info := range dm.networks {
101
+		allb[key] = info.Info
100 102
 	}
101 103
 
102 104
 	return allb
103 105
 }
104 106
 
105 107
 // AddNetwork adds a network to the blocked list.
106
-func (dm *DLineManager) AddNetwork(network net.IPNet, length *IPRestrictTime, reason, operReason, operName string) {
107
-	netString := network.String()
108
-	dln := dLineNet{
109
-		Network: network,
110
-		Info: IPBanInfo{
111
-			Time:       length,
112
-			Reason:     reason,
113
-			OperReason: operReason,
114
-			OperName:   operName,
115
-		},
108
+func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, reason, operReason, operName string) error {
109
+	dm.persistenceMutex.Lock()
110
+	defer dm.persistenceMutex.Unlock()
111
+
112
+	// assemble ban info
113
+	info := IPBanInfo{
114
+		Reason:      reason,
115
+		OperReason:  operReason,
116
+		OperName:    operName,
117
+		TimeCreated: time.Now(),
118
+		Duration:    duration,
116 119
 	}
117
-	dm.Lock()
118
-	dm.networks[netString] = &dln
119
-	dm.Unlock()
120
-}
121 120
 
122
-// RemoveNetwork removes a network from the blocked list.
123
-func (dm *DLineManager) RemoveNetwork(network net.IPNet) {
124
-	netString := network.String()
125
-	dm.Lock()
126
-	delete(dm.networks, netString)
127
-	dm.Unlock()
121
+	id := dm.addNetworkInternal(network, info)
122
+	return dm.persistDline(id, info)
128 123
 }
129 124
 
130
-// AddIP adds an IP address to the blocked list.
131
-func (dm *DLineManager) AddIP(addr net.IP, length *IPRestrictTime, reason, operReason, operName string) {
132
-	addrString := addr.String()
133
-	dla := dLineAddr{
134
-		Address: addr,
135
-		Info: IPBanInfo{
136
-			Time:       length,
137
-			Reason:     reason,
138
-			OperReason: operReason,
139
-			OperName:   operName,
140
-		},
125
+func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (id string) {
126
+	network = utils.NormalizeNet(network)
127
+	id = utils.NetToNormalizedString(network)
128
+
129
+	var timeLeft time.Duration
130
+	if info.Duration != 0 {
131
+		timeLeft = info.timeLeft()
132
+		if timeLeft <= 0 {
133
+			return
134
+		}
141 135
 	}
136
+
142 137
 	dm.Lock()
143
-	dm.addresses[addrString] = &dla
144
-	dm.Unlock()
138
+	defer dm.Unlock()
139
+
140
+	dm.networks[id] = dLineNet{
141
+		Network: network,
142
+		Info:    info,
143
+	}
144
+
145
+	dm.cancelTimer(id)
146
+
147
+	if info.Duration == 0 {
148
+		return
149
+	}
150
+
151
+	// set up new expiration timer
152
+	timeCreated := info.TimeCreated
153
+	processExpiration := func() {
154
+		dm.Lock()
155
+		defer dm.Unlock()
156
+
157
+		netBan, ok := dm.networks[id]
158
+		if ok && netBan.Info.TimeCreated.Equal(timeCreated) {
159
+			delete(dm.networks, id)
160
+			// TODO(slingamn) here's where we'd remove it from the radix tree
161
+			delete(dm.expirationTimers, id)
162
+		}
163
+	}
164
+	dm.expirationTimers[id] = time.AfterFunc(timeLeft, processExpiration)
165
+
166
+	return
145 167
 }
146 168
 
147
-// RemoveIP removes an IP from the blocked list.
148
-func (dm *DLineManager) RemoveIP(addr net.IP) {
149
-	addrString := addr.String()
150
-	dm.Lock()
151
-	delete(dm.addresses, addrString)
152
-	dm.Unlock()
169
+func (dm *DLineManager) cancelTimer(id string) {
170
+	oldTimer := dm.expirationTimers[id]
171
+	if oldTimer != nil {
172
+		oldTimer.Stop()
173
+		delete(dm.expirationTimers, id)
174
+	}
153 175
 }
154 176
 
155
-// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
156
-func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info *IPBanInfo) {
157
-	// check IP addr
158
-	addrString := addr.String()
159
-	dm.RLock()
160
-	addrInfo := dm.addresses[addrString]
161
-	dm.RUnlock()
162
-
163
-	if addrInfo != nil {
164
-		if addrInfo.Info.Time != nil {
165
-			if addrInfo.Info.Time.IsExpired() {
166
-				// ban on IP has expired, remove it from our blocked list
167
-				dm.RemoveIP(addr)
168
-			} else {
169
-				return true, &addrInfo.Info
170
-			}
171
-		} else {
172
-			return true, &addrInfo.Info
173
-		}
177
+func (dm *DLineManager) persistDline(id string, info IPBanInfo) error {
178
+	// save in datastore
179
+	dlineKey := fmt.Sprintf(keyDlineEntry, id)
180
+	// assemble json from ban info
181
+	b, err := json.Marshal(info)
182
+	if err != nil {
183
+		dm.server.logger.Error("internal", "couldn't marshal d-line", err.Error())
184
+		return err
185
+	}
186
+	bstr := string(b)
187
+	var setOptions *buntdb.SetOptions
188
+	if info.Duration != 0 {
189
+		setOptions = &buntdb.SetOptions{Expires: true, TTL: info.Duration}
174 190
 	}
175 191
 
176
-	// check networks
177
-	doCleanup := false
178
-	defer func() {
179
-		if doCleanup {
180
-			go func() {
181
-				dm.Lock()
182
-				defer dm.Unlock()
183
-				for key, netInfo := range dm.networks {
184
-					if netInfo.Info.Time.IsExpired() {
185
-						delete(dm.networks, key)
186
-					}
187
-				}
188
-			}()
189
-		}
192
+	err = dm.server.store.Update(func(tx *buntdb.Tx) error {
193
+		_, _, err := tx.Set(dlineKey, bstr, setOptions)
194
+		return err
195
+	})
196
+	if err != nil {
197
+		dm.server.logger.Error("internal", "couldn't store d-line", err.Error())
198
+	}
199
+	return err
200
+}
201
+
202
+func (dm *DLineManager) unpersistDline(id string) error {
203
+	dlineKey := fmt.Sprintf(keyDlineEntry, id)
204
+	return dm.server.store.Update(func(tx *buntdb.Tx) error {
205
+		_, err := tx.Delete(dlineKey)
206
+		return err
207
+	})
208
+}
209
+
210
+// RemoveNetwork removes a network from the blocked list.
211
+func (dm *DLineManager) RemoveNetwork(network net.IPNet) error {
212
+	dm.persistenceMutex.Lock()
213
+	defer dm.persistenceMutex.Unlock()
214
+
215
+	id := utils.NetToNormalizedString(utils.NormalizeNet(network))
216
+
217
+	present := func() bool {
218
+		dm.Lock()
219
+		defer dm.Unlock()
220
+		_, ok := dm.networks[id]
221
+		delete(dm.networks, id)
222
+		dm.cancelTimer(id)
223
+		return ok
190 224
 	}()
191 225
 
226
+	if !present {
227
+		return errNoExistingBan
228
+	}
229
+
230
+	return dm.unpersistDline(id)
231
+}
232
+
233
+// AddIP adds an IP address to the blocked list.
234
+func (dm *DLineManager) AddIP(addr net.IP, duration time.Duration, reason, operReason, operName string) error {
235
+	return dm.AddNetwork(utils.NormalizeIPToNet(addr), duration, reason, operReason, operName)
236
+}
237
+
238
+// RemoveIP removes an IP address from the blocked list.
239
+func (dm *DLineManager) RemoveIP(addr net.IP) error {
240
+	return dm.RemoveNetwork(utils.NormalizeIPToNet(addr))
241
+}
242
+
243
+// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
244
+func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info IPBanInfo) {
245
+	addr = addr.To16() // almost certainly unnecessary
246
+
192 247
 	dm.RLock()
193 248
 	defer dm.RUnlock()
194 249
 
195
-	for _, netInfo := range dm.networks {
196
-		if netInfo.Info.Time != nil && netInfo.Info.Time.IsExpired() {
197
-			// expired ban, ignore and clean up later
198
-			doCleanup = true
199
-		} else if netInfo.Network.Contains(addr) {
200
-			return true, &netInfo.Info
250
+	// check networks
251
+	// TODO(slingamn) use a radix tree as the data plane for this
252
+	for _, netBan := range dm.networks {
253
+		if netBan.Network.Contains(addr) {
254
+			return true, netBan.Info
201 255
 		}
202 256
 	}
203 257
 	// no matches!
204
-	return false, nil
258
+	isBanned = false
259
+	return
205 260
 }
206 261
 
207
-func (s *Server) loadDLines() {
208
-	s.dlines = NewDLineManager()
262
+func (dm *DLineManager) loadFromDatastore() {
263
+	dlinePrefix := fmt.Sprintf(keyDlineEntry, "")
264
+	dm.server.store.View(func(tx *buntdb.Tx) error {
265
+		tx.AscendGreaterOrEqual("", dlinePrefix, func(key, value string) bool {
266
+			if !strings.HasPrefix(key, dlinePrefix) {
267
+				return false
268
+			}
209 269
 
210
-	// load from datastore
211
-	s.store.View(func(tx *buntdb.Tx) error {
212
-		//TODO(dan): We could make this safer
213
-		tx.AscendKeys("bans.dline *", func(key, value string) bool {
214 270
 			// get address name
215
-			key = key[len("bans.dline "):]
271
+			key = strings.TrimPrefix(key, dlinePrefix)
216 272
 
217 273
 			// load addr/net
218
-			var hostAddr net.IP
219
-			var hostNet *net.IPNet
220
-			_, hostNet, err := net.ParseCIDR(key)
274
+			hostNet, err := utils.NormalizedNetFromString(key)
221 275
 			if err != nil {
222
-				hostAddr = net.ParseIP(key)
276
+				dm.server.logger.Error("internal", "bad dline cidr", err.Error())
277
+				return true
223 278
 			}
224 279
 
225 280
 			// load ban info
226 281
 			var info IPBanInfo
227
-			json.Unmarshal([]byte(value), &info)
282
+			err = json.Unmarshal([]byte(value), &info)
283
+			if err != nil {
284
+				dm.server.logger.Error("internal", "bad dline data", err.Error())
285
+				return true
286
+			}
228 287
 
229 288
 			// set opername if it isn't already set
230 289
 			if info.OperName == "" {
231
-				info.OperName = s.name
290
+				info.OperName = dm.server.name
232 291
 			}
233 292
 
234 293
 			// add to the server
235
-			if hostNet == nil {
236
-				s.dlines.AddIP(hostAddr, info.Time, info.Reason, info.OperReason, info.OperName)
237
-			} else {
238
-				s.dlines.AddNetwork(*hostNet, info.Time, info.Reason, info.OperReason, info.OperName)
239
-			}
294
+			dm.addNetworkInternal(hostNet, info)
240 295
 
241
-			return true // true to continue I guess?
296
+			return true
242 297
 		})
243 298
 		return nil
244 299
 	})
245 300
 }
301
+
302
+func (s *Server) loadDLines() {
303
+	s.dlines = NewDLineManager(s)
304
+}

+ 52
- 190
irc/handlers.go Bestand weergeven

@@ -9,9 +9,7 @@ package irc
9 9
 import (
10 10
 	"bytes"
11 11
 	"encoding/base64"
12
-	"encoding/json"
13 12
 	"fmt"
14
-	"net"
15 13
 	"os"
16 14
 	"runtime"
17 15
 	"runtime/debug"
@@ -30,7 +28,6 @@ import (
30 28
 	"github.com/oragono/oragono/irc/modes"
31 29
 	"github.com/oragono/oragono/irc/sno"
32 30
 	"github.com/oragono/oragono/irc/utils"
33
-	"github.com/tidwall/buntdb"
34 31
 	"golang.org/x/crypto/bcrypt"
35 32
 )
36 33
 
@@ -578,6 +575,33 @@ func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
578 575
 	return false
579 576
 }
580 577
 
578
+// helper for parsing the reason args to DLINE and KLINE
579
+func getReasonsFromParams(params []string, currentArg int) (reason, operReason string) {
580
+	reason = "No reason given"
581
+	operReason = ""
582
+	if len(params) > currentArg {
583
+		reasons := strings.SplitN(strings.Join(params[currentArg:], " "), "|", 2)
584
+		if len(reasons) == 1 {
585
+			reason = strings.TrimSpace(reasons[0])
586
+		} else if len(reasons) == 2 {
587
+			reason = strings.TrimSpace(reasons[0])
588
+			operReason = strings.TrimSpace(reasons[1])
589
+		}
590
+	}
591
+	return
592
+}
593
+
594
+func formatBanForListing(client *Client, key string, info IPBanInfo) string {
595
+	desc := info.Reason
596
+	if info.OperReason != "" && info.OperReason != info.Reason {
597
+		desc = fmt.Sprintf("%s | %s", info.Reason, info.OperReason)
598
+	}
599
+	if info.Duration != 0 {
600
+		desc = fmt.Sprintf("%s [%s]", desc, info.TimeLeft())
601
+	}
602
+	return fmt.Sprintf(client.t("Ban - %[1]s - added by %[2]s - %[3]s"), key, info.OperName, desc)
603
+}
604
+
581 605
 // DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
582 606
 // DLINE LIST
583 607
 func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
@@ -599,7 +623,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
599 623
 		}
600 624
 
601 625
 		for key, info := range bans {
602
-			rb.Notice(fmt.Sprintf(client.t("Ban - %[1]s - added by %[2]s - %[3]s"), key, info.OperName, info.BanMessage("%s")))
626
+			client.Notice(formatBanForListing(client, key, info))
603 627
 		}
604 628
 
605 629
 		return false
@@ -622,8 +646,9 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
622 646
 
623 647
 	// duration
624 648
 	duration, err := custime.ParseDuration(msg.Params[currentArg])
625
-	durationIsUsed := err == nil
626
-	if durationIsUsed {
649
+	if err != nil {
650
+		duration = 0
651
+	} else {
627 652
 		currentArg++
628 653
 	}
629 654
 
@@ -636,31 +661,16 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
636 661
 	currentArg++
637 662
 
638 663
 	// check host
639
-	var hostAddr net.IP
640
-	var hostNet *net.IPNet
664
+	hostNet, err := utils.NormalizedNetFromString(hostString)
641 665
 
642
-	_, hostNet, err = net.ParseCIDR(hostString)
643 666
 	if err != nil {
644
-		hostAddr = net.ParseIP(hostString)
645
-	}
646
-
647
-	if hostAddr == nil && hostNet == nil {
648 667
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Could not parse IP address or CIDR network"))
649 668
 		return false
650 669
 	}
651 670
 
652
-	if hostNet == nil {
653
-		hostString = hostAddr.String()
654
-		if !dlineMyself && hostAddr.Equal(client.IP()) {
655
-			rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("This ban matches you. To DLINE yourself, you must use the command:  /DLINE MYSELF <arguments>"))
656
-			return false
657
-		}
658
-	} else {
659
-		hostString = hostNet.String()
660
-		if !dlineMyself && hostNet.Contains(client.IP()) {
661
-			rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("This ban matches you. To DLINE yourself, you must use the command:  /DLINE MYSELF <arguments>"))
662
-			return false
663
-		}
671
+	if !dlineMyself && hostNet.Contains(client.IP()) {
672
+		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("This ban matches you. To DLINE yourself, you must use the command:  /DLINE MYSELF <arguments>"))
673
+		return false
664 674
 	}
665 675
 
666 676
 	// check remote
@@ -670,71 +680,23 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
670 680
 	}
671 681
 
672 682
 	// get comment(s)
673
-	reason := "No reason given"
674
-	operReason := "No reason given"
675
-	if len(msg.Params) > currentArg {
676
-		tempReason := strings.TrimSpace(msg.Params[currentArg])
677
-		if len(tempReason) > 0 && tempReason != "|" {
678
-			tempReasons := strings.SplitN(tempReason, "|", 2)
679
-			if tempReasons[0] != "" {
680
-				reason = tempReasons[0]
681
-			}
682
-			if len(tempReasons) > 1 && tempReasons[1] != "" {
683
-				operReason = tempReasons[1]
684
-			} else {
685
-				operReason = reason
686
-			}
687
-		}
688
-	}
683
+	reason, operReason := getReasonsFromParams(msg.Params, currentArg)
684
+
689 685
 	operName := oper.Name
690 686
 	if operName == "" {
691 687
 		operName = server.name
692 688
 	}
693 689
 
694
-	// assemble ban info
695
-	var banTime *IPRestrictTime
696
-	if durationIsUsed {
697
-		banTime = &IPRestrictTime{
698
-			Duration: duration,
699
-			Expires:  time.Now().Add(duration),
700
-		}
701
-	}
702
-
703
-	info := IPBanInfo{
704
-		Reason:     reason,
705
-		OperReason: operReason,
706
-		OperName:   operName,
707
-		Time:       banTime,
708
-	}
709
-
710
-	// save in datastore
711
-	err = server.store.Update(func(tx *buntdb.Tx) error {
712
-		dlineKey := fmt.Sprintf(keyDlineEntry, hostString)
713
-
714
-		// assemble json from ban info
715
-		b, err := json.Marshal(info)
716
-		if err != nil {
717
-			return err
718
-		}
719
-
720
-		tx.Set(dlineKey, string(b), nil)
721
-
722
-		return nil
723
-	})
690
+	err = server.dlines.AddNetwork(hostNet, duration, reason, operReason, operName)
724 691
 
725 692
 	if err != nil {
726 693
 		rb.Notice(fmt.Sprintf(client.t("Could not successfully save new D-LINE: %s"), err.Error()))
727 694
 		return false
728 695
 	}
729 696
 
730
-	if hostNet == nil {
731
-		server.dlines.AddIP(hostAddr, banTime, reason, operReason, operName)
732
-	} else {
733
-		server.dlines.AddNetwork(*hostNet, banTime, reason, operReason, operName)
734
-	}
735
-
736 697
 	var snoDescription string
737
-	if durationIsUsed {
698
+	hostString = utils.NetToNormalizedString(hostNet)
699
+	if duration != 0 {
738 700
 		rb.Notice(fmt.Sprintf(client.t("Added temporary (%[1]s) D-Line for %[2]s"), duration.String(), hostString))
739 701
 		snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added temporary (%s) D-Line for %s"), client.nick, operName, duration.String(), hostString)
740 702
 	} else {
@@ -747,16 +709,9 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
747 709
 	if andKill {
748 710
 		var clientsToKill []*Client
749 711
 		var killedClientNicks []string
750
-		var toKill bool
751 712
 
752 713
 		for _, mcl := range server.clients.AllClients() {
753
-			if hostNet == nil {
754
-				toKill = hostAddr.Equal(mcl.IP())
755
-			} else {
756
-				toKill = hostNet.Contains(mcl.IP())
757
-			}
758
-
759
-			if toKill {
714
+			if hostNet.Contains(mcl.IP()) {
760 715
 				clientsToKill = append(clientsToKill, mcl)
761 716
 				killedClientNicks = append(killedClientNicks, mcl.nick)
762 717
 			}
@@ -1047,7 +1002,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1047 1002
 		}
1048 1003
 
1049 1004
 		for key, info := range bans {
1050
-			client.Notice(fmt.Sprintf(client.t("Ban - %[1]s - added by %[2]s - %[3]s"), key, info.OperName, info.BanMessage("%s")))
1005
+			client.Notice(formatBanForListing(client, key, info))
1051 1006
 		}
1052 1007
 
1053 1008
 		return false
@@ -1070,8 +1025,9 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1070 1025
 
1071 1026
 	// duration
1072 1027
 	duration, err := custime.ParseDuration(msg.Params[currentArg])
1073
-	durationIsUsed := err == nil
1074
-	if durationIsUsed {
1028
+	if err != nil {
1029
+		duration = 0
1030
+	} else {
1075 1031
 		currentArg++
1076 1032
 	}
1077 1033
 
@@ -1112,63 +1068,16 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1112 1068
 	}
1113 1069
 
1114 1070
 	// get comment(s)
1115
-	reason := "No reason given"
1116
-	operReason := "No reason given"
1117
-	if len(msg.Params) > currentArg {
1118
-		tempReason := strings.TrimSpace(msg.Params[currentArg])
1119
-		if len(tempReason) > 0 && tempReason != "|" {
1120
-			tempReasons := strings.SplitN(tempReason, "|", 2)
1121
-			if tempReasons[0] != "" {
1122
-				reason = tempReasons[0]
1123
-			}
1124
-			if len(tempReasons) > 1 && tempReasons[1] != "" {
1125
-				operReason = tempReasons[1]
1126
-			} else {
1127
-				operReason = reason
1128
-			}
1129
-		}
1130
-	}
1131
-
1132
-	// assemble ban info
1133
-	var banTime *IPRestrictTime
1134
-	if durationIsUsed {
1135
-		banTime = &IPRestrictTime{
1136
-			Duration: duration,
1137
-			Expires:  time.Now().Add(duration),
1138
-		}
1139
-	}
1140
-
1141
-	info := IPBanInfo{
1142
-		Reason:     reason,
1143
-		OperReason: operReason,
1144
-		OperName:   operName,
1145
-		Time:       banTime,
1146
-	}
1147
-
1148
-	// save in datastore
1149
-	err = server.store.Update(func(tx *buntdb.Tx) error {
1150
-		klineKey := fmt.Sprintf(keyKlineEntry, mask)
1151
-
1152
-		// assemble json from ban info
1153
-		b, err := json.Marshal(info)
1154
-		if err != nil {
1155
-			return err
1156
-		}
1157
-
1158
-		tx.Set(klineKey, string(b), nil)
1159
-
1160
-		return nil
1161
-	})
1071
+	reason, operReason := getReasonsFromParams(msg.Params, currentArg)
1162 1072
 
1073
+	err = server.klines.AddMask(mask, duration, reason, operReason, operName)
1163 1074
 	if err != nil {
1164 1075
 		rb.Notice(fmt.Sprintf(client.t("Could not successfully save new K-LINE: %s"), err.Error()))
1165 1076
 		return false
1166 1077
 	}
1167 1078
 
1168
-	server.klines.AddMask(mask, banTime, reason, operReason, operName)
1169
-
1170 1079
 	var snoDescription string
1171
-	if durationIsUsed {
1080
+	if duration != 0 {
1172 1081
 		rb.Notice(fmt.Sprintf(client.t("Added temporary (%[1]s) K-Line for %[2]s"), duration.String(), mask))
1173 1082
 		snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added temporary (%s) K-Line for %s"), client.nick, operName, duration.String(), mask)
1174 1083
 	} else {
@@ -2219,52 +2128,21 @@ func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
2219 2128
 	hostString := msg.Params[0]
2220 2129
 
2221 2130
 	// check host
2222
-	var hostAddr net.IP
2223
-	var hostNet *net.IPNet
2131
+	hostNet, err := utils.NormalizedNetFromString(hostString)
2224 2132
 
2225
-	_, hostNet, err := net.ParseCIDR(hostString)
2226 2133
 	if err != nil {
2227
-		hostAddr = net.ParseIP(hostString)
2228
-	}
2229
-
2230
-	if hostAddr == nil && hostNet == nil {
2231 2134
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Could not parse IP address or CIDR network"))
2232 2135
 		return false
2233 2136
 	}
2234 2137
 
2235
-	if hostNet == nil {
2236
-		hostString = hostAddr.String()
2237
-	} else {
2238
-		hostString = hostNet.String()
2239
-	}
2240
-
2241
-	// save in datastore
2242
-	err = server.store.Update(func(tx *buntdb.Tx) error {
2243
-		dlineKey := fmt.Sprintf(keyDlineEntry, hostString)
2244
-
2245
-		// check if it exists or not
2246
-		val, err := tx.Get(dlineKey)
2247
-		if val == "" {
2248
-			return errNoExistingBan
2249
-		} else if err != nil {
2250
-			return err
2251
-		}
2252
-
2253
-		tx.Delete(dlineKey)
2254
-		return nil
2255
-	})
2138
+	err = server.dlines.RemoveNetwork(hostNet)
2256 2139
 
2257 2140
 	if err != nil {
2258 2141
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, fmt.Sprintf(client.t("Could not remove ban [%s]"), err.Error()))
2259 2142
 		return false
2260 2143
 	}
2261 2144
 
2262
-	if hostNet == nil {
2263
-		server.dlines.RemoveIP(hostAddr)
2264
-	} else {
2265
-		server.dlines.RemoveNetwork(*hostNet)
2266
-	}
2267
-
2145
+	hostString = utils.NetToNormalizedString(hostNet)
2268 2146
 	rb.Notice(fmt.Sprintf(client.t("Removed D-Line for %s"), hostString))
2269 2147
 	server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed D-Line for %s"), client.nick, hostString))
2270 2148
 	return false
@@ -2288,29 +2166,13 @@ func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
2288 2166
 		mask = mask + "@*"
2289 2167
 	}
2290 2168
 
2291
-	// save in datastore
2292
-	err := server.store.Update(func(tx *buntdb.Tx) error {
2293
-		klineKey := fmt.Sprintf(keyKlineEntry, mask)
2294
-
2295
-		// check if it exists or not
2296
-		val, err := tx.Get(klineKey)
2297
-		if val == "" {
2298
-			return errNoExistingBan
2299
-		} else if err != nil {
2300
-			return err
2301
-		}
2302
-
2303
-		tx.Delete(klineKey)
2304
-		return nil
2305
-	})
2169
+	err := server.klines.RemoveMask(mask)
2306 2170
 
2307 2171
 	if err != nil {
2308 2172
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, fmt.Sprintf(client.t("Could not remove ban [%s]"), err.Error()))
2309 2173
 		return false
2310 2174
 	}
2311 2175
 
2312
-	server.klines.RemoveMask(mask)
2313
-
2314 2176
 	rb.Notice(fmt.Sprintf(client.t("Removed K-Line for %s"), mask))
2315 2177
 	server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed K-Line for %s"), client.nick, mask))
2316 2178
 	return false

+ 149
- 58
irc/kline.go Bestand weergeven

@@ -5,14 +5,17 @@ package irc
5 5
 
6 6
 import (
7 7
 	"encoding/json"
8
+	"fmt"
9
+	"strings"
8 10
 	"sync"
11
+	"time"
9 12
 
10 13
 	"github.com/goshuirc/irc-go/ircmatch"
11 14
 	"github.com/tidwall/buntdb"
12 15
 )
13 16
 
14 17
 const (
15
-	keyKlineEntry = "bans.kline %s"
18
+	keyKlineEntry = "bans.klinev2 %s"
16 19
 )
17 20
 
18 21
 // KLineInfo contains the address itself and expiration time for a given network.
@@ -27,15 +30,23 @@ type KLineInfo struct {
27 30
 
28 31
 // KLineManager manages and klines.
29 32
 type KLineManager struct {
30
-	sync.RWMutex // tier 1
33
+	sync.RWMutex                // tier 1
34
+	persistenceMutex sync.Mutex // tier 2
31 35
 	// kline'd entries
32
-	entries map[string]*KLineInfo
36
+	entries          map[string]KLineInfo
37
+	expirationTimers map[string]*time.Timer
38
+	server           *Server
33 39
 }
34 40
 
35 41
 // NewKLineManager returns a new KLineManager.
36
-func NewKLineManager() *KLineManager {
42
+func NewKLineManager(s *Server) *KLineManager {
37 43
 	var km KLineManager
38
-	km.entries = make(map[string]*KLineInfo)
44
+	km.entries = make(map[string]KLineInfo)
45
+	km.expirationTimers = make(map[string]*time.Timer)
46
+	km.server = s
47
+
48
+	km.loadFromDatastore()
49
+
39 50
 	return &km
40 51
 }
41 52
 
@@ -53,97 +64,177 @@ func (km *KLineManager) AllBans() map[string]IPBanInfo {
53 64
 }
54 65
 
55 66
 // AddMask adds to the blocked list.
56
-func (km *KLineManager) AddMask(mask string, length *IPRestrictTime, reason, operReason, operName string) {
67
+func (km *KLineManager) AddMask(mask string, duration time.Duration, reason, operReason, operName string) error {
68
+	km.persistenceMutex.Lock()
69
+	defer km.persistenceMutex.Unlock()
70
+
71
+	info := IPBanInfo{
72
+		Reason:      reason,
73
+		OperReason:  operReason,
74
+		OperName:    operName,
75
+		TimeCreated: time.Now(),
76
+		Duration:    duration,
77
+	}
78
+	km.addMaskInternal(mask, info)
79
+	return km.persistKLine(mask, info)
80
+}
81
+
82
+func (km *KLineManager) addMaskInternal(mask string, info IPBanInfo) {
57 83
 	kln := KLineInfo{
58 84
 		Mask:    mask,
59 85
 		Matcher: ircmatch.MakeMatch(mask),
60
-		Info: IPBanInfo{
61
-			Time:       length,
62
-			Reason:     reason,
63
-			OperReason: operReason,
64
-			OperName:   operName,
65
-		},
86
+		Info:    info,
66 87
 	}
88
+
89
+	var timeLeft time.Duration
90
+	if info.Duration > 0 {
91
+		timeLeft = info.timeLeft()
92
+		if timeLeft <= 0 {
93
+			return
94
+		}
95
+	}
96
+
67 97
 	km.Lock()
68
-	km.entries[mask] = &kln
69
-	km.Unlock()
98
+	defer km.Unlock()
99
+
100
+	km.entries[mask] = kln
101
+	km.cancelTimer(mask)
102
+
103
+	if info.Duration == 0 {
104
+		return
105
+	}
106
+
107
+	// set up new expiration timer
108
+	timeCreated := info.TimeCreated
109
+	processExpiration := func() {
110
+		km.Lock()
111
+		defer km.Unlock()
112
+
113
+		maskBan, ok := km.entries[mask]
114
+		if ok && maskBan.Info.TimeCreated.Equal(timeCreated) {
115
+			delete(km.entries, mask)
116
+			delete(km.expirationTimers, mask)
117
+		}
118
+	}
119
+	km.expirationTimers[mask] = time.AfterFunc(timeLeft, processExpiration)
70 120
 }
71 121
 
72
-// RemoveMask removes a mask from the blocked list.
73
-func (km *KLineManager) RemoveMask(mask string) {
74
-	km.Lock()
75
-	delete(km.entries, mask)
76
-	km.Unlock()
122
+func (km *KLineManager) cancelTimer(id string) {
123
+	oldTimer := km.expirationTimers[id]
124
+	if oldTimer != nil {
125
+		oldTimer.Stop()
126
+		delete(km.expirationTimers, id)
127
+	}
77 128
 }
78 129
 
79
-// CheckMasks returns whether or not the hostmask(s) are banned, and how long they are banned for.
80
-func (km *KLineManager) CheckMasks(masks ...string) (isBanned bool, info *IPBanInfo) {
81
-	doCleanup := false
82
-	defer func() {
83
-		// asynchronously remove expired bans
84
-		if doCleanup {
85
-			go func() {
86
-				km.Lock()
87
-				defer km.Unlock()
88
-				for key, entry := range km.entries {
89
-					if entry.Info.Time.IsExpired() {
90
-						delete(km.entries, key)
91
-					}
92
-				}
93
-			}()
130
+func (km *KLineManager) persistKLine(mask string, info IPBanInfo) error {
131
+	// save in datastore
132
+	klineKey := fmt.Sprintf(keyKlineEntry, mask)
133
+	// assemble json from ban info
134
+	b, err := json.Marshal(info)
135
+	if err != nil {
136
+		return err
137
+	}
138
+	bstr := string(b)
139
+	var setOptions *buntdb.SetOptions
140
+	if info.Duration != 0 {
141
+		setOptions = &buntdb.SetOptions{Expires: true, TTL: info.Duration}
142
+	}
143
+
144
+	err = km.server.store.Update(func(tx *buntdb.Tx) error {
145
+		_, _, err := tx.Set(klineKey, bstr, setOptions)
146
+		return err
147
+	})
148
+
149
+	return err
150
+
151
+}
152
+
153
+func (km *KLineManager) unpersistKLine(mask string) error {
154
+	// save in datastore
155
+	klineKey := fmt.Sprintf(keyKlineEntry, mask)
156
+	return km.server.store.Update(func(tx *buntdb.Tx) error {
157
+		_, err := tx.Delete(klineKey)
158
+		return err
159
+	})
160
+}
161
+
162
+// RemoveMask removes a mask from the blocked list.
163
+func (km *KLineManager) RemoveMask(mask string) error {
164
+	km.persistenceMutex.Lock()
165
+	defer km.persistenceMutex.Unlock()
166
+
167
+	present := func() bool {
168
+		km.Lock()
169
+		defer km.Unlock()
170
+		_, ok := km.entries[mask]
171
+		if ok {
172
+			delete(km.entries, mask)
94 173
 		}
174
+		km.cancelTimer(mask)
175
+		return ok
95 176
 	}()
96 177
 
178
+	if !present {
179
+		return errNoExistingBan
180
+	}
181
+
182
+	return km.unpersistKLine(mask)
183
+}
184
+
185
+// CheckMasks returns whether or not the hostmask(s) are banned, and how long they are banned for.
186
+func (km *KLineManager) CheckMasks(masks ...string) (isBanned bool, info IPBanInfo) {
97 187
 	km.RLock()
98 188
 	defer km.RUnlock()
99 189
 
100 190
 	for _, entryInfo := range km.entries {
101
-		if entryInfo.Info.Time != nil && entryInfo.Info.Time.IsExpired() {
102
-			doCleanup = true
103
-			continue
104
-		}
105
-
106
-		matches := false
107 191
 		for _, mask := range masks {
108 192
 			if entryInfo.Matcher.Match(mask) {
109
-				matches = true
110
-				break
193
+				return true, entryInfo.Info
111 194
 			}
112 195
 		}
113
-		if matches {
114
-			return true, &entryInfo.Info
115
-		}
116 196
 	}
117 197
 
118 198
 	// no matches!
119
-	return false, nil
199
+	isBanned = false
200
+	return
120 201
 }
121 202
 
122
-func (s *Server) loadKLines() {
123
-	s.klines = NewKLineManager()
124
-
203
+func (km *KLineManager) loadFromDatastore() {
125 204
 	// load from datastore
126
-	s.store.View(func(tx *buntdb.Tx) error {
127
-		//TODO(dan): We could make this safer
128
-		tx.AscendKeys("bans.kline *", func(key, value string) bool {
205
+	klinePrefix := fmt.Sprintf(keyKlineEntry, "")
206
+	km.server.store.View(func(tx *buntdb.Tx) error {
207
+		tx.AscendGreaterOrEqual("", klinePrefix, func(key, value string) bool {
208
+			if !strings.HasPrefix(key, klinePrefix) {
209
+				return false
210
+			}
211
+
129 212
 			// get address name
130
-			key = key[len("bans.kline "):]
131
-			mask := key
213
+			mask := strings.TrimPrefix(key, klinePrefix)
132 214
 
133 215
 			// load ban info
134 216
 			var info IPBanInfo
135
-			json.Unmarshal([]byte(value), &info)
217
+			err := json.Unmarshal([]byte(value), &info)
218
+			if err != nil {
219
+				km.server.logger.Error("internal", "couldn't unmarshal kline", err.Error())
220
+				return true
221
+			}
136 222
 
137 223
 			// add oper name if it doesn't exist already
138 224
 			if info.OperName == "" {
139
-				info.OperName = s.name
225
+				info.OperName = km.server.name
140 226
 			}
141 227
 
142 228
 			// add to the server
143
-			s.klines.AddMask(mask, info.Time, info.Reason, info.OperReason, info.OperName)
229
+			km.addMaskInternal(mask, info)
144 230
 
145
-			return true // true to continue I guess?
231
+			return true
146 232
 		})
147 233
 		return nil
148 234
 	})
235
+
236
+}
237
+
238
+func (s *Server) loadKLines() {
239
+	s.klines = NewKLineManager(s)
149 240
 }

+ 4
- 9
irc/server.go Bestand weergeven

@@ -285,11 +285,10 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
285 285
 	if err != nil {
286 286
 		// too many connections too quickly from client, tell them and close the connection
287 287
 		duration := server.connectionThrottler.BanDuration()
288
-		length := &IPRestrictTime{
289
-			Duration: duration,
290
-			Expires:  time.Now().Add(duration),
288
+		if duration == 0 {
289
+			return false, ""
291 290
 		}
292
-		server.dlines.AddIP(ipaddr, length, server.connectionThrottler.BanMessage(), "Exceeded automated connection throttle", "auto.connection.throttler")
291
+		server.dlines.AddIP(ipaddr, duration, server.connectionThrottler.BanMessage(), "Exceeded automated connection throttle", "auto.connection.throttler")
293 292
 
294 293
 		// they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now,
295 294
 		// and once their temporary DLINE is finished they can fill up the throttler again
@@ -409,11 +408,7 @@ func (server *Server) tryRegister(c *Client) {
409 408
 	// check KLINEs
410 409
 	isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
411 410
 	if isBanned {
412
-		reason := info.Reason
413
-		if info.Time != nil {
414
-			reason += fmt.Sprintf(" [%s]", info.Time.Duration.String())
415
-		}
416
-		c.Quit(fmt.Sprintf(c.t("You are banned from this server (%s)"), reason))
411
+		c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")))
417 412
 		c.destroy(false)
418 413
 		return
419 414
 	}

+ 62
- 0
irc/utils/net.go Bestand weergeven

@@ -9,6 +9,11 @@ import (
9 9
 	"strings"
10 10
 )
11 11
 
12
+var (
13
+	// subnet mask for an ipv6 /128:
14
+	mask128 = net.CIDRMask(128, 128)
15
+)
16
+
12 17
 // IPString returns a simple IP string from the given net.Addr.
13 18
 func IPString(addr net.Addr) string {
14 19
 	addrStr := addr.String()
@@ -94,3 +99,60 @@ func IsHostname(name string) bool {
94 99
 
95 100
 	return true
96 101
 }
102
+
103
+// NormalizeIPToNet represents an address (v4 or v6) as the v6 /128 CIDR
104
+// containing only it.
105
+func NormalizeIPToNet(addr net.IP) (network net.IPNet) {
106
+	// represent ipv4 addresses as ipv6 addresses, using the 4-in-6 prefix
107
+	// (actually this should be a no-op for any address returned by ParseIP)
108
+	addr = addr.To16()
109
+	// the network corresponding to this address is now an ipv6 /128:
110
+	return net.IPNet{
111
+		IP:   addr,
112
+		Mask: mask128,
113
+	}
114
+}
115
+
116
+// NormalizeNet normalizes an IPNet to a v6 CIDR, using the 4-in-6 prefix.
117
+// (this is like IP.To16(), but for IPNet instead of IP)
118
+func NormalizeNet(network net.IPNet) (result net.IPNet) {
119
+	if len(network.IP) == 16 {
120
+		return network
121
+	}
122
+	ones, _ := network.Mask.Size()
123
+	return net.IPNet{
124
+		IP: network.IP.To16(),
125
+		// include the 96 bits of the 4-in-6 prefix
126
+		Mask: net.CIDRMask(96+ones, 128),
127
+	}
128
+}
129
+
130
+// Given a network, produce a human-readable string
131
+// (i.e., CIDR if it's actually a network, IPv6 address if it's a v6 /128,
132
+// dotted quad if it's a v4 /32).
133
+func NetToNormalizedString(network net.IPNet) string {
134
+	ones, bits := network.Mask.Size()
135
+	if ones == bits && ones == len(network.IP)*8 {
136
+		// either a /32 or a /128, output the address:
137
+		return network.IP.String()
138
+	}
139
+	return network.String()
140
+}
141
+
142
+// Parse a human-readable description (an address or CIDR, either v4 or v6)
143
+// into a normalized v6 net.IPNet.
144
+func NormalizedNetFromString(str string) (result net.IPNet, err error) {
145
+	_, network, err := net.ParseCIDR(str)
146
+	if err == nil {
147
+		return NormalizeNet(*network), nil
148
+	}
149
+	ip := net.ParseIP(str)
150
+	if ip == nil {
151
+		err = &net.AddrError{
152
+			Err:  "Couldn't interpret as either CIDR or address",
153
+			Addr: str,
154
+		}
155
+		return
156
+	}
157
+	return NormalizeIPToNet(ip), nil
158
+}

+ 99
- 0
irc/utils/net_test.go Bestand weergeven

@@ -4,8 +4,16 @@
4 4
 
5 5
 package utils
6 6
 
7
+import "net"
8
+import "reflect"
7 9
 import "testing"
8 10
 
11
+func assertEqual(supplied, expected interface{}, t *testing.T) {
12
+	if !reflect.DeepEqual(supplied, expected) {
13
+		t.Errorf("expected %v but got %v", expected, supplied)
14
+	}
15
+}
16
+
9 17
 // hostnames from https://github.com/DanielOaks/irc-parser-tests
10 18
 var (
11 19
 	goodHostnames = []string{
@@ -47,3 +55,94 @@ func TestIsHostname(t *testing.T) {
47 55
 		}
48 56
 	}
49 57
 }
58
+
59
+func TestNormalizeToNet(t *testing.T) {
60
+	a := net.ParseIP("8.8.8.8")
61
+	b := net.ParseIP("8.8.4.4")
62
+	if a == nil || b == nil {
63
+		panic("something has gone very wrong")
64
+	}
65
+
66
+	aNetwork := NormalizeIPToNet(a)
67
+	bNetwork := NormalizeIPToNet(b)
68
+
69
+	assertEqual(aNetwork.Contains(a), true, t)
70
+	assertEqual(bNetwork.Contains(b), true, t)
71
+	assertEqual(aNetwork.Contains(b), false, t)
72
+	assertEqual(bNetwork.Contains(a), false, t)
73
+
74
+	c := net.ParseIP("2001:4860:4860::8888")
75
+	d := net.ParseIP("2001:db8::1")
76
+	if c == nil || d == nil {
77
+		panic("something has gone very wrong")
78
+	}
79
+
80
+	cNetwork := NormalizeIPToNet(c)
81
+	dNetwork := NormalizeIPToNet(d)
82
+
83
+	assertEqual(cNetwork.Contains(c), true, t)
84
+	assertEqual(dNetwork.Contains(d), true, t)
85
+	assertEqual(dNetwork.Contains(c), false, t)
86
+	assertEqual(dNetwork.Contains(a), false, t)
87
+	assertEqual(cNetwork.Contains(b), false, t)
88
+	assertEqual(aNetwork.Contains(c), false, t)
89
+	assertEqual(bNetwork.Contains(c), false, t)
90
+
91
+	assertEqual(NetToNormalizedString(aNetwork), "8.8.8.8", t)
92
+	assertEqual(NetToNormalizedString(bNetwork), "8.8.4.4", t)
93
+	assertEqual(NetToNormalizedString(cNetwork), "2001:4860:4860::8888", t)
94
+	assertEqual(NetToNormalizedString(dNetwork), "2001:db8::1", t)
95
+}
96
+
97
+func TestNormalizedNetToString(t *testing.T) {
98
+	_, network, err := net.ParseCIDR("8.8.0.0/16")
99
+	if err != nil {
100
+		panic(err)
101
+	}
102
+	assertEqual(NetToNormalizedString(*network), "8.8.0.0/16", t)
103
+
104
+	normalized := NormalizeNet(*network)
105
+	assertEqual(normalized.Contains(net.ParseIP("8.8.4.4")), true, t)
106
+	assertEqual(normalized.Contains(net.ParseIP("1.1.1.1")), false, t)
107
+	assertEqual(NetToNormalizedString(normalized), "8.8.0.0/16", t)
108
+
109
+	_, network, err = net.ParseCIDR("8.8.4.4/32")
110
+	if err != nil {
111
+		panic(err)
112
+	}
113
+	assertEqual(NetToNormalizedString(*network), "8.8.4.4", t)
114
+
115
+	normalized = NormalizeNet(*network)
116
+	assertEqual(normalized.Contains(net.ParseIP("8.8.4.4")), true, t)
117
+	assertEqual(normalized.Contains(net.ParseIP("8.8.8.8")), false, t)
118
+	assertEqual(NetToNormalizedString(normalized), "8.8.4.4", t)
119
+}
120
+
121
+func TestNormalizedNet(t *testing.T) {
122
+	_, network, err := net.ParseCIDR("::ffff:8.8.4.4/128")
123
+	assertEqual(err, nil, t)
124
+	assertEqual(NetToNormalizedString(*network), "8.8.4.4", t)
125
+
126
+	normalizedNet := NormalizeIPToNet(net.ParseIP("8.8.4.4"))
127
+	assertEqual(NetToNormalizedString(normalizedNet), "8.8.4.4", t)
128
+
129
+	_, network, err = net.ParseCIDR("::ffff:8.8.0.0/112")
130
+	assertEqual(err, nil, t)
131
+	assertEqual(NetToNormalizedString(*network), "8.8.0.0/16", t)
132
+	_, v4Network, err := net.ParseCIDR("8.8.0.0/16")
133
+	assertEqual(err, nil, t)
134
+	normalizedNet = NormalizeNet(*v4Network)
135
+	assertEqual(NetToNormalizedString(normalizedNet), "8.8.0.0/16", t)
136
+}
137
+
138
+func TestNormalizedNetFromString(t *testing.T) {
139
+	network, err := NormalizedNetFromString("8.8.4.4/16")
140
+	assertEqual(err, nil, t)
141
+	assertEqual(NetToNormalizedString(network), "8.8.0.0/16", t)
142
+	assertEqual(network.Contains(net.ParseIP("8.8.8.8")), true, t)
143
+
144
+	network, err = NormalizedNetFromString("2001:0db8::1")
145
+	assertEqual(err, nil, t)
146
+	assertEqual(NetToNormalizedString(network), "2001:db8::1", t)
147
+	assertEqual(network.Contains(net.ParseIP("2001:0db8::1")), true, t)
148
+}

Laden…
Annuleren
Opslaan