2017-03-11 13:01:40 +01:00
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
2018-06-05 11:23:36 +02:00
"bytes"
2017-03-11 13:01:40 +01:00
"fmt"
2018-06-05 11:23:36 +02:00
"hash/crc32"
2018-05-23 21:35:50 +02:00
"sort"
2018-06-05 11:23:36 +02:00
"strconv"
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"
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. `
2019-12-17 01:50:15 +01:00
const chanservMask = "ChanServ!ChanServ@localhost"
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
} ,
"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 ,
help : ` Syntax : $ bPURGE # channel [ reason ] $ b
PURGE blacklists a channel from the server , making it impossible to join
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
channels that do not currently have members . ` ,
helpShort : ` $bPURGE$b blacklists a channel from the server. ` ,
capabs : [ ] string { "chanreg" } ,
minParams : 1 ,
maxParams : 2 ,
unsplitFinalParam : true ,
} ,
"unpurge" : {
handler : csUnpurgeHandler ,
help : ` Syntax : $ bUNPURGE # channel $ b
UNPURGE removes any blacklisting of a channel that was previously
set using PURGE . ` ,
helpShort : ` $bUNPURGE$b undoes a previous PURGE command. ` ,
capabs : [ ] string { "chanreg" } ,
minParams : 1 ,
} ,
"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 ,
} ,
2018-04-01 03:51:34 +02:00
}
)
// csNotice sends the client a notice from ChanServ
func csNotice ( rb * ResponseBuffer , text string ) {
2019-12-17 01:50:15 +01:00
rb . Add ( nil , chanservMask , "NOTICE" , rb . target . Nick ( ) , text )
2017-03-11 13:01:40 +01:00
}
2019-01-04 04:32:07 +01:00
func csAmodeHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
channelName := params [ 0 ]
2018-05-23 21:35:50 +02:00
channel := server . channels . Get ( channelName )
if channel == nil {
csNotice ( rb , client . t ( "Channel does not exist" ) )
return
2018-05-25 06:38:20 +02:00
} else if channel . Founder ( ) == "" {
csNotice ( 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 {
csNotice ( rb , client . t ( "Invalid mode change" ) )
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 {
csNotice ( rb , client . t ( "Account does not exist" ) )
return
}
2018-05-25 06:38:20 +02:00
affectedModes , err := channel . ProcessAccountToUmodeChange ( client , change )
if err == errInsufficientPrivs {
csNotice ( rb , client . t ( "Insufficient privileges" ) )
return
} else if err != nil {
csNotice ( rb , client . t ( "Internal error" ) )
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 )
} )
2019-02-22 03:37:11 +01:00
csNotice ( 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 {
2019-02-22 03:37:11 +01:00
csNotice ( 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 {
csNotice ( rb , fmt . Sprintf ( client . t ( "Successfully set mode %s" ) , change . String ( ) ) )
} else {
2019-02-22 03:37:11 +01:00
csNotice ( rb , client . t ( "No changes were made" ) )
2018-05-25 06:38:20 +02:00
}
2018-05-23 21:35:50 +02:00
}
}
2019-01-04 04:32:07 +01:00
func csOpHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
channelInfo := server . channels . Get ( params [ 0 ] )
2018-04-01 01:33:58 +02:00
if channelInfo == nil {
2018-04-01 03:51:34 +02:00
csNotice ( 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 ( ) {
2019-05-17 05:41:32 +02:00
csNotice ( 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 {
2018-04-01 03:51:34 +02:00
csNotice ( 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
}
2019-02-17 12:51:48 +01:00
change := channelInfo . applyModeToMember ( client , givenMode , modes . Add , target . NickCasefolded ( ) , rb )
2018-03-31 17:26:31 +02:00
if change != nil {
//TODO(dan): we should change the name of String and make it return a slice here
//TODO(dan): unify this code with code in modes.go
args := append ( [ ] string { channelName } , strings . Split ( change . String ( ) , " " ) ... )
for _ , member := range channelInfo . Members ( ) {
member . Send ( nil , fmt . Sprintf ( "ChanServ!services@%s" , client . server . name ) , "MODE" , args ... )
}
}
2018-04-01 03:51:34 +02:00
csNotice ( rb , fmt . Sprintf ( client . t ( "Successfully op'd in channel %s" ) , channelName ) )
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
}
2019-01-04 04:32:07 +01:00
func csRegisterHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
channelName := params [ 0 ]
2017-03-11 13:01:40 +01:00
2018-02-03 12:38:28 +01:00
channelKey , err := CasefoldChannel ( channelName )
if err != nil {
2018-04-01 03:51:34 +02:00
csNotice ( rb , client . t ( "Channel name is not valid" ) )
2018-02-03 12:38:28 +01:00
return
}
2017-03-11 13:01:40 +01:00
2018-02-03 12:38:28 +01:00
channelInfo := server . channels . Get ( channelKey )
if channelInfo == nil || ! channelInfo . ClientIsAtLeast ( client , modes . ChannelOperator ) {
2018-04-01 03:51:34 +02:00
csNotice ( 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 ( )
2019-12-17 01:50:15 +01:00
if ! checkChanLimit ( 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:
2019-03-12 00:24:45 +01:00
err = server . channels . SetRegistered ( channelKey , account )
2018-02-03 12:38:28 +01:00
if err != nil {
2018-04-01 03:51:34 +02:00
csNotice ( rb , err . Error ( ) )
2018-02-03 12:38:28 +01:00
return
}
2017-03-11 13:01:40 +01:00
2018-04-01 03:51:34 +02:00
csNotice ( 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
2018-04-23 00:47:10 +02:00
change := channelInfo . applyModeToMember ( client , modes . ChannelFounder , modes . Add , client . NickCasefolded ( ) , rb )
2018-02-03 12:38:28 +01:00
if change != nil {
//TODO(dan): we should change the name of String and make it return a slice here
//TODO(dan): unify this code with code in modes.go
args := append ( [ ] string { channelName } , strings . Split ( change . String ( ) , " " ) ... )
for _ , member := range channelInfo . Members ( ) {
member . Send ( nil , fmt . Sprintf ( "ChanServ!services@%s" , client . server . name ) , "MODE" , args ... )
2017-11-09 04:19:50 +01:00
}
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
func checkChanLimit ( client * Client , rb * ResponseBuffer ) ( ok bool ) {
account := client . Account ( )
channelsAlreadyRegistered := client . server . accounts . ChannelsForAccount ( account )
ok = len ( channelsAlreadyRegistered ) < client . server . Config ( ) . Channels . Registration . MaxChannelsPerAccount || client . HasRoleCapabs ( "chanreg" )
if ! ok {
csNotice ( rb , client . t ( "You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER" ) )
}
return
}
2020-02-19 01:38:42 +01:00
func csPrivsCheck ( channel RegisteredChannel , client * Client , rb * ResponseBuffer ) ( success bool ) {
founder := channel . Founder
if founder == "" {
csNotice ( rb , client . t ( "That channel is not registered" ) )
return false
}
if client . HasRoleCapabs ( "chanreg" ) {
return true
}
if founder != client . Account ( ) {
csNotice ( rb , client . t ( "Insufficient privileges" ) )
return false
}
return true
}
2019-01-04 04:32:07 +01:00
func csUnregisterHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
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 {
csNotice ( rb , client . t ( "No such channel" ) )
return
}
2020-02-19 01:38:42 +01:00
info := channel . ExportRegistration ( 0 )
channelKey := info . NameCasefolded
if ! csPrivsCheck ( info , client , rb ) {
2018-06-04 11:02:22 +02:00
return
}
2018-06-19 10:03:40 +02:00
expectedCode := unregisterConfirmationCode ( info . Name , info . RegisteredAt )
if expectedCode != verificationCode {
csNotice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: unregistering this channel will remove all stored channel attributes.$b" ) ) )
2019-02-22 03:37:11 +01:00
csNotice ( rb , fmt . Sprintf ( client . t ( "To confirm channel unregistration, type: /CS UNREGISTER %[1]s %[2]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 )
2018-06-04 11:02:22 +02:00
csNotice ( rb , fmt . Sprintf ( client . t ( "Channel %s is now unregistered" ) , channelKey ) )
}
2018-06-19 10:03:40 +02:00
// deterministically generates a confirmation code for unregistering a channel / account
func unregisterConfirmationCode ( name string , registeredAt time . Time ) ( code string ) {
var codeInput bytes . Buffer
codeInput . WriteString ( name )
codeInput . WriteString ( strconv . FormatInt ( registeredAt . Unix ( ) , 16 ) )
return strconv . Itoa ( int ( crc32 . ChecksumIEEE ( codeInput . Bytes ( ) ) ) )
}
2019-12-17 01:50:15 +01:00
func csClearHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
channel := server . channels . Get ( params [ 0 ] )
if channel == nil {
csNotice ( rb , client . t ( "Channel does not exist" ) )
return
}
2020-02-19 01:38:42 +01:00
if ! csPrivsCheck ( channel . ExportRegistration ( 0 ) , client , rb ) {
2019-12-17 01:50:15 +01:00
return
}
switch strings . ToLower ( params [ 1 ] ) {
case "access" :
channel . resetAccess ( )
csNotice ( rb , client . t ( "Successfully reset channel access" ) )
case "users" :
for _ , target := range channel . Members ( ) {
if target != client {
channel . Kick ( client , target , "Cleared by ChanServ" , rb , true )
}
}
default :
csNotice ( rb , client . t ( "Invalid parameters" ) )
}
}
func csTransferHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
if strings . ToLower ( params [ 0 ] ) == "accept" {
processTransferAccept ( client , params [ 1 ] , rb )
return
}
chname := params [ 0 ]
channel := server . channels . Get ( chname )
if channel == nil {
csNotice ( rb , client . t ( "Channel does not exist" ) )
return
}
regInfo := channel . ExportRegistration ( 0 )
chname = regInfo . Name
account := client . Account ( )
isFounder := account != "" && account == regInfo . Founder
hasPrivs := client . HasRoleCapabs ( "chanreg" )
if ! ( isFounder || hasPrivs ) {
csNotice ( rb , client . t ( "Insufficient privileges" ) )
return
}
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 {
csNotice ( rb , client . t ( "Account does not exist" ) )
return
}
2019-12-25 21:56:57 +01:00
if targetAccount . NameCasefolded != account {
expectedCode := unregisterConfirmationCode ( regInfo . Name , regInfo . RegisteredAt )
codeValidated := 2 < len ( params ) && params [ 2 ] == expectedCode
if ! codeValidated {
csNotice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: you are about to transfer control of your channel to another user.$b" ) ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s" ) , chname , target , expectedCode ) )
return
}
2019-12-17 01:50:15 +01:00
}
status , err := channel . Transfer ( client , target , hasPrivs )
if err == nil {
switch status {
case channelTransferComplete :
csNotice ( rb , fmt . Sprintf ( client . t ( "Successfully transferred channel %[1]s to account %[2]s" ) , chname , target ) )
case channelTransferPending :
sendTransferPendingNotice ( server , target , chname )
csNotice ( rb , fmt . Sprintf ( client . t ( "Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance" ) , chname , target ) )
case channelTransferCancelled :
csNotice ( rb , fmt . Sprintf ( client . t ( "Cancelled pending transfer of channel %s" ) , chname ) )
}
} else {
csNotice ( rb , client . t ( "Could not transfer channel" ) )
}
}
func sendTransferPendingNotice ( server * Server , account , chname string ) {
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
}
}
2019-12-30 18:44:07 +01:00
client . Send ( nil , chanservMask , "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
}
func processTransferAccept ( client * Client , chname string , rb * ResponseBuffer ) {
channel := client . server . channels . Get ( chname )
if channel == nil {
csNotice ( rb , client . t ( "Channel does not exist" ) )
return
}
if ! checkChanLimit ( client , rb ) {
return
}
switch channel . AcceptTransfer ( client ) {
case nil :
csNotice ( rb , fmt . Sprintf ( client . t ( "Successfully accepted ownership of channel %s" ) , channel . Name ( ) ) )
case errChannelTransferNotOffered :
csNotice ( rb , fmt . Sprintf ( client . t ( "You weren't offered ownership of channel %s" ) , channel . Name ( ) ) )
default :
csNotice ( rb , fmt . Sprintf ( client . t ( "Could not accept ownership of channel %s" ) , channel . Name ( ) ) )
}
}
func csPurgeHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
oper := client . Oper ( )
if oper == nil {
return // should be impossible because you need oper capabs for this
}
chname := params [ 0 ]
var reason string
if 1 < len ( params ) {
reason = params [ 1 ]
}
purgeRecord := ChannelPurgeRecord {
Oper : oper . Name ,
PurgedAt : time . Now ( ) . UTC ( ) ,
Reason : reason ,
}
switch server . channels . Purge ( chname , purgeRecord ) {
case nil :
channel := server . channels . Get ( chname )
if channel != nil { // channel need not exist to be purged
for _ , target := range channel . Members ( ) {
channel . Kick ( client , target , "Cleared by ChanServ" , rb , true )
}
}
csNotice ( rb , fmt . Sprintf ( client . t ( "Successfully purged channel %s from the server" ) , chname ) )
case errInvalidChannelName :
csNotice ( rb , fmt . Sprintf ( client . t ( "Can't purge invalid channel %s" ) , chname ) )
default :
csNotice ( rb , client . t ( "An error occurred" ) )
}
}
func csUnpurgeHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
chname := params [ 0 ]
switch server . channels . Unpurge ( chname ) {
case nil :
csNotice ( rb , fmt . Sprintf ( client . t ( "Successfully unpurged channel %s from the server" ) , chname ) )
case errNoSuchChannel :
csNotice ( rb , fmt . Sprintf ( client . t ( "Channel %s wasn't previously purged from the server" ) , chname ) )
default :
csNotice ( rb , client . t ( "An error occurred" ) )
}
}
func csInfoHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
chname , err := CasefoldChannel ( params [ 0 ] )
if err != nil {
csNotice ( rb , client . t ( "Invalid channel name" ) )
return
}
// purge status
if client . HasRoleCapabs ( "chanreg" ) {
purgeRecord , err := server . channelRegistry . LoadPurgeRecord ( chname )
if err == nil {
csNotice ( rb , fmt . Sprintf ( client . t ( "Channel %s was purged by the server operators and cannot be used" ) , chname ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "Purged by operator: %s" ) , purgeRecord . Oper ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "Purged at: %s" ) , purgeRecord . PurgedAt . Format ( time . RFC1123 ) ) )
if purgeRecord . Reason != "" {
csNotice ( rb , fmt . Sprintf ( client . t ( "Purge reason: %s" ) , purgeRecord . Reason ) )
}
}
} else {
if server . channels . IsPurged ( chname ) {
csNotice ( rb , fmt . Sprintf ( client . t ( "Channel %s was purged by the server operators and cannot be used" ) , chname ) )
}
}
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 ) {
csNotice ( rb , client . t ( "An error occurred" ) )
return
}
}
// channel exists but is unregistered, or doesn't exist:
if chinfo . Founder == "" {
csNotice ( rb , fmt . Sprintf ( client . t ( "Channel %s is not registered" ) , chname ) )
return
}
csNotice ( rb , fmt . Sprintf ( client . t ( "Channel %s is registered" ) , chinfo . Name ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "Founder: %s" ) , chinfo . Founder ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "Registered at: %s" ) , chinfo . RegisteredAt . Format ( time . RFC1123 ) ) )
}
2020-02-19 01:38:42 +01:00
func displayChannelSetting ( settingName string , settings ChannelSettings , client * Client , rb * ResponseBuffer ) {
config := client . server . Config ( )
switch strings . ToLower ( settingName ) {
case "history" :
effectiveValue := historyEnabled ( config . History . Persistent . RegisteredChannels , settings . History )
csNotice ( rb , fmt . Sprintf ( client . t ( "The stored channel history setting is: %s" ) , historyStatusToString ( settings . History ) ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "Given current server settings, the channel history setting is: %s" ) , historyStatusToString ( effectiveValue ) ) )
default :
csNotice ( rb , client . t ( "Invalid params" ) )
}
}
func csGetHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
chname , setting := params [ 0 ] , params [ 1 ]
channel := server . channels . Get ( chname )
if channel == nil {
csNotice ( rb , client . t ( "No such channel" ) )
return
}
info := channel . ExportRegistration ( IncludeSettings )
if ! csPrivsCheck ( info , client , rb ) {
return
}
displayChannelSetting ( setting , info . Settings , client , rb )
}
func csSetHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
chname , setting , value := params [ 0 ] , params [ 1 ] , params [ 2 ]
channel := server . channels . Get ( chname )
if channel == nil {
csNotice ( rb , client . t ( "No such channel" ) )
return
}
info := channel . ExportRegistration ( IncludeSettings )
settings := info . Settings
if ! csPrivsCheck ( info , client , rb ) {
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 :
csNotice ( rb , client . t ( "Successfully changed the channel settings" ) )
displayChannelSetting ( setting , settings , client , rb )
case errInvalidParams :
csNotice ( rb , client . t ( "Invalid parameters" ) )
default :
server . logger . Error ( "internal" , "CS SET error:" , err . Error ( ) )
csNotice ( rb , client . t ( "An error occurred" ) )
}
}