2016-06-15 13:50:56 +02:00
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
2017-03-12 23:08:18 +01:00
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2016-06-15 13:50:56 +02:00
// released under the MIT license
2014-03-13 21:18:40 +01:00
package irc
import (
2019-10-13 12:07:30 +02:00
"fmt"
2016-06-26 13:06:28 +02:00
"strconv"
2014-03-13 21:18:40 +01:00
"strings"
2016-06-20 14:53:45 +02:00
2018-02-03 11:21:32 +01:00
"github.com/oragono/oragono/irc/modes"
2017-06-14 20:00:53 +02:00
"github.com/oragono/oragono/irc/sno"
2014-03-13 21:18:40 +01:00
)
var (
2017-06-19 22:53:16 +02:00
// DefaultChannelModes are enabled on brand new channels when they're created.
2017-09-06 23:34:38 +02:00
// this can be overridden in the `channels` config, with the `default-modes` key
2018-02-03 11:21:32 +01:00
DefaultChannelModes = modes . Modes {
modes . NoOutside , modes . OpOnlyTopic ,
2016-04-14 01:35:36 +02:00
}
2014-03-13 21:18:40 +01:00
)
2018-04-23 00:47:10 +02:00
// ApplyUserModeChanges applies the given changes, and returns the applied changes.
2020-03-17 18:19:27 +01:00
// `oper` is the operclass of the client gaining +o, when applicable (this is just
// to confirm that the client actually has a valid operclass)
func ApplyUserModeChanges ( client * Client , changes modes . ModeChanges , force bool , oper * Oper ) modes . ModeChanges {
2018-02-03 11:21:32 +01:00
applied := make ( modes . ModeChanges , 0 )
2017-03-12 23:08:18 +01:00
for _ , change := range changes {
2018-02-03 11:21:32 +01:00
switch change . Mode {
case modes . Bot , modes . Invisible , modes . WallOps , modes . UserRoleplaying , modes . Operator , modes . LocalOperator , modes . RegisteredOnly :
switch change . Op {
case modes . Add :
2020-03-17 18:19:27 +01:00
if ( change . Mode == modes . Operator || change . Mode == modes . LocalOperator ) && ! ( force && oper != nil ) {
2017-05-08 01:15:16 +02:00
continue
}
2018-04-24 02:03:26 +02:00
if client . SetMode ( change . Mode , true ) {
2018-04-23 00:47:10 +02:00
if change . Mode == modes . Invisible {
client . server . stats . ChangeInvisible ( 1 )
} else if change . Mode == modes . Operator || change . Mode == modes . LocalOperator {
client . server . stats . ChangeOperators ( 1 )
}
applied = append ( applied , change )
2018-04-20 22:48:15 +02:00
}
2018-02-03 11:21:32 +01:00
case modes . Remove :
2020-02-21 12:10:35 +01:00
var removedSnomasks string
2018-04-24 02:03:26 +02:00
if client . SetMode ( change . Mode , false ) {
2018-04-23 00:47:10 +02:00
if change . Mode == modes . Invisible {
client . server . stats . ChangeInvisible ( - 1 )
} else if change . Mode == modes . Operator || change . Mode == modes . LocalOperator {
2020-02-21 12:10:35 +01:00
removedSnomasks = client . server . snomasks . String ( client )
2018-04-23 00:47:10 +02:00
client . server . stats . ChangeOperators ( - 1 )
2020-02-21 12:10:35 +01:00
applyOper ( client , nil , nil )
if removedSnomasks != "" {
client . server . snomasks . RemoveClient ( client )
}
2018-04-23 00:47:10 +02:00
}
applied = append ( applied , change )
2020-02-21 12:10:35 +01:00
if removedSnomasks != "" {
applied = append ( applied , modes . ModeChange {
Mode : modes . ServerNotice ,
Op : modes . Remove ,
Arg : removedSnomasks ,
} )
}
2018-04-20 22:48:15 +02:00
}
2017-03-12 23:08:18 +01:00
}
2018-02-03 11:21:32 +01:00
case modes . ServerNotice :
2018-04-23 00:47:10 +02:00
if ! client . HasMode ( modes . Operator ) {
2017-05-08 01:15:16 +02:00
continue
}
var masks [ ] sno . Mask
2018-02-03 11:21:32 +01:00
if change . Op == modes . Add || change . Op == modes . Remove {
2018-04-16 05:20:37 +02:00
var newArg string
2018-02-03 11:21:32 +01:00
for _ , char := range change . Arg {
2018-04-16 05:20:37 +02:00
mask := sno . Mask ( char )
if sno . ValidMasks [ mask ] {
masks = append ( masks , mask )
newArg += string ( char )
}
2017-03-12 23:08:18 +01:00
}
2018-04-16 05:20:37 +02:00
change . Arg = newArg
2017-05-08 01:15:16 +02:00
}
2018-02-03 11:21:32 +01:00
if change . Op == modes . Add {
2017-05-08 01:15:16 +02:00
client . server . snomasks . AddMasks ( client , masks ... )
applied = append ( applied , change )
2018-02-03 11:21:32 +01:00
} else if change . Op == modes . Remove {
2017-05-08 01:15:16 +02:00
client . server . snomasks . RemoveMasks ( client , masks ... )
2017-03-12 23:08:18 +01:00
applied = append ( applied , change )
}
}
// can't do anything to TLS mode
}
// return the changes we could actually apply
return applied
2016-06-20 14:53:45 +02:00
}
2017-09-06 23:34:38 +02:00
// ParseDefaultChannelModes parses the `default-modes` line of the config
2018-07-16 09:46:40 +02:00
func ParseDefaultChannelModes ( rawModes * string ) modes . Modes {
if rawModes == nil {
2017-09-06 23:34:38 +02:00
// not present in config, fall back to compile-time default
return DefaultChannelModes
}
2018-07-16 09:46:40 +02:00
modeChangeStrings := strings . Fields ( * rawModes )
2018-04-23 00:47:10 +02:00
modeChanges , _ := modes . ParseChannelModeChanges ( modeChangeStrings ... )
2018-02-03 11:21:32 +01:00
defaultChannelModes := make ( modes . Modes , 0 )
2017-09-06 23:34:38 +02:00
for _ , modeChange := range modeChanges {
2018-02-03 11:21:32 +01:00
if modeChange . Op == modes . Add {
defaultChannelModes = append ( defaultChannelModes , modeChange . Mode )
2017-09-06 23:34:38 +02:00
}
}
return defaultChannelModes
}
2017-03-24 03:23:21 +01:00
// ApplyChannelModeChanges applies a given set of mode changes.
2019-10-13 12:07:30 +02:00
func ( channel * Channel ) ApplyChannelModeChanges ( client * Client , isSamode bool , changes modes . ModeChanges , rb * ResponseBuffer ) ( applied modes . ModeChanges ) {
2017-03-24 03:23:21 +01:00
// so we only output one warning for each list type when full
2018-02-03 11:21:32 +01:00
listFullWarned := make ( map [ modes . Mode ] bool )
2016-11-03 07:59:57 +01:00
2017-03-24 03:23:21 +01:00
var alreadySentPrivError bool
2019-10-13 12:07:30 +02:00
maskOpCount := 0
chname := channel . Name ( )
details := client . Details ( )
2017-11-13 08:55:26 +01:00
2018-02-03 11:21:32 +01:00
hasPrivs := func ( change modes . ModeChange ) bool {
2017-11-13 08:55:26 +01:00
if isSamode {
return true
}
2019-12-17 01:50:15 +01:00
if details . account != "" && details . account == channel . Founder ( ) {
return true
}
2018-02-03 11:21:32 +01:00
switch change . Mode {
case modes . ChannelFounder , modes . ChannelAdmin , modes . ChannelOperator , modes . Halfop , modes . Voice :
2018-04-23 00:47:10 +02:00
// List on these modes is a no-op anyway
2018-02-03 11:21:32 +01:00
if change . Op == modes . List {
2017-11-13 08:55:26 +01:00
return true
}
2018-02-03 11:21:32 +01:00
cfarg , _ := CasefoldName ( change . Arg )
2019-10-13 12:07:30 +02:00
isSelfChange := cfarg == details . nickCasefolded
2018-04-23 00:47:10 +02:00
if change . Op == modes . Remove && isSelfChange {
2017-11-13 08:55:26 +01:00
// "There is no restriction, however, on anyone `deopping' themselves"
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
return true
}
2019-04-23 06:05:12 +02:00
return channelUserModeHasPrivsOver ( channel . HighestUserMode ( client ) , change . Mode )
2019-10-13 12:07:30 +02:00
case modes . InviteMask , modes . ExceptMask :
// listing these requires privileges
2018-08-17 22:22:01 +02:00
return channel . ClientIsAtLeast ( client , modes . ChannelOperator )
2019-10-13 12:07:30 +02:00
default :
// #163: allow unprivileged users to list ban masks, and any other modes
return change . Op == modes . List || channel . ClientIsAtLeast ( client , modes . ChannelOperator )
2017-11-13 08:55:26 +01:00
}
}
2017-03-24 03:23:21 +01:00
for _ , change := range changes {
2017-11-13 08:55:26 +01:00
if ! hasPrivs ( change ) {
2017-03-24 03:23:21 +01:00
if ! alreadySentPrivError {
alreadySentPrivError = true
2019-10-13 12:07:30 +02:00
rb . Add ( nil , client . server . name , ERR_CHANOPRIVSNEEDED , details . nick , channel . name , client . t ( "You're not a channel operator" ) )
2016-11-03 07:59:57 +01:00
}
2017-03-24 03:23:21 +01:00
continue
}
2016-11-03 07:59:57 +01:00
2018-02-03 11:21:32 +01:00
switch change . Mode {
case modes . BanMask , modes . ExceptMask , modes . InviteMask :
2019-10-13 12:07:30 +02:00
maskOpCount += 1
if change . Op == modes . List {
2018-02-05 15:21:08 +01:00
channel . ShowMaskList ( client , change . Mode , rb )
2017-03-24 03:23:21 +01:00
continue
}
2019-10-10 10:17:44 +02:00
mask := change . Arg
2018-02-03 11:21:32 +01:00
switch change . Op {
case modes . Add :
2019-05-23 01:07:12 +02:00
if channel . lists [ change . Mode ] . Length ( ) >= client . server . Config ( ) . Limits . ChanListModes {
2018-02-03 11:21:32 +01:00
if ! listFullWarned [ change . Mode ] {
2019-10-13 12:07:30 +02:00
rb . Add ( nil , client . server . name , ERR_BANLISTFULL , details . nick , chname , change . Mode . String ( ) , client . t ( "Channel list is full" ) )
2018-02-03 11:21:32 +01:00
listFullWarned [ change . Mode ] = true
2017-03-24 03:23:21 +01:00
}
2016-10-11 15:51:46 +02:00
continue
}
2019-10-13 12:07:30 +02:00
maskAdded , err := channel . lists [ change . Mode ] . Add ( mask , details . nickMask , details . accountName )
if maskAdded != "" {
appliedChange := change
appliedChange . Arg = maskAdded
applied = append ( applied , appliedChange )
} else if err != nil {
2019-12-30 18:44:07 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , mask , fmt . Sprintf ( client . t ( "Invalid mode %[1]s parameter: %[2]s" ) , string ( change . Mode ) , mask ) )
2019-10-13 12:07:30 +02:00
} else {
2019-12-30 18:44:07 +01:00
rb . Add ( nil , client . server . name , ERR_LISTMODEALREADYSET , chname , mask , string ( change . Mode ) , fmt . Sprintf ( client . t ( "Channel %[1]s list already contains %[2]s" ) , chname , mask ) )
2019-10-10 10:17:44 +02:00
}
2016-10-23 16:50:18 +02:00
2018-02-03 11:21:32 +01:00
case modes . Remove :
2019-10-13 12:07:30 +02:00
maskRemoved , err := channel . lists [ change . Mode ] . Remove ( mask )
if maskRemoved != "" {
appliedChange := change
appliedChange . Arg = maskRemoved
applied = append ( applied , appliedChange )
} else if err != nil {
2019-12-30 18:44:07 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , mask , fmt . Sprintf ( client . t ( "Invalid mode %[1]s parameter: %[2]s" ) , string ( change . Mode ) , mask ) )
2019-10-13 12:07:30 +02:00
} else {
2019-12-30 18:44:07 +01:00
rb . Add ( nil , client . server . name , ERR_LISTMODENOTSET , chname , mask , string ( change . Mode ) , fmt . Sprintf ( client . t ( "Channel %[1]s list does not contain %[2]s" ) , chname , mask ) )
2019-10-10 10:17:44 +02:00
}
2017-03-24 03:23:21 +01:00
}
2016-06-22 13:35:26 +02:00
2018-02-03 11:21:32 +01:00
case modes . UserLimit :
switch change . Op {
case modes . Add :
2018-12-28 19:45:55 +01:00
val , err := strconv . Atoi ( change . Arg )
2017-03-24 03:23:21 +01:00
if err == nil {
2017-10-23 01:50:16 +02:00
channel . setUserLimit ( val )
2016-06-22 13:35:26 +02:00
applied = append ( applied , change )
}
2018-02-03 11:21:32 +01:00
case modes . Remove :
2017-10-23 01:50:16 +02:00
channel . setUserLimit ( 0 )
2017-03-24 03:23:21 +01:00
applied = append ( applied , change )
}
2016-09-14 11:48:47 +02:00
2018-02-03 11:21:32 +01:00
case modes . Key :
switch change . Op {
case modes . Add :
channel . setKey ( change . Arg )
2019-02-13 21:09:23 +01:00
applied = append ( applied , change )
2018-02-03 11:21:32 +01:00
case modes . Remove :
2017-10-23 01:50:16 +02:00
channel . setKey ( "" )
2019-02-13 21:09:23 +01:00
applied = append ( applied , change )
2017-03-24 03:23:21 +01:00
}
2016-09-14 11:48:47 +02:00
2020-01-27 02:50:27 +01:00
case modes . InviteOnly , modes . Moderated , modes . NoOutside , modes . OpOnlyTopic , modes . RegisteredOnly , modes . Secret , modes . ChanRoleplaying , modes . NoCTCP :
2018-02-03 11:21:32 +01:00
if change . Op == modes . List {
2017-10-23 01:50:16 +02:00
continue
}
2016-09-14 11:48:47 +02:00
2018-04-24 02:03:26 +02:00
if channel . flags . SetMode ( change . Mode , change . Op == modes . Add ) {
2017-03-24 03:23:21 +01:00
applied = append ( applied , change )
}
2016-06-22 13:35:26 +02:00
2018-02-03 11:21:32 +01:00
case modes . ChannelFounder , modes . ChannelAdmin , modes . ChannelOperator , modes . Halfop , modes . Voice :
if change . Op == modes . List {
2017-10-23 01:50:16 +02:00
continue
}
2016-06-22 13:35:26 +02:00
2018-04-23 00:47:10 +02:00
nick := change . Arg
if nick == "" {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , client . server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , "MODE" , client . t ( "Not enough parameters" ) )
2019-10-13 12:07:30 +02:00
continue
2018-04-23 00:47:10 +02:00
}
2020-03-25 17:08:08 +01:00
success , change := channel . applyModeToMember ( client , change , rb )
if success {
applied = append ( applied , change )
2016-06-22 13:35:26 +02:00
}
}
}
2020-03-25 17:08:08 +01:00
var includeFlags uint
for _ , change := range applied {
switch change . Mode {
case modes . BanMask , modes . ExceptMask , modes . InviteMask :
includeFlags |= IncludeLists
case modes . ChannelFounder , modes . ChannelAdmin , modes . ChannelOperator , modes . Halfop , modes . Voice :
// these are never persisted currently, but might be in the future (see discussion on #729)
default :
includeFlags |= IncludeModes
}
}
if includeFlags != 0 {
channel . MarkDirty ( includeFlags )
}
2019-10-13 12:07:30 +02:00
// #649: don't send 324 RPL_CHANNELMODEIS if we were only working with mask lists
if len ( applied ) == 0 && ! alreadySentPrivError && ( maskOpCount == 0 || maskOpCount < len ( changes ) ) {
args := append ( [ ] string { details . nick , chname } , channel . modeStrings ( client ) ... )
rb . Add ( nil , client . server . name , RPL_CHANNELMODEIS , args ... )
rb . Add ( nil , client . server . name , RPL_CREATIONTIME , details . nick , chname , strconv . FormatInt ( channel . createdTime . Unix ( ) , 10 ) )
}
2017-03-24 03:23:21 +01:00
return applied
}
2018-05-25 06:38:20 +02:00
// tests whether l > r, in the channel-user mode ordering (e.g., Halfop > Voice)
func umodeGreaterThan ( l modes . Mode , r modes . Mode ) bool {
for _ , mode := range modes . ChannelUserModes {
if l == mode && r != mode {
return true
} else if r == mode {
return false
}
}
return false
}
// ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes.
func ( channel * Channel ) ProcessAccountToUmodeChange ( client * Client , change modes . ModeChange ) ( results [ ] modes . ModeChange , err error ) {
2019-04-07 08:12:14 +02:00
changed := false
defer func ( ) {
if changed {
channel . MarkDirty ( IncludeLists )
}
} ( )
2018-05-25 06:38:20 +02:00
account := client . Account ( )
isOperChange := client . HasRoleCapabs ( "chanreg" )
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
clientMode := channel . accountToUMode [ account ]
targetModeNow := channel . accountToUMode [ change . Arg ]
var targetModeAfter modes . Mode
if change . Op == modes . Add {
targetModeAfter = change . Mode
}
2019-12-17 01:50:15 +01:00
// server operators and founders can do anything:
2018-05-25 06:38:20 +02:00
hasPrivs := isOperChange || ( account != "" && account == channel . registeredFounder )
2019-12-17 01:50:15 +01:00
// halfop and up can list:
2019-04-23 06:05:12 +02:00
if change . Op == modes . List && ( clientMode == modes . Halfop || umodeGreaterThan ( clientMode , modes . Halfop ) ) {
2018-05-25 06:38:20 +02:00
hasPrivs = true
2019-12-17 01:50:15 +01:00
// you can do adds or removes at levels you have "privileges over":
2019-04-23 06:05:12 +02:00
} else if channelUserModeHasPrivsOver ( clientMode , targetModeNow ) && channelUserModeHasPrivsOver ( clientMode , targetModeAfter ) {
2018-05-25 06:38:20 +02:00
hasPrivs = true
2019-12-17 01:50:15 +01:00
// and you can always de-op yourself:
2019-05-24 00:33:41 +02:00
} else if change . Op == modes . Remove && account == change . Arg {
hasPrivs = true
2018-05-25 06:38:20 +02:00
}
if ! hasPrivs {
return nil , errInsufficientPrivs
}
switch change . Op {
case modes . Add :
if targetModeNow != targetModeAfter {
channel . accountToUMode [ change . Arg ] = change . Mode
2019-04-07 08:12:14 +02:00
changed = true
2018-05-25 06:38:20 +02:00
return [ ] modes . ModeChange { change } , nil
}
return nil , nil
case modes . Remove :
if targetModeNow == change . Mode {
delete ( channel . accountToUMode , change . Arg )
2019-04-07 08:12:14 +02:00
changed = true
2018-05-25 06:38:20 +02:00
return [ ] modes . ModeChange { change } , nil
}
return nil , nil
case modes . List :
result := make ( [ ] modes . ModeChange , len ( channel . accountToUMode ) )
pos := 0
for account , mode := range channel . accountToUMode {
result [ pos ] = modes . ModeChange {
Mode : mode ,
Arg : account ,
Op : modes . Add ,
}
pos ++
}
return result , nil
default :
// shouldn't happen
return nil , errInvalidCharacter
}
}