Browse Source

Add very initial snomasks

tags/v0.8.0
Daniel Oaks 7 years ago
parent
commit
fd793d6adb
8 changed files with 286 additions and 34 deletions
  1. 2
    0
      CHANGELOG.md
  2. 3
    0
      irc/config.go
  3. 101
    30
      irc/modes.go
  4. 1
    0
      irc/numerics.go
  5. 24
    4
      irc/server.go
  6. 35
    0
      irc/sno/constants.go
  7. 117
    0
      irc/snomanager.go
  8. 3
    0
      oragono.yaml

+ 2
- 0
CHANGELOG.md View File

9
 
9
 
10
 ### Config Changes
10
 ### Config Changes
11
 * Added `debug` section containing additional debug settings.
11
 * Added `debug` section containing additional debug settings.
12
+* Added `modes` key on oper config, for setting modes on oper-up.
12
 * Added ability to log to `stdout` in logger methods.
13
 * Added ability to log to `stdout` in logger methods.
13
 
14
 
14
 ### Security
15
 ### Security
16
 ### Added
17
 ### Added
17
 * Added ability to log to stdout.
18
 * Added ability to log to stdout.
18
 * Added ability to use StackImpact profiling.
19
 * Added ability to use StackImpact profiling.
20
+* Added initial server notice masks (snomasks).
19
 
21
 
20
 ### Changed
22
 ### Changed
21
 * Socket code rewritten to be a lot faster and safer.
23
 * Socket code rewritten to be a lot faster and safer.

+ 3
- 0
irc/config.go View File

94
 	Vhost     string
94
 	Vhost     string
95
 	WhoisLine string `yaml:"whois-line"`
95
 	WhoisLine string `yaml:"whois-line"`
96
 	Password  string
96
 	Password  string
97
+	Modes     string
97
 }
98
 }
98
 
99
 
99
 func (conf *OperConfig) PasswordBytes() []byte {
100
 func (conf *OperConfig) PasswordBytes() []byte {
323
 	WhoisLine string
324
 	WhoisLine string
324
 	Vhost     string
325
 	Vhost     string
325
 	Pass      []byte
326
 	Pass      []byte
327
+	Modes     string
326
 }
328
 }
327
 
329
 
328
 // Operators returns a map of operator configs from the given OperClass and config.
330
 // Operators returns a map of operator configs from the given OperClass and config.
349
 		} else {
351
 		} else {
350
 			oper.WhoisLine = class.WhoisLine
352
 			oper.WhoisLine = class.WhoisLine
351
 		}
353
 		}
354
+		oper.Modes = strings.TrimSpace(opConf.Modes)
352
 
355
 
353
 		// successful, attach to list of opers
356
 		// successful, attach to list of opers
354
 		operators[name] = oper
357
 		operators[name] = oper

+ 101
- 30
irc/modes.go View File

10
 	"strings"
10
 	"strings"
11
 
11
 
12
 	"github.com/DanielOaks/girc-go/ircmsg"
12
 	"github.com/DanielOaks/girc-go/ircmsg"
13
+	"github.com/DanielOaks/oragono/irc/sno"
13
 	"github.com/tidwall/buntdb"
14
 	"github.com/tidwall/buntdb"
14
 )
15
 )
15
 
16
 
97
 	LocalOperator   Mode = 'O'
98
 	LocalOperator   Mode = 'O'
98
 	Operator        Mode = 'o'
99
 	Operator        Mode = 'o'
99
 	Restricted      Mode = 'r'
100
 	Restricted      Mode = 'r'
100
-	ServerNotice    Mode = 's' // deprecated
101
+	ServerNotice    Mode = 's'
101
 	TLS             Mode = 'Z'
102
 	TLS             Mode = 'Z'
102
 	UserRoleplaying Mode = 'E'
103
 	UserRoleplaying Mode = 'E'
103
 	WallOps         Mode = 'w'
104
 	WallOps         Mode = 'w'
105
 
106
 
106
 var (
107
 var (
107
 	SupportedUserModes = Modes{
108
 	SupportedUserModes = Modes{
108
-		Away, Invisible, Operator, UserRoleplaying,
109
+		Away, Invisible, Operator, ServerNotice, UserRoleplaying,
109
 	}
110
 	}
110
 	// supportedUserModesString acts as a cache for when we introduce users
111
 	// supportedUserModesString acts as a cache for when we introduce users
111
 	supportedUserModesString = SupportedUserModes.String()
112
 	supportedUserModesString = SupportedUserModes.String()
210
 	return umodeHandler(server, client, msg)
211
 	return umodeHandler(server, client, msg)
211
 }
212
 }
212
 
213
 
214
+// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
215
+func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
216
+	changes := make(ModeChanges, 0)
217
+	unknown := make(map[rune]bool)
218
+
219
+	if 0 < len(params) {
220
+		modeArg := params[0]
221
+		op := ModeOp(modeArg[0])
222
+		if (op == Add) || (op == Remove) {
223
+			modeArg = modeArg[1:]
224
+		} else {
225
+			unknown[rune(modeArg[0])] = true
226
+			return changes, unknown
227
+		}
228
+
229
+		skipArgs := 1
230
+
231
+		for _, mode := range modeArg {
232
+			if mode == '-' || mode == '+' {
233
+				op = ModeOp(mode)
234
+				continue
235
+			}
236
+			change := ModeChange{
237
+				mode: Mode(mode),
238
+				op:   op,
239
+			}
240
+
241
+			// put arg into modechange if needed
242
+			switch Mode(mode) {
243
+			case ServerNotice:
244
+				// always require arg
245
+				if len(params) > skipArgs {
246
+					change.arg = params[skipArgs]
247
+					skipArgs++
248
+				} else {
249
+					continue
250
+				}
251
+			}
252
+
253
+			var isKnown bool
254
+			for _, supportedMode := range SupportedUserModes {
255
+				if rune(supportedMode) == mode {
256
+					isKnown = true
257
+					break
258
+				}
259
+			}
260
+			if !isKnown {
261
+				unknown[mode] = true
262
+				continue
263
+			}
264
+
265
+			changes = append(changes, change)
266
+		}
267
+	}
268
+
269
+	return changes, unknown
270
+}
271
+
213
 // applyUserModeChanges applies the given changes, and returns the applied changes.
272
 // applyUserModeChanges applies the given changes, and returns the applied changes.
214
-func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges {
273
+func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) ModeChanges {
215
 	applied := make(ModeChanges, 0)
274
 	applied := make(ModeChanges, 0)
216
 
275
 
217
 	for _, change := range changes {
276
 	for _, change := range changes {
218
 		switch change.mode {
277
 		switch change.mode {
219
-		case Invisible, ServerNotice, WallOps, UserRoleplaying:
278
+		case Invisible, WallOps, UserRoleplaying, Operator, LocalOperator:
220
 			switch change.op {
279
 			switch change.op {
221
 			case Add:
280
 			case Add:
281
+				if !force && (change.mode == Operator || change.mode == LocalOperator) {
282
+					continue
283
+				}
284
+
222
 				if client.flags[change.mode] {
285
 				if client.flags[change.mode] {
223
 					continue
286
 					continue
224
 				}
287
 				}
233
 				applied = append(applied, change)
296
 				applied = append(applied, change)
234
 			}
297
 			}
235
 
298
 
236
-		case Operator, LocalOperator:
237
-			if change.op == Remove {
238
-				if !client.flags[change.mode] {
239
-					continue
299
+		case ServerNotice:
300
+			if !client.flags[Operator] {
301
+				continue
302
+			}
303
+			var masks []sno.Mask
304
+			if change.op == Add || change.op == Remove {
305
+				for _, char := range change.arg {
306
+					masks = append(masks, sno.Mask(char))
240
 				}
307
 				}
241
-				delete(client.flags, change.mode)
308
+			}
309
+			if change.op == Add {
310
+				client.server.snomasks.AddMasks(client, masks...)
311
+				applied = append(applied, change)
312
+			} else if change.op == Remove {
313
+				client.server.snomasks.RemoveMasks(client, masks...)
242
 				applied = append(applied, change)
314
 				applied = append(applied, change)
243
 			}
315
 			}
244
 		}
316
 		}
272
 		return false
344
 		return false
273
 	}
345
 	}
274
 
346
 
275
-	// assemble changes
276
-	changes := make(ModeChanges, 0)
347
+	// applied mode changes
277
 	applied := make(ModeChanges, 0)
348
 	applied := make(ModeChanges, 0)
278
 
349
 
279
-	if len(msg.Params) > 1 {
280
-		modeArg := msg.Params[1]
281
-		op := ModeOp(modeArg[0])
282
-		if (op == Add) || (op == Remove) {
283
-			modeArg = modeArg[1:]
284
-		} else {
285
-			client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me")
286
-			return false
287
-		}
350
+	if 1 < len(msg.Params) {
351
+		// parse out real mode changes
352
+		params := msg.Params[1:]
353
+		changes, unknown := ParseUserModeChanges(params...)
288
 
354
 
289
-		for _, mode := range modeArg {
290
-			if mode == '-' || mode == '+' {
291
-				op = ModeOp(mode)
292
-				continue
293
-			}
294
-			changes = append(changes, ModeChange{
295
-				mode: Mode(mode),
296
-				op:   op,
297
-			})
355
+		// alert for unknown mode changes
356
+		for char := range unknown {
357
+			client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), "is an unknown mode character to me")
358
+		}
359
+		if len(unknown) == 1 && len(changes) == 0 {
360
+			return false
298
 		}
361
 		}
299
 
362
 
300
-		applied = target.applyUserModeChanges(changes)
363
+		// apply mode changes
364
+		applied = target.applyUserModeChanges(msg.Command == "SAMODE", changes)
301
 	}
365
 	}
302
 
366
 
303
 	if len(applied) > 0 {
367
 	if len(applied) > 0 {
304
 		client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String())
368
 		client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String())
305
 	} else if client == target {
369
 	} else if client == target {
306
 		client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nick, target.ModeString())
370
 		client.Send(nil, target.nickMaskString, RPL_UMODEIS, target.nick, target.ModeString())
371
+		if client.flags[LocalOperator] || client.flags[Operator] {
372
+			masks := server.snomasks.String(client)
373
+			if 0 < len(masks) {
374
+				client.Send(nil, target.nickMaskString, RPL_SNOMASKIS, target.nick, masks, "Server notice masks")
375
+			}
376
+		}
307
 	}
377
 	}
308
 	return false
378
 	return false
309
 }
379
 }
372
 			}
442
 			}
373
 			if !isKnown {
443
 			if !isKnown {
374
 				unknown[mode] = true
444
 				unknown[mode] = true
445
+				continue
375
 			}
446
 			}
376
 
447
 
377
 			changes = append(changes, change)
448
 			changes = append(changes, change)

+ 1
- 0
irc/numerics.go View File

17
 	RPL_CREATED                     = "003"
17
 	RPL_CREATED                     = "003"
18
 	RPL_MYINFO                      = "004"
18
 	RPL_MYINFO                      = "004"
19
 	RPL_ISUPPORT                    = "005"
19
 	RPL_ISUPPORT                    = "005"
20
+	RPL_SNOMASKIS                   = "008"
20
 	RPL_BOUNCE                      = "010"
21
 	RPL_BOUNCE                      = "010"
21
 	RPL_TRACELINK                   = "200"
22
 	RPL_TRACELINK                   = "200"
22
 	RPL_TRACECONNECTING             = "201"
23
 	RPL_TRACECONNECTING             = "201"

+ 24
- 4
irc/server.go View File

23
 	"syscall"
23
 	"syscall"
24
 	"time"
24
 	"time"
25
 
25
 
26
+	"github.com/DanielOaks/girc-go/ircfmt"
26
 	"github.com/DanielOaks/girc-go/ircmsg"
27
 	"github.com/DanielOaks/girc-go/ircmsg"
27
 	"github.com/DanielOaks/oragono/irc/logger"
28
 	"github.com/DanielOaks/oragono/irc/logger"
29
+	"github.com/DanielOaks/oragono/irc/sno"
28
 	"github.com/tidwall/buntdb"
30
 	"github.com/tidwall/buntdb"
29
 )
31
 )
30
 
32
 
123
 	rehashSignal                 chan os.Signal
125
 	rehashSignal                 chan os.Signal
124
 	restAPI                      *RestAPIConfig
126
 	restAPI                      *RestAPIConfig
125
 	signals                      chan os.Signal
127
 	signals                      chan os.Signal
128
+	snomasks                     *SnoManager
126
 	store                        *buntdb.DB
129
 	store                        *buntdb.DB
127
 	stsEnabled                   bool
130
 	stsEnabled                   bool
128
 	whoWas                       *WhoWasList
131
 	whoWas                       *WhoWasList
233
 		rehashSignal:       make(chan os.Signal, 1),
236
 		rehashSignal:       make(chan os.Signal, 1),
234
 		restAPI:            &config.Server.RestAPI,
237
 		restAPI:            &config.Server.RestAPI,
235
 		signals:            make(chan os.Signal, len(ServerExitSignals)),
238
 		signals:            make(chan os.Signal, len(ServerExitSignals)),
239
+		snomasks:           NewSnoManager(),
236
 		stsEnabled:         config.Server.STS.Enabled,
240
 		stsEnabled:         config.Server.STS.Enabled,
237
 		whoWas:             NewWhoWasList(config.Limits.WhowasEntries),
241
 		whoWas:             NewWhoWasList(config.Limits.WhowasEntries),
238
 	}
242
 	}
474
 				}
478
 				}
475
 
479
 
476
 				server.logger.Debug("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr))
480
 				server.logger.Debug("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr))
481
+				// prolly don't need to alert snomasks on this, only on connection reg
477
 
482
 
478
 				go NewClient(server, conn.Conn, conn.IsTLS)
483
 				go NewClient(server, conn.Conn, conn.IsTLS)
479
 				continue
484
 				continue
664
 
669
 
665
 	// continue registration
670
 	// continue registration
666
 	server.logger.Debug("localconnect", fmt.Sprintf("Client registered [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
671
 	server.logger.Debug("localconnect", fmt.Sprintf("Client registered [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
672
+	server.snomasks.Send(sno.LocalConnects, fmt.Sprintf(ircfmt.Unescape("Client registered $c[grey][$r%s$c[grey]] [u:$r%s$c[grey]] [h:$r%s$c[grey]] [r:$r%s$c[grey]]"), c.nick, c.username, c.rawHostname, c.realname))
667
 	c.Register()
673
 	c.Register()
668
 
674
 
669
 	// send welcome text
675
 	// send welcome text
1263
 		client.updateNickMask()
1269
 		client.updateNickMask()
1264
 	}
1270
 	}
1265
 
1271
 
1272
+	// set new modes
1273
+	var applied ModeChanges
1274
+	if 0 < len(server.operators[name].Modes) {
1275
+		modeChanges, unknownChanges := ParseUserModeChanges(strings.Split(server.operators[name].Modes, " ")...)
1276
+		applied = client.applyUserModeChanges(true, modeChanges)
1277
+		if 0 < len(unknownChanges) {
1278
+			var runes string
1279
+			for r := range unknownChanges {
1280
+				runes += string(r)
1281
+			}
1282
+			client.Notice(fmt.Sprintf("Could not apply mode changes: +%s", runes))
1283
+		}
1284
+	}
1285
+
1266
 	client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator")
1286
 	client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator")
1267
-	//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
1268
-	modech := ModeChanges{ModeChange{
1287
+
1288
+	applied = append(applied, ModeChange{
1269
 		mode: Operator,
1289
 		mode: Operator,
1270
 		op:   Add,
1290
 		op:   Add,
1271
-	}}
1272
-	client.Send(nil, server.name, "MODE", client.nick, modech.String())
1291
+	})
1292
+	client.Send(nil, server.name, "MODE", client.nick, applied.String())
1273
 	return false
1293
 	return false
1274
 }
1294
 }
1275
 
1295
 

+ 35
- 0
irc/sno/constants.go View File

1
+// Package sno holds Server Notice masks for easy reference.
2
+package sno
3
+
4
+// Mask is a type of server notice mask.
5
+type Mask rune
6
+
7
+// Notice mask types
8
+const (
9
+	LocalAccouncements Mask = 'a'
10
+	LocalConnects      Mask = 'c'
11
+	LocalChannels      Mask = 'j'
12
+	LocalKills         Mask = 'k'
13
+	LocalNicks         Mask = 'n'
14
+	LocalOpers         Mask = 'o'
15
+	LocalQuits         Mask = 'q'
16
+	Stats              Mask = 't'
17
+	LocalAccounts      Mask = 'u'
18
+	LocalXline         Mask = 'x'
19
+)
20
+
21
+var (
22
+	// NoticeMaskNames has readable names for our snomask types.
23
+	NoticeMaskNames = map[Mask]string{
24
+		LocalAccouncements: "ANNOUNCEMENT",
25
+		LocalConnects:      "CONNECT",
26
+		LocalChannels:      "CHANNEL",
27
+		LocalKills:         "KILL",
28
+		LocalNicks:         "NICK",
29
+		LocalOpers:         "OPER",
30
+		LocalQuits:         "QUIT",
31
+		Stats:              "STATS",
32
+		LocalAccounts:      "ACCOUNT",
33
+		LocalXline:         "XLINE",
34
+	}
35
+)

+ 117
- 0
irc/snomanager.go View File

1
+package irc
2
+
3
+import (
4
+	"fmt"
5
+	"sync"
6
+
7
+	"github.com/DanielOaks/girc-go/ircfmt"
8
+	"github.com/DanielOaks/oragono/irc/sno"
9
+)
10
+
11
+// SnoManager keeps track of which clients to send snomasks to.
12
+type SnoManager struct {
13
+	sendListMutex sync.RWMutex
14
+	sendLists     map[sno.Mask]map[*Client]bool
15
+}
16
+
17
+// NewSnoManager returns a new SnoManager
18
+func NewSnoManager() *SnoManager {
19
+	var m SnoManager
20
+	m.sendLists = make(map[sno.Mask]map[*Client]bool)
21
+	return &m
22
+}
23
+
24
+// AddMasks adds the given snomasks to the client.
25
+func (m *SnoManager) AddMasks(client *Client, masks ...sno.Mask) {
26
+	m.sendListMutex.Lock()
27
+	defer m.sendListMutex.Unlock()
28
+
29
+	for _, mask := range masks {
30
+		currentClientList := m.sendLists[mask]
31
+
32
+		if currentClientList == nil {
33
+			currentClientList = map[*Client]bool{}
34
+		}
35
+
36
+		currentClientList[client] = true
37
+
38
+		m.sendLists[mask] = currentClientList
39
+	}
40
+}
41
+
42
+// RemoveMasks removes the given snomasks from the client.
43
+func (m *SnoManager) RemoveMasks(client *Client, masks ...sno.Mask) {
44
+	m.sendListMutex.Lock()
45
+	defer m.sendListMutex.Unlock()
46
+
47
+	for _, mask := range masks {
48
+		currentClientList := m.sendLists[mask]
49
+
50
+		if currentClientList == nil || len(currentClientList) == 0 {
51
+			continue
52
+		}
53
+
54
+		delete(currentClientList, client)
55
+
56
+		m.sendLists[mask] = currentClientList
57
+	}
58
+}
59
+
60
+// RemoveClient removes the given client from all of our lists.
61
+func (m *SnoManager) RemoveClient(client *Client) {
62
+	m.sendListMutex.Lock()
63
+	defer m.sendListMutex.Unlock()
64
+
65
+	for mask := range m.sendLists {
66
+		currentClientList := m.sendLists[mask]
67
+
68
+		if currentClientList == nil || len(currentClientList) == 0 {
69
+			continue
70
+		}
71
+
72
+		delete(currentClientList, client)
73
+
74
+		m.sendLists[mask] = currentClientList
75
+	}
76
+}
77
+
78
+// Send sends the given snomask to all users signed up for it.
79
+func (m *SnoManager) Send(mask sno.Mask, content string) {
80
+	m.sendListMutex.RLock()
81
+	defer m.sendListMutex.RUnlock()
82
+
83
+	currentClientList := m.sendLists[mask]
84
+
85
+	if currentClientList == nil || len(currentClientList) == 0 {
86
+		return
87
+	}
88
+
89
+	// make the message
90
+	name := sno.NoticeMaskNames[mask]
91
+	if name == "" {
92
+		name = string(mask)
93
+	}
94
+	message := fmt.Sprintf(ircfmt.Unescape("$c[grey]-$r%s$c[grey]-$c %s"), name, content)
95
+
96
+	// send it out
97
+	for client := range currentClientList {
98
+		client.Notice(message)
99
+	}
100
+}
101
+
102
+// String returns the snomasks currently enabled.
103
+func (m *SnoManager) String(client *Client) string {
104
+	m.sendListMutex.RLock()
105
+	defer m.sendListMutex.RUnlock()
106
+
107
+	var masks string
108
+	for mask, clients := range m.sendLists {
109
+		for c := range clients {
110
+			if c == client {
111
+				masks += string(mask)
112
+				break
113
+			}
114
+		}
115
+	}
116
+	return masks
117
+}

+ 3
- 0
oragono.yaml View File

196
         # custom hostname
196
         # custom hostname
197
         vhost: "n"
197
         vhost: "n"
198
 
198
 
199
+        # modes are the modes to auto-set upon opering-up
200
+        modes: +is acjknoqtux
201
+
199
         # password to login with /OPER command
202
         # password to login with /OPER command
200
         # generated using  "oragono genpasswd"
203
         # generated using  "oragono genpasswd"
201
         password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu
204
         password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu

Loading…
Cancel
Save