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

+ 3
- 0
irc/config.go View File

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

+ 101
- 30
irc/modes.go View File

@@ -10,6 +10,7 @@ import (
10 10
 	"strings"
11 11
 
12 12
 	"github.com/DanielOaks/girc-go/ircmsg"
13
+	"github.com/DanielOaks/oragono/irc/sno"
13 14
 	"github.com/tidwall/buntdb"
14 15
 )
15 16
 
@@ -97,7 +98,7 @@ const (
97 98
 	LocalOperator   Mode = 'O'
98 99
 	Operator        Mode = 'o'
99 100
 	Restricted      Mode = 'r'
100
-	ServerNotice    Mode = 's' // deprecated
101
+	ServerNotice    Mode = 's'
101 102
 	TLS             Mode = 'Z'
102 103
 	UserRoleplaying Mode = 'E'
103 104
 	WallOps         Mode = 'w'
@@ -105,7 +106,7 @@ const (
105 106
 
106 107
 var (
107 108
 	SupportedUserModes = Modes{
108
-		Away, Invisible, Operator, UserRoleplaying,
109
+		Away, Invisible, Operator, ServerNotice, UserRoleplaying,
109 110
 	}
110 111
 	// supportedUserModesString acts as a cache for when we introduce users
111 112
 	supportedUserModesString = SupportedUserModes.String()
@@ -210,15 +211,77 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
210 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 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 274
 	applied := make(ModeChanges, 0)
216 275
 
217 276
 	for _, change := range changes {
218 277
 		switch change.mode {
219
-		case Invisible, ServerNotice, WallOps, UserRoleplaying:
278
+		case Invisible, WallOps, UserRoleplaying, Operator, LocalOperator:
220 279
 			switch change.op {
221 280
 			case Add:
281
+				if !force && (change.mode == Operator || change.mode == LocalOperator) {
282
+					continue
283
+				}
284
+
222 285
 				if client.flags[change.mode] {
223 286
 					continue
224 287
 				}
@@ -233,12 +296,21 @@ func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges {
233 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 314
 				applied = append(applied, change)
243 315
 			}
244 316
 		}
@@ -272,38 +344,36 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
272 344
 		return false
273 345
 	}
274 346
 
275
-	// assemble changes
276
-	changes := make(ModeChanges, 0)
347
+	// applied mode changes
277 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 367
 	if len(applied) > 0 {
304 368
 		client.Send(nil, client.nickMaskString, "MODE", target.nick, applied.String())
305 369
 	} else if client == target {
306 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 378
 	return false
309 379
 }
@@ -372,6 +442,7 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
372 442
 			}
373 443
 			if !isKnown {
374 444
 				unknown[mode] = true
445
+				continue
375 446
 			}
376 447
 
377 448
 			changes = append(changes, change)

+ 1
- 0
irc/numerics.go View File

@@ -17,6 +17,7 @@ const (
17 17
 	RPL_CREATED                     = "003"
18 18
 	RPL_MYINFO                      = "004"
19 19
 	RPL_ISUPPORT                    = "005"
20
+	RPL_SNOMASKIS                   = "008"
20 21
 	RPL_BOUNCE                      = "010"
21 22
 	RPL_TRACELINK                   = "200"
22 23
 	RPL_TRACECONNECTING             = "201"

+ 24
- 4
irc/server.go View File

@@ -23,8 +23,10 @@ import (
23 23
 	"syscall"
24 24
 	"time"
25 25
 
26
+	"github.com/DanielOaks/girc-go/ircfmt"
26 27
 	"github.com/DanielOaks/girc-go/ircmsg"
27 28
 	"github.com/DanielOaks/oragono/irc/logger"
29
+	"github.com/DanielOaks/oragono/irc/sno"
28 30
 	"github.com/tidwall/buntdb"
29 31
 )
30 32
 
@@ -123,6 +125,7 @@ type Server struct {
123 125
 	rehashSignal                 chan os.Signal
124 126
 	restAPI                      *RestAPIConfig
125 127
 	signals                      chan os.Signal
128
+	snomasks                     *SnoManager
126 129
 	store                        *buntdb.DB
127 130
 	stsEnabled                   bool
128 131
 	whoWas                       *WhoWasList
@@ -233,6 +236,7 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (*
233 236
 		rehashSignal:       make(chan os.Signal, 1),
234 237
 		restAPI:            &config.Server.RestAPI,
235 238
 		signals:            make(chan os.Signal, len(ServerExitSignals)),
239
+		snomasks:           NewSnoManager(),
236 240
 		stsEnabled:         config.Server.STS.Enabled,
237 241
 		whoWas:             NewWhoWasList(config.Limits.WhowasEntries),
238 242
 	}
@@ -474,6 +478,7 @@ func (server *Server) Run() {
474 478
 				}
475 479
 
476 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 483
 				go NewClient(server, conn.Conn, conn.IsTLS)
479 484
 				continue
@@ -664,6 +669,7 @@ func (server *Server) tryRegister(c *Client) {
664 669
 
665 670
 	// continue registration
666 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 673
 	c.Register()
668 674
 
669 675
 	// send welcome text
@@ -1263,13 +1269,27 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
1263 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 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 1289
 		mode: Operator,
1270 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 1293
 	return false
1274 1294
 }
1275 1295
 

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

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,117 @@
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,6 +196,9 @@ opers:
196 196
         # custom hostname
197 197
         vhost: "n"
198 198
 
199
+        # modes are the modes to auto-set upon opering-up
200
+        modes: +is acjknoqtux
201
+
199 202
         # password to login with /OPER command
200 203
         # generated using  "oragono genpasswd"
201 204
         password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu

Loading…
Cancel
Save