2017-03-11 13:01:40 +01:00
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
"fmt"
2020-05-04 02:51:39 +02:00
"regexp"
2018-05-23 21:35:50 +02:00
"sort"
2017-03-11 13:01:40 +01:00
"strings"
2018-06-19 10:03:40 +02:00
"time"
2017-03-11 13:01:40 +01:00
2017-06-15 18:14:19 +02:00
"github.com/goshuirc/irc-go/ircfmt"
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-02-23 04:32:19 +01:00
"github.com/oragono/oragono/irc/utils"
2017-03-11 13:01:40 +01:00
)
2019-07-02 18:36:32 +02:00
const chanservHelp = ` ChanServ lets you register and manage channels. `
2018-04-01 03:51:34 +02:00
2019-01-04 04:32:07 +01:00
func chanregEnabled ( config * Config ) bool {
return config . Channels . Registration . Enabled
2018-06-04 11:02:22 +02:00
}
2018-04-01 03:51:34 +02:00
var (
2018-04-19 08:48:19 +02:00
chanservCommands = map [ string ] * serviceCommand {
2018-04-01 03:51:34 +02:00
"op" : {
handler : csOpHandler ,
help : ` Syntax : $ bOP # channel [ nickname ] $ b
OP makes the given nickname , or yourself , a channel admin . You can only use
this command if you ' re the founder of the channel . ` ,
2018-04-19 08:48:19 +02:00
helpShort : ` $bOP$b makes the given user (or yourself) a channel admin. ` ,
authRequired : true ,
2018-06-04 11:02:22 +02:00
enabled : chanregEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-04-01 03:51:34 +02:00
} ,
2020-10-26 17:18:54 +01:00
"deop" : {
handler : csDeopHandler ,
help : ` Syntax : $ bDEOP # channel [ nickname ] $ b
DEOP removes the given nickname , or yourself , the channel admin . You can only use
this command if you ' re the founder of the channel . ` ,
2020-10-27 04:08:05 +01:00
helpShort : ` $bDEOP$b removes the given user (or yourself) from a channel admin. ` ,
enabled : chanregEnabled ,
minParams : 1 ,
2020-10-26 17:18:54 +01:00
} ,
2018-04-01 03:51:34 +02:00
"register" : {
handler : csRegisterHandler ,
help : ` Syntax : $ bREGISTER # channel $ b
REGISTER lets you own the given channel . If you rejoin this channel , you ' ll be
given admin privs on it . Modes set on the channel and the topic will also be
remembered . ` ,
2018-04-19 08:48:19 +02:00
helpShort : ` $bREGISTER$b lets you own a given channel. ` ,
authRequired : true ,
2018-06-04 11:02:22 +02:00
enabled : chanregEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-06-04 11:02:22 +02:00
} ,
"unregister" : {
handler : csUnregisterHandler ,
2018-06-05 11:23:36 +02:00
help : ` Syntax : $ bUNREGISTER # channel [ code ] $ b
2018-06-04 11:02:22 +02:00
2018-06-05 11:23:36 +02:00
UNREGISTER deletes a channel registration , allowing someone else to claim it .
To prevent accidental unregistrations , a verification code is required ;
invoking the command without a code will display the necessary code . ` ,
2018-06-04 11:02:22 +02:00
helpShort : ` $bUNREGISTER$b deletes a channel registration. ` ,
enabled : chanregEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-04-01 03:51:34 +02:00
} ,
2018-08-06 15:21:29 +02:00
"drop" : {
aliasOf : "unregister" ,
} ,
2018-05-23 21:35:50 +02:00
"amode" : {
handler : csAmodeHandler ,
help : ` Syntax : $ bAMODE # channel [ mode change ] [ account ] $ b
AMODE lists or modifies persistent mode settings that affect channel members .
For example , $ bAMODE # channel + o dan $ b grants the the holder of the "dan"
account the + o operator mode every time they join # channel . To list current
accounts and modes , use $ bAMODE # channel $ b . Note that users are always
referenced by their registered account names , not their nicknames . ` ,
2018-05-25 06:38:20 +02:00
helpShort : ` $bAMODE$b modifies persistent mode settings for channel members. ` ,
2018-06-04 11:02:22 +02:00
enabled : chanregEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-05-23 21:35:50 +02:00
} ,
2019-12-17 01:50:15 +01:00
"clear" : {
handler : csClearHandler ,
help : ` Syntax : $ bCLEAR # channel target $ b
CLEAR removes users or settings from a channel . Specifically :
$ bCLEAR # channel users $ b kicks all users except for you .
$ bCLEAR # channel access $ b resets all stored bans , invites , ban exceptions ,
and persistent user - mode grants made with CS AMODE . ` ,
helpShort : ` $bCLEAR$b removes users or settings from a channel. ` ,
enabled : chanregEnabled ,
minParams : 2 ,
} ,
"transfer" : {
handler : csTransferHandler ,
help : ` Syntax : $ bTRANSFER [ accept ] # channel user [ code ] $ b
TRANSFER transfers ownership of a channel from one user to another .
To prevent accidental transfers , a verification code is required . For
example , $ bTRANSFER # channel alice $ b displays the required confirmation
code , then $ bTRANSFER # channel alice 2930242125 $ b initiates the transfer .
Unless you are an IRC operator with the correct permissions , alice must
2019-12-25 21:56:57 +01:00
then accept the transfer , which she can do with $ bTRANSFER accept # channel $ b .
To cancel a pending transfer , transfer the channel to yourself . ` ,
2019-12-17 01:50:15 +01:00
helpShort : ` $bTRANSFER$b transfers ownership of a channel to another user. ` ,
enabled : chanregEnabled ,
minParams : 2 ,
} ,
"purge" : {
handler : csPurgeHandler ,
2020-12-15 10:00:44 +01:00
help : ` Syntax : $ bPURGE < ADD | DEL | LIST > # channel [ code ] [ reason ] $ b
2019-12-17 01:50:15 +01:00
2020-12-15 10:00:44 +01:00
PURGE ADD blacklists a channel from the server , making it impossible to join
2019-12-17 01:50:15 +01:00
or otherwise interact with the channel . If the channel currently has members ,
they will be kicked from it . PURGE may also be applied preemptively to
2020-12-15 10:00:44 +01:00
channels that do not currently have members . A purge can be undone with
PURGE DEL . To list purged channels , use PURGE LIST . ` ,
2019-12-17 01:50:15 +01:00
helpShort : ` $bPURGE$b blacklists a channel from the server. ` ,
capabs : [ ] string { "chanreg" } ,
minParams : 1 ,
2020-12-15 10:00:44 +01:00
maxParams : 3 ,
2019-12-17 01:50:15 +01:00
unsplitFinalParam : true ,
} ,
2020-05-04 02:51:39 +02:00
"list" : {
handler : csListHandler ,
help : ` Syntax : $ bLIST [ regex ] $ b
LIST returns the list of registered channels , which match the given regex .
If no regex is provided , all registered channels are returned . ` ,
helpShort : ` $bLIST$b searches the list of registered channels. ` ,
capabs : [ ] string { "chanreg" } ,
minParams : 0 ,
} ,
2019-12-17 01:50:15 +01:00
"info" : {
handler : csInfoHandler ,
help : ` Syntax : $ INFO # channel $ b
INFO displays info about a registered channel . ` ,
helpShort : ` $bINFO$b displays info about a registered channel. ` ,
2019-12-29 17:59:49 +01:00
enabled : chanregEnabled ,
2019-12-17 01:50:15 +01:00
minParams : 1 ,
} ,
2020-02-19 01:38:42 +01:00
"get" : {
handler : csGetHandler ,
help : ` Syntax : $ bGET # channel < setting > $ b
GET queries the current values of the channel settings . For more information
on the settings and their possible values , see HELP SET . ` ,
helpShort : ` $bGET$b queries the current values of a channel's settings ` ,
enabled : chanregEnabled ,
minParams : 2 ,
} ,
"set" : {
handler : csSetHandler ,
helpShort : ` $bSET$b modifies a channel's settings ` ,
// these are broken out as separate strings so they can be translated separately
helpStrings : [ ] string {
` Syntax $ bSET # channel < setting > < value > $ b
SET modifies a channel ' s settings . The following settings are available : ` ,
` $ bHISTORY $ b
' history ' lets you control how channel history is stored . Your options are :
1. ' off ' [ no history ]
2. ' ephemeral ' [ a limited amount of temporary history , not stored on disk ]
3. ' on ' [ history stored in a permanent database , if available ]
4. ' default ' [ use the server default ] ` ,
} ,
enabled : chanregEnabled ,
minParams : 3 ,
} ,
2021-01-19 14:49:45 +01:00
"howtoban" : {
handler : csHowToBanHandler ,
helpShort : ` $bHOWTOBAN$b suggests the best available way of banning a user ` ,
help : ` Syntax : $ bHOWTOBAN # channel < nick >
The best way to ban a user from a channel will depend on how they are
connected to the server . $ bHOWTOBAN $ b suggests a ban command that will
( ideally ) prevent the user from returning to the channel . ` ,
enabled : chanregEnabled ,
minParams : 2 ,
} ,
2018-04-01 03:51:34 +02:00
}
)
2020-11-29 05:27:11 +01:00
func csAmodeHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-04 04:32:07 +01:00
channelName := params [ 0 ]
2018-05-23 21:35:50 +02:00
channel := server . channels . Get ( channelName )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel does not exist" ) )
2018-05-23 21:35:50 +02:00
return
2018-05-25 06:38:20 +02:00
} else if channel . Founder ( ) == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel is not registered" ) )
2018-05-23 21:35:50 +02:00
return
}
2019-01-04 16:03:12 +01:00
modeChanges , unknown := modes . ParseChannelModeChanges ( params [ 1 : ] ... )
2018-05-25 06:38:20 +02:00
var change modes . ModeChange
2018-05-23 21:35:50 +02:00
if len ( modeChanges ) > 1 || len ( unknown ) > 0 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid mode change" ) )
2018-05-23 21:35:50 +02:00
return
2018-05-25 06:38:20 +02:00
} else if len ( modeChanges ) == 1 {
change = modeChanges [ 0 ]
} else {
change = modes . ModeChange { Op : modes . List }
2018-05-23 21:35:50 +02:00
}
2018-05-25 06:38:20 +02:00
// normalize and validate the account argument
2018-05-23 21:35:50 +02:00
accountIsValid := false
change . Arg , _ = CasefoldName ( change . Arg )
2018-05-25 06:38:20 +02:00
switch change . Op {
case modes . List :
accountIsValid = true
case modes . Add :
// if we're adding a mode, the account must exist
if change . Arg != "" {
_ , err := server . accounts . LoadAccount ( change . Arg )
accountIsValid = ( err == nil )
}
case modes . Remove :
// allow removal of accounts that may have been deleted
accountIsValid = ( change . Arg != "" )
2018-05-23 21:35:50 +02:00
}
if ! accountIsValid {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Account does not exist" ) )
2018-05-23 21:35:50 +02:00
return
}
2018-05-25 06:38:20 +02:00
affectedModes , err := channel . ProcessAccountToUmodeChange ( client , change )
if err == errInsufficientPrivs {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
2018-05-25 06:38:20 +02:00
return
} else if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Internal error" ) )
2018-05-25 06:38:20 +02:00
return
}
switch change . Op {
case modes . List :
// sort the persistent modes in descending order of priority
sort . Slice ( affectedModes , func ( i , j int ) bool {
return umodeGreaterThan ( affectedModes [ i ] . Mode , affectedModes [ j ] . Mode )
} )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %[1]s has %[2]d persistent modes set" ) , channelName , len ( affectedModes ) ) )
2018-05-25 06:38:20 +02:00
for _ , modeChange := range affectedModes {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Account %[1]s receives mode +%[2]s" ) , modeChange . Arg , string ( modeChange . Mode ) ) )
2018-05-25 06:38:20 +02:00
}
case modes . Add , modes . Remove :
if len ( affectedModes ) > 0 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully set persistent mode %[1]s on %[2]s" ) , strings . Join ( [ ] string { string ( change . Op ) , string ( change . Mode ) } , "" ) , change . Arg ) )
2020-03-25 17:08:08 +01:00
// #729: apply change to current membership
for _ , member := range channel . Members ( ) {
if member . Account ( ) == change . Arg {
applied , change := channel . applyModeToMember ( client , change , rb )
if applied {
2020-11-12 17:50:28 +01:00
announceCmodeChanges ( channel , modes . ModeChanges { change } , server . name , "*" , "" , rb )
2020-03-25 17:08:08 +01:00
}
}
}
2018-05-25 06:38:20 +02:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No changes were made" ) )
2018-05-25 06:38:20 +02:00
}
2018-05-23 21:35:50 +02:00
}
}
2020-11-29 05:27:11 +01:00
func csOpHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-04 04:32:07 +01:00
channelInfo := server . channels . Get ( params [ 0 ] )
2018-04-01 01:33:58 +02:00
if channelInfo == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel does not exist" ) )
2018-03-31 17:26:31 +02:00
return
}
2019-01-04 04:32:07 +01:00
channelName := channelInfo . Name ( )
2018-03-31 17:26:31 +02:00
clientAccount := client . Account ( )
2018-04-19 08:48:19 +02:00
if clientAccount == "" || clientAccount != channelInfo . Founder ( ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Only the channel founder can do this" ) )
2018-03-31 17:26:31 +02:00
return
}
var target * Client
2019-01-04 04:32:07 +01:00
if len ( params ) > 1 {
target = server . clients . Get ( params [ 1 ] )
if target == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Could not find given client" ) )
2018-03-31 17:26:31 +02:00
return
}
} else {
target = client
}
// give them privs
givenMode := modes . ChannelOperator
2019-02-17 12:51:48 +01:00
if clientAccount == target . Account ( ) {
2018-03-31 17:26:31 +02:00
givenMode = modes . ChannelFounder
}
2020-03-25 17:08:08 +01:00
applied , change := channelInfo . applyModeToMember ( client ,
modes . ModeChange { Mode : givenMode ,
Op : modes . Add ,
Arg : target . NickCasefolded ( ) ,
} ,
rb )
if applied {
2020-11-12 17:50:28 +01:00
announceCmodeChanges ( channelInfo , modes . ModeChanges { change } , server . name , "*" , "" , rb )
2018-03-31 17:26:31 +02:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully granted operator privileges" ) )
2018-03-31 17:26:31 +02:00
2019-01-04 04:32:07 +01:00
tnick := target . Nick ( )
server . logger . Info ( "services" , fmt . Sprintf ( "Client %s op'd [%s] in channel %s" , client . Nick ( ) , tnick , channelName ) )
server . snomasks . Send ( sno . LocalChannels , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]" ) , client . NickMaskString ( ) , tnick , channelName ) )
2018-03-31 17:26:31 +02:00
}
2020-11-29 05:27:11 +01:00
func csDeopHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-10-27 04:08:05 +01:00
channel := server . channels . Get ( params [ 0 ] )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel does not exist" ) )
2020-10-26 17:18:54 +01:00
return
}
2020-10-27 04:08:05 +01:00
if ! channel . hasClient ( client ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You're not on that channel" ) )
2020-10-26 17:18:54 +01:00
return
}
var target * Client
if len ( params ) > 1 {
target = server . clients . Get ( params [ 1 ] )
if target == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Could not find given client" ) )
2020-10-26 17:18:54 +01:00
return
}
} else {
target = client
}
2020-10-27 04:08:05 +01:00
present , cumodes := channel . ClientStatus ( target )
if ! present || len ( cumodes ) == 0 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Target has no privileges to remove" ) )
2020-10-27 04:08:05 +01:00
return
2020-10-26 17:18:54 +01:00
}
2020-10-27 04:08:05 +01:00
tnick := target . Nick ( )
modeChanges := make ( modes . ModeChanges , len ( cumodes ) )
for i , mode := range cumodes {
modeChanges [ i ] = modes . ModeChange {
Mode : mode ,
Op : modes . Remove ,
Arg : tnick ,
}
}
// use the user's own permissions for the check, then announce
// the changes as coming from chanserv
applied := channel . ApplyChannelModeChanges ( client , false , modeChanges , rb )
details := client . Details ( )
announceCmodeChanges ( channel , applied , details . nickMask , details . accountName , details . account , rb )
if len ( applied ) == 0 {
return
2020-10-26 17:18:54 +01:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully removed operator privileges" ) )
2020-10-26 17:18:54 +01:00
}
2020-11-29 05:27:11 +01:00
func csRegisterHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-03-18 11:55:30 +01:00
if server . Config ( ) . Channels . Registration . OperatorOnly && ! client . HasRoleCapabs ( "chanreg" ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel registration is restricted to server operators" ) )
2020-03-18 11:55:30 +01:00
return
}
2019-01-04 04:32:07 +01:00
channelName := params [ 0 ]
2020-03-18 11:55:30 +01:00
channelInfo := server . channels . Get ( channelName )
if channelInfo == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such channel" ) )
2018-02-03 12:38:28 +01:00
return
}
2020-03-18 11:55:30 +01:00
if ! channelInfo . ClientIsAtLeast ( client , modes . ChannelOperator ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You must be an oper on the channel to register it" ) )
2018-02-03 12:38:28 +01:00
return
}
2017-03-11 13:01:40 +01:00
2019-02-06 10:32:04 +01:00
account := client . Account ( )
2020-11-29 05:27:11 +01:00
if ! checkChanLimit ( service , client , rb ) {
2019-02-06 10:32:04 +01:00
return
}
2018-02-03 12:38:28 +01:00
// this provides the synchronization that allows exactly one registration of the channel:
2020-03-18 11:55:30 +01:00
err := server . channels . SetRegistered ( channelName , account )
2018-02-03 12:38:28 +01:00
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , err . Error ( ) )
2018-02-03 12:38:28 +01:00
return
}
2017-03-11 13:01:40 +01:00
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s successfully registered" ) , channelName ) )
2017-03-11 13:01:40 +01:00
2019-12-17 01:50:15 +01:00
server . logger . Info ( "services" , fmt . Sprintf ( "Client %s registered channel %s" , client . Nick ( ) , channelName ) )
2018-02-03 12:38:28 +01:00
server . snomasks . Send ( sno . LocalChannels , fmt . Sprintf ( ircfmt . Unescape ( "Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]" ) , channelName , client . nickMaskString ) )
2017-11-09 04:19:50 +01:00
2018-02-03 12:38:28 +01:00
// give them founder privs
2020-03-25 17:08:08 +01:00
applied , change := channelInfo . applyModeToMember ( client ,
modes . ModeChange {
Mode : modes . ChannelFounder ,
Op : modes . Add ,
Arg : client . NickCasefolded ( ) ,
} ,
rb )
if applied {
2020-11-29 05:27:11 +01:00
announceCmodeChanges ( channelInfo , modes . ModeChanges { change } , service . prefix , "*" , "" , rb )
2017-03-11 13:01:40 +01:00
}
}
2018-06-04 11:02:22 +02:00
2019-12-17 01:50:15 +01:00
// check whether a client has already registered too many channels
2020-11-29 05:27:11 +01:00
func checkChanLimit ( service * ircService , client * Client , rb * ResponseBuffer ) ( ok bool ) {
2019-12-17 01:50:15 +01:00
account := client . Account ( )
channelsAlreadyRegistered := client . server . accounts . ChannelsForAccount ( account )
ok = len ( channelsAlreadyRegistered ) < client . server . Config ( ) . Channels . Registration . MaxChannelsPerAccount || client . HasRoleCapabs ( "chanreg" )
if ! ok {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER" ) )
2019-12-17 01:50:15 +01:00
}
return
}
2020-11-29 05:27:11 +01:00
func csPrivsCheck ( service * ircService , channel RegisteredChannel , client * Client , rb * ResponseBuffer ) ( success bool ) {
2020-02-19 01:38:42 +01:00
founder := channel . Founder
if founder == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "That channel is not registered" ) )
2020-02-19 01:38:42 +01:00
return false
}
if client . HasRoleCapabs ( "chanreg" ) {
return true
}
if founder != client . Account ( ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
2020-02-19 01:38:42 +01:00
return false
}
return true
}
2020-11-29 05:27:11 +01:00
func csUnregisterHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-04 04:32:07 +01:00
channelName := params [ 0 ]
var verificationCode string
if len ( params ) > 1 {
verificationCode = params [ 1 ]
}
2020-02-19 01:38:42 +01:00
channel := server . channels . Get ( channelName )
2018-06-04 11:02:22 +02:00
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such channel" ) )
2018-06-04 11:02:22 +02:00
return
}
2020-02-19 01:38:42 +01:00
info := channel . ExportRegistration ( 0 )
channelKey := info . NameCasefolded
2020-11-29 05:27:11 +01:00
if ! csPrivsCheck ( service , info , client , rb ) {
2018-06-04 11:02:22 +02:00
return
}
2020-02-23 04:32:19 +01:00
expectedCode := utils . ConfirmationCode ( info . Name , info . RegisteredAt )
2018-06-19 10:03:40 +02:00
if expectedCode != verificationCode {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: unregistering this channel will remove all stored channel attributes.$b" ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "To confirm, run this command: %s" ) , fmt . Sprintf ( "/CS UNREGISTER %s %s" , channelKey , expectedCode ) ) )
2018-06-05 11:23:36 +02:00
return
}
2020-02-19 01:38:42 +01:00
server . channels . SetUnregistered ( channelKey , info . Founder )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s is now unregistered" ) , channelKey ) )
2018-06-04 11:02:22 +02:00
}
2018-06-19 10:03:40 +02:00
2020-11-29 05:27:11 +01:00
func csClearHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-12-17 01:50:15 +01:00
channel := server . channels . Get ( params [ 0 ] )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel does not exist" ) )
2019-12-17 01:50:15 +01:00
return
}
2020-11-29 05:27:11 +01:00
if ! csPrivsCheck ( service , channel . ExportRegistration ( 0 ) , client , rb ) {
2019-12-17 01:50:15 +01:00
return
}
switch strings . ToLower ( params [ 1 ] ) {
case "access" :
channel . resetAccess ( )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully reset channel access" ) )
2019-12-17 01:50:15 +01:00
case "users" :
for _ , target := range channel . Members ( ) {
if target != client {
channel . Kick ( client , target , "Cleared by ChanServ" , rb , true )
}
}
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2019-12-17 01:50:15 +01:00
}
}
2020-11-29 05:27:11 +01:00
func csTransferHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-12-17 01:50:15 +01:00
if strings . ToLower ( params [ 0 ] ) == "accept" {
2020-11-29 05:27:11 +01:00
processTransferAccept ( service , client , params [ 1 ] , rb )
2019-12-17 01:50:15 +01:00
return
}
chname := params [ 0 ]
channel := server . channels . Get ( chname )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel does not exist" ) )
2019-12-17 01:50:15 +01:00
return
}
regInfo := channel . ExportRegistration ( 0 )
chname = regInfo . Name
account := client . Account ( )
isFounder := account != "" && account == regInfo . Founder
2021-01-15 15:26:34 +01:00
var oper * Oper
if ! isFounder {
oper = client . Oper ( )
if ! oper . HasRoleCapab ( "chanreg" ) {
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
return
}
2019-12-17 01:50:15 +01:00
}
target := params [ 1 ]
2019-12-25 21:56:57 +01:00
targetAccount , err := server . accounts . LoadAccount ( params [ 1 ] )
2019-12-17 01:50:15 +01:00
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Account does not exist" ) )
2019-12-17 01:50:15 +01:00
return
}
2019-12-25 21:56:57 +01:00
if targetAccount . NameCasefolded != account {
2020-02-23 04:32:19 +01:00
expectedCode := utils . ConfirmationCode ( regInfo . Name , regInfo . RegisteredAt )
2019-12-25 21:56:57 +01:00
codeValidated := 2 < len ( params ) && params [ 2 ] == expectedCode
if ! codeValidated {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: you are about to transfer control of your channel to another user.$b" ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s" ) , chname , target , expectedCode ) )
2019-12-25 21:56:57 +01:00
return
}
2019-12-17 01:50:15 +01:00
}
2021-01-15 15:26:34 +01:00
if ! isFounder {
message := fmt . Sprintf ( "Operator %s ran CS TRANSFER on %s to account %s" , oper . Name , chname , target )
server . snomasks . Send ( sno . LocalOpers , message )
server . logger . Info ( "opers" , message )
}
status , err := channel . Transfer ( client , target , oper != nil )
2019-12-17 01:50:15 +01:00
if err == nil {
switch status {
case channelTransferComplete :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully transferred channel %[1]s to account %[2]s" ) , chname , target ) )
2019-12-17 01:50:15 +01:00
case channelTransferPending :
2020-11-29 05:27:11 +01:00
sendTransferPendingNotice ( service , server , target , chname )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance" ) , chname , target ) )
2019-12-17 01:50:15 +01:00
case channelTransferCancelled :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Cancelled pending transfer of channel %s" ) , chname ) )
2019-12-17 01:50:15 +01:00
}
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Could not transfer channel" ) )
2019-12-17 01:50:15 +01:00
}
}
2020-11-29 05:27:11 +01:00
func sendTransferPendingNotice ( service * ircService , server * Server , account , chname string ) {
2019-12-17 01:50:15 +01:00
clients := server . accounts . AccountToClients ( account )
if len ( clients ) == 0 {
return
}
var client * Client
for _ , candidate := range clients {
client = candidate
if candidate . NickCasefolded ( ) == candidate . Account ( ) {
break // prefer the login where the nick is the account
}
}
2020-11-29 05:27:11 +01:00
client . Send ( nil , service . prefix , "NOTICE" , client . Nick ( ) , fmt . Sprintf ( client . t ( "You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s" ) , chname ) )
2019-12-17 01:50:15 +01:00
}
2020-11-29 05:27:11 +01:00
func processTransferAccept ( service * ircService , client * Client , chname string , rb * ResponseBuffer ) {
2019-12-17 01:50:15 +01:00
channel := client . server . channels . Get ( chname )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Channel does not exist" ) )
2019-12-17 01:50:15 +01:00
return
}
2020-11-29 05:27:11 +01:00
if ! checkChanLimit ( service , client , rb ) {
2019-12-17 01:50:15 +01:00
return
}
switch channel . AcceptTransfer ( client ) {
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully accepted ownership of channel %s" ) , channel . Name ( ) ) )
2019-12-17 01:50:15 +01:00
case errChannelTransferNotOffered :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "You weren't offered ownership of channel %s" ) , channel . Name ( ) ) )
2019-12-17 01:50:15 +01:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Could not accept ownership of channel %s" ) , channel . Name ( ) ) )
2019-12-17 01:50:15 +01:00
}
}
2020-11-29 05:27:11 +01:00
func csPurgeHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-12-17 01:50:15 +01:00
oper := client . Oper ( )
if oper == nil {
return // should be impossible because you need oper capabs for this
}
2020-12-15 10:00:44 +01:00
switch strings . ToLower ( params [ 0 ] ) {
case "add" :
csPurgeAddHandler ( service , client , params [ 1 : ] , oper . Name , rb )
case "del" , "remove" :
csPurgeDelHandler ( service , client , params [ 1 : ] , oper . Name , rb )
case "list" :
csPurgeListHandler ( service , client , rb )
default :
service . Notice ( rb , client . t ( "Invalid parameters" ) )
}
}
func csPurgeAddHandler ( service * ircService , client * Client , params [ ] string , operName string , rb * ResponseBuffer ) {
if len ( params ) == 0 {
service . Notice ( rb , client . t ( "Invalid parameters" ) )
return
}
2019-12-17 01:50:15 +01:00
chname := params [ 0 ]
2020-12-15 10:00:44 +01:00
params = params [ 1 : ]
channel := client . server . channels . Get ( chname ) // possibly nil
var ctime time . Time
if channel != nil {
chname = channel . Name ( )
ctime = channel . Ctime ( )
}
code := utils . ConfirmationCode ( chname , ctime )
if len ( params ) == 0 || params [ 0 ] != code {
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: you are about to empty this channel and remove it from the server.$b" ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "To confirm, run this command: %s" ) , fmt . Sprintf ( "/CS PURGE ADD %s %s" , chname , code ) ) )
return
}
params = params [ 1 : ]
2019-12-17 01:50:15 +01:00
var reason string
if 1 < len ( params ) {
reason = params [ 1 ]
}
2020-12-15 10:00:44 +01:00
2019-12-17 01:50:15 +01:00
purgeRecord := ChannelPurgeRecord {
2020-12-15 10:00:44 +01:00
Oper : operName ,
2019-12-17 01:50:15 +01:00
PurgedAt : time . Now ( ) . UTC ( ) ,
Reason : reason ,
}
2020-12-15 10:00:44 +01:00
switch client . server . channels . Purge ( chname , purgeRecord ) {
2019-12-17 01:50:15 +01:00
case nil :
if channel != nil { // channel need not exist to be purged
for _ , target := range channel . Members ( ) {
channel . Kick ( client , target , "Cleared by ChanServ" , rb , true )
}
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully purged channel %s from the server" ) , chname ) )
2019-12-17 01:50:15 +01:00
case errInvalidChannelName :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Can't purge invalid channel %s" ) , chname ) )
2019-12-17 01:50:15 +01:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2019-12-17 01:50:15 +01:00
}
}
2020-12-15 10:00:44 +01:00
func csPurgeDelHandler ( service * ircService , client * Client , params [ ] string , operName string , rb * ResponseBuffer ) {
if len ( params ) == 0 {
service . Notice ( rb , client . t ( "Invalid parameters" ) )
return
}
2019-12-17 01:50:15 +01:00
chname := params [ 0 ]
2020-12-15 10:00:44 +01:00
switch client . server . channels . Unpurge ( chname ) {
2019-12-17 01:50:15 +01:00
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully unpurged channel %s from the server" ) , chname ) )
2019-12-17 01:50:15 +01:00
case errNoSuchChannel :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s wasn't previously purged from the server" ) , chname ) )
2019-12-17 01:50:15 +01:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2019-12-17 01:50:15 +01:00
}
}
2020-12-15 10:00:44 +01:00
func csPurgeListHandler ( service * ircService , client * Client , rb * ResponseBuffer ) {
l := client . server . channels . ListPurged ( )
service . Notice ( rb , fmt . Sprintf ( client . t ( "There are %d purged channel(s)." ) , len ( l ) ) )
for i , c := range l {
service . Notice ( rb , fmt . Sprintf ( "%d: %s" , i + 1 , c ) )
}
}
2020-11-29 05:27:11 +01:00
func csListHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-05-04 02:51:39 +02:00
if ! client . HasRoleCapabs ( "chanreg" ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
2020-05-04 02:51:39 +02:00
return
}
var searchRegex * regexp . Regexp
if len ( params ) > 0 {
var err error
searchRegex , err = regexp . Compile ( params [ 0 ] )
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid regex" ) )
2020-05-04 02:51:39 +02:00
return
}
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "*** $bChanServ LIST$b ***" ) ) )
2020-05-04 02:51:39 +02:00
channels := server . channelRegistry . AllChannels ( )
for _ , channel := range channels {
if searchRegex == nil || searchRegex . MatchString ( channel ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( " %s" , channel ) )
2020-05-04 02:51:39 +02:00
}
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "*** $bEnd of ChanServ LIST$b ***" ) ) )
2020-05-04 02:51:39 +02:00
}
2020-11-29 05:27:11 +01:00
func csInfoHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-12-17 01:50:15 +01:00
chname , err := CasefoldChannel ( params [ 0 ] )
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid channel name" ) )
2019-12-17 01:50:15 +01:00
return
}
// purge status
if client . HasRoleCapabs ( "chanreg" ) {
purgeRecord , err := server . channelRegistry . LoadPurgeRecord ( chname )
if err == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s was purged by the server operators and cannot be used" ) , chname ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Purged by operator: %s" ) , purgeRecord . Oper ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Purged at: %s" ) , purgeRecord . PurgedAt . Format ( time . RFC1123 ) ) )
2019-12-17 01:50:15 +01:00
if purgeRecord . Reason != "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Purge reason: %s" ) , purgeRecord . Reason ) )
2019-12-17 01:50:15 +01:00
}
}
} else {
if server . channels . IsPurged ( chname ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s was purged by the server operators and cannot be used" ) , chname ) )
2019-12-17 01:50:15 +01:00
}
}
var chinfo RegisteredChannel
channel := server . channels . Get ( params [ 0 ] )
if channel != nil {
chinfo = channel . ExportRegistration ( 0 )
} else {
chinfo , err = server . channelRegistry . LoadChannel ( chname )
if err != nil && ! ( err == errNoSuchChannel || err == errFeatureDisabled ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2019-12-17 01:50:15 +01:00
return
}
}
// channel exists but is unregistered, or doesn't exist:
if chinfo . Founder == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s is not registered" ) , chname ) )
2019-12-17 01:50:15 +01:00
return
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Channel %s is registered" ) , chinfo . Name ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Founder: %s" ) , chinfo . Founder ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Registered at: %s" ) , chinfo . RegisteredAt . Format ( time . RFC1123 ) ) )
2019-12-17 01:50:15 +01:00
}
2020-02-19 01:38:42 +01:00
2020-11-29 05:27:11 +01:00
func displayChannelSetting ( service * ircService , settingName string , settings ChannelSettings , client * Client , rb * ResponseBuffer ) {
2020-02-19 01:38:42 +01:00
config := client . server . Config ( )
switch strings . ToLower ( settingName ) {
case "history" :
effectiveValue := historyEnabled ( config . History . Persistent . RegisteredChannels , settings . History )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "The stored channel history setting is: %s" ) , historyStatusToString ( settings . History ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Given current server settings, the channel history setting is: %s" ) , historyStatusToString ( effectiveValue ) ) )
2020-02-19 01:38:42 +01:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid params" ) )
2020-02-19 01:38:42 +01:00
}
}
2020-11-29 05:27:11 +01:00
func csGetHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-02-19 01:38:42 +01:00
chname , setting := params [ 0 ] , params [ 1 ]
channel := server . channels . Get ( chname )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such channel" ) )
2020-02-19 01:38:42 +01:00
return
}
info := channel . ExportRegistration ( IncludeSettings )
2020-11-29 05:27:11 +01:00
if ! csPrivsCheck ( service , info , client , rb ) {
2020-02-19 01:38:42 +01:00
return
}
2020-11-29 05:27:11 +01:00
displayChannelSetting ( service , setting , info . Settings , client , rb )
2020-02-19 01:38:42 +01:00
}
2020-11-29 05:27:11 +01:00
func csSetHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-02-19 01:38:42 +01:00
chname , setting , value := params [ 0 ] , params [ 1 ] , params [ 2 ]
channel := server . channels . Get ( chname )
if channel == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such channel" ) )
2020-02-19 01:38:42 +01:00
return
}
info := channel . ExportRegistration ( IncludeSettings )
settings := info . Settings
2020-11-29 05:27:11 +01:00
if ! csPrivsCheck ( service , info , client , rb ) {
2020-02-19 01:38:42 +01:00
return
}
var err error
switch strings . ToLower ( setting ) {
case "history" :
settings . History , err = historyStatusFromString ( value )
if err != nil {
err = errInvalidParams
break
}
channel . SetSettings ( settings )
channel . resizeHistory ( server . Config ( ) )
}
switch err {
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully changed the channel settings" ) )
displayChannelSetting ( service , setting , settings , client , rb )
2020-02-19 01:38:42 +01:00
case errInvalidParams :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2020-02-19 01:38:42 +01:00
default :
server . logger . Error ( "internal" , "CS SET error:" , err . Error ( ) )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2020-02-19 01:38:42 +01:00
}
}
2021-01-19 14:49:45 +01:00
func csHowToBanHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
success := false
defer func ( ) {
if success {
service . Notice ( rb , client . t ( "Note that if the user is currently in the channel, you must /KICK them after you ban them" ) )
}
} ( )
chname , nick := params [ 0 ] , params [ 1 ]
channel := server . channels . Get ( chname )
if channel == nil {
service . Notice ( rb , client . t ( "No such channel" ) )
return
}
if ! channel . ClientIsAtLeast ( client , modes . Operator ) || client . HasRoleCapabs ( "samode" ) {
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
return
}
var details WhoWas
target := server . clients . Get ( nick )
if target == nil {
whowasList := server . whoWas . Find ( nick , 1 )
if len ( whowasList ) == 0 {
service . Notice ( rb , client . t ( "No such nick" ) )
return
}
service . Notice ( rb , fmt . Sprintf ( client . t ( "Warning: %s is not currently connected to the server. Using WHOWAS data, which may be inaccurate:" ) , nick ) )
details = whowasList [ 0 ]
} else {
details = target . Details ( ) . WhoWas
}
if details . account != "" {
if channel . getAmode ( details . account ) != modes . Mode ( 0 ) {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Warning: account %s currently has a persistent channel privilege granted with CS AMODE. If this mode is not removed, bans will not be respected" ) , details . accountName ) )
return
} else if details . account == channel . Founder ( ) {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Warning: account %s is the channel founder and cannot be banned" ) , details . accountName ) )
return
}
}
config := server . Config ( )
if ! config . Server . Cloaks . EnabledForAlwaysOn {
service . Notice ( rb , client . t ( "Warning: server.ip-cloaking.enabled-for-always-on is disabled. This reduces the precision of channel bans." ) )
}
if details . account != "" {
if config . Accounts . NickReservation . ForceNickEqualsAccount || target . AlwaysOn ( ) {
service . Notice ( rb , fmt . Sprintf ( client . t ( "User %[1]s is authenticated and can be banned by nickname: /MODE %[2]s +b %[3]s!*@*" ) , details . nick , channel . Name ( ) , details . nick ) )
success = true
return
}
}
ban := fmt . Sprintf ( "*!*@%s" , strings . ToLower ( details . hostname ) )
banRe , err := utils . CompileGlob ( ban , false )
if err != nil {
server . logger . Error ( "internal" , "couldn't compile ban regex" , ban , err . Error ( ) )
service . Notice ( rb , "An error occurred" )
return
}
var collateralDamage [ ] string
for _ , mcl := range channel . Members ( ) {
if mcl != target && banRe . MatchString ( mcl . NickMaskCasefolded ( ) ) {
collateralDamage = append ( collateralDamage , mcl . Nick ( ) )
}
}
service . Notice ( rb , fmt . Sprintf ( client . t ( "User %[1]s can be banned by hostname: /MODE %[2]s +b %[3]s" ) , details . nick , channel . Name ( ) , ban ) )
success = true
if len ( collateralDamage ) != 0 {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Warning: this ban will affect %d other users:" ) , len ( collateralDamage ) ) )
for _ , line := range utils . BuildTokenLines ( 400 , collateralDamage , " " ) {
service . Notice ( rb , line )
}
}
}