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"
2020-12-14 11:00:21 +01:00
"github.com/oragono/oragono/irc/utils"
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
}
2020-04-30 05:43:55 +02:00
// DefaultUserModes are set on all users when they login.
2020-04-30 06:38:19 +02:00
// this can be overridden in the `accounts` config, with the `default-user-modes` key
2020-05-28 17:53:14 +02:00
DefaultUserModes = modes . Modes { }
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 )
2021-04-19 02:18:02 +02:00
// #1617: if the user is offline, they are not counted in LUSERS,
// so don't modify the LUSERS stats for +i or +o.
present := len ( client . Sessions ( ) ) != 0
2017-03-12 23:08:18 +01:00
for _ , change := range changes {
2020-10-02 01:52:50 +02:00
if change . Mode != modes . ServerNotice {
2018-02-03 11:21:32 +01:00
switch change . Op {
case modes . Add :
2021-02-07 04:45:34 +01:00
if ( change . Mode == modes . Operator ) && ! ( 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 ) {
2021-04-19 02:18:02 +02:00
if change . Mode == modes . Invisible && present {
2018-04-23 00:47:10 +02:00
client . server . stats . ChangeInvisible ( 1 )
2021-04-19 02:18:02 +02:00
} else if change . Mode == modes . Operator && present {
2018-04-23 00:47:10 +02:00
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 ) {
2021-04-19 02:18:02 +02:00
if change . Mode == modes . Invisible && present {
2018-04-23 00:47:10 +02:00
client . server . stats . ChangeInvisible ( - 1 )
2021-02-07 04:45:34 +01:00
} else if change . Mode == modes . Operator {
2020-02-21 12:10:35 +01:00
removedSnomasks = client . server . snomasks . String ( client )
2021-04-19 02:18:02 +02:00
if present {
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
}
2020-10-02 01:52:50 +02:00
} else {
// server notices are weird
2021-02-08 17:43:13 +01:00
if ! client . HasMode ( modes . Operator ) || change . Op == modes . List {
2017-05-08 01:15:16 +02:00
continue
}
2021-02-08 17:43:13 +01:00
currentMasks := client . server . snomasks . MasksEnabled ( client )
addMasks , removeMasks , newArg := sno . EvaluateSnomaskChanges ( change . Op == modes . Add , change . Arg , currentMasks )
success := false
if len ( addMasks ) != 0 {
2021-02-07 04:45:34 +01:00
oper := client . Oper ( )
// #1176: require special operator privileges to subscribe to snomasks
2021-04-19 02:06:00 +02:00
if force || oper . HasRoleCapab ( "snomasks" ) || oper . HasRoleCapab ( "ban" ) {
2021-02-08 17:43:13 +01:00
success = true
client . server . snomasks . AddMasks ( client , addMasks ... )
2021-02-07 04:45:34 +01:00
}
2021-02-08 17:43:13 +01:00
}
if len ( removeMasks ) != 0 {
success = true
client . server . snomasks . RemoveMasks ( client , removeMasks ... )
}
if success {
change . Arg = newArg
2017-03-12 23:08:18 +01:00
applied = append ( applied , change )
}
}
}
2020-05-19 20:38:56 +02:00
if len ( applied ) != 0 {
client . markDirty ( IncludeUserModes )
}
2017-03-12 23:08:18 +01:00
// return the changes we could actually apply
return applied
2016-06-20 14:53:45 +02:00
}
2020-05-28 17:53:14 +02:00
// parseDefaultModes uses the provided mode change parser to parse the rawModes.
func parseDefaultModes ( rawModes string , parser func ( params ... string ) ( modes . ModeChanges , map [ rune ] bool ) ) modes . Modes {
modeChangeStrings := strings . Fields ( rawModes )
modeChanges , _ := parser ( modeChangeStrings ... )
defaultModes := make ( modes . Modes , 0 )
for _ , modeChange := range modeChanges {
if modeChange . Op == modes . Add {
defaultModes = append ( defaultModes , modeChange . Mode )
}
}
return defaultModes
}
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
}
2020-05-28 17:53:14 +02:00
return parseDefaultModes ( * rawModes , modes . ParseChannelModeChanges )
2020-04-30 05:43:55 +02:00
}
// ParseDefaultUserModes parses the `default-user-modes` line of the config
2020-05-28 17:53:14 +02:00
func ParseDefaultUserModes ( rawModes * string ) modes . Modes {
2020-04-30 05:43:55 +02:00
if rawModes == nil {
// not present in config, fall back to compile-time default
return DefaultUserModes
2017-09-06 23:34:38 +02:00
}
2020-05-28 17:53:14 +02:00
return parseDefaultModes ( * rawModes , modes . ParseUserModeChanges )
2017-09-06 23:34:38 +02:00
}
2020-05-17 20:02:59 +02:00
// #1021: channel key must be valid as a non-final parameter
func validateChannelKey ( key string ) bool {
// empty string is valid in this context because it unsets the mode
if len ( key ) == 0 {
return true
}
return key [ 0 ] != ':' && strings . IndexByte ( key , ' ' ) == - 1
}
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 {
2021-02-28 03:51:14 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , string ( change . Mode ) , utils . SafeErrorParam ( 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 {
2021-02-28 03:51:14 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , string ( change . Mode ) , utils . SafeErrorParam ( 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
2020-12-14 11:00:21 +01:00
case modes . Forward :
switch change . Op {
case modes . Add :
ch := client . server . channels . Get ( change . Arg )
if ch == nil {
2021-02-28 03:51:14 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , string ( change . Mode ) , utils . SafeErrorParam ( change . Arg ) , fmt . Sprintf ( client . t ( "No such channel" ) ) )
2020-12-14 11:00:21 +01:00
} else if ch == channel {
2021-02-28 03:51:14 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , string ( change . Mode ) , utils . SafeErrorParam ( change . Arg ) , fmt . Sprintf ( client . t ( "You can't forward a channel to itself" ) ) )
2020-12-14 11:00:21 +01:00
} else {
if ! ch . ClientIsAtLeast ( client , modes . ChannelOperator ) {
rb . Add ( nil , client . server . name , ERR_CHANOPRIVSNEEDED , details . nick , ch . Name ( ) , client . t ( "You must be a channel operator in the channel you are forwarding to" ) )
} else {
change . Arg = ch . Name ( )
channel . setForward ( change . Arg )
applied = append ( applied , change )
}
}
case modes . Remove :
channel . setForward ( "" )
applied = append ( applied , change )
}
2018-02-03 11:21:32 +01:00
case modes . Key :
switch change . Op {
case modes . Add :
2020-05-17 20:02:59 +02:00
if validateChannelKey ( change . Arg ) {
channel . setKey ( change . Arg )
applied = append ( applied , change )
} else {
2021-02-28 03:51:14 +01:00
rb . Add ( nil , client . server . name , ERR_INVALIDMODEPARAM , details . nick , string ( change . Mode ) , utils . SafeErrorParam ( change . Arg ) , fmt . Sprintf ( client . t ( "Invalid mode %[1]s parameter: %[2]s" ) , string ( change . Mode ) , change . Arg ) )
2020-05-17 20:02:59 +02:00
}
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
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-10-02 01:52:50 +02:00
default :
// all channel modes with no args, e.g., InviteOnly, Secret
if change . Op == modes . List {
continue
}
if channel . flags . SetMode ( change . Mode , change . Op == modes . Add ) {
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 :
2020-12-14 11:00:21 +01:00
// these are persisted on the client object, via (*Channel).applyModeToMember
2020-03-25 17:08:08 +01:00
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
}
}