Browse Source

review fixes and updates

tags/v0.12.0
Shivaram Lingamneni 6 years ago
parent
commit
d3815fbe61
6 changed files with 140 additions and 75 deletions
  1. 48
    41
      irc/chanserv.go
  2. 1
    0
      irc/errors.go
  3. 0
    34
      irc/getters.go
  4. 76
    0
      irc/modes.go
  5. 14
    0
      irc/modes_test.go
  6. 1
    0
      oragono.yaml

+ 48
- 41
irc/chanserv.go View File

@@ -52,8 +52,7 @@ For example, $bAMODE #channel +o dan$b grants the the holder of the "dan"
52 52
 account the +o operator mode every time they join #channel. To list current
53 53
 accounts and modes, use $bAMODE #channel$b. Note that users are always
54 54
 referenced by their registered account names, not their nicknames.`,
55
-			helpShort:    `$bAMODE$b modifies persistent mode settings for channel members.`,
56
-			authRequired: true,
55
+			helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`,
57 56
 		},
58 57
 	}
59 58
 )
@@ -70,61 +69,69 @@ func csAmodeHandler(server *Server, client *Client, command, params string, rb *
70 69
 	if channel == nil {
71 70
 		csNotice(rb, client.t("Channel does not exist"))
72 71
 		return
73
-	}
74
-
75
-	clientAccount := client.Account()
76
-	if clientAccount == "" || clientAccount != channel.Founder() {
77
-		csNotice(rb, client.t("You must be the channel founder to use AMODE"))
72
+	} else if channel.Founder() == "" {
73
+		csNotice(rb, client.t("Channel is not registered"))
78 74
 		return
79 75
 	}
80 76
 
81 77
 	modeChanges, unknown := modes.ParseChannelModeChanges(strings.Fields(modeChange)...)
82
-
78
+	var change modes.ModeChange
83 79
 	if len(modeChanges) > 1 || len(unknown) > 0 {
84 80
 		csNotice(rb, client.t("Invalid mode change"))
85 81
 		return
82
+	} else if len(modeChanges) == 1 {
83
+		change = modeChanges[0]
84
+	} else {
85
+		change = modes.ModeChange{Op: modes.List}
86 86
 	}
87 87
 
88
-	if len(modeChanges) == 0 || modeChanges[0].Op == modes.List {
89
-		persistentModes := channel.AccountToUmode()
90
-		// sort the persistent modes in descending order of priority, i.e.,
91
-		// ascending order of their index in the ChannelUserModes list
92
-		sort.Slice(persistentModes, func(i, j int) bool {
93
-			index := func(modeChange modes.ModeChange) int {
94
-				for idx, mode := range modes.ChannelUserModes {
95
-					if modeChange.Mode == mode {
96
-						return idx
97
-					}
98
-				}
99
-				return len(modes.ChannelUserModes)
100
-			}
101
-			return index(persistentModes[i]) < index(persistentModes[j])
102
-		})
103
-		csNotice(rb, fmt.Sprintf(client.t("Channel %s has %d persistent modes set"), channelName, len(persistentModes)))
104
-		for _, modeChange := range persistentModes {
105
-			csNotice(rb, fmt.Sprintf(client.t("Account %s receives mode +%s"), modeChange.Arg, string(modeChange.Mode)))
106
-		}
107
-		return
108
-	}
109
-
88
+	// normalize and validate the account argument
110 89
 	accountIsValid := false
111
-	change := modeChanges[0]
112
-	// Arg is the account name, casefold it here
113 90
 	change.Arg, _ = CasefoldName(change.Arg)
114
-	if change.Arg != "" {
115
-		_, err := server.accounts.LoadAccount(change.Arg)
116
-		accountIsValid = (err == nil)
91
+	switch change.Op {
92
+	case modes.List:
93
+		accountIsValid = true
94
+	case modes.Add:
95
+		// if we're adding a mode, the account must exist
96
+		if change.Arg != "" {
97
+			_, err := server.accounts.LoadAccount(change.Arg)
98
+			accountIsValid = (err == nil)
99
+		}
100
+	case modes.Remove:
101
+		// allow removal of accounts that may have been deleted
102
+		accountIsValid = (change.Arg != "")
117 103
 	}
118 104
 	if !accountIsValid {
119 105
 		csNotice(rb, client.t("Account does not exist"))
120 106
 		return
121 107
 	}
122
-	applied := channel.ApplyAccountToUmodeChange(change)
123
-	if applied {
124
-		csNotice(rb, fmt.Sprintf(client.t("Successfully set mode %s"), change.String()))
125
-		go server.channelRegistry.StoreChannel(channel, IncludeLists)
126
-	} else {
127
-		csNotice(rb, client.t("Change was a no-op"))
108
+
109
+	affectedModes, err := channel.ProcessAccountToUmodeChange(client, change)
110
+
111
+	if err == errInsufficientPrivs {
112
+		csNotice(rb, client.t("Insufficient privileges"))
113
+		return
114
+	} else if err != nil {
115
+		csNotice(rb, client.t("Internal error"))
116
+		return
117
+	}
118
+
119
+	switch change.Op {
120
+	case modes.List:
121
+		// sort the persistent modes in descending order of priority
122
+		sort.Slice(affectedModes, func(i, j int) bool {
123
+			return umodeGreaterThan(affectedModes[i].Mode, affectedModes[j].Mode)
124
+		})
125
+		csNotice(rb, fmt.Sprintf(client.t("Channel %s has %d persistent modes set"), channelName, len(affectedModes)))
126
+		for _, modeChange := range affectedModes {
127
+			csNotice(rb, fmt.Sprintf(client.t("Account %s receives mode +%s"), modeChange.Arg, string(modeChange.Mode)))
128
+		}
129
+	case modes.Add, modes.Remove:
130
+		if len(affectedModes) > 0 {
131
+			csNotice(rb, fmt.Sprintf(client.t("Successfully set mode %s"), change.String()))
132
+		} else {
133
+			csNotice(rb, client.t("Change was a no-op"))
134
+		}
128 135
 	}
129 136
 }
130 137
 

+ 1
- 0
irc/errors.go View File

@@ -34,6 +34,7 @@ var (
34 34
 	errNoExistingBan                  = errors.New("Ban does not exist")
35 35
 	errNoSuchChannel                  = errors.New("No such channel")
36 36
 	errRenamePrivsNeeded              = errors.New("Only chanops can rename channels")
37
+	errInsufficientPrivs              = errors.New("Insufficient privileges")
37 38
 	errSaslFail                       = errors.New("SASL failed")
38 39
 )
39 40
 

+ 0
- 34
irc/getters.go View File

@@ -303,37 +303,3 @@ func (channel *Channel) Founder() string {
303 303
 	defer channel.stateMutex.RUnlock()
304 304
 	return channel.registeredFounder
305 305
 }
306
-
307
-func (channel *Channel) AccountToUmode() (result []modes.ModeChange) {
308
-	channel.stateMutex.RLock()
309
-	defer channel.stateMutex.RUnlock()
310
-	for account, mode := range channel.accountToUMode {
311
-		result = append(result, modes.ModeChange{
312
-			Mode: mode,
313
-			Arg:  account,
314
-			Op:   modes.Add,
315
-		})
316
-	}
317
-
318
-	return
319
-}
320
-
321
-func (channel *Channel) ApplyAccountToUmodeChange(change modes.ModeChange) (applied bool) {
322
-	channel.stateMutex.Lock()
323
-	defer channel.stateMutex.Unlock()
324
-
325
-	current := channel.accountToUMode[change.Arg]
326
-	switch change.Op {
327
-	case modes.Add:
328
-		applied = (current != change.Mode)
329
-		if applied {
330
-			channel.accountToUMode[change.Arg] = change.Mode
331
-		}
332
-	case modes.Remove:
333
-		applied = (current == change.Mode)
334
-		if applied {
335
-			delete(channel.accountToUMode, change.Arg)
336
-		}
337
-	}
338
-	return
339
-}

+ 76
- 0
irc/modes.go View File

@@ -240,3 +240,79 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
240 240
 
241 241
 	return applied
242 242
 }
243
+
244
+// tests whether l > r, in the channel-user mode ordering (e.g., Halfop > Voice)
245
+func umodeGreaterThan(l modes.Mode, r modes.Mode) bool {
246
+	for _, mode := range modes.ChannelUserModes {
247
+		if l == mode && r != mode {
248
+			return true
249
+		} else if r == mode {
250
+			return false
251
+		}
252
+	}
253
+	return false
254
+}
255
+
256
+// ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes.
257
+func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes.ModeChange) (results []modes.ModeChange, err error) {
258
+	umodeGEQ := func(l modes.Mode, r modes.Mode) bool {
259
+		return l == r || umodeGreaterThan(l, r)
260
+	}
261
+
262
+	account := client.Account()
263
+	isOperChange := client.HasRoleCapabs("chanreg")
264
+
265
+	channel.stateMutex.Lock()
266
+	defer channel.stateMutex.Unlock()
267
+
268
+	clientMode := channel.accountToUMode[account]
269
+	targetModeNow := channel.accountToUMode[change.Arg]
270
+	var targetModeAfter modes.Mode
271
+	if change.Op == modes.Add {
272
+		targetModeAfter = change.Mode
273
+	}
274
+
275
+	// operators and founders can do anything
276
+	hasPrivs := isOperChange || (account != "" && account == channel.registeredFounder)
277
+	// halfop and up can list, and do add/removes at levels <= their own
278
+	if change.Op == modes.List && umodeGEQ(clientMode, modes.Halfop) {
279
+		hasPrivs = true
280
+	} else if umodeGEQ(clientMode, modes.Halfop) && umodeGEQ(clientMode, targetModeNow) && umodeGEQ(clientMode, targetModeAfter) {
281
+		hasPrivs = true
282
+	}
283
+	if !hasPrivs {
284
+		return nil, errInsufficientPrivs
285
+	}
286
+
287
+	switch change.Op {
288
+	case modes.Add:
289
+		if targetModeNow != targetModeAfter {
290
+			channel.accountToUMode[change.Arg] = change.Mode
291
+			go client.server.channelRegistry.StoreChannel(channel, IncludeLists)
292
+			return []modes.ModeChange{change}, nil
293
+		}
294
+		return nil, nil
295
+	case modes.Remove:
296
+		if targetModeNow == change.Mode {
297
+			delete(channel.accountToUMode, change.Arg)
298
+			go client.server.channelRegistry.StoreChannel(channel, IncludeLists)
299
+			return []modes.ModeChange{change}, nil
300
+		}
301
+		return nil, nil
302
+	case modes.List:
303
+		result := make([]modes.ModeChange, len(channel.accountToUMode))
304
+		pos := 0
305
+		for account, mode := range channel.accountToUMode {
306
+			result[pos] = modes.ModeChange{
307
+				Mode: mode,
308
+				Arg:  account,
309
+				Op:   modes.Add,
310
+			}
311
+			pos++
312
+		}
313
+		return result, nil
314
+	default:
315
+		// shouldn't happen
316
+		return nil, errInvalidCharacter
317
+	}
318
+}

+ 14
- 0
irc/modes_test.go View File

@@ -36,3 +36,17 @@ func TestParseDefaultChannelModes(t *testing.T) {
36 36
 		}
37 37
 	}
38 38
 }
39
+
40
+func TestUmodeGreaterThan(t *testing.T) {
41
+	if !umodeGreaterThan(modes.Halfop, modes.Voice) {
42
+		t.Errorf("expected Halfop > Voice")
43
+	}
44
+
45
+	if !umodeGreaterThan(modes.Voice, modes.Mode(0)) {
46
+		t.Errorf("expected Voice > 0 (the zero value of modes.Mode)")
47
+	}
48
+
49
+	if umodeGreaterThan(modes.ChannelAdmin, modes.ChannelAdmin) {
50
+		t.Errorf("modes should not be greater than themselves")
51
+	}
52
+}

+ 1
- 0
oragono.yaml View File

@@ -283,6 +283,7 @@ oper-classes:
283 283
             - "unregister"
284 284
             - "samode"
285 285
             - "vhosts"
286
+            - "chanreg"
286 287
 
287 288
 # ircd operators
288 289
 opers:

Loading…
Cancel
Save