2018-02-03 10:28:02 +01:00
// Copyright (c) 2012-2014 Jeremy Latt
2018-02-03 10:48:30 +01:00
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2018 Daniel Oaks <daniel@danieloaks.net>
// Copyright (c) 2017-2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2018-02-03 10:28:02 +01:00
// released under the MIT license
package irc
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmatch"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/custime"
2018-11-26 11:23:27 +01:00
"github.com/oragono/oragono/irc/history"
2018-02-03 11:21:32 +01:00
"github.com/oragono/oragono/irc/modes"
2018-02-03 10:28:02 +01:00
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
2018-04-19 08:48:19 +02:00
"golang.org/x/crypto/bcrypt"
2018-02-03 10:28:02 +01:00
)
2018-02-03 20:48:44 +01:00
// ACC [REGISTER|VERIFY] ...
2018-02-05 15:21:08 +01:00
func accHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-20 10:20:30 +01:00
// make sure reg is enabled
if ! server . AccountConfig ( ) . Registration . Enabled {
rb . Add ( nil , server . name , ERR_REG_UNSPECIFIED_ERROR , client . nick , "*" , client . t ( "Account registration is disabled" ) )
return false
}
2018-02-03 10:28:02 +01:00
subcommand := strings . ToLower ( msg . Params [ 0 ] )
if subcommand == "register" {
2018-02-05 15:21:08 +01:00
return accRegisterHandler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
} else if subcommand == "verify" {
2018-02-20 10:20:30 +01:00
return accVerifyHandler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , "ACC" , msg . Params [ 0 ] , client . t ( "Unknown subcommand" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-20 10:44:44 +01:00
// helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234
2018-02-20 10:20:30 +01:00
func parseCallback ( spec string , config * AccountConfig ) ( callbackNamespace string , callbackValue string ) {
callback := strings . ToLower ( spec )
if callback == "*" {
callbackNamespace = "*"
} else if strings . Contains ( callback , ":" ) {
callbackValues := strings . SplitN ( callback , ":" , 2 )
callbackNamespace , callbackValue = callbackValues [ 0 ] , callbackValues [ 1 ]
} else {
// "the IRC server MAY choose to use mailto as a default"
callbackNamespace = "mailto"
callbackValue = callback
}
// ensure the callback namespace is valid
// need to search callback list, maybe look at using a map later?
for _ , name := range config . Registration . EnabledCallbacks {
if callbackNamespace == name {
return
}
2018-02-03 10:28:02 +01:00
}
2018-02-20 10:20:30 +01:00
// error value
callbackNamespace = ""
return
}
2018-02-03 10:28:02 +01:00
2018-02-20 10:20:30 +01:00
// ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
func accRegisterHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-01-01 22:45:37 +01:00
nick := client . Nick ( )
2018-02-03 10:28:02 +01:00
// clients can't reg new accounts if they're already logged in
if client . LoggedIntoAccount ( ) {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_REG_UNSPECIFIED_ERROR , nick , "*" , client . t ( "You're already logged into an account" ) )
2018-08-15 04:50:20 +02:00
return false
2018-02-03 10:28:02 +01:00
}
// get and sanitise account name
account := strings . TrimSpace ( msg . Params [ 1 ] )
casefoldedAccount , err := CasefoldName ( account )
// probably don't need explicit check for "*" here... but let's do it anyway just to make sure
if err != nil || msg . Params [ 1 ] == "*" {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_REG_UNSPECIFIED_ERROR , nick , account , client . t ( "Account name is not valid" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
if len ( msg . Params ) < 4 {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , nick , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-20 10:20:30 +01:00
callbackSpec := msg . Params [ 2 ]
callbackNamespace , callbackValue := parseCallback ( callbackSpec , server . AccountConfig ( ) )
2018-02-03 10:28:02 +01:00
2018-02-20 10:20:30 +01:00
if callbackNamespace == "" {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_REG_INVALID_CALLBACK , nick , account , callbackSpec , client . t ( "Callback namespace is not supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get credential type/value
var credentialType , credentialValue string
if len ( msg . Params ) > 4 {
credentialType = strings . ToLower ( msg . Params [ 3 ] )
credentialValue = msg . Params [ 4 ]
2018-02-11 11:30:40 +01:00
} else {
// exactly 4 params
2018-02-03 10:28:02 +01:00
credentialType = "passphrase" // default from the spec
credentialValue = msg . Params [ 3 ]
}
// ensure the credential type is valid
var credentialValid bool
2018-02-11 11:30:40 +01:00
for _ , name := range server . AccountConfig ( ) . Registration . EnabledCredentialTypes {
2018-02-03 10:28:02 +01:00
if credentialType == name {
credentialValid = true
}
}
if credentialType == "certfp" && client . certfp == "" {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_REG_INVALID_CRED_TYPE , nick , credentialType , callbackNamespace , client . t ( "You are not using a TLS certificate" ) )
2018-02-03 10:28:02 +01:00
return false
}
if ! credentialValid {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_REG_INVALID_CRED_TYPE , nick , credentialType , callbackNamespace , client . t ( "Credential type is not supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
var passphrase , certfp string
if credentialType == "certfp" {
certfp = client . certfp
} else if credentialType == "passphrase" {
passphrase = credentialValue
}
2019-01-01 22:45:37 +01:00
throttled , remainingTime := client . loginThrottle . Touch ( )
if throttled {
rb . Add ( nil , server . name , ERR_REG_UNSPECIFIED_ERROR , nick , fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime ) )
return false
}
2018-02-11 11:30:40 +01:00
err = server . accounts . Register ( client , account , callbackNamespace , callbackValue , passphrase , certfp )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-11 11:30:40 +01:00
msg := "Unknown"
code := ERR_UNKNOWNERROR
2018-02-03 10:28:02 +01:00
if err == errCertfpAlreadyExists {
2018-02-11 11:30:40 +01:00
msg = "An account already exists for your certificate fingerprint"
} else if err == errAccountAlreadyRegistered {
msg = "Account already exists"
code = ERR_ACCOUNT_ALREADY_EXISTS
2018-08-06 04:51:39 +02:00
} else if err == errAccountBadPassphrase {
msg = "Passphrase contains forbidden characters or is otherwise invalid"
2018-02-11 11:30:40 +01:00
}
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
msg = err . Error ( )
2018-02-03 10:28:02 +01:00
}
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , code , nick , "ACC" , "REGISTER" , client . t ( msg ) )
2018-02-03 10:28:02 +01:00
return false
}
// automatically complete registration
if callbackNamespace == "*" {
2018-02-11 11:30:40 +01:00
err := server . accounts . Verify ( client , casefoldedAccount , "" )
2018-02-03 10:28:02 +01:00
if err != nil {
return false
}
2018-02-20 10:20:30 +01:00
sendSuccessfulRegResponse ( client , rb , false )
} else {
messageTemplate := client . t ( "Account created, pending verification; verification code has been sent to %s:%s" )
message := fmt . Sprintf ( messageTemplate , callbackNamespace , callbackValue )
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , RPL_REG_VERIFICATION_REQUIRED , nick , casefoldedAccount , message )
2018-02-03 10:28:02 +01:00
}
2018-02-20 10:20:30 +01:00
return false
}
2018-02-20 10:44:44 +01:00
// helper function to dispatch messages when a client successfully registers
2018-02-20 10:20:30 +01:00
func sendSuccessfulRegResponse ( client * Client , rb * ResponseBuffer , forNS bool ) {
if forNS {
rb . Notice ( client . t ( "Account created" ) )
2018-02-20 10:44:44 +01:00
} else {
rb . Add ( nil , client . server . name , RPL_REGISTRATION_SUCCESS , client . nick , client . AccountName ( ) , client . t ( "Account created" ) )
2018-02-20 10:20:30 +01:00
}
sendSuccessfulSaslAuth ( client , rb , forNS )
}
// sendSuccessfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
func sendSuccessfulSaslAuth ( client * Client , rb * ResponseBuffer , forNS bool ) {
account := client . AccountName ( )
if forNS {
rb . Notice ( fmt . Sprintf ( client . t ( "You're now logged in as %s" ) , client . AccountName ( ) ) )
2018-02-20 10:44:44 +01:00
} else {
2018-04-01 10:31:30 +02:00
rb . Add ( nil , client . server . name , RPL_LOGGEDIN , client . nick , client . nickMaskString , account , fmt . Sprintf ( client . t ( "You are now logged in as %s" ) , account ) )
rb . Add ( nil , client . server . name , RPL_SASLSUCCESS , client . nick , client . t ( "Authentication successful" ) )
2018-02-20 10:20:30 +01:00
}
// dispatch account-notify
for friend := range client . Friends ( caps . AccountNotify ) {
friend . Send ( nil , client . nickMaskString , "ACCOUNT" , account )
}
client . server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]" ) , client . nickMaskString , account ) )
}
// ACC VERIFY <accountname> <auth_code>
func accVerifyHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
account := strings . TrimSpace ( msg . Params [ 1 ] )
err := server . accounts . Verify ( client , account , msg . Params [ 2 ] )
var code string
var message string
if err == errAccountVerificationInvalidCode {
code = ERR_ACCOUNT_INVALID_VERIFY_CODE
message = err . Error ( )
} else if err == errAccountAlreadyVerified {
code = ERR_ACCOUNT_ALREADY_VERIFIED
message = err . Error ( )
} else if err != nil {
code = ERR_UNKNOWNERROR
message = errAccountVerificationFailed . Error ( )
}
if err == nil {
sendSuccessfulRegResponse ( client , rb , false )
} else {
rb . Add ( nil , server . name , code , client . nick , account , client . t ( message ) )
}
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE [<mechanism>|<data>|*]
2018-02-05 15:21:08 +01:00
func authenticateHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// sasl abort
2018-02-11 11:30:40 +01:00
if ! server . AccountConfig ( ) . AuthenticationEnabled || len ( msg . Params ) == 1 && msg . Params [ 0 ] == "*" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLABORTED , client . nick , client . t ( "SASL authentication aborted" ) )
2018-02-03 10:28:02 +01:00
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return false
}
// start new sasl session
if ! client . saslInProgress {
mechanism := strings . ToUpper ( msg . Params [ 0 ] )
_ , mechanismIsEnabled := EnabledSaslMechanisms [ mechanism ]
if mechanismIsEnabled {
client . saslInProgress = true
client . saslMechanism = mechanism
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "AUTHENTICATE" , "+" )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// continue existing sasl session
rawData := msg . Params [ 0 ]
if len ( rawData ) > 400 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLTOOLONG , client . nick , client . t ( "SASL message too long" ) )
2018-02-03 10:28:02 +01:00
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return false
} else if len ( rawData ) == 400 {
client . saslValue += rawData
// allow 4 'continuation' lines before rejecting for length
if len ( client . saslValue ) > 400 * 4 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed: Passphrase too long" ) )
2018-02-03 10:28:02 +01:00
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return false
}
return false
}
if rawData != "+" {
client . saslValue += rawData
}
var data [ ] byte
var err error
if client . saslValue != "+" {
data , err = base64 . StdEncoding . DecodeString ( client . saslValue )
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed: Invalid b64 encoding" ) )
2018-02-03 10:28:02 +01:00
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return false
}
}
// call actual handler
handler , handlerExists := EnabledSaslMechanisms [ client . saslMechanism ]
// like 100% not required, but it's good to be safe I guess
if ! handlerExists {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed" ) )
2018-02-03 10:28:02 +01:00
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return false
}
// let the SASL handler do its thing
2018-02-05 15:21:08 +01:00
exiting := handler ( server , client , client . saslMechanism , data , rb )
2018-02-03 10:28:02 +01:00
2018-02-27 03:44:03 +01:00
if client . LoggedIntoAccount ( ) && server . AccountConfig ( ) . SkipServerPassword {
client . SetAuthorized ( true )
}
2018-02-03 10:28:02 +01:00
// wait 'til SASL is done before emptying the sasl vars
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return exiting
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE PLAIN
2018-02-05 15:21:08 +01:00
func authPlainHandler ( server * Server , client * Client , mechanism string , value [ ] byte , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
splitValue := bytes . Split ( value , [ ] byte { '\000' } )
var accountKey , authzid string
2019-01-01 22:45:37 +01:00
nick := client . Nick ( )
2018-02-03 10:28:02 +01:00
if len ( splitValue ) == 3 {
accountKey = string ( splitValue [ 0 ] )
authzid = string ( splitValue [ 1 ] )
if accountKey == "" {
accountKey = authzid
} else if accountKey != authzid {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , client . t ( "SASL authentication failed: authcid and authzid should be the same" ) )
2018-02-03 10:28:02 +01:00
return false
}
} else {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , client . t ( "SASL authentication failed: Invalid auth blob" ) )
return false
}
throttled , remainingTime := client . loginThrottle . Touch ( )
if throttled {
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
password := string ( splitValue [ 2 ] )
2018-03-02 23:04:24 +01:00
err := server . accounts . AuthenticateByPassphrase ( client , accountKey , password )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-11 11:30:40 +01:00
msg := authErrorToMessage ( server , err )
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , fmt . Sprintf ( "%s: %s" , client . t ( "SASL authentication failed" ) , client . t ( msg ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-20 10:20:30 +01:00
sendSuccessfulSaslAuth ( client , rb , false )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
func authErrorToMessage ( server * Server , err error ) ( msg string ) {
if err == errAccountDoesNotExist || err == errAccountUnverified || err == errAccountInvalidCredentials {
msg = err . Error ( )
} else {
2018-12-31 17:33:42 +01:00
server . logger . Error ( "internal" , "sasl authentication failure" , err . Error ( ) )
2018-02-11 11:30:40 +01:00
msg = "Unknown"
}
return
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE EXTERNAL
2018-02-05 15:21:08 +01:00
func authExternalHandler ( server * Server , client * Client , mechanism string , value [ ] byte , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if client . certfp == "" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed, you are not connecting with a certificate" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
err := server . accounts . AuthenticateByCertFP ( client )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-11 11:30:40 +01:00
msg := authErrorToMessage ( server , err )
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , fmt . Sprintf ( "%s: %s" , client . t ( "SASL authentication failed" ) , client . t ( msg ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-20 10:20:30 +01:00
sendSuccessfulSaslAuth ( client , rb , false )
2018-02-03 10:28:02 +01:00
return false
}
// AWAY [<message>]
2018-02-05 15:21:08 +01:00
func awayHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var isAway bool
var text string
if len ( msg . Params ) > 0 {
isAway = true
text = msg . Params [ 0 ]
awayLen := server . Limits ( ) . AwayLen
if len ( text ) > awayLen {
text = text [ : awayLen ]
}
}
2018-04-23 00:47:10 +02:00
client . SetMode ( modes . Away , isAway )
2018-02-03 10:28:02 +01:00
client . awayMessage = text
2018-02-03 11:21:32 +01:00
var op modes . ModeOp
2018-04-23 00:47:10 +02:00
if isAway {
2018-02-03 11:21:32 +01:00
op = modes . Add
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_NOWAWAY , client . nick , client . t ( "You have been marked as being away" ) )
2018-02-03 10:28:02 +01:00
} else {
2018-02-03 11:21:32 +01:00
op = modes . Remove
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_UNAWAY , client . nick , client . t ( "You are no longer marked as being away" ) )
2018-02-03 10:28:02 +01:00
}
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
2018-02-03 11:21:32 +01:00
modech := modes . ModeChanges { modes . ModeChange {
Mode : modes . Away ,
Op : op ,
2018-02-03 10:28:02 +01:00
} }
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "MODE" , client . nick , modech . String ( ) )
2018-02-03 10:28:02 +01:00
// dispatch away-notify
for friend := range client . Friends ( caps . AwayNotify ) {
2018-04-23 00:47:10 +02:00
if isAway {
2018-02-03 10:28:02 +01:00
friend . SendFromClient ( "" , client , nil , "AWAY" , client . awayMessage )
} else {
friend . SendFromClient ( "" , client , nil , "AWAY" )
}
}
return false
}
// CAP <subcmd> [<caps>]
2018-02-05 15:21:08 +01:00
func capHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
subCommand := strings . ToUpper ( msg . Params [ 0 ] )
2019-02-03 02:00:23 +01:00
toAdd := caps . NewSet ( )
toRemove := caps . NewSet ( )
2018-02-03 10:28:02 +01:00
var capString string
2019-02-03 02:00:23 +01:00
badCaps := false
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 1 {
capString = msg . Params [ 1 ]
2018-06-26 00:08:15 +02:00
strs := strings . Fields ( capString )
2018-02-03 10:28:02 +01:00
for _ , str := range strs {
2019-02-03 02:00:23 +01:00
remove := false
if str [ 0 ] == '-' {
str = str [ 1 : ]
remove = true
}
2018-06-26 00:08:15 +02:00
capab , err := caps . NameToCapability ( str )
2019-02-03 02:00:23 +01:00
if err != nil || ( ! remove && ! SupportedCapabilities . Has ( capab ) ) {
badCaps = true
} else if ! remove {
toAdd . Enable ( capab )
2018-06-26 00:08:15 +02:00
} else {
2019-02-03 02:00:23 +01:00
toRemove . Enable ( capab )
2018-02-03 10:28:02 +01:00
}
}
}
switch subCommand {
case "LS" :
if ! client . registered {
2018-02-03 12:15:07 +01:00
client . capState = caps . NegotiatingState
2018-02-03 10:28:02 +01:00
}
if len ( msg . Params ) > 1 && msg . Params [ 1 ] == "302" {
client . capVersion = 302
}
// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
// the server.name source... otherwise it doesn't respond to the CAP message with
// anything and just hangs on connection.
//TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "CAP" , client . nick , subCommand , SupportedCapabilities . String ( client . capVersion , CapValues ) )
2018-02-03 10:28:02 +01:00
case "LIST" :
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "CAP" , client . nick , subCommand , client . capabilities . String ( caps . Cap301 , CapValues ) ) // values not sent on LIST so force 3.1
2018-02-03 10:28:02 +01:00
case "REQ" :
if ! client . registered {
2018-02-03 12:15:07 +01:00
client . capState = caps . NegotiatingState
2018-02-03 10:28:02 +01:00
}
// make sure all capabilities actually exist
2019-02-03 02:00:23 +01:00
if badCaps {
2018-06-26 00:08:15 +02:00
rb . Add ( nil , server . name , "CAP" , client . nick , "NAK" , capString )
return false
2018-02-03 10:28:02 +01:00
}
2019-02-03 02:00:23 +01:00
client . capabilities . Union ( toAdd )
client . capabilities . Subtract ( toRemove )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "CAP" , client . nick , "ACK" , capString )
2018-02-03 10:28:02 +01:00
2018-11-26 11:23:27 +01:00
// if this is the first time the client is requesting a resume token,
// send it to them
2019-02-03 02:00:23 +01:00
if toAdd . Has ( caps . Resume ) {
2018-11-26 11:23:27 +01:00
token , err := client . generateResumeToken ( )
if err == nil {
rb . Add ( nil , server . name , "RESUME" , "TOKEN" , token )
}
}
2018-02-03 10:28:02 +01:00
case "END" :
2018-02-27 03:44:03 +01:00
if ! client . Registered ( ) {
2018-02-03 12:15:07 +01:00
client . capState = caps . NegotiatedState
2018-02-03 10:28:02 +01:00
}
default :
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_INVALIDCAPCMD , client . nick , subCommand , client . t ( "Invalid CAP subcommand" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// DEBUG <subcmd>
2018-02-05 15:21:08 +01:00
func debugHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-12 05:48:58 +01:00
param := strings . ToUpper ( msg . Params [ 0 ] )
2018-02-03 10:28:02 +01:00
2018-02-11 11:30:40 +01:00
switch param {
2018-02-03 10:28:02 +01:00
case "GCSTATS" :
stats := debug . GCStats {
Pause : make ( [ ] time . Duration , 10 ) ,
PauseQuantiles : make ( [ ] time . Duration , 5 ) ,
}
debug . ReadGCStats ( & stats )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "last GC: %s" , stats . LastGC . Format ( time . RFC1123 ) ) )
rb . Notice ( fmt . Sprintf ( "num GC: %d" , stats . NumGC ) )
rb . Notice ( fmt . Sprintf ( "pause total: %s" , stats . PauseTotal ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles min%%: %s" , stats . PauseQuantiles [ 0 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles 25%%: %s" , stats . PauseQuantiles [ 1 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles 50%%: %s" , stats . PauseQuantiles [ 2 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles 75%%: %s" , stats . PauseQuantiles [ 3 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles max%%: %s" , stats . PauseQuantiles [ 4 ] ) )
2018-02-03 10:28:02 +01:00
case "NUMGOROUTINE" :
count := runtime . NumGoroutine ( )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "num goroutines: %d" , count ) )
2018-02-03 10:28:02 +01:00
case "PROFILEHEAP" :
profFile := "oragono.mprof"
file , err := os . Create ( profFile )
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "error: %s" , err ) )
2018-02-03 10:28:02 +01:00
break
}
defer file . Close ( )
pprof . Lookup ( "heap" ) . WriteTo ( file , 0 )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "written to %s" , profFile ) )
2018-02-03 10:28:02 +01:00
case "STARTCPUPROFILE" :
profFile := "oragono.prof"
file , err := os . Create ( profFile )
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "error: %s" , err ) )
2018-02-03 10:28:02 +01:00
break
}
if err := pprof . StartCPUProfile ( file ) ; err != nil {
defer file . Close ( )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "error: %s" , err ) )
2018-02-03 10:28:02 +01:00
break
}
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "CPU profile writing to %s" , profFile ) )
2018-02-03 10:28:02 +01:00
case "STOPCPUPROFILE" :
pprof . StopCPUProfile ( )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "CPU profiling stopped" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2019-01-22 11:01:01 +01:00
// helper for parsing the reason args to DLINE and KLINE
func getReasonsFromParams ( params [ ] string , currentArg int ) ( reason , operReason string ) {
reason = "No reason given"
operReason = ""
if len ( params ) > currentArg {
reasons := strings . SplitN ( strings . Join ( params [ currentArg : ] , " " ) , "|" , 2 )
if len ( reasons ) == 1 {
reason = strings . TrimSpace ( reasons [ 0 ] )
} else if len ( reasons ) == 2 {
reason = strings . TrimSpace ( reasons [ 0 ] )
operReason = strings . TrimSpace ( reasons [ 1 ] )
}
}
return
}
func formatBanForListing ( client * Client , key string , info IPBanInfo ) string {
desc := info . Reason
if info . OperReason != "" && info . OperReason != info . Reason {
desc = fmt . Sprintf ( "%s | %s" , info . Reason , info . OperReason )
}
if info . Duration != 0 {
desc = fmt . Sprintf ( "%s [%s]" , desc , info . TimeLeft ( ) )
}
return fmt . Sprintf ( client . t ( "Ban - %[1]s - added by %[2]s - %[3]s" ) , key , info . OperName , desc )
}
2018-02-03 10:28:02 +01:00
// DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
// DLINE LIST
2018-02-05 15:21:08 +01:00
func dlineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_ban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
currentArg := 0
// if they say LIST, we just list the current dlines
if len ( msg . Params ) == currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "list" {
bans := server . dlines . AllBans ( )
if len ( bans ) == 0 {
2018-02-05 15:21:08 +01:00
rb . Notice ( client . t ( "No DLINEs have been set!" ) )
2018-02-03 10:28:02 +01:00
}
for key , info := range bans {
2019-01-22 11:01:01 +01:00
client . Notice ( formatBanForListing ( client , key , info ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// when setting a ban, if they say "ANDKILL" we should also kill all users who match it
var andKill bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "andkill" {
andKill = true
currentArg ++
}
// when setting a ban that covers the oper's current connection, we require them to say
// "DLINE MYSELF" so that we're sure they really mean it.
var dlineMyself bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "myself" {
dlineMyself = true
currentArg ++
}
// duration
duration , err := custime . ParseDuration ( msg . Params [ currentArg ] )
2019-01-22 11:01:01 +01:00
if err != nil {
duration = 0
} else {
2018-02-03 10:28:02 +01:00
currentArg ++
}
// get host
if len ( msg . Params ) < currentArg + 1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
hostString := msg . Params [ currentArg ]
currentArg ++
// check host
2019-01-22 11:01:01 +01:00
hostNet , err := utils . NormalizedNetFromString ( hostString )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Could not parse IP address or CIDR network" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-01-22 11:01:01 +01:00
if ! dlineMyself && hostNet . Contains ( client . IP ( ) ) {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "This ban matches you. To DLINE yourself, you must use the command: /DLINE MYSELF <arguments>" ) )
return false
2018-02-03 10:28:02 +01:00
}
// check remote
if len ( msg . Params ) > currentArg && msg . Params [ currentArg ] == "ON" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Remote servers not yet supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get comment(s)
2019-01-22 11:01:01 +01:00
reason , operReason := getReasonsFromParams ( msg . Params , currentArg )
2018-04-19 08:48:19 +02:00
operName := oper . Name
2018-02-03 10:28:02 +01:00
if operName == "" {
operName = server . name
}
2019-01-22 11:01:01 +01:00
err = server . dlines . AddNetwork ( hostNet , duration , reason , operReason , operName )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Could not successfully save new D-LINE: %s" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
var snoDescription string
2019-01-22 11:01:01 +01:00
hostString = utils . NetToNormalizedString ( hostNet )
if duration != 0 {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added temporary (%[1]s) D-Line for %[2]s" ) , duration . String ( ) , hostString ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added temporary (%s) D-Line for %s" ) , client . nick , operName , duration . String ( ) , hostString )
} else {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added D-Line for %s" ) , hostString ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added D-Line for %s" ) , client . nick , operName , hostString )
}
server . snomasks . Send ( sno . LocalXline , snoDescription )
var killClient bool
if andKill {
var clientsToKill [ ] * Client
var killedClientNicks [ ] string
for _ , mcl := range server . clients . AllClients ( ) {
2019-01-22 11:01:01 +01:00
if hostNet . Contains ( mcl . IP ( ) ) {
2018-02-03 10:28:02 +01:00
clientsToKill = append ( clientsToKill , mcl )
killedClientNicks = append ( killedClientNicks , mcl . nick )
}
}
for _ , mcl := range clientsToKill {
mcl . exitedSnomaskSent = true
mcl . Quit ( fmt . Sprintf ( mcl . t ( "You have been banned from this server (%s)" ) , reason ) )
if mcl == client {
killClient = true
} else {
// if mcl == client, we kill them below
mcl . destroy ( false )
}
}
// send snomask
sort . Strings ( killedClientNicks )
server . snomasks . Send ( sno . LocalKills , fmt . Sprintf ( ircfmt . Unescape ( "%s [%s] killed %d clients with a DLINE $c[grey][$r%s$c[grey]]" ) , client . nick , operName , len ( killedClientNicks ) , strings . Join ( killedClientNicks , ", " ) ) )
}
return killClient
}
2018-02-03 20:48:44 +01:00
// HELP [<query>]
2018-02-05 15:21:08 +01:00
func helpHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
argument := strings . ToLower ( strings . TrimSpace ( strings . Join ( msg . Params , " " ) ) )
if len ( argument ) < 1 {
client . sendHelp ( "HELPOP" , client . t ( ` HELPOP < argument >
2018-02-05 15:21:08 +01:00
Get an explanation of < argument > , or "index" for a list of help topics . ` ) , rb )
2018-02-03 10:28:02 +01:00
return false
}
// handle index
if argument == "index" {
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . Operator ) {
2018-02-05 15:21:08 +01:00
client . sendHelp ( "HELP" , GetHelpIndex ( client . languages , HelpIndexOpers ) , rb )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
client . sendHelp ( "HELP" , GetHelpIndex ( client . languages , HelpIndex ) , rb )
2018-02-03 10:28:02 +01:00
}
return false
}
helpHandler , exists := Help [ argument ]
2018-04-23 00:47:10 +02:00
if exists && ( ! helpHandler . oper || ( helpHandler . oper && client . HasMode ( modes . Operator ) ) ) {
2018-02-03 10:28:02 +01:00
if helpHandler . textGenerator != nil {
2018-02-05 15:21:08 +01:00
client . sendHelp ( strings . ToUpper ( argument ) , client . t ( helpHandler . textGenerator ( client ) ) , rb )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
client . sendHelp ( strings . ToUpper ( argument ) , client . t ( helpHandler . text ) , rb )
2018-02-03 10:28:02 +01:00
}
} else {
args := msg . Params
args = append ( args , client . t ( "Help not found" ) )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_HELPNOTFOUND , args ... )
2018-02-03 10:28:02 +01:00
}
return false
}
// INFO
2018-02-05 15:21:08 +01:00
func infoHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// we do the below so that the human-readable lines in info can be translated.
for _ , line := range infoString1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , line )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Oragono is released under the MIT license." ) )
rb . Add ( nil , server . name , RPL_INFO , client . nick , "" )
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Thanks to Jeremy Latt for founding Ergonomadic, the project this is based on" ) + " <3" )
rb . Add ( nil , server . name , RPL_INFO , client . nick , "" )
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Core Developers:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range infoString2 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , line )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Contributors and Former Developers:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range infoString3 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , line )
2018-02-03 10:28:02 +01:00
}
// show translators for languages other than good ole' regular English
tlines := server . languages . Translators ( )
if 0 < len ( tlines ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Translators:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range tlines {
2019-02-03 11:21:07 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , " " + strings . Replace ( line , "\n" , ", " , - 1 ) )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , "" )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ENDOFINFO , client . nick , client . t ( "End of /INFO" ) )
2018-02-03 10:28:02 +01:00
return false
}
// INVITE <nickname> <channel>
2018-02-05 15:21:08 +01:00
func inviteHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname := msg . Params [ 0 ]
channelName := msg . Params [ 1 ]
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
casefoldedChannelName , err := CasefoldChannel ( channelName )
channel := server . channels . Get ( casefoldedChannelName )
if err != nil || channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , channelName , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-05 15:21:08 +01:00
channel . Invite ( target , client , rb )
2018-02-03 10:28:02 +01:00
return false
}
// ISON <nick>{ <nick>}
2018-02-05 15:21:08 +01:00
func isonHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var nicks = msg . Params
var err error
var casefoldedNick string
ison := make ( [ ] string , 0 )
for _ , nick := range nicks {
casefoldedNick , err = CasefoldName ( nick )
if err != nil {
continue
}
if iclient := server . clients . Get ( casefoldedNick ) ; iclient != nil {
ison = append ( ison , iclient . nick )
}
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ISON , client . nick , strings . Join ( nicks , " " ) )
2018-02-03 10:28:02 +01:00
return false
}
// JOIN <channel>{,<channel>} [<key>{,<key>}]
2018-02-05 15:21:08 +01:00
func joinHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// kill JOIN 0 requests
if msg . Params [ 0 ] == "0" {
2018-02-05 15:21:08 +01:00
rb . Notice ( client . t ( "JOIN 0 is not allowed" ) )
2018-02-03 10:28:02 +01:00
return false
}
// handle regular JOINs
channels := strings . Split ( msg . Params [ 0 ] , "," )
var keys [ ] string
if len ( msg . Params ) > 1 {
keys = strings . Split ( msg . Params [ 1 ] , "," )
}
for i , name := range channels {
var key string
if len ( keys ) > i {
key = keys [ i ]
}
2018-05-25 08:46:36 +02:00
err := server . channels . Join ( client , name , key , false , rb )
2018-02-03 13:03:36 +01:00
if err == errNoSuchChannel {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , name , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2018-05-25 08:46:36 +02:00
// SAJOIN [nick] #channel{,#channel}
func sajoinHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
var target * Client
var channelString string
if strings . HasPrefix ( msg . Params [ 0 ] , "#" ) {
target = client
channelString = msg . Params [ 0 ]
} else {
if len ( msg . Params ) == 1 {
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , "KICK" , client . t ( "Not enough parameters" ) )
return false
} else {
target = server . clients . Get ( msg . Params [ 0 ] )
if target == nil {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . Nick ( ) , msg . Params [ 0 ] , "No such nick" )
return false
}
channelString = msg . Params [ 1 ]
rb = NewResponseBuffer ( target )
}
}
channels := strings . Split ( channelString , "," )
for _ , chname := range channels {
server . channels . Join ( target , chname , "" , true , rb )
}
if client != target {
2018-12-28 19:45:55 +01:00
rb . Send ( false )
2018-05-25 08:46:36 +02:00
}
return false
}
2018-02-03 10:28:02 +01:00
// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
2018-02-05 15:21:08 +01:00
func kickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
users := strings . Split ( msg . Params [ 1 ] , "," )
if ( len ( channels ) != len ( users ) ) && ( len ( users ) != 1 ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , "KICK" , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
var kicks [ ] [ ] string
for index , channel := range channels {
if len ( users ) == 1 {
kicks = append ( kicks , [ ] string { channel , users [ 0 ] } )
} else {
kicks = append ( kicks , [ ] string { channel , users [ index ] } )
}
}
var comment string
if len ( msg . Params ) > 2 {
comment = msg . Params [ 2 ]
}
for _ , info := range kicks {
chname := info [ 0 ]
nickname := info [ 1 ]
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
if err != nil || channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
continue
}
if comment == "" {
comment = nickname
}
2018-02-05 15:21:08 +01:00
channel . Kick ( client , target , comment , rb )
2018-02-03 10:28:02 +01:00
}
return false
}
// KILL <nickname> <comment>
2018-02-05 15:21:08 +01:00
func killHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname := msg . Params [ 0 ]
comment := "<no reason supplied>"
if len ( msg . Params ) > 1 {
comment = msg . Params [ 1 ]
}
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
quitMsg := fmt . Sprintf ( "Killed (%s (%s))" , client . nick , comment )
server . snomasks . Send ( sno . LocalKills , fmt . Sprintf ( ircfmt . Unescape ( "%s$r was killed by %s $c[grey][$r%s$c[grey]]" ) , target . nick , client . nick , comment ) )
target . exitedSnomaskSent = true
target . Quit ( quitMsg )
target . destroy ( false )
return false
}
// KLINE [ANDKILL] [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]]
// KLINE LIST
2018-02-05 15:21:08 +01:00
func klineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_ban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
currentArg := 0
// if they say LIST, we just list the current klines
if len ( msg . Params ) == currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "list" {
bans := server . klines . AllBans ( )
if len ( bans ) == 0 {
client . Notice ( "No KLINEs have been set!" )
}
for key , info := range bans {
2019-01-22 11:01:01 +01:00
client . Notice ( formatBanForListing ( client , key , info ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// when setting a ban, if they say "ANDKILL" we should also kill all users who match it
var andKill bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "andkill" {
andKill = true
currentArg ++
}
// when setting a ban that covers the oper's current connection, we require them to say
// "KLINE MYSELF" so that we're sure they really mean it.
var klineMyself bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "myself" {
klineMyself = true
currentArg ++
}
// duration
duration , err := custime . ParseDuration ( msg . Params [ currentArg ] )
2019-01-22 11:01:01 +01:00
if err != nil {
duration = 0
} else {
2018-02-03 10:28:02 +01:00
currentArg ++
}
// get mask
if len ( msg . Params ) < currentArg + 1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
mask := strings . ToLower ( msg . Params [ currentArg ] )
currentArg ++
// check mask
if ! strings . Contains ( mask , "!" ) && ! strings . Contains ( mask , "@" ) {
mask = mask + "!*@*"
} else if ! strings . Contains ( mask , "@" ) {
mask = mask + "@*"
}
matcher := ircmatch . MakeMatch ( mask )
for _ , clientMask := range client . AllNickmasks ( ) {
if ! klineMyself && matcher . Match ( clientMask ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>" ) )
2018-02-03 10:28:02 +01:00
return false
}
}
// check remote
if len ( msg . Params ) > currentArg && msg . Params [ currentArg ] == "ON" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Remote servers not yet supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get oper name
2018-04-19 08:48:19 +02:00
operName := oper . Name
2018-02-03 10:28:02 +01:00
if operName == "" {
operName = server . name
}
// get comment(s)
2019-01-22 11:01:01 +01:00
reason , operReason := getReasonsFromParams ( msg . Params , currentArg )
2018-02-03 10:28:02 +01:00
2019-01-22 11:01:01 +01:00
err = server . klines . AddMask ( mask , duration , reason , operReason , operName )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Could not successfully save new K-LINE: %s" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
var snoDescription string
2019-01-22 11:01:01 +01:00
if duration != 0 {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added temporary (%[1]s) K-Line for %[2]s" ) , duration . String ( ) , mask ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added temporary (%s) K-Line for %s" ) , client . nick , operName , duration . String ( ) , mask )
} else {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added K-Line for %s" ) , mask ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added K-Line for %s" ) , client . nick , operName , mask )
}
server . snomasks . Send ( sno . LocalXline , snoDescription )
var killClient bool
if andKill {
var clientsToKill [ ] * Client
var killedClientNicks [ ] string
for _ , mcl := range server . clients . AllClients ( ) {
for _ , clientMask := range mcl . AllNickmasks ( ) {
if matcher . Match ( clientMask ) {
clientsToKill = append ( clientsToKill , mcl )
killedClientNicks = append ( killedClientNicks , mcl . nick )
}
}
}
for _ , mcl := range clientsToKill {
mcl . exitedSnomaskSent = true
mcl . Quit ( fmt . Sprintf ( mcl . t ( "You have been banned from this server (%s)" ) , reason ) )
if mcl == client {
killClient = true
} else {
// if mcl == client, we kill them below
mcl . destroy ( false )
}
}
// send snomask
sort . Strings ( killedClientNicks )
server . snomasks . Send ( sno . LocalKills , fmt . Sprintf ( ircfmt . Unescape ( "%s [%s] killed %d clients with a KLINE $c[grey][$r%s$c[grey]]" ) , client . nick , operName , len ( killedClientNicks ) , strings . Join ( killedClientNicks , ", " ) ) )
}
return killClient
}
// LANGUAGE <code>{ <code>}
2018-02-05 15:21:08 +01:00
func languageHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
alreadyDoneLanguages := make ( map [ string ] bool )
var appliedLanguages [ ] string
supportedLanguagesCount := server . languages . Count ( )
if supportedLanguagesCount < len ( msg . Params ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_TOOMANYLANGUAGES , client . nick , strconv . Itoa ( supportedLanguagesCount ) , client . t ( "You specified too many languages" ) )
2018-02-03 10:28:02 +01:00
return false
}
for _ , value := range msg . Params {
value = strings . ToLower ( value )
// strip ~ from the language if it has it
value = strings . TrimPrefix ( value , "~" )
// silently ignore empty languages or those with spaces in them
if len ( value ) == 0 || strings . Contains ( value , " " ) {
continue
}
_ , exists := server . languages . Info [ value ]
if ! exists {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_NOLANGUAGE , client . nick , client . t ( "Languages are not supported by this server" ) )
2018-02-03 10:28:02 +01:00
return false
}
// if we've already applied the given language, skip it
_ , exists = alreadyDoneLanguages [ value ]
if exists {
continue
}
appliedLanguages = append ( appliedLanguages , value )
}
client . stateMutex . Lock ( )
if len ( appliedLanguages ) == 1 && appliedLanguages [ 0 ] == "en" {
// premature optimisation ahoy!
client . languages = [ ] string { }
} else {
client . languages = appliedLanguages
}
client . stateMutex . Unlock ( )
params := [ ] string { client . nick }
for _ , lang := range appliedLanguages {
params = append ( params , lang )
}
params = append ( params , client . t ( "Language preferences have been set" ) )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , RPL_YOURLANGUAGESARE , params ... )
2018-02-03 10:28:02 +01:00
return false
}
// LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
2018-02-05 15:21:08 +01:00
func listHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// get channels
var channels [ ] string
for _ , param := range msg . Params {
if 0 < len ( param ) && param [ 0 ] == '#' {
for _ , channame := range strings . Split ( param , "," ) {
if 0 < len ( channame ) && channame [ 0 ] == '#' {
channels = append ( channels , channame )
}
}
}
}
// get elist conditions
var matcher elistMatcher
for _ , param := range msg . Params {
if len ( param ) < 1 {
continue
}
if param [ 0 ] == '<' {
param = param [ 1 : ]
val , err := strconv . Atoi ( param )
if err != nil {
continue
}
matcher . MaxClientsActive = true
matcher . MaxClients = val - 1 // -1 because < means less than the given number
}
if param [ 0 ] == '>' {
param = param [ 1 : ]
val , err := strconv . Atoi ( param )
if err != nil {
continue
}
matcher . MinClientsActive = true
matcher . MinClients = val + 1 // +1 because > means more than the given number
}
}
2018-04-23 00:47:10 +02:00
clientIsOp := client . HasMode ( modes . Operator )
2018-02-03 10:28:02 +01:00
if len ( channels ) == 0 {
for _ , channel := range server . channels . Channels ( ) {
2018-04-23 00:47:10 +02:00
if ! clientIsOp && channel . flags . HasMode ( modes . Secret ) {
2018-02-03 10:28:02 +01:00
continue
}
if matcher . Matches ( channel ) {
2018-02-05 15:21:08 +01:00
client . RplList ( channel , rb )
2018-02-03 10:28:02 +01:00
}
}
} else {
// limit regular users to only listing one channel
2018-04-23 00:47:10 +02:00
if ! clientIsOp {
2018-02-03 10:28:02 +01:00
channels = channels [ : 1 ]
}
for _ , chname := range channels {
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
2018-04-23 00:47:10 +02:00
if err != nil || channel == nil || ( ! clientIsOp && channel . flags . HasMode ( modes . Secret ) ) {
2018-02-03 10:28:02 +01:00
if len ( chname ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
continue
}
if matcher . Matches ( channel ) {
2018-02-05 15:21:08 +01:00
client . RplList ( channel , rb )
2018-02-03 10:28:02 +01:00
}
}
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_LISTEND , client . nick , client . t ( "End of LIST" ) )
2018-02-03 10:28:02 +01:00
return false
}
// LUSERS [<mask> [<server>]]
2018-02-05 15:21:08 +01:00
func lusersHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
//TODO(vegax87) Fix network statistics and additional parameters
2018-04-20 22:48:15 +02:00
totalCount , invisibleCount , operCount := server . stats . GetStats ( )
rb . Add ( nil , server . name , RPL_LUSERCLIENT , client . nick , fmt . Sprintf ( client . t ( "There are %[1]d users and %[2]d invisible on %[3]d server(s)" ) , totalCount - invisibleCount , invisibleCount , 1 ) )
rb . Add ( nil , server . name , RPL_LUSEROP , client . nick , strconv . Itoa ( operCount ) , client . t ( "IRC Operators online" ) )
rb . Add ( nil , server . name , RPL_LUSERCHANNELS , client . nick , strconv . Itoa ( server . channels . Len ( ) ) , client . t ( "channels formed" ) )
rb . Add ( nil , server . name , RPL_LUSERME , client . nick , fmt . Sprintf ( client . t ( "I have %[1]d clients and %[2]d servers" ) , totalCount , 1 ) )
2018-02-03 10:28:02 +01:00
return false
}
// MODE <target> [<modestring> [<mode arguments>...]]
2018-02-05 15:21:08 +01:00
func modeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
_ , errChan := CasefoldChannel ( msg . Params [ 0 ] )
if errChan == nil {
2018-02-05 15:21:08 +01:00
return cmodeHandler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
return umodeHandler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-03 20:48:44 +01:00
// MODE <channel> [<modestring> [<mode arguments>...]]
2018-02-05 15:21:08 +01:00
func cmodeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channelName , err := CasefoldChannel ( msg . Params [ 0 ] )
channel := server . channels . Get ( channelName )
if err != nil || channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , msg . Params [ 0 ] , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
return false
}
// applied mode changes
2018-02-03 11:21:32 +01:00
applied := make ( modes . ModeChanges , 0 )
2018-02-03 10:28:02 +01:00
if 1 < len ( msg . Params ) {
// parse out real mode changes
params := msg . Params [ 1 : ]
2018-04-23 00:47:10 +02:00
changes , unknown := modes . ParseChannelModeChanges ( params ... )
2018-02-03 10:28:02 +01:00
// alert for unknown mode changes
for char := range unknown {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNMODE , client . nick , string ( char ) , client . t ( "is an unknown mode character to me" ) )
2018-02-03 10:28:02 +01:00
}
if len ( unknown ) == 1 && len ( changes ) == 0 {
return false
}
// apply mode changes
2018-02-05 15:21:08 +01:00
applied = channel . ApplyChannelModeChanges ( client , msg . Command == "SAMODE" , changes , rb )
2018-02-03 10:28:02 +01:00
}
2018-04-04 03:49:40 +02:00
// save changes
var includeFlags uint
2018-02-03 10:28:02 +01:00
for _ , change := range applied {
2018-04-04 03:49:40 +02:00
includeFlags |= IncludeModes
if change . Mode == modes . BanMask || change . Mode == modes . ExceptMask || change . Mode == modes . InviteMask {
includeFlags |= IncludeLists
2018-02-03 10:28:02 +01:00
}
}
2018-04-04 03:49:40 +02:00
if channel . IsRegistered ( ) && includeFlags != 0 {
go server . channelRegistry . StoreChannel ( channel , includeFlags )
2018-02-03 10:28:02 +01:00
}
// send out changes
if len ( applied ) > 0 {
//TODO(dan): we should change the name of String and make it return a slice here
args := append ( [ ] string { channel . name } , strings . Split ( applied . String ( ) , " " ) ... )
for _ , member := range channel . Members ( ) {
2018-02-05 15:21:08 +01:00
if member == client {
rb . Add ( nil , client . nickMaskString , "MODE" , args ... )
} else {
member . Send ( nil , client . nickMaskString , "MODE" , args ... )
}
2018-02-03 10:28:02 +01:00
}
} else {
args := append ( [ ] string { client . nick , channel . name } , channel . modeStrings ( client ) ... )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . nickMaskString , RPL_CHANNELMODEIS , args ... )
rb . Add ( nil , client . nickMaskString , RPL_CHANNELCREATED , client . nick , channel . name , strconv . FormatInt ( channel . createdTime . Unix ( ) , 10 ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// MODE <client> [<modestring> [<mode arguments>...]]
2018-02-05 15:21:08 +01:00
func umodeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname , err := CasefoldName ( msg . Params [ 0 ] )
target := server . clients . Get ( nickname )
if err != nil || target == nil {
if len ( msg . Params [ 0 ] ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , msg . Params [ 0 ] , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
targetNick := target . Nick ( )
hasPrivs := client == target || msg . Command == "SAMODE"
if ! hasPrivs {
if len ( msg . Params ) > 1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_USERSDONTMATCH , client . nick , client . t ( "Can't change modes for other users" ) )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_USERSDONTMATCH , client . nick , client . t ( "Can't view modes for other users" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// applied mode changes
2018-02-03 11:21:32 +01:00
applied := make ( modes . ModeChanges , 0 )
2018-02-03 10:28:02 +01:00
if 1 < len ( msg . Params ) {
// parse out real mode changes
params := msg . Params [ 1 : ]
2018-02-03 11:21:32 +01:00
changes , unknown := modes . ParseUserModeChanges ( params ... )
2018-02-03 10:28:02 +01:00
// alert for unknown mode changes
for char := range unknown {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNMODE , client . nick , string ( char ) , client . t ( "is an unknown mode character to me" ) )
2018-02-03 10:28:02 +01:00
}
if len ( unknown ) == 1 && len ( changes ) == 0 {
return false
}
// apply mode changes
2018-04-23 00:47:10 +02:00
applied = ApplyUserModeChanges ( client , changes , msg . Command == "SAMODE" )
2018-02-03 10:28:02 +01:00
}
if len ( applied ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . nickMaskString , "MODE" , targetNick , applied . String ( ) )
2018-02-03 10:28:02 +01:00
} else if hasPrivs {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , target . nickMaskString , RPL_UMODEIS , targetNick , target . ModeString ( ) )
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . LocalOperator ) || client . HasMode ( modes . Operator ) {
2018-02-03 10:28:02 +01:00
masks := server . snomasks . String ( client )
if 0 < len ( masks ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , target . nickMaskString , RPL_SNOMASKIS , targetNick , masks , client . t ( "Server notice masks" ) )
2018-02-03 10:28:02 +01:00
}
}
}
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR <subcmd> [params...]
2018-02-05 15:21:08 +01:00
func monitorHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-04-12 02:11:45 +02:00
handler , exists := monitorSubcommands [ strings . ToLower ( msg . Params [ 0 ] ) ]
2018-02-03 10:28:02 +01:00
if ! exists {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , "MONITOR" , msg . Params [ 0 ] , client . t ( "Unknown subcommand" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-05 15:21:08 +01:00
return handler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-03 20:48:44 +01:00
// MONITOR - <target>{,<target>}
2018-02-05 15:21:08 +01:00
func monitorRemoveHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) < 2 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
targets := strings . Split ( msg . Params [ 1 ] , "," )
for _ , target := range targets {
cfnick , err := CasefoldName ( target )
if err != nil {
continue
}
server . monitorManager . Remove ( client , cfnick )
}
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR + <target>{,<target>}
2018-02-05 15:21:08 +01:00
func monitorAddHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) < 2 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
var online [ ] string
var offline [ ] string
2018-07-16 09:46:40 +02:00
limits := server . Limits ( )
2018-02-03 10:28:02 +01:00
targets := strings . Split ( msg . Params [ 1 ] , "," )
for _ , target := range targets {
// check name length
2018-07-16 09:46:40 +02:00
if len ( target ) < 1 || len ( targets ) > limits . NickLen {
2018-02-03 10:28:02 +01:00
continue
}
// add target
casefoldedTarget , err := CasefoldName ( target )
if err != nil {
continue
}
2018-07-16 09:46:40 +02:00
err = server . monitorManager . Add ( client , casefoldedTarget , limits . MonitorEntries )
2018-02-03 13:03:36 +01:00
if err == errMonitorLimitExceeded {
2018-07-16 09:46:40 +02:00
rb . Add ( nil , server . name , ERR_MONLISTFULL , client . Nick ( ) , strconv . Itoa ( limits . MonitorEntries ) , strings . Join ( targets , "," ) )
2018-02-03 10:28:02 +01:00
break
} else if err != nil {
continue
}
// add to online / offline lists
if targetClient := server . clients . Get ( casefoldedTarget ) ; targetClient == nil {
offline = append ( offline , target )
} else {
online = append ( online , targetClient . Nick ( ) )
}
}
if len ( online ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_MONONLINE , client . Nick ( ) , strings . Join ( online , "," ) )
2018-02-03 10:28:02 +01:00
}
if len ( offline ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_MONOFFLINE , client . Nick ( ) , strings . Join ( offline , "," ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR C
2018-02-05 15:21:08 +01:00
func monitorClearHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
server . monitorManager . RemoveAll ( client )
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR L
2018-02-05 15:21:08 +01:00
func monitorListHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
monitorList := server . monitorManager . List ( client )
var nickList [ ] string
for _ , cfnick := range monitorList {
replynick := cfnick
// report the uncasefolded nick if it's available, i.e., the client is online
if mclient := server . clients . Get ( cfnick ) ; mclient != nil {
replynick = mclient . Nick ( )
}
nickList = append ( nickList , replynick )
}
for _ , line := range utils . ArgsToStrings ( maxLastArgLength , nickList , "," ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_MONLIST , client . Nick ( ) , line )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ENDOFMONLIST , "End of MONITOR list" )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR S
2018-02-05 15:21:08 +01:00
func monitorStatusHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var online [ ] string
var offline [ ] string
monitorList := server . monitorManager . List ( client )
for _ , name := range monitorList {
target := server . clients . Get ( name )
if target == nil {
offline = append ( offline , name )
} else {
online = append ( online , target . Nick ( ) )
}
}
if len ( online ) > 0 {
for _ , line := range utils . ArgsToStrings ( maxLastArgLength , online , "," ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_MONONLINE , client . Nick ( ) , line )
2018-02-03 10:28:02 +01:00
}
}
if len ( offline ) > 0 {
for _ , line := range utils . ArgsToStrings ( maxLastArgLength , offline , "," ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_MONOFFLINE , client . Nick ( ) , line )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2018-02-03 20:48:44 +01:00
// MOTD
2018-02-05 15:21:08 +01:00
func motdHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
server . MOTD ( client , rb )
return false
}
2018-02-25 11:02:42 +01:00
// NAMES [<channel>{,<channel>} [target]]
2018-02-05 15:21:08 +01:00
func namesHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
var channels [ ] string
if len ( msg . Params ) > 0 {
channels = strings . Split ( msg . Params [ 0 ] , "," )
}
2018-02-25 11:02:42 +01:00
// TODO: in a post-federation world, process `target` (server to forward request to)
2018-02-05 15:21:08 +01:00
if len ( channels ) == 0 {
for _ , channel := range server . channels . Channels ( ) {
channel . Names ( client , rb )
}
return false
}
for _ , chname := range channels {
2018-02-25 11:02:42 +01:00
channel := server . channels . Get ( chname )
if channel != nil {
channel . Names ( client , rb )
} else if chname != "" {
rb . Add ( nil , server . name , RPL_ENDOFNAMES , client . Nick ( ) , chname , client . t ( "End of NAMES list" ) )
2018-02-05 15:21:08 +01:00
}
}
2018-02-03 10:28:02 +01:00
return false
}
// NICK <nickname>
2018-02-05 15:21:08 +01:00
func nickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-27 03:44:03 +01:00
if client . Registered ( ) {
performNickChange ( server , client , client , msg . Params [ 0 ] , rb )
} else {
2019-01-02 05:45:47 +01:00
client . preregNick = msg . Params [ 0 ]
2018-02-03 10:28:02 +01:00
}
2018-02-27 03:44:03 +01:00
return false
2018-02-03 10:28:02 +01:00
}
// NOTICE <target>{,<target>} <message>
2018-02-05 15:21:08 +01:00
func noticeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 12:15:07 +01:00
clientOnlyTags := utils . GetClientOnlyTags ( msg . Tags )
2018-02-03 10:28:02 +01:00
targets := strings . Split ( msg . Params [ 0 ] , "," )
message := msg . Params [ 1 ]
// split privmsg
2018-11-26 11:23:27 +01:00
splitMsg := utils . MakeSplitMessage ( message , ! client . capabilities . Has ( caps . MaxLine ) )
2018-02-03 10:28:02 +01:00
for i , targetString := range targets {
// max of four targets per privmsg
if i > maxTargets - 1 {
break
}
2018-02-03 11:21:32 +01:00
prefixes , targetString := modes . SplitChannelMembershipPrefixes ( targetString )
lowestPrefix := modes . GetLowestChannelModePrefix ( prefixes )
2018-02-03 10:28:02 +01:00
target , cerr := CasefoldChannel ( targetString )
if cerr == nil {
channel := server . channels . Get ( target )
if channel == nil {
// errors silently ignored with NOTICE as per RFC
continue
}
if ! channel . CanSpeak ( client ) {
// errors silently ignored with NOTICE as per RFC
continue
}
msgid := server . generateMessageID ( )
2018-02-05 15:21:08 +01:00
channel . SplitNotice ( msgid , lowestPrefix , clientOnlyTags , client , splitMsg , rb )
2018-02-03 10:28:02 +01:00
} else {
target , err := CasefoldName ( targetString )
if err != nil {
continue
}
2018-04-19 08:48:19 +02:00
// NOTICEs sent to services are ignored
if _ , isService := OragonoServices [ target ] ; isService {
2018-02-03 10:28:02 +01:00
continue
}
user := server . clients . Get ( target )
if user == nil {
// errors silently ignored with NOTICE as per RFC
continue
}
if ! user . capabilities . Has ( caps . MessageTags ) {
clientOnlyTags = nil
}
msgid := server . generateMessageID ( )
// restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine
2018-04-23 00:47:10 +02:00
if ! user . HasMode ( modes . RegisteredOnly ) || client . LoggedIntoAccount ( ) {
2018-02-10 23:57:15 +01:00
user . SendSplitMsgFromClient ( msgid , client , clientOnlyTags , "NOTICE" , user . nick , splitMsg )
2018-02-03 10:28:02 +01:00
}
2018-12-28 19:45:55 +01:00
nickMaskString := client . NickMaskString ( )
accountName := client . AccountName ( )
2018-02-03 10:28:02 +01:00
if client . capabilities . Has ( caps . EchoMessage ) {
2018-12-28 19:45:55 +01:00
rb . AddSplitMessageFromClient ( msgid , nickMaskString , accountName , clientOnlyTags , "NOTICE" , user . nick , splitMsg )
2018-02-03 10:28:02 +01:00
}
2018-11-26 11:23:27 +01:00
user . history . Add ( history . Item {
Type : history . Notice ,
Msgid : msgid ,
Message : splitMsg ,
2018-12-28 19:45:55 +01:00
Nick : nickMaskString ,
AccountName : accountName ,
2018-11-26 11:23:27 +01:00
} )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2018-02-03 20:48:44 +01:00
// NPC <target> <sourcenick> <message>
2018-02-05 15:21:08 +01:00
func npcHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
fakeSource := msg . Params [ 1 ]
message := msg . Params [ 2 ]
_ , err := CasefoldName ( fakeSource )
if err != nil {
client . Send ( nil , client . server . name , ERR_CANNOTSENDRP , target , client . t ( "Fake source must be a valid nickname" ) )
return false
}
sourceString := fmt . Sprintf ( npcNickMask , fakeSource , client . nick )
2018-02-05 15:21:08 +01:00
sendRoleplayMessage ( server , client , sourceString , target , false , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// NPCA <target> <sourcenick> <message>
2018-02-05 15:21:08 +01:00
func npcaHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
fakeSource := msg . Params [ 1 ]
message := msg . Params [ 2 ]
sourceString := fmt . Sprintf ( npcNickMask , fakeSource , client . nick )
_ , err := CasefoldName ( fakeSource )
if err != nil {
client . Send ( nil , client . server . name , ERR_CANNOTSENDRP , target , client . t ( "Fake source must be a valid nickname" ) )
return false
}
2018-02-05 15:21:08 +01:00
sendRoleplayMessage ( server , client , sourceString , target , true , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
// OPER <name> <password>
2018-02-05 15:21:08 +01:00
func operHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . Operator ) == true {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , "OPER" , client . t ( "You're already opered-up!" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-04-19 08:48:19 +02:00
authorized := false
oper := server . GetOperator ( msg . Params [ 0 ] )
if oper != nil {
password := [ ] byte ( msg . Params [ 1 ] )
authorized = ( bcrypt . CompareHashAndPassword ( oper . Pass , password ) == nil )
}
if ! authorized {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_PASSWDMISMATCH , client . nick , client . t ( "Password incorrect" ) )
2018-02-03 10:28:02 +01:00
return true
}
2018-04-19 08:48:19 +02:00
oldNickmask := client . NickMaskString ( )
client . SetOper ( oper )
if client . NickMaskString ( ) != oldNickmask {
client . sendChghost ( oldNickmask , oper . Vhost )
2018-02-03 10:28:02 +01:00
}
2018-04-23 00:47:10 +02:00
// set new modes: modes.Operator, plus anything specified in the config
modeChanges := make ( [ ] modes . ModeChange , len ( oper . Modes ) + 1 )
modeChanges [ 0 ] = modes . ModeChange {
Mode : modes . Operator ,
Op : modes . Add ,
2018-02-03 10:28:02 +01:00
}
2018-04-23 00:47:10 +02:00
copy ( modeChanges [ 1 : ] , oper . Modes )
applied := ApplyUserModeChanges ( client , modeChanges , true )
2018-02-03 10:28:02 +01:00
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_YOUREOPER , client . nick , client . t ( "You are now an IRC operator" ) )
rb . Add ( nil , server . name , "MODE" , client . nick , applied . String ( ) )
2018-02-03 10:28:02 +01:00
2018-04-19 08:48:19 +02:00
server . snomasks . Send ( sno . LocalOpers , fmt . Sprintf ( ircfmt . Unescape ( "Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]" ) , client . nickMaskString , oper . Name ) )
2018-03-22 16:04:21 +01:00
// client may now be unthrottled by the fakelag system
client . resetFakelag ( )
2018-02-03 10:28:02 +01:00
return false
}
// PART <channel>{,<channel>} [<reason>]
2018-02-05 15:21:08 +01:00
func partHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
var reason string //TODO(dan): if this isn't supplied here, make sure the param doesn't exist in the PART message sent to other users
if len ( msg . Params ) > 1 {
reason = msg . Params [ 1 ]
}
for _ , chname := range channels {
2018-02-05 15:21:08 +01:00
err := server . channels . Part ( client , chname , reason , rb )
2018-02-03 13:03:36 +01:00
if err == errNoSuchChannel {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}
// PASS <password>
2018-02-05 15:21:08 +01:00
func passHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-27 03:44:03 +01:00
if client . Registered ( ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_ALREADYREGISTRED , client . nick , client . t ( "You may not reregister" ) )
2018-02-03 10:28:02 +01:00
return false
}
// if no password exists, skip checking
2018-07-16 09:46:40 +02:00
serverPassword := server . Password ( )
if serverPassword == nil {
2018-02-27 03:44:03 +01:00
client . SetAuthorized ( true )
2018-02-03 10:28:02 +01:00
return false
}
// check the provided password
password := [ ] byte ( msg . Params [ 0 ] )
2018-08-06 04:51:39 +02:00
if bcrypt . CompareHashAndPassword ( serverPassword , password ) != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_PASSWDMISMATCH , client . nick , client . t ( "Password incorrect" ) )
rb . Add ( nil , server . name , "ERROR" , client . t ( "Password incorrect" ) )
2018-02-03 10:28:02 +01:00
return true
}
2018-02-27 03:44:03 +01:00
client . SetAuthorized ( true )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// PING [params...]
2018-02-05 15:21:08 +01:00
func pingHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
rb . Add ( nil , server . name , "PONG" , msg . Params ... )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// PONG [params...]
2018-02-05 15:21:08 +01:00
func pongHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// client gets touched when they send this command, so we don't need to do anything
return false
}
// PRIVMSG <target>{,<target>} <message>
2018-02-05 15:21:08 +01:00
func privmsgHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 12:15:07 +01:00
clientOnlyTags := utils . GetClientOnlyTags ( msg . Tags )
2018-02-03 10:28:02 +01:00
targets := strings . Split ( msg . Params [ 0 ] , "," )
message := msg . Params [ 1 ]
// split privmsg
2018-11-26 11:23:27 +01:00
splitMsg := utils . MakeSplitMessage ( message , ! client . capabilities . Has ( caps . MaxLine ) )
2018-02-03 10:28:02 +01:00
for i , targetString := range targets {
// max of four targets per privmsg
if i > maxTargets - 1 {
break
}
2018-02-03 11:21:32 +01:00
prefixes , targetString := modes . SplitChannelMembershipPrefixes ( targetString )
lowestPrefix := modes . GetLowestChannelModePrefix ( prefixes )
2018-02-03 10:28:02 +01:00
// eh, no need to notify them
if len ( targetString ) < 1 {
continue
}
target , err := CasefoldChannel ( targetString )
if err == nil {
channel := server . channels . Get ( target )
if channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , targetString , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
if ! channel . CanSpeak ( client ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_CANNOTSENDTOCHAN , channel . name , client . t ( "Cannot send to channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
msgid := server . generateMessageID ( )
2018-02-05 15:21:08 +01:00
channel . SplitPrivMsg ( msgid , lowestPrefix , clientOnlyTags , client , splitMsg , rb )
2018-02-03 10:28:02 +01:00
} else {
target , err = CasefoldName ( targetString )
2018-04-19 08:48:19 +02:00
if service , isService := OragonoServices [ target ] ; isService {
servicePrivmsgHandler ( service , server , client , message , rb )
2018-02-03 10:28:02 +01:00
continue
}
user := server . clients . Get ( target )
if err != nil || user == nil {
if len ( target ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHNICK , client . nick , target , "No such nick" )
}
continue
}
if ! user . capabilities . Has ( caps . MessageTags ) {
clientOnlyTags = nil
}
msgid := server . generateMessageID ( )
// restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine
2018-04-23 00:47:10 +02:00
if ! user . HasMode ( modes . RegisteredOnly ) || client . LoggedIntoAccount ( ) {
2018-02-10 23:57:15 +01:00
user . SendSplitMsgFromClient ( msgid , client , clientOnlyTags , "PRIVMSG" , user . nick , splitMsg )
2018-02-03 10:28:02 +01:00
}
2018-12-28 19:45:55 +01:00
nickMaskString := client . NickMaskString ( )
accountName := client . AccountName ( )
2018-02-03 10:28:02 +01:00
if client . capabilities . Has ( caps . EchoMessage ) {
2018-12-28 19:45:55 +01:00
rb . AddSplitMessageFromClient ( msgid , nickMaskString , accountName , clientOnlyTags , "PRIVMSG" , user . nick , splitMsg )
2018-02-03 10:28:02 +01:00
}
2018-04-23 00:47:10 +02:00
if user . HasMode ( modes . Away ) {
2018-02-03 10:28:02 +01:00
//TODO(dan): possibly implement cooldown of away notifications to users
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_AWAY , user . nick , user . awayMessage )
2018-02-03 10:28:02 +01:00
}
2018-11-26 11:23:27 +01:00
user . history . Add ( history . Item {
Type : history . Privmsg ,
Msgid : msgid ,
Message : splitMsg ,
2018-12-28 19:45:55 +01:00
Nick : nickMaskString ,
AccountName : accountName ,
2018-11-26 11:23:27 +01:00
} )
2018-02-03 10:28:02 +01:00
}
}
return false
}
// QUIT [<reason>]
2018-02-05 15:21:08 +01:00
func quitHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
reason := "Quit"
if len ( msg . Params ) > 0 {
reason += ": " + msg . Params [ 0 ]
}
client . Quit ( reason )
return true
}
// REHASH
2018-02-05 15:21:08 +01:00
func rehashHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-02-03 03:12:17 +01:00
server . logger . Info ( "server" , fmt . Sprintf ( "REHASH command used by %s" , client . nick ) )
2018-02-03 10:28:02 +01:00
err := server . rehash ( )
if err == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_REHASHING , client . nick , "ircd.yaml" , client . t ( "Rehashing" ) )
2018-02-03 10:28:02 +01:00
} else {
2019-02-03 03:12:17 +01:00
server . logger . Error ( "server" , fmt . Sprintln ( "Failed to rehash:" , err . Error ( ) ) )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , "REHASH" , err . Error ( ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// RENAME <oldchan> <newchan> [<reason>]
2018-02-05 15:21:08 +01:00
func renameHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) ( result bool ) {
2018-02-03 10:28:02 +01:00
result = false
errorResponse := func ( err error , name string ) {
// TODO: send correct error codes, e.g., ERR_CANNOTRENAME, ERR_CHANNAMEINUSE
var code string
switch err {
2018-02-03 13:03:36 +01:00
case errNoSuchChannel :
2018-02-03 10:28:02 +01:00
code = ERR_NOSUCHCHANNEL
2018-02-03 13:03:36 +01:00
case errRenamePrivsNeeded :
2018-02-03 10:28:02 +01:00
code = ERR_CHANOPRIVSNEEDED
2018-02-03 13:03:36 +01:00
case errInvalidChannelName :
2018-02-03 10:28:02 +01:00
code = ERR_UNKNOWNERROR
2018-02-03 13:03:36 +01:00
case errChannelNameInUse :
2018-02-03 10:28:02 +01:00
code = ERR_UNKNOWNERROR
default :
code = ERR_UNKNOWNERROR
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , code , client . Nick ( ) , "RENAME" , name , err . Error ( ) )
2018-02-03 10:28:02 +01:00
}
oldName := strings . TrimSpace ( msg . Params [ 0 ] )
newName := strings . TrimSpace ( msg . Params [ 1 ] )
if oldName == "" || newName == "" {
2018-02-03 13:03:36 +01:00
errorResponse ( errInvalidChannelName , "<empty>" )
2018-02-03 10:28:02 +01:00
return
}
casefoldedOldName , err := CasefoldChannel ( oldName )
if err != nil {
2018-02-03 13:03:36 +01:00
errorResponse ( errInvalidChannelName , oldName )
2018-02-03 10:28:02 +01:00
return
}
reason := "No reason"
if 2 < len ( msg . Params ) {
reason = msg . Params [ 2 ]
}
channel := server . channels . Get ( oldName )
if channel == nil {
2018-02-03 13:03:36 +01:00
errorResponse ( errNoSuchChannel , oldName )
2018-02-03 10:28:02 +01:00
return
}
//TODO(dan): allow IRCops to do this?
2018-02-03 11:21:32 +01:00
if ! channel . ClientIsAtLeast ( client , modes . Operator ) {
2018-02-03 13:03:36 +01:00
errorResponse ( errRenamePrivsNeeded , oldName )
2018-02-03 10:28:02 +01:00
return
}
founder := channel . Founder ( )
2018-02-11 11:30:40 +01:00
if founder != "" && founder != client . Account ( ) {
2018-02-03 10:28:02 +01:00
//TODO(dan): Change this to ERR_CANNOTRENAME
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , "RENAME" , oldName , client . t ( "Only channel founders can change registered channels" ) )
2018-02-03 10:28:02 +01:00
return false
}
// perform the channel rename
err = server . channels . Rename ( oldName , newName )
if err != nil {
errorResponse ( err , newName )
return
}
// rename succeeded, persist it
go server . channelRegistry . Rename ( channel , casefoldedOldName )
// send RENAME messages
for _ , mcl := range channel . Members ( ) {
if mcl . capabilities . Has ( caps . Rename ) {
mcl . Send ( nil , client . nickMaskString , "RENAME" , oldName , newName , reason )
} else {
mcl . Send ( nil , mcl . nickMaskString , "PART" , oldName , fmt . Sprintf ( mcl . t ( "Channel renamed: %s" ) , reason ) )
if mcl . capabilities . Has ( caps . ExtendedJoin ) {
2018-02-11 11:30:40 +01:00
mcl . Send ( nil , mcl . nickMaskString , "JOIN" , newName , mcl . AccountName ( ) , mcl . realname )
2018-02-03 10:28:02 +01:00
} else {
mcl . Send ( nil , mcl . nickMaskString , "JOIN" , newName )
}
}
}
return false
}
2018-11-26 11:23:27 +01:00
// RESUME <oldnick> <token> [timestamp]
2018-02-05 15:21:08 +01:00
func resumeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
oldnick := msg . Params [ 0 ]
2018-11-26 11:23:27 +01:00
token := msg . Params [ 1 ]
2018-02-03 10:28:02 +01:00
if client . Registered ( ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_CANNOT_RESUME , oldnick , client . t ( "Cannot resume connection, connection registration has already been completed" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-11-26 11:23:27 +01:00
var timestamp time . Time
if 2 < len ( msg . Params ) {
ts , err := time . Parse ( IRCv3TimestampFormat , msg . Params [ 2 ] )
2018-02-03 10:28:02 +01:00
if err == nil {
2018-11-26 11:23:27 +01:00
timestamp = ts
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_CANNOT_RESUME , oldnick , client . t ( "Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it" ) )
2018-02-03 10:28:02 +01:00
}
}
client . resumeDetails = & ResumeDetails {
2018-11-26 11:23:27 +01:00
OldNick : oldnick ,
Timestamp : timestamp ,
PresentedToken : token ,
2018-02-03 10:28:02 +01:00
}
return false
}
// SANICK <oldnick> <nickname>
2018-02-05 15:21:08 +01:00
func sanickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
targetNick := strings . TrimSpace ( msg . Params [ 0 ] )
target := server . clients . Get ( targetNick )
if target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , msg . Params [ 0 ] , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-27 03:44:03 +01:00
performNickChange ( server , client , target , msg . Params [ 1 ] , rb )
return false
2018-02-03 10:28:02 +01:00
}
2018-02-03 20:48:44 +01:00
// SCENE <target> <message>
2018-02-05 15:21:08 +01:00
func sceneHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
message := msg . Params [ 1 ]
sourceString := fmt . Sprintf ( sceneNickMask , client . nick )
2018-02-05 15:21:08 +01:00
sendRoleplayMessage ( server , client , sourceString , target , false , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
// TAGMSG <target>{,<target>}
2018-02-05 15:21:08 +01:00
func tagmsgHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 12:15:07 +01:00
clientOnlyTags := utils . GetClientOnlyTags ( msg . Tags )
2018-02-03 10:28:02 +01:00
// no client-only tags, so we can drop it
if clientOnlyTags == nil {
return false
}
targets := strings . Split ( msg . Params [ 0 ] , "," )
for i , targetString := range targets {
// max of four targets per privmsg
if i > maxTargets - 1 {
break
}
2018-02-03 11:21:32 +01:00
prefixes , targetString := modes . SplitChannelMembershipPrefixes ( targetString )
lowestPrefix := modes . GetLowestChannelModePrefix ( prefixes )
2018-02-03 10:28:02 +01:00
// eh, no need to notify them
if len ( targetString ) < 1 {
continue
}
target , err := CasefoldChannel ( targetString )
if err == nil {
channel := server . channels . Get ( target )
if channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , targetString , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
if ! channel . CanSpeak ( client ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_CANNOTSENDTOCHAN , channel . name , client . t ( "Cannot send to channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
msgid := server . generateMessageID ( )
2018-02-05 15:21:08 +01:00
channel . TagMsg ( msgid , lowestPrefix , clientOnlyTags , client , rb )
2018-02-03 10:28:02 +01:00
} else {
target , err = CasefoldName ( targetString )
user := server . clients . Get ( target )
if err != nil || user == nil {
if len ( target ) > 0 {
client . Send ( nil , server . name , ERR_NOSUCHNICK , client . nick , target , client . t ( "No such nick" ) )
}
continue
}
msgid := server . generateMessageID ( )
// end user can't receive tagmsgs
if ! user . capabilities . Has ( caps . MessageTags ) {
continue
}
2018-02-10 23:57:15 +01:00
user . SendFromClient ( msgid , client , clientOnlyTags , "TAGMSG" , user . nick )
2018-02-03 10:28:02 +01:00
if client . capabilities . Has ( caps . EchoMessage ) {
2018-12-28 19:45:55 +01:00
rb . AddFromClient ( msgid , client . NickMaskString ( ) , client . AccountName ( ) , clientOnlyTags , "TAGMSG" , user . nick )
2018-02-03 10:28:02 +01:00
}
2018-04-23 00:47:10 +02:00
if user . HasMode ( modes . Away ) {
2018-02-03 10:28:02 +01:00
//TODO(dan): possibly implement cooldown of away notifications to users
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_AWAY , user . nick , user . awayMessage )
2018-02-03 10:28:02 +01:00
}
}
}
return false
}
2018-02-03 20:48:44 +01:00
// TIME
2018-02-05 15:21:08 +01:00
func timeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
rb . Add ( nil , server . name , RPL_TIME , client . nick , server . name , time . Now ( ) . Format ( time . RFC1123 ) )
2018-02-03 10:28:02 +01:00
return false
}
// TOPIC <channel> [<topic>]
2018-02-05 15:21:08 +01:00
func topicHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
name , err := CasefoldChannel ( msg . Params [ 0 ] )
channel := server . channels . Get ( name )
if err != nil || channel == nil {
if len ( msg . Params [ 0 ] ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , msg . Params [ 0 ] , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
if len ( msg . Params ) > 1 {
2018-02-05 15:21:08 +01:00
channel . SetTopic ( client , msg . Params [ 1 ] , rb )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
channel . SendTopic ( client , rb )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// UNDLINE <ip>|<net>
2018-02-05 15:21:08 +01:00
func unDLineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_unban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get host
hostString := msg . Params [ 0 ]
// check host
2019-01-22 11:01:01 +01:00
hostNet , err := utils . NormalizedNetFromString ( hostString )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Could not parse IP address or CIDR network" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-01-22 11:01:01 +01:00
err = server . dlines . RemoveNetwork ( hostNet )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , fmt . Sprintf ( client . t ( "Could not remove ban [%s]" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-01-22 11:01:01 +01:00
hostString = utils . NetToNormalizedString ( hostNet )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Removed D-Line for %s" ) , hostString ) )
2018-02-03 10:28:02 +01:00
server . snomasks . Send ( sno . LocalXline , fmt . Sprintf ( ircfmt . Unescape ( "%s$r removed D-Line for %s" ) , client . nick , hostString ) )
return false
}
2018-02-03 20:48:44 +01:00
// UNKLINE <mask>
2018-02-05 15:21:08 +01:00
func unKLineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_unban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get host
mask := msg . Params [ 0 ]
if ! strings . Contains ( mask , "!" ) && ! strings . Contains ( mask , "@" ) {
mask = mask + "!*@*"
} else if ! strings . Contains ( mask , "@" ) {
mask = mask + "@*"
}
2019-01-22 11:01:01 +01:00
err := server . klines . RemoveMask ( mask )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , fmt . Sprintf ( client . t ( "Could not remove ban [%s]" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Removed K-Line for %s" ) , mask ) )
2018-02-03 10:28:02 +01:00
server . snomasks . Send ( sno . LocalXline , fmt . Sprintf ( ircfmt . Unescape ( "%s$r removed K-Line for %s" ) , client . nick , mask ) )
return false
}
// USER <username> * 0 <realname>
2018-02-05 15:21:08 +01:00
func userHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-27 03:44:03 +01:00
if client . Registered ( ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_ALREADYREGISTRED , client . nick , client . t ( "You may not reregister" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-11-26 11:23:27 +01:00
err := client . SetNames ( msg . Params [ 0 ] , msg . Params [ 3 ] )
if err == errInvalidUsername {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , "" , "ERROR" , client . t ( "Malformed username" ) )
2018-02-03 10:28:02 +01:00
return true
}
return false
}
2018-02-03 20:48:44 +01:00
// USERHOST <nickname>{ <nickname>}
2018-02-05 15:21:08 +01:00
func userhostHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
returnedNicks := make ( map [ string ] bool )
for i , nickname := range msg . Params {
if i >= 10 {
break
}
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
if returnedNicks [ casefoldedNickname ] {
continue
}
// to prevent returning multiple results for a single nick
returnedNicks [ casefoldedNickname ] = true
var isOper , isAway string
2018-04-23 00:47:10 +02:00
if target . HasMode ( modes . Operator ) {
2018-02-03 10:28:02 +01:00
isOper = "*"
}
2018-04-23 00:47:10 +02:00
if target . HasMode ( modes . Away ) {
2018-02-03 10:28:02 +01:00
isAway = "-"
} else {
isAway = "+"
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , RPL_USERHOST , client . nick , fmt . Sprintf ( "%s%s=%s%s@%s" , target . nick , isOper , isAway , target . username , target . hostname ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// VERSION
2018-02-05 15:21:08 +01:00
func versionHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
rb . Add ( nil , server . name , RPL_VERSION , client . nick , Ver , server . name )
client . RplISupport ( rb )
2018-02-03 10:28:02 +01:00
return false
}
// WEBIRC <password> <gateway> <hostname> <ip> [:flag1 flag2=x flag3]
2018-02-05 15:21:08 +01:00
func webircHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// only allow unregistered clients to use this command
2018-02-27 03:44:03 +01:00
if client . Registered ( ) || client . proxiedIP != nil {
2018-02-03 10:28:02 +01:00
return false
}
// process flags
var secure bool
if 4 < len ( msg . Params ) {
for _ , x := range strings . Split ( msg . Params [ 4 ] , " " ) {
// split into key=value
var key string
if strings . Contains ( x , "=" ) {
y := strings . SplitN ( x , "=" , 2 )
key , _ = y [ 0 ] , y [ 1 ]
} else {
key = x
}
lkey := strings . ToLower ( key )
if lkey == "tls" || lkey == "secure" {
// only accept "tls" flag if the gateway's connection to us is secure as well
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . TLS ) || utils . AddrIsLocal ( client . socket . conn . RemoteAddr ( ) ) {
2018-02-03 10:28:02 +01:00
secure = true
}
}
}
}
for _ , info := range server . WebIRCConfig ( ) {
for _ , gateway := range info . Hosts {
if isGatewayAllowed ( client . socket . conn . RemoteAddr ( ) , gateway ) {
// confirm password and/or fingerprint
givenPassword := msg . Params [ 0 ]
2018-08-06 04:51:39 +02:00
if 0 < len ( info . Password ) && bcrypt . CompareHashAndPassword ( info . Password , [ ] byte ( givenPassword ) ) != nil {
2018-02-03 10:28:02 +01:00
continue
}
if 0 < len ( info . Fingerprint ) && client . certfp != info . Fingerprint {
continue
}
proxiedIP := msg . Params [ 3 ]
2018-08-28 20:20:33 +02:00
// see #211; websocket gateways will wrap ipv6 addresses in square brackets
// because IRC parameters can't start with :
if strings . HasPrefix ( proxiedIP , "[" ) && strings . HasSuffix ( proxiedIP , "]" ) {
proxiedIP = proxiedIP [ 1 : len ( proxiedIP ) - 1 ]
}
2018-09-03 06:19:10 +02:00
return ! client . ApplyProxiedIP ( proxiedIP , secure )
2018-02-03 10:28:02 +01:00
}
}
}
client . Quit ( client . t ( "WEBIRC command is not usable from your address or incorrect password given" ) )
return true
}
2018-02-03 20:48:44 +01:00
// WHO [<mask> [o]]
2018-02-05 15:21:08 +01:00
func whoHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if msg . Params [ 0 ] == "" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , "WHO" , client . t ( "First param must be a mask or channel" ) )
2018-02-03 10:28:02 +01:00
return false
}
var mask string
if len ( msg . Params ) > 0 {
casefoldedMask , err := Casefold ( msg . Params [ 0 ] )
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , "WHO" , client . t ( "Mask isn't valid" ) )
2018-02-03 10:28:02 +01:00
return false
}
mask = casefoldedMask
}
friends := client . Friends ( )
//TODO(dan): is this used and would I put this param in the Modern doc?
// if not, can we remove it?
//var operatorOnly bool
//if len(msg.Params) > 1 && msg.Params[1] == "o" {
// operatorOnly = true
//}
if mask [ 0 ] == '#' {
// TODO implement wildcard matching
//TODO(dan): ^ only for opers
channel := server . channels . Get ( mask )
if channel != nil {
2018-02-05 15:21:08 +01:00
whoChannel ( client , channel , friends , rb )
2018-02-03 10:28:02 +01:00
}
} else {
for mclient := range server . clients . FindAll ( mask ) {
2018-02-05 15:21:08 +01:00
client . rplWhoReply ( nil , mclient , rb )
2018-02-03 10:28:02 +01:00
}
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHO , client . nick , mask , client . t ( "End of WHO list" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// WHOIS [<target>] <mask>{,<mask>}
2018-02-05 15:21:08 +01:00
func whoisHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var masksString string
//var target string
if len ( msg . Params ) > 1 {
//target = msg.Params[0]
masksString = msg . Params [ 1 ]
} else {
masksString = msg . Params [ 0 ]
}
if len ( strings . TrimSpace ( masksString ) ) < 1 {
2018-02-04 12:32:48 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "No masks given" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-12-30 12:45:23 +01:00
handleService := func ( nick string ) bool {
cfnick , _ := CasefoldName ( nick )
service , ok := OragonoServices [ cfnick ]
if ! ok {
return false
}
clientNick := client . Nick ( )
rb . Add ( nil , client . server . name , RPL_WHOISUSER , clientNick , service . Name , service . Name , "localhost" , "*" , fmt . Sprintf ( client . t ( "Network service, for more info /msg %s HELP" ) , service . Name ) )
// hehe
if client . HasMode ( modes . TLS ) {
rb . Add ( nil , client . server . name , RPL_WHOISSECURE , clientNick , service . Name , client . t ( "is using a secure connection" ) )
}
return true
}
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . Operator ) {
2018-12-30 12:45:23 +01:00
for _ , mask := range strings . Split ( masksString , "," ) {
matches := server . clients . FindAll ( mask )
if len ( matches ) == 0 && ! handleService ( mask ) {
2018-02-04 12:32:48 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , mask , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
continue
}
for mclient := range matches {
2018-02-04 12:32:48 +01:00
client . getWhoisOf ( mclient , rb )
2018-02-03 10:28:02 +01:00
}
}
} else {
2018-12-30 12:45:23 +01:00
// only get the first request; also require a nick, not a mask
nick := strings . Split ( masksString , "," ) [ 0 ]
mclient := server . clients . Get ( nick )
if mclient != nil {
2018-02-04 12:32:48 +01:00
client . getWhoisOf ( mclient , rb )
2018-12-30 12:45:23 +01:00
} else if ! handleService ( nick ) {
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , masksString , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
}
2018-12-30 12:45:23 +01:00
// fall through, ENDOFWHOIS is always sent
2018-02-03 10:28:02 +01:00
}
2018-02-04 12:32:48 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHOIS , client . nick , masksString , client . t ( "End of /WHOIS list" ) )
2018-02-03 10:28:02 +01:00
return false
}
// WHOWAS <nickname> [<count> [<server>]]
2018-02-05 15:21:08 +01:00
func whowasHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nicknames := strings . Split ( msg . Params [ 0 ] , "," )
2018-05-04 06:24:54 +02:00
// 0 means "all the entries", as does a negative number
var count uint64
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 1 {
2018-05-04 06:24:54 +02:00
count , _ = strconv . ParseUint ( msg . Params [ 1 ] , 10 , 64 )
2018-02-03 10:28:02 +01:00
}
//var target string
//if len(msg.Params) > 2 {
// target = msg.Params[2]
//}
2018-05-04 06:24:54 +02:00
cnick := client . Nick ( )
2018-02-03 10:28:02 +01:00
for _ , nickname := range nicknames {
2018-05-04 06:24:54 +02:00
results := server . whoWas . Find ( nickname , int ( count ) )
2018-02-03 10:28:02 +01:00
if len ( results ) == 0 {
if len ( nickname ) > 0 {
2018-05-04 06:24:54 +02:00
rb . Add ( nil , server . name , ERR_WASNOSUCHNICK , cnick , nickname , client . t ( "There was no such nickname" ) )
2018-02-03 10:28:02 +01:00
}
} else {
for _ , whoWas := range results {
2019-01-01 19:00:16 +01:00
rb . Add ( nil , server . name , RPL_WHOWASUSER , cnick , whoWas . nick , whoWas . username , whoWas . hostname , "*" , whoWas . realname )
2018-02-03 10:28:02 +01:00
}
}
if len ( nickname ) > 0 {
2018-05-04 06:24:54 +02:00
rb . Add ( nil , server . name , RPL_ENDOFWHOWAS , cnick , nickname , client . t ( "End of WHOWAS" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}