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"
"encoding/json"
"fmt"
"net"
"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-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/passwd"
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
"github.com/tidwall/buntdb"
)
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 {
2018-02-03 10:28:02 +01:00
// clients can't reg new accounts if they're already logged in
if client . LoggedIntoAccount ( ) {
2018-02-11 11:30:40 +01:00
if server . AccountConfig ( ) . Registration . AllowMultiplePerConnection {
server . accounts . Logout ( client )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_REG_UNSPECIFIED_ERROR , client . nick , "*" , client . t ( "You're already logged into an account" ) )
2018-02-03 10:28:02 +01:00
return false
}
}
// 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 ] == "*" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_REG_UNSPECIFIED_ERROR , client . 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 {
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
}
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 == "" {
rb . Add ( nil , server . name , ERR_REG_INVALID_CALLBACK , client . 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 == "" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_REG_INVALID_CRED_TYPE , client . nick , credentialType , callbackNamespace , client . t ( "You are not using a TLS certificate" ) )
2018-02-03 10:28:02 +01:00
return false
}
if ! credentialValid {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_REG_INVALID_CRED_TYPE , client . 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
}
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
}
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
msg = err . Error ( )
2018-02-03 10:28:02 +01:00
}
2018-02-11 11:30:40 +01:00
rb . Add ( nil , server . name , code , client . 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 )
rb . Add ( nil , server . name , RPL_REG_VERIFICATION_REQUIRED , client . 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 {
rb . Add ( nil , client . server . name , RPL_LOGGEDIN , client . nick , client . nickMaskString , account , fmt . Sprintf ( "You are now logged in as %s" , account ) )
rb . Add ( nil , client . server . name , RPL_SASLSUCCESS , client . nick , client . t ( "SASL 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
if len ( splitValue ) == 3 {
accountKey = string ( splitValue [ 0 ] )
authzid = string ( splitValue [ 1 ] )
if accountKey == "" {
accountKey = authzid
} else if accountKey != authzid {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed: authcid and authzid should be the same" ) )
2018-02-03 10:28:02 +01:00
return false
}
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed: Invalid auth blob" ) )
2018-02-03 10:28:02 +01:00
return false
}
// keep it the same as in the REG CREATE stage
accountKey , err := CasefoldName ( accountKey )
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: Bad account name" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
password := string ( splitValue [ 2 ] )
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 )
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
}
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 {
server . logger . Error ( "internal" , fmt . Sprintf ( "sasl authentication failure: %v" , err ) )
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 ]
}
}
if isAway {
2018-02-03 11:21:32 +01:00
client . flags [ modes . Away ] = true
2018-02-03 10:28:02 +01:00
} else {
2018-02-03 11:21:32 +01:00
delete ( client . flags , modes . Away )
2018-02-03 10:28:02 +01:00
}
client . awayMessage = text
2018-02-03 11:21:32 +01:00
var op modes . ModeOp
if client . flags [ modes . Away ] {
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-02-03 11:21:32 +01:00
if client . flags [ modes . Away ] {
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 ] )
capabilities := caps . NewSet ( )
var capString string
if len ( msg . Params ) > 1 {
capString = msg . Params [ 1 ]
strs := strings . Split ( capString , " " )
for _ , str := range strs {
if len ( str ) > 0 {
capabilities . Enable ( caps . Capability ( str ) )
}
}
}
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
for _ , capability := range capabilities . List ( ) {
if ! SupportedCapabilities . Has ( capability ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "CAP" , client . nick , "NAK" , capString )
2018-02-03 10:28:02 +01:00
return false
}
}
client . capabilities . Enable ( capabilities . List ( ) ... )
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
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
// CHANSERV [...]
2018-02-05 15:21:08 +01:00
func csHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
server . chanservPrivmsgHandler ( client , strings . Join ( msg . Params , " " ) , rb )
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
}
// 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
if ! client . 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 {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Ban - %[1]s - added by %[2]s - %[3]s" ) , key , info . OperName , info . BanMessage ( "%s" ) ) )
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 ] )
durationIsUsed := err == nil
if durationIsUsed {
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
var hostAddr net . IP
var hostNet * net . IPNet
_ , hostNet , err = net . ParseCIDR ( hostString )
if err != nil {
hostAddr = net . ParseIP ( hostString )
}
if hostAddr == nil && hostNet == 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
}
if hostNet == nil {
hostString = hostAddr . String ( )
if ! dlineMyself && hostAddr . Equal ( client . IP ( ) ) {
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 DLINE yourself, you must use the command: /DLINE MYSELF <arguments>" ) )
2018-02-03 10:28:02 +01:00
return false
}
} else {
hostString = hostNet . String ( )
if ! dlineMyself && hostNet . Contains ( client . IP ( ) ) {
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 DLINE yourself, you must use the command: /DLINE 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 comment(s)
reason := "No reason given"
operReason := "No reason given"
if len ( msg . Params ) > currentArg {
tempReason := strings . TrimSpace ( msg . Params [ currentArg ] )
if len ( tempReason ) > 0 && tempReason != "|" {
tempReasons := strings . SplitN ( tempReason , "|" , 2 )
if tempReasons [ 0 ] != "" {
reason = tempReasons [ 0 ]
}
if len ( tempReasons ) > 1 && tempReasons [ 1 ] != "" {
operReason = tempReasons [ 1 ]
} else {
operReason = reason
}
}
}
operName := client . operName
if operName == "" {
operName = server . name
}
// assemble ban info
var banTime * IPRestrictTime
if durationIsUsed {
banTime = & IPRestrictTime {
Duration : duration ,
Expires : time . Now ( ) . Add ( duration ) ,
}
}
info := IPBanInfo {
Reason : reason ,
OperReason : operReason ,
OperName : operName ,
Time : banTime ,
}
// save in datastore
err = server . store . Update ( func ( tx * buntdb . Tx ) error {
dlineKey := fmt . Sprintf ( keyDlineEntry , hostString )
// assemble json from ban info
b , err := json . Marshal ( info )
if err != nil {
return err
}
tx . Set ( dlineKey , string ( b ) , nil )
return nil
} )
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
}
if hostNet == nil {
server . dlines . AddIP ( hostAddr , banTime , reason , operReason , operName )
} else {
server . dlines . AddNetwork ( * hostNet , banTime , reason , operReason , operName )
}
var snoDescription string
if durationIsUsed {
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
var toKill bool
for _ , mcl := range server . clients . AllClients ( ) {
if hostNet == nil {
toKill = hostAddr . Equal ( mcl . IP ( ) )
} else {
toKill = hostNet . Contains ( mcl . IP ( ) )
}
if toKill {
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-02-03 11:21:32 +01:00
if client . flags [ 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-02-03 11:21:32 +01:00
if exists && ( ! helpHandler . oper || ( helpHandler . oper && client . flags [ 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 {
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 , "" )
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-02-05 15:21:08 +01:00
err := server . channels . Join ( client , name , key , 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
}
// 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
if ! client . 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 {
client . Notice ( fmt . Sprintf ( client . t ( "Ban - %s - added by %s - %s" ) , key , info . OperName , info . BanMessage ( "%s" ) ) )
}
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 ] )
durationIsUsed := err == nil
if durationIsUsed {
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
operName := client . operName
if operName == "" {
operName = server . name
}
// get comment(s)
reason := "No reason given"
operReason := "No reason given"
if len ( msg . Params ) > currentArg {
tempReason := strings . TrimSpace ( msg . Params [ currentArg ] )
if len ( tempReason ) > 0 && tempReason != "|" {
tempReasons := strings . SplitN ( tempReason , "|" , 2 )
if tempReasons [ 0 ] != "" {
reason = tempReasons [ 0 ]
}
if len ( tempReasons ) > 1 && tempReasons [ 1 ] != "" {
operReason = tempReasons [ 1 ]
} else {
operReason = reason
}
}
}
// assemble ban info
var banTime * IPRestrictTime
if durationIsUsed {
banTime = & IPRestrictTime {
Duration : duration ,
Expires : time . Now ( ) . Add ( duration ) ,
}
}
info := IPBanInfo {
Reason : reason ,
OperReason : operReason ,
OperName : operName ,
Time : banTime ,
}
// save in datastore
err = server . store . Update ( func ( tx * buntdb . Tx ) error {
klineKey := fmt . Sprintf ( keyKlineEntry , mask )
// assemble json from ban info
b , err := json . Marshal ( info )
if err != nil {
return err
}
tx . Set ( klineKey , string ( b ) , nil )
return nil
} )
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
}
server . klines . AddMask ( mask , banTime , reason , operReason , operName )
var snoDescription string
if durationIsUsed {
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
}
}
if len ( channels ) == 0 {
for _ , channel := range server . channels . Channels ( ) {
2018-02-03 11:21:32 +01:00
if ! client . flags [ modes . Operator ] && channel . flags [ 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-02-03 11:21:32 +01:00
if ! client . flags [ modes . Operator ] {
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-02-03 11:21:32 +01:00
if err != nil || channel == nil || ( ! client . flags [ modes . Operator ] && channel . flags [ 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
var totalcount , invisiblecount , opercount int
for _ , onlineusers := range server . clients . AllClients ( ) {
totalcount ++
2018-02-03 11:21:32 +01:00
if onlineusers . flags [ modes . Invisible ] {
2018-02-03 10:28:02 +01:00
invisiblecount ++
}
2018-02-03 11:21:32 +01:00
if onlineusers . flags [ modes . Operator ] {
2018-02-03 10:28:02 +01:00
opercount ++
}
}
2018-02-05 15:21:08 +01:00
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 , 1 ) )
rb . Add ( nil , server . name , RPL_LUSEROP , client . nick , fmt . Sprintf ( client . t ( "%d IRC Operators online" ) , opercount ) )
rb . Add ( nil , server . name , RPL_LUSERCHANNELS , client . nick , fmt . Sprintf ( client . t ( "%d channels formed" ) , server . channels . Len ( ) ) )
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 : ]
changes , unknown := ParseChannelModeChanges ( params ... )
// 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
}
// save changes to banlist/exceptlist/invexlist
var banlistUpdated , exceptlistUpdated , invexlistUpdated bool
for _ , change := range applied {
2018-02-03 11:21:32 +01:00
if change . Mode == modes . BanMask {
2018-02-03 10:28:02 +01:00
banlistUpdated = true
2018-02-03 11:21:32 +01:00
} else if change . Mode == modes . ExceptMask {
2018-02-03 10:28:02 +01:00
exceptlistUpdated = true
2018-02-03 11:21:32 +01:00
} else if change . Mode == modes . InviteMask {
2018-02-03 10:28:02 +01:00
invexlistUpdated = true
}
}
if ( banlistUpdated || exceptlistUpdated || invexlistUpdated ) && channel . IsRegistered ( ) {
go server . channelRegistry . StoreChannel ( channel , true )
}
// 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
applied = target . applyUserModeChanges ( msg . Command == "SAMODE" , changes )
}
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-02-03 11:21:32 +01:00
if client . flags [ modes . LocalOperator ] || client . flags [ 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-02-03 10:28:02 +01:00
handler , exists := metadataSubcommands [ strings . ToLower ( msg . Params [ 0 ] ) ]
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
limit := server . Limits ( ) . MonitorEntries
targets := strings . Split ( msg . Params [ 1 ] , "," )
for _ , target := range targets {
// check name length
if len ( target ) < 1 || len ( targets ) > server . limits . NickLen {
continue
}
// add target
casefoldedTarget , err := CasefoldName ( target )
if err != nil {
continue
}
err = server . monitorManager . Add ( client , casefoldedTarget , limit )
2018-02-03 13:03:36 +01:00
if err == errMonitorLimitExceeded {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_MONLISTFULL , client . Nick ( ) , strconv . Itoa ( server . 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 {
client . SetPreregNick ( 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
splitMsg := server . splitMessage ( message , ! client . capabilities . Has ( caps . MaxLine ) )
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
}
if target == "chanserv" {
2018-02-05 15:21:08 +01:00
server . chanservNoticeHandler ( client , message , rb )
2018-02-03 10:28:02 +01:00
continue
} else if target == "nickserv" {
2018-02-05 15:21:08 +01:00
server . nickservNoticeHandler ( client , message , rb )
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-02-03 11:21:32 +01:00
if ! user . flags [ modes . RegisteredOnly ] || client . registered {
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
}
if client . capabilities . Has ( caps . EchoMessage ) {
2018-02-05 15:21:08 +01:00
rb . AddSplitMessageFromClient ( msgid , client , clientOnlyTags , "NOTICE" , user . nick , splitMsg )
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
}
2018-02-03 20:48:44 +01:00
// NICKSERV [params...]
2018-02-05 15:21:08 +01:00
func nsHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
server . nickservPrivmsgHandler ( client , strings . Join ( msg . Params , " " ) , 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-02-03 10:28:02 +01:00
name , err := CasefoldName ( msg . Params [ 0 ] )
if err != nil {
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-02-03 11:21:32 +01:00
if client . flags [ 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
}
server . configurableStateMutex . RLock ( )
oper := server . operators [ name ]
server . configurableStateMutex . RUnlock ( )
password := [ ] byte ( msg . Params [ 1 ] )
err = passwd . ComparePassword ( oper . Pass , password )
if ( oper . Pass == nil ) || ( err != nil ) {
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-02-03 11:21:32 +01:00
client . flags [ modes . Operator ] = true
2018-02-03 10:28:02 +01:00
client . operName = name
client . class = oper . Class
client . whoisLine = oper . WhoisLine
// push new vhost if one is set
if len ( oper . Vhost ) > 0 {
for fClient := range client . Friends ( caps . ChgHost ) {
fClient . SendFromClient ( "" , client , nil , "CHGHOST" , client . username , oper . Vhost )
}
// CHGHOST requires prefix nickmask to have original hostname, so do that before updating nickmask
client . vhost = oper . Vhost
client . updateNickMask ( "" )
}
// set new modes
2018-02-03 11:21:32 +01:00
var applied modes . ModeChanges
2018-02-03 10:28:02 +01:00
if 0 < len ( oper . Modes ) {
2018-02-03 11:21:32 +01:00
modeChanges , unknownChanges := modes . ParseUserModeChanges ( strings . Split ( oper . Modes , " " ) ... )
2018-02-03 10:28:02 +01:00
applied = client . applyUserModeChanges ( true , modeChanges )
if 0 < len ( unknownChanges ) {
var runes string
for r := range unknownChanges {
runes += string ( r )
}
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Could not apply mode changes: +%s" ) , runes ) )
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" ) )
2018-02-03 10:28:02 +01:00
2018-02-03 11:21:32 +01:00
applied = append ( applied , modes . ModeChange {
Mode : modes . Operator ,
Op : modes . Add ,
2018-02-03 10:28:02 +01:00
} )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "MODE" , client . nick , applied . String ( ) )
2018-02-03 10:28:02 +01: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 , client . operName ) )
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
if len ( server . password ) == 0 {
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 ] )
if passwd . ComparePassword ( server . password , 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
splitMsg := server . splitMessage ( message , ! client . capabilities . Has ( caps . MaxLine ) )
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 )
if target == "chanserv" {
2018-02-05 15:21:08 +01:00
server . chanservPrivmsgHandler ( client , message , rb )
2018-02-03 10:28:02 +01:00
continue
} else if target == "nickserv" {
2018-02-05 15:21:08 +01:00
server . nickservPrivmsgHandler ( 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-02-03 11:21:32 +01:00
if ! user . flags [ modes . RegisteredOnly ] || client . registered {
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
}
if client . capabilities . Has ( caps . EchoMessage ) {
2018-02-05 15:21:08 +01:00
rb . AddSplitMessageFromClient ( msgid , client , clientOnlyTags , "PRIVMSG" , user . nick , splitMsg )
2018-02-03 10:28:02 +01:00
}
2018-02-03 11:21:32 +01:00
if user . flags [ 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
}
// PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT
// http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
2018-02-05 15:21:08 +01:00
func proxyHandler ( 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
}
for _ , gateway := range server . ProxyAllowedFrom ( ) {
if isGatewayAllowed ( client . socket . conn . RemoteAddr ( ) , gateway ) {
proxiedIP := msg . Params [ 1 ]
// assume PROXY connections are always secure
return client . ApplyProxiedIP ( proxiedIP , true )
}
}
client . Quit ( client . t ( "PROXY command is not usable from your address" ) )
return true
}
// 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 {
2018-02-03 10:28:02 +01:00
server . logger . Info ( "rehash" , fmt . Sprintf ( "REHASH command used by %s" , client . nick ) )
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 {
server . logger . Error ( "rehash" , 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
}
// RESUME <oldnick> [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 ]
if strings . Contains ( oldnick , " " ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_CANNOT_RESUME , "*" , client . t ( "Cannot resume connection, old nickname contains spaces" ) )
2018-02-03 10:28:02 +01:00
return false
}
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
}
var timestamp * time . Time
if 1 < len ( msg . Params ) {
ts , err := time . Parse ( "2006-01-02T15:04:05.999Z" , msg . Params [ 1 ] )
if err == nil {
timestamp = & ts
} 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 {
OldNick : oldnick ,
Timestamp : timestamp ,
}
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-02-05 15:21:08 +01:00
rb . AddFromClient ( msgid , client , clientOnlyTags , "TAGMSG" , user . nick )
2018-02-03 10:28:02 +01:00
}
2018-02-03 11:21:32 +01:00
if user . flags [ 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
if ! client . 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
var hostAddr net . IP
var hostNet * net . IPNet
_ , hostNet , err := net . ParseCIDR ( hostString )
if err != nil {
hostAddr = net . ParseIP ( hostString )
}
if hostAddr == nil && hostNet == 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
}
if hostNet == nil {
hostString = hostAddr . String ( )
} else {
hostString = hostNet . String ( )
}
// save in datastore
err = server . store . Update ( func ( tx * buntdb . Tx ) error {
dlineKey := fmt . Sprintf ( keyDlineEntry , hostString )
// check if it exists or not
val , err := tx . Get ( dlineKey )
if val == "" {
return errNoExistingBan
} else if err != nil {
return err
}
tx . Delete ( dlineKey )
return nil
} )
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
}
if hostNet == nil {
server . dlines . RemoveIP ( hostAddr )
} else {
server . dlines . RemoveNetwork ( * 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
if ! client . 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 + "@*"
}
// save in datastore
err := server . store . Update ( func ( tx * buntdb . Tx ) error {
klineKey := fmt . Sprintf ( keyKlineEntry , mask )
// check if it exists or not
val , err := tx . Get ( klineKey )
if val == "" {
return errNoExistingBan
} else if err != nil {
return err
}
tx . Delete ( klineKey )
return nil
} )
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
}
server . klines . RemoveMask ( mask )
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
}
if client . username != "" && client . realname != "" {
return false
}
// confirm that username is valid
//
_ , err := CasefoldName ( msg . Params [ 0 ] )
if err != nil {
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
}
if ! client . HasUsername ( ) {
client . username = "~" + msg . Params [ 0 ]
// don't bother updating nickmask here, it's not valid anyway
}
if client . realname == "" {
client . realname = msg . Params [ 3 ]
}
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-02-03 11:21:32 +01:00
if target . flags [ modes . Operator ] {
2018-02-03 10:28:02 +01:00
isOper = "*"
}
2018-02-03 11:21:32 +01:00
if target . flags [ 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-02-03 11:21:32 +01:00
if client . flags [ 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 ]
if 0 < len ( info . Password ) && passwd . ComparePasswordString ( info . Password , givenPassword ) != nil {
continue
}
if 0 < len ( info . Fingerprint ) && client . certfp != info . Fingerprint {
continue
}
proxiedIP := msg . Params [ 3 ]
return client . ApplyProxiedIP ( proxiedIP , secure )
}
}
}
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-02-03 11:21:32 +01:00
if client . flags [ modes . Operator ] {
2018-02-03 10:28:02 +01:00
masks := strings . Split ( masksString , "," )
for _ , mask := range masks {
casefoldedMask , err := Casefold ( mask )
if err != nil {
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
}
matches := server . clients . FindAll ( casefoldedMask )
if len ( matches ) == 0 {
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 {
// only get the first request
casefoldedMask , err := Casefold ( strings . Split ( masksString , "," ) [ 0 ] )
mclient := server . clients . Get ( casefoldedMask )
if err != nil || mclient == nil {
2018-02-04 12:32:48 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , masksString , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
// fall through, ENDOFWHOIS is always sent
} else {
2018-02-04 12:32:48 +01:00
client . getWhoisOf ( mclient , rb )
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 ] , "," )
var count int64
if len ( msg . Params ) > 1 {
count , _ = strconv . ParseInt ( msg . Params [ 1 ] , 10 , 64 )
}
//var target string
//if len(msg.Params) > 2 {
// target = msg.Params[2]
//}
for _ , nickname := range nicknames {
results := server . whoWas . Find ( nickname , count )
if len ( results ) == 0 {
if len ( nickname ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_WASNOSUCHNICK , client . nick , nickname , client . t ( "There was no such nickname" ) )
2018-02-03 10:28:02 +01:00
}
} else {
for _ , whoWas := range results {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_WHOWASUSER , client . nick , whoWas . nickname , whoWas . username , whoWas . hostname , "*" , whoWas . realname )
2018-02-03 10:28:02 +01:00
}
}
if len ( nickname ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHOWAS , client . nick , nickname , client . t ( "End of WHOWAS" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}