Bläddra i källkod

Merge pull request #1528 from slingamn/issue1176_operprivs

enhancements to operator privilege handling
tags/v2.6.0-rc1
Shivaram Lingamneni 3 år sedan
förälder
incheckning
bb39399f97
Inget konto är kopplat till bidragsgivarens mejladress
17 ändrade filer med 269 tillägg och 105 borttagningar
  1. 1
    0
      Makefile
  2. 1
    0
      default.yaml
  3. 4
    8
      irc/channel.go
  4. 1
    1
      irc/chanserv.go
  5. 1
    1
      irc/client.go
  6. 1
    1
      irc/client_lookup_set.go
  7. 6
    15
      irc/commands.go
  8. 23
    22
      irc/handlers.go
  9. 21
    19
      irc/modes.go
  10. 1
    4
      irc/modes/modes.go
  11. 32
    0
      irc/modes/modes_test.go
  12. 12
    11
      irc/server.go
  13. 15
    13
      irc/sno/constants.go
  14. 87
    0
      irc/sno/utils.go
  15. 53
    0
      irc/sno/utils_test.go
  16. 9
    10
      irc/snomanager.go
  17. 1
    0
      traditional.yaml

+ 1
- 0
Makefile Visa fil

@@ -32,6 +32,7 @@ test:
32 32
 	cd irc/modes && go test . && go vet .
33 33
 	cd irc/mysql && go test . && go vet .
34 34
 	cd irc/passwd && go test . && go vet .
35
+	cd irc/sno && go test . && go vet .
35 36
 	cd irc/utils && go test . && go vet .
36 37
 	./.check-gofmt.sh
37 38
 

+ 1
- 0
default.yaml Visa fil

@@ -593,6 +593,7 @@ oper-classes:
593 593
             - "vhosts"
594 594
             - "sajoin"
595 595
             - "samode"
596
+            - "snomasks"
596 597
 
597 598
     # server admin: has full control of the ircd, including nickname and
598 599
     # channel registrations

+ 4
- 8
irc/channel.go Visa fil

@@ -439,7 +439,7 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
439 439
 	channel.stateMutex.RLock()
440 440
 	clientData, isJoined := channel.members[client]
441 441
 	channel.stateMutex.RUnlock()
442
-	isOper := client.HasMode(modes.Operator)
442
+	isOper := client.HasRoleCapabs("sajoin")
443 443
 	respectAuditorium := channel.flags.HasMode(modes.Auditorium) && !isOper &&
444 444
 		(!isJoined || clientData.modes.HighestChannelUserMode() == modes.Mode(0))
445 445
 	isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
@@ -607,7 +607,7 @@ func (channel *Channel) hasClient(client *Client) bool {
607 607
 
608 608
 // <mode> <mode params>
609 609
 func (channel *Channel) modeStrings(client *Client) (result []string) {
610
-	hasPrivs := client.HasMode(modes.Operator)
610
+	hasPrivs := client.HasRoleCapabs("sajoin")
611 611
 
612 612
 	channel.stateMutex.RLock()
613 613
 	defer channel.stateMutex.RUnlock()
@@ -1245,12 +1245,12 @@ func (channel *Channel) SendTopic(client *Client, rb *ResponseBuffer, sendNoTopi
1245 1245
 
1246 1246
 // SetTopic sets the topic of this channel, if the client is allowed to do so.
1247 1247
 func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffer) {
1248
-	if !(client.HasMode(modes.Operator) || channel.hasClient(client)) {
1248
+	if !channel.hasClient(client) {
1249 1249
 		rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.Name(), client.t("You're not on that channel"))
1250 1250
 		return
1251 1251
 	}
1252 1252
 
1253
-	if channel.flags.HasMode(modes.OpOnlyTopic) && !channel.ClientIsAtLeast(client, modes.Halfop) {
1253
+	if channel.flags.HasMode(modes.OpOnlyTopic) && !(channel.ClientIsAtLeast(client, modes.Halfop) || client.HasRoleCapabs("samode")) {
1254 1254
 		rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You're not a channel operator"))
1255 1255
 		return
1256 1256
 	}
@@ -1487,10 +1487,6 @@ func (channel *Channel) Quit(client *Client) {
1487 1487
 
1488 1488
 func (channel *Channel) Kick(client *Client, target *Client, comment string, rb *ResponseBuffer, hasPrivs bool) {
1489 1489
 	if !hasPrivs {
1490
-		if !(client.HasMode(modes.Operator) || channel.hasClient(client)) {
1491
-			rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.Name(), client.t("You're not on that channel"))
1492
-			return
1493
-		}
1494 1490
 		if !channel.ClientHasPrivsOver(client, target) {
1495 1491
 			rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You don't have enough channel privileges"))
1496 1492
 			return

+ 1
- 1
irc/chanserv.go Visa fil

@@ -876,7 +876,7 @@ func csHowToBanHandler(service *ircService, server *Server, client *Client, comm
876 876
 		return
877 877
 	}
878 878
 
879
-	if !(channel.ClientIsAtLeast(client, modes.Operator) || client.HasRoleCapabs("samode")) {
879
+	if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("samode")) {
880 880
 		service.Notice(rb, client.t("Insufficient privileges"))
881 881
 		return
882 882
 	}

+ 1
- 1
irc/client.go Visa fil

@@ -1512,7 +1512,7 @@ func (client *Client) destroy(session *Session) {
1512 1512
 	// decrement stats if we have no more sessions, even if the client will not be destroyed
1513 1513
 	if shouldDecrement {
1514 1514
 		invisible := client.HasMode(modes.Invisible)
1515
-		operator := client.HasMode(modes.LocalOperator) || client.HasMode(modes.Operator)
1515
+		operator := client.HasMode(modes.Operator)
1516 1516
 		client.server.stats.Remove(registered, invisible, operator)
1517 1517
 	}
1518 1518
 

+ 1
- 1
irc/client_lookup_set.go Visa fil

@@ -222,7 +222,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
222 222
 		}
223 223
 		if numSessions == 1 {
224 224
 			invisible := currentClient.HasMode(modes.Invisible)
225
-			operator := currentClient.HasMode(modes.Operator) || currentClient.HasMode(modes.LocalOperator)
225
+			operator := currentClient.HasMode(modes.Operator)
226 226
 			client.server.stats.AddRegistered(invisible, operator)
227 227
 		}
228 228
 		session.autoreplayMissedSince = lastSeen

+ 6
- 15
irc/commands.go Visa fil

@@ -7,13 +7,11 @@ package irc
7 7
 
8 8
 import (
9 9
 	"github.com/goshuirc/irc-go/ircmsg"
10
-	"github.com/oragono/oragono/irc/modes"
11 10
 )
12 11
 
13 12
 // Command represents a command accepted from a client.
14 13
 type Command struct {
15 14
 	handler        func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
16
-	oper           bool
17 15
 	usablePreReg   bool
18 16
 	allowedInBatch bool // allowed in client-to-server batches
19 17
 	minParams      int
@@ -32,10 +30,6 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
32 30
 			rb.Add(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
33 31
 			return false
34 32
 		}
35
-		if cmd.oper && !client.HasMode(modes.Operator) {
36
-			rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied - You're not an IRC operator"))
37
-			return false
38
-		}
39 33
 		if len(cmd.capabs) > 0 && !client.HasRoleCapabs(cmd.capabs...) {
40 34
 			rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied"))
41 35
 			return false
@@ -115,7 +109,7 @@ func init() {
115 109
 		"DEBUG": {
116 110
 			handler:   debugHandler,
117 111
 			minParams: 1,
118
-			oper:      true,
112
+			capabs:    []string{"rehash"},
119 113
 		},
120 114
 		"DEFCON": {
121 115
 			handler: defconHandler,
@@ -124,12 +118,11 @@ func init() {
124 118
 		"DEOPER": {
125 119
 			handler:   deoperHandler,
126 120
 			minParams: 0,
127
-			oper:      true,
128 121
 		},
129 122
 		"DLINE": {
130 123
 			handler:   dlineHandler,
131 124
 			minParams: 1,
132
-			oper:      true,
125
+			capabs:    []string{"ban"},
133 126
 		},
134 127
 		"EXTJWT": {
135 128
 			handler:   extjwtHandler,
@@ -169,13 +162,12 @@ func init() {
169 162
 		"KILL": {
170 163
 			handler:   killHandler,
171 164
 			minParams: 1,
172
-			oper:      true,
173 165
 			capabs:    []string{"kill"},
174 166
 		},
175 167
 		"KLINE": {
176 168
 			handler:   klineHandler,
177 169
 			minParams: 1,
178
-			oper:      true,
170
+			capabs:    []string{"ban"},
179 171
 		},
180 172
 		"LANGUAGE": {
181 173
 			handler:      languageHandler,
@@ -278,7 +270,7 @@ func init() {
278 270
 		"SANICK": {
279 271
 			handler:   sanickHandler,
280 272
 			minParams: 2,
281
-			oper:      true,
273
+			capabs:    []string{"samode"},
282 274
 		},
283 275
 		"SAMODE": {
284 276
 			handler:   modeHandler,
@@ -308,7 +300,6 @@ func init() {
308 300
 		"REHASH": {
309 301
 			handler:   rehashHandler,
310 302
 			minParams: 0,
311
-			oper:      true,
312 303
 			capabs:    []string{"rehash"},
313 304
 		},
314 305
 		"TIME": {
@@ -327,7 +318,7 @@ func init() {
327 318
 		"UNDLINE": {
328 319
 			handler:   unDLineHandler,
329 320
 			minParams: 1,
330
-			oper:      true,
321
+			capabs:    []string{"ban"},
331 322
 		},
332 323
 		"UNINVITE": {
333 324
 			handler:   inviteHandler,
@@ -336,7 +327,7 @@ func init() {
336 327
 		"UNKLINE": {
337 328
 			handler:   unKLineHandler,
338 329
 			minParams: 1,
339
-			oper:      true,
330
+			capabs:    []string{"ban"},
340 331
 		},
341 332
 		"USER": {
342 333
 			handler:      userHandler,

+ 23
- 22
irc/handlers.go Visa fil

@@ -757,10 +757,6 @@ func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
757 757
 		rb.Notice(fmt.Sprintf("CPU profiling stopped"))
758 758
 
759 759
 	case "CRASHSERVER":
760
-		if !client.HasRoleCapabs("rehash") {
761
-			rb.Notice(client.t("You must have rehash permissions in order to execute DEBUG CRASHSERVER"))
762
-			return false
763
-		}
764 760
 		code := utils.ConfirmationCode(server.name, server.ctime)
765 761
 		if len(msg.Params) == 1 || msg.Params[1] != code {
766 762
 			rb.Notice(fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/DEBUG CRASHSERVER %s", code)))
@@ -1293,6 +1289,7 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
1293 1289
 
1294 1290
 // KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
1295 1291
 func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1292
+	hasPrivs := client.HasRoleCapabs("samode")
1296 1293
 	channels := strings.Split(msg.Params[0], ",")
1297 1294
 	users := strings.Split(msg.Params[1], ",")
1298 1295
 	if (len(channels) != len(users)) && (len(users) != 1) {
@@ -1336,7 +1333,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1336 1333
 		if comment == "" {
1337 1334
 			comment = kick.nick
1338 1335
 		}
1339
-		channel.Kick(client, target, comment, rb, false)
1336
+		channel.Kick(client, target, comment, rb, hasPrivs)
1340 1337
 	}
1341 1338
 	return false
1342 1339
 }
@@ -1618,7 +1615,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1618 1615
 		rb.Add(nil, client.server.name, RPL_LIST, nick, name, strconv.Itoa(members), topic)
1619 1616
 	}
1620 1617
 
1621
-	clientIsOp := client.HasMode(modes.Operator)
1618
+	clientIsOp := client.HasRoleCapabs("sajoin")
1622 1619
 	if len(channels) == 0 {
1623 1620
 		for _, channel := range server.channels.Channels() {
1624 1621
 			if !clientIsOp && channel.flags.HasMode(modes.Secret) {
@@ -1775,7 +1772,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1775 1772
 		rb.Add(nil, cDetails.nickMask, "MODE", args...)
1776 1773
 	} else if hasPrivs {
1777 1774
 		rb.Add(nil, server.name, RPL_UMODEIS, targetNick, target.ModeString())
1778
-		if target.HasMode(modes.LocalOperator) || target.HasMode(modes.Operator) {
1775
+		if target.HasMode(modes.Operator) {
1779 1776
 			masks := server.snomasks.String(target)
1780 1777
 			if 0 < len(masks) {
1781 1778
 				rb.Add(nil, server.name, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
@@ -1959,7 +1956,7 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1959 1956
 	success := false
1960 1957
 	channel := server.channels.Get(chname)
1961 1958
 	if channel != nil {
1962
-		if !channel.flags.HasMode(modes.Secret) || channel.hasClient(client) || client.HasMode(modes.Operator) {
1959
+		if !channel.flags.HasMode(modes.Secret) || channel.hasClient(client) || client.HasRoleCapabs("sajoin") {
1963 1960
 			channel.Names(client, rb)
1964 1961
 			success = true
1965 1962
 		}
@@ -2338,6 +2335,10 @@ func applyOper(client *Client, oper *Oper, rb *ResponseBuffer) {
2338 2335
 
2339 2336
 // DEOPER
2340 2337
 func deoperHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2338
+	if client.Oper() == nil {
2339
+		rb.Notice(client.t("Insufficient oper privs"))
2340
+		return false
2341
+	}
2341 2342
 	// pretend they sent /MODE $nick -o
2342 2343
 	fakeModeMsg := ircmsg.MakeMessage(nil, "", "MODE", client.Nick(), "-o")
2343 2344
 	return umodeHandler(server, client, fakeModeMsg, rb)
@@ -2944,7 +2945,7 @@ func operStatusVisible(client, target *Client, hasPrivs bool) bool {
2944 2945
 
2945 2946
 // USERHOST <nickname>{ <nickname>}
2946 2947
 func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2947
-	hasPrivs := client.HasMode(modes.Operator) // TODO(#1176) figure out the right capab for this
2948
+	hasPrivs := client.HasMode(modes.Operator)
2948 2949
 	returnedClients := make(ClientSet)
2949 2950
 
2950 2951
 	var tl utils.TokenLineBuilder
@@ -3083,7 +3084,7 @@ func (fields whoxFields) Has(field rune) bool {
3083 3084
 // <channel> <user> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] :<hopcount> <real name>
3084 3085
 // whox format:
3085 3086
 // <type> <channel> <user> <ip> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] <hops> <idle> <account> <rank> :<real name>
3086
-func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *ResponseBuffer, hasPrivs, includeRFlag, isWhox bool, fields whoxFields, whoType string) {
3087
+func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *ResponseBuffer, canSeeIPs, canSeeOpers, includeRFlag, isWhox bool, fields whoxFields, whoType string) {
3087 3088
 	params := []string{client.Nick()}
3088 3089
 
3089 3090
 	details := target.Details()
@@ -3103,7 +3104,7 @@ func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *Response
3103 3104
 	}
3104 3105
 	if fields.Has('i') {
3105 3106
 		fIP := "255.255.255.255"
3106
-		if hasPrivs || client == target {
3107
+		if canSeeIPs || client == target {
3107 3108
 			// you can only see a target's IP if they're you or you're an oper
3108 3109
 			fIP = target.IPString()
3109 3110
 		}
@@ -3126,7 +3127,7 @@ func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *Response
3126 3127
 			flags.WriteRune('H') // Here
3127 3128
 		}
3128 3129
 
3129
-		if target.HasMode(modes.Operator) && operStatusVisible(client, target, hasPrivs) {
3130
+		if target.HasMode(modes.Operator) && operStatusVisible(client, target, canSeeOpers) {
3130 3131
 			flags.WriteRune('*')
3131 3132
 		}
3132 3133
 
@@ -3229,23 +3230,23 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
3229 3230
 	//	operatorOnly = true
3230 3231
 	//}
3231 3232
 
3232
-	isOper := client.HasMode(modes.Operator)
3233
+	oper := client.Oper()
3234
+	hasPrivs := oper.HasRoleCapab("sajoin")
3235
+	canSeeIPs := oper.HasRoleCapab("ban")
3233 3236
 	if mask[0] == '#' {
3234
-		// TODO implement wildcard matching
3235
-		//TODO(dan): ^ only for opers
3236 3237
 		channel := server.channels.Get(mask)
3237 3238
 		if channel != nil {
3238 3239
 			isJoined := channel.hasClient(client)
3239
-			if !channel.flags.HasMode(modes.Secret) || isJoined || isOper {
3240
+			if !channel.flags.HasMode(modes.Secret) || isJoined || hasPrivs {
3240 3241
 				var members []*Client
3241
-				if isOper {
3242
+				if hasPrivs {
3242 3243
 					members = channel.Members()
3243 3244
 				} else {
3244 3245
 					members = channel.auditoriumFriends(client)
3245 3246
 				}
3246 3247
 				for _, member := range members {
3247
-					if !member.HasMode(modes.Invisible) || isJoined || isOper {
3248
-						client.rplWhoReply(channel, member, rb, isOper, includeRFlag, isWhox, fields, whoType)
3248
+					if !member.HasMode(modes.Invisible) || isJoined || hasPrivs {
3249
+						client.rplWhoReply(channel, member, rb, canSeeIPs, oper != nil, includeRFlag, isWhox, fields, whoType)
3249 3250
 					}
3250 3251
 				}
3251 3252
 			}
@@ -3275,8 +3276,8 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
3275 3276
 		}
3276 3277
 
3277 3278
 		for mclient := range server.clients.FindAll(mask) {
3278
-			if isOper || !mclient.HasMode(modes.Invisible) || isFriend(mclient) {
3279
-				client.rplWhoReply(nil, mclient, rb, isOper, includeRFlag, isWhox, fields, whoType)
3279
+			if hasPrivs || !mclient.HasMode(modes.Invisible) || isFriend(mclient) {
3280
+				client.rplWhoReply(nil, mclient, rb, canSeeIPs, oper != nil, includeRFlag, isWhox, fields, whoType)
3280 3281
 			}
3281 3282
 		}
3282 3283
 	}
@@ -3319,7 +3320,7 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
3319 3320
 		return true
3320 3321
 	}
3321 3322
 
3322
-	hasPrivs := client.HasMode(modes.Operator) // TODO(#1176) figure out the right capab for this
3323
+	hasPrivs := client.HasRoleCapabs("samode")
3323 3324
 	if hasPrivs {
3324 3325
 		for _, mask := range strings.Split(masksString, ",") {
3325 3326
 			matches := server.clients.FindAll(mask)

+ 21
- 19
irc/modes.go Visa fil

@@ -37,14 +37,14 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
37 37
 		if change.Mode != modes.ServerNotice {
38 38
 			switch change.Op {
39 39
 			case modes.Add:
40
-				if (change.Mode == modes.Operator || change.Mode == modes.LocalOperator) && !(force && oper != nil) {
40
+				if (change.Mode == modes.Operator) && !(force && oper != nil) {
41 41
 					continue
42 42
 				}
43 43
 
44 44
 				if client.SetMode(change.Mode, true) {
45 45
 					if change.Mode == modes.Invisible {
46 46
 						client.server.stats.ChangeInvisible(1)
47
-					} else if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
47
+					} else if change.Mode == modes.Operator {
48 48
 						client.server.stats.ChangeOperators(1)
49 49
 					}
50 50
 					applied = append(applied, change)
@@ -55,7 +55,7 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
55 55
 				if client.SetMode(change.Mode, false) {
56 56
 					if change.Mode == modes.Invisible {
57 57
 						client.server.stats.ChangeInvisible(-1)
58
-					} else if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
58
+					} else if change.Mode == modes.Operator {
59 59
 						removedSnomasks = client.server.snomasks.String(client)
60 60
 						client.server.stats.ChangeOperators(-1)
61 61
 						applyOper(client, nil, nil)
@@ -75,26 +75,28 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
75 75
 			}
76 76
 		} else {
77 77
 			// server notices are weird
78
-			if !client.HasMode(modes.Operator) {
78
+			if !client.HasMode(modes.Operator) || change.Op == modes.List {
79 79
 				continue
80 80
 			}
81
-			var masks []sno.Mask
82
-			if change.Op == modes.Add || change.Op == modes.Remove {
83
-				var newArg string
84
-				for _, char := range change.Arg {
85
-					mask := sno.Mask(char)
86
-					if sno.ValidMasks[mask] {
87
-						masks = append(masks, mask)
88
-						newArg += string(char)
89
-					}
81
+
82
+			currentMasks := client.server.snomasks.MasksEnabled(client)
83
+			addMasks, removeMasks, newArg := sno.EvaluateSnomaskChanges(change.Op == modes.Add, change.Arg, currentMasks)
84
+
85
+			success := false
86
+			if len(addMasks) != 0 {
87
+				oper := client.Oper()
88
+				// #1176: require special operator privileges to subscribe to snomasks
89
+				if oper.HasRoleCapab("snomasks") || oper.HasRoleCapab("ban") {
90
+					success = true
91
+					client.server.snomasks.AddMasks(client, addMasks...)
90 92
 				}
91
-				change.Arg = newArg
92 93
 			}
93
-			if change.Op == modes.Add {
94
-				client.server.snomasks.AddMasks(client, masks...)
95
-				applied = append(applied, change)
96
-			} else if change.Op == modes.Remove {
97
-				client.server.snomasks.RemoveMasks(client, masks...)
94
+			if len(removeMasks) != 0 {
95
+				success = true
96
+				client.server.snomasks.RemoveMasks(client, removeMasks...)
97
+			}
98
+			if success {
99
+				change.Arg = newArg
98 100
 				applied = append(applied, change)
99 101
 			}
100 102
 		}

+ 1
- 4
irc/modes/modes.go Visa fil

@@ -101,7 +101,6 @@ func (modes Modes) String() string {
101 101
 const (
102 102
 	Bot             Mode = 'B'
103 103
 	Invisible       Mode = 'i'
104
-	LocalOperator   Mode = 'O'
105 104
 	Operator        Mode = 'o'
106 105
 	Restricted      Mode = 'r'
107 106
 	RegisteredOnly  Mode = 'R'
@@ -213,12 +212,10 @@ func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
213 212
 			// put arg into modechange if needed
214 213
 			switch Mode(mode) {
215 214
 			case ServerNotice:
216
-				// always require arg
215
+				// arg is optional for ServerNotice (we accept bare `-s`)
217 216
 				if len(params) > skipArgs {
218 217
 					change.Arg = params[skipArgs]
219 218
 					skipArgs++
220
-				} else {
221
-					continue
222 219
 				}
223 220
 			}
224 221
 

+ 32
- 0
irc/modes/modes_test.go Visa fil

@@ -15,6 +15,38 @@ func assertEqual(supplied, expected interface{}, t *testing.T) {
15 15
 	}
16 16
 }
17 17
 
18
+func TestParseUserModeChanges(t *testing.T) {
19
+	emptyUnknown := make(map[rune]bool)
20
+	changes, unknown := ParseUserModeChanges("+i")
21
+	assertEqual(unknown, emptyUnknown, t)
22
+	assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}}, t)
23
+
24
+	// no-op change to sno
25
+	changes, unknown = ParseUserModeChanges("+is")
26
+	assertEqual(unknown, emptyUnknown, t)
27
+	assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}, ModeChange{Op: Add, Mode: ServerNotice}}, t)
28
+
29
+	// add snomasks
30
+	changes, unknown = ParseUserModeChanges("+is", "ac")
31
+	assertEqual(unknown, emptyUnknown, t)
32
+	assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}, ModeChange{Op: Add, Mode: ServerNotice, Arg: "ac"}}, t)
33
+
34
+	// remove snomasks
35
+	changes, unknown = ParseUserModeChanges("+s", "-cx")
36
+	assertEqual(unknown, emptyUnknown, t)
37
+	assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: ServerNotice, Arg: "-cx"}}, t)
38
+
39
+	// remove all snomasks (arg is parsed but has no meaning)
40
+	changes, unknown = ParseUserModeChanges("-is", "ac")
41
+	assertEqual(unknown, emptyUnknown, t)
42
+	assertEqual(changes, ModeChanges{ModeChange{Op: Remove, Mode: Invisible}, ModeChange{Op: Remove, Mode: ServerNotice, Arg: "ac"}}, t)
43
+
44
+	// remove all snomasks
45
+	changes, unknown = ParseUserModeChanges("-is")
46
+	assertEqual(unknown, emptyUnknown, t)
47
+	assertEqual(changes, ModeChanges{ModeChange{Op: Remove, Mode: Invisible}, ModeChange{Op: Remove, Mode: ServerNotice}}, t)
48
+}
49
+
18 50
 func TestIssue874(t *testing.T) {
19 51
 	emptyUnknown := make(map[rune]bool)
20 52
 	modes, unknown := ParseChannelModeChanges("+k")

+ 12
- 11
irc/server.go Visa fil

@@ -459,15 +459,13 @@ func (server *Server) MOTD(client *Client, rb *ResponseBuffer) {
459 459
 	rb.Add(nil, server.name, RPL_ENDOFMOTD, client.nick, client.t("End of MOTD command"))
460 460
 }
461 461
 
462
-// WhoisChannelsNames returns the common channel names between two users.
463
-func (client *Client) WhoisChannelsNames(target *Client, multiPrefix bool) []string {
462
+func (client *Client) whoisChannelsNames(target *Client, multiPrefix bool, hasPrivs bool) []string {
464 463
 	var chstrs []string
464
+	targetInvis := target.HasMode(modes.Invisible)
465 465
 	for _, channel := range target.Channels() {
466
-		// channel is secret and the target can't see it
467
-		if !client.HasMode(modes.Operator) {
468
-			if (target.HasMode(modes.Invisible) || channel.flags.HasMode(modes.Secret)) && !channel.hasClient(client) {
469
-				continue
470
-			}
466
+		if !hasPrivs && (targetInvis || channel.flags.HasMode(modes.Secret)) && !channel.hasClient(client) {
467
+			// client can't see *this* channel membership
468
+			continue
471 469
 		}
472 470
 		chstrs = append(chstrs, channel.ClientPrefixes(target, multiPrefix)+channel.name)
473 471
 	}
@@ -475,23 +473,26 @@ func (client *Client) WhoisChannelsNames(target *Client, multiPrefix bool) []str
475 473
 }
476 474
 
477 475
 func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuffer) {
476
+	oper := client.Oper()
478 477
 	cnick := client.Nick()
479 478
 	targetInfo := target.Details()
480 479
 	rb.Add(nil, client.server.name, RPL_WHOISUSER, cnick, targetInfo.nick, targetInfo.username, targetInfo.hostname, "*", targetInfo.realname)
481 480
 	tnick := targetInfo.nick
482 481
 
483
-	whoischannels := client.WhoisChannelsNames(target, rb.session.capabilities.Has(caps.MultiPrefix))
482
+	whoischannels := client.whoisChannelsNames(target, rb.session.capabilities.Has(caps.MultiPrefix), oper.HasRoleCapab("sajoin"))
484 483
 	if whoischannels != nil {
485 484
 		rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, cnick, tnick, strings.Join(whoischannels, " "))
486 485
 	}
487
-	if target.HasMode(modes.Operator) && operStatusVisible(client, target, hasPrivs) {
486
+	if target.HasMode(modes.Operator) && operStatusVisible(client, target, oper != nil) {
488 487
 		tOper := target.Oper()
489 488
 		if tOper != nil {
490 489
 			rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, cnick, tnick, tOper.WhoisLine)
491 490
 		}
492 491
 	}
493
-	if client == target || hasPrivs {
492
+	if client == target || oper.HasRoleCapab("ban") {
494 493
 		rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, target.RawHostname()), target.IPString(), client.t("Actual user@host, Actual IP"))
494
+	}
495
+	if client == target || oper.HasRoleCapab("samode") {
495 496
 		rb.Add(nil, client.server.name, RPL_WHOISMODES, cnick, tnick, fmt.Sprintf(client.t("is using modes +%s"), target.modes.String()))
496 497
 	}
497 498
 	if target.HasMode(modes.TLS) {
@@ -504,7 +505,7 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff
504 505
 		rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, fmt.Sprintf(ircfmt.Unescape(client.t("is a $bBot$b on %s")), client.server.Config().Network.Name))
505 506
 	}
506 507
 
507
-	if client == target || hasPrivs {
508
+	if client == target || oper.HasRoleCapab("ban") {
508 509
 		for _, session := range target.Sessions() {
509 510
 			if session.certfp != "" {
510 511
 				rb.Add(nil, client.server.name, RPL_WHOISCERTFP, cnick, tnick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), session.certfp))

+ 15
- 13
irc/sno/constants.go Visa fil

@@ -7,6 +7,8 @@ package sno
7 7
 // Mask is a type of server notice mask.
8 8
 type Mask rune
9 9
 
10
+type Masks []Mask
11
+
10 12
 // Notice mask types
11 13
 const (
12 14
 	LocalAnnouncements Mask = 'a'
@@ -18,8 +20,8 @@ const (
18 20
 	LocalQuits         Mask = 'q'
19 21
 	Stats              Mask = 't'
20 22
 	LocalAccounts      Mask = 'u'
21
-	LocalXline         Mask = 'x'
22 23
 	LocalVhosts        Mask = 'v'
24
+	LocalXline         Mask = 'x'
23 25
 )
24 26
 
25 27
 var (
@@ -39,17 +41,17 @@ var (
39 41
 	}
40 42
 
41 43
 	// ValidMasks contains the snomasks that we support.
42
-	ValidMasks = map[Mask]bool{
43
-		LocalAnnouncements: true,
44
-		LocalConnects:      true,
45
-		LocalChannels:      true,
46
-		LocalKills:         true,
47
-		LocalNicks:         true,
48
-		LocalOpers:         true,
49
-		LocalQuits:         true,
50
-		Stats:              true,
51
-		LocalAccounts:      true,
52
-		LocalXline:         true,
53
-		LocalVhosts:        true,
44
+	ValidMasks = []Mask{
45
+		LocalAnnouncements,
46
+		LocalConnects,
47
+		LocalChannels,
48
+		LocalKills,
49
+		LocalNicks,
50
+		LocalOpers,
51
+		LocalQuits,
52
+		Stats,
53
+		LocalAccounts,
54
+		LocalVhosts,
55
+		LocalXline,
54 56
 	}
55 57
 )

+ 87
- 0
irc/sno/utils.go Visa fil

@@ -0,0 +1,87 @@
1
+// Copyright (c) 2020 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package sno
5
+
6
+import (
7
+	"strings"
8
+)
9
+
10
+func IsValidMask(r rune) bool {
11
+	for _, m := range ValidMasks {
12
+		if m == Mask(r) {
13
+			return true
14
+		}
15
+	}
16
+	return false
17
+}
18
+
19
+func (masks Masks) String() string {
20
+	var buf strings.Builder
21
+	buf.Grow(len(masks))
22
+	for _, m := range masks {
23
+		buf.WriteRune(rune(m))
24
+	}
25
+	return buf.String()
26
+}
27
+
28
+func (masks Masks) Contains(mask Mask) bool {
29
+	for _, m := range masks {
30
+		if mask == m {
31
+			return true
32
+		}
33
+	}
34
+	return false
35
+}
36
+
37
+// Evaluate changes to snomasks made with MODE. There are several cases:
38
+// adding snomasks with `/mode +s a` or `/mode +s +a`, removing them with `/mode +s -a`,
39
+// adding all with `/mode +s *` or `/mode +s +*`, removing all with `/mode +s -*` or `/mode -s`
40
+func EvaluateSnomaskChanges(add bool, arg string, currentMasks Masks) (addMasks, removeMasks Masks, newArg string) {
41
+	if add {
42
+		if len(arg) == 0 {
43
+			return
44
+		}
45
+		add := true
46
+		switch arg[0] {
47
+		case '+':
48
+			arg = arg[1:]
49
+		case '-':
50
+			add = false
51
+			arg = arg[1:]
52
+		default:
53
+			// add
54
+		}
55
+		if strings.IndexByte(arg, '*') != -1 {
56
+			if add {
57
+				for _, mask := range ValidMasks {
58
+					if !currentMasks.Contains(mask) {
59
+						addMasks = append(addMasks, mask)
60
+					}
61
+				}
62
+			} else {
63
+				removeMasks = currentMasks
64
+			}
65
+		} else {
66
+			for _, r := range arg {
67
+				if IsValidMask(r) {
68
+					m := Mask(r)
69
+					if add && !currentMasks.Contains(m) {
70
+						addMasks = append(addMasks, m)
71
+					} else if !add && currentMasks.Contains(m) {
72
+						removeMasks = append(removeMasks, m)
73
+					}
74
+				}
75
+			}
76
+		}
77
+		if len(addMasks) != 0 {
78
+			newArg = "+" + addMasks.String()
79
+		} else if len(removeMasks) != 0 {
80
+			newArg = "-" + removeMasks.String()
81
+		}
82
+	} else {
83
+		removeMasks = currentMasks
84
+		newArg = ""
85
+	}
86
+	return
87
+}

+ 53
- 0
irc/sno/utils_test.go Visa fil

@@ -0,0 +1,53 @@
1
+// Copyright (c) 2020 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package sno
5
+
6
+import (
7
+	"fmt"
8
+	"reflect"
9
+	"testing"
10
+)
11
+
12
+func assertEqual(supplied, expected interface{}, t *testing.T) {
13
+	if !reflect.DeepEqual(supplied, expected) {
14
+		panic(fmt.Sprintf("expected %#v but got %#v", expected, supplied))
15
+	}
16
+}
17
+
18
+func TestEvaluateSnomaskChanges(t *testing.T) {
19
+	add, remove, newArg := EvaluateSnomaskChanges(true, "*", nil)
20
+	assertEqual(add, Masks{'a', 'c', 'j', 'k', 'n', 'o', 'q', 't', 'u', 'v', 'x'}, t)
21
+	assertEqual(len(remove), 0, t)
22
+	assertEqual(newArg, "+acjknoqtuvx", t)
23
+
24
+	add, remove, newArg = EvaluateSnomaskChanges(true, "*", Masks{'a', 'u'})
25
+	assertEqual(add, Masks{'c', 'j', 'k', 'n', 'o', 'q', 't', 'v', 'x'}, t)
26
+	assertEqual(len(remove), 0, t)
27
+	assertEqual(newArg, "+cjknoqtvx", t)
28
+
29
+	add, remove, newArg = EvaluateSnomaskChanges(true, "-a", Masks{'a', 'u'})
30
+	assertEqual(len(add), 0, t)
31
+	assertEqual(remove, Masks{'a'}, t)
32
+	assertEqual(newArg, "-a", t)
33
+
34
+	add, remove, newArg = EvaluateSnomaskChanges(true, "-*", Masks{'a', 'u'})
35
+	assertEqual(len(add), 0, t)
36
+	assertEqual(remove, Masks{'a', 'u'}, t)
37
+	assertEqual(newArg, "-au", t)
38
+
39
+	add, remove, newArg = EvaluateSnomaskChanges(true, "+c", Masks{'a', 'u'})
40
+	assertEqual(add, Masks{'c'}, t)
41
+	assertEqual(len(remove), 0, t)
42
+	assertEqual(newArg, "+c", t)
43
+
44
+	add, remove, newArg = EvaluateSnomaskChanges(false, "", Masks{'a', 'u'})
45
+	assertEqual(len(add), 0, t)
46
+	assertEqual(remove, Masks{'a', 'u'}, t)
47
+	assertEqual(newArg, "", t)
48
+
49
+	add, remove, newArg = EvaluateSnomaskChanges(false, "*", Masks{'a', 'u'})
50
+	assertEqual(len(add), 0, t)
51
+	assertEqual(remove, Masks{'a', 'u'}, t)
52
+	assertEqual(newArg, "", t)
53
+}

+ 9
- 10
irc/snomanager.go Visa fil

@@ -24,11 +24,6 @@ func (m *SnoManager) AddMasks(client *Client, masks ...sno.Mask) {
24 24
 	defer m.sendListMutex.Unlock()
25 25
 
26 26
 	for _, mask := range masks {
27
-		// confirm mask is valid
28
-		if !sno.ValidMasks[mask] {
29
-			continue
30
-		}
31
-
32 27
 		currentClientList := m.sendLists[mask]
33 28
 
34 29
 		if currentClientList == nil {
@@ -101,19 +96,23 @@ func (m *SnoManager) Send(mask sno.Mask, content string) {
101 96
 	}
102 97
 }
103 98
 
104
-// String returns the snomasks currently enabled.
105
-func (m *SnoManager) String(client *Client) string {
99
+// MasksEnabled returns the snomasks currently enabled.
100
+func (m *SnoManager) MasksEnabled(client *Client) (result sno.Masks) {
106 101
 	m.sendListMutex.RLock()
107 102
 	defer m.sendListMutex.RUnlock()
108 103
 
109
-	var masks string
110 104
 	for mask, clients := range m.sendLists {
111 105
 		for c := range clients {
112 106
 			if c == client {
113
-				masks += string(mask)
107
+				result = append(result, mask)
114 108
 				break
115 109
 			}
116 110
 		}
117 111
 	}
118
-	return masks
112
+	return
113
+}
114
+
115
+func (m *SnoManager) String(client *Client) string {
116
+	masks := m.MasksEnabled(client)
117
+	return masks.String()
119 118
 }

+ 1
- 0
traditional.yaml Visa fil

@@ -565,6 +565,7 @@ oper-classes:
565 565
             - "vhosts"
566 566
             - "sajoin"
567 567
             - "samode"
568
+            - "snomasks"
568 569
 
569 570
     # server admin: has full control of the ircd, including nickname and
570 571
     # channel registrations

Laddar…
Avbryt
Spara