2018-02-03 10:28:02 +01:00
// Copyright (c) 2012-2014 Jeremy Latt
2018-02-03 10:48:30 +01:00
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2018 Daniel Oaks <daniel@danieloaks.net>
// Copyright (c) 2017-2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2018-02-03 10:28:02 +01:00
// released under the MIT license
package irc
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmatch"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/custime"
2018-11-26 11:23:27 +01:00
"github.com/oragono/oragono/irc/history"
2018-02-03 11:21:32 +01:00
"github.com/oragono/oragono/irc/modes"
2018-02-03 10:28:02 +01:00
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
2018-04-19 08:48:19 +02:00
"golang.org/x/crypto/bcrypt"
2018-02-03 10:28:02 +01:00
)
2019-04-08 02:40:19 +02:00
// ACC [LS|REGISTER|VERIFY] ...
2018-02-05 15:21:08 +01:00
func accHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-04-08 02:40:19 +02:00
subcommand := strings . ToLower ( msg . Params [ 0 ] )
if subcommand == "ls" {
config := server . Config ( ) . Accounts
rb . Add ( nil , server . name , "ACC" , "LS" , "SUBCOMMANDS" , "LS REGISTER VERIFY" )
2019-04-08 03:36:48 +02:00
// this list is sorted by the config loader, yay
rb . Add ( nil , server . name , "ACC" , "LS" , "CALLBACKS" , strings . Join ( config . Registration . EnabledCallbacks , " " ) )
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "ACC" , "LS" , "CREDTYPES" , "passphrase certfp" )
2019-04-08 03:36:48 +02:00
flags := [ ] string { "nospaces" }
2019-04-08 02:40:19 +02:00
if config . NickReservation . Enabled {
2019-04-08 03:36:48 +02:00
flags = append ( flags , "regnick" )
2019-04-08 02:40:19 +02:00
}
2019-04-08 03:36:48 +02:00
sort . Strings ( flags )
rb . Add ( nil , server . name , "ACC" , "LS" , "FLAGS" , strings . Join ( flags , " " ) )
2019-04-08 02:40:19 +02:00
return false
}
// disallow account stuff before connection registration has completed, for now
if ! client . Registered ( ) {
client . Send ( nil , server . name , ERR_NOTREGISTERED , "*" , client . t ( "You need to register before you can use that command" ) )
return false
}
2018-02-20 10:20:30 +01:00
// make sure reg is enabled
if ! server . AccountConfig ( ) . Registration . Enabled {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_UNAVAILABLE" , client . t ( "Account registration is disabled" ) )
2018-02-20 10:20:30 +01:00
return false
}
2018-02-03 10:28:02 +01:00
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 {
2019-04-08 02:40:19 +02:00
// "If a callback namespace is not ... provided, the IRC server MUST use mailto""
2018-02-20 10:20:30 +01:00
callbackNamespace = "mailto"
callbackValue = callback
}
// ensure the callback namespace is valid
// need to search callback list, maybe look at using a map later?
for _ , name := range config . Registration . EnabledCallbacks {
if callbackNamespace == name {
return
}
2018-02-03 10:28:02 +01:00
}
2018-02-20 10:20:30 +01:00
// error value
callbackNamespace = ""
return
}
2018-02-03 10:28:02 +01:00
2018-02-20 10:20:30 +01:00
// ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
func accRegisterHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-01-01 22:45:37 +01:00
nick := client . Nick ( )
2019-04-08 02:40:19 +02:00
if len ( msg . Params ) < 4 {
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , nick , msg . Command , client . t ( "Not enough parameters" ) )
return false
}
2019-04-08 03:36:48 +02:00
account := msg . Params [ 1 ]
2019-04-08 02:40:19 +02:00
// check for account name of *
if account == "*" {
account = nick
} else {
if server . Config ( ) . Accounts . NickReservation . Enabled {
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_MUST_USE_REGNICK" , account , client . t ( "Must register with current nickname instead of separate account name" ) )
return false
}
}
2018-02-03 10:28:02 +01:00
// clients can't reg new accounts if they're already logged in
if client . LoggedIntoAccount ( ) {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_UNSPECIFIED_ERROR" , account , client . t ( "You're already logged into an account" ) )
2018-08-15 04:50:20 +02:00
return false
2018-02-03 10:28:02 +01:00
}
2019-04-08 02:40:19 +02:00
// sanitise account name
2018-02-03 10:28:02 +01:00
casefoldedAccount , err := CasefoldName ( account )
2019-04-08 02:54:52 +02:00
if err != nil {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_INVALID_ACCOUNT_NAME" , account , client . t ( "Account name is not valid" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-20 10:20:30 +01:00
callbackSpec := msg . Params [ 2 ]
callbackNamespace , callbackValue := parseCallback ( callbackSpec , server . AccountConfig ( ) )
2018-02-03 10:28:02 +01:00
2018-02-20 10:20:30 +01:00
if callbackNamespace == "" {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_INVALID_CALLBACK" , account , callbackSpec , client . t ( "Cannot send verification code there" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get credential type/value
var credentialType , credentialValue string
if len ( msg . Params ) > 4 {
credentialType = strings . ToLower ( msg . Params [ 3 ] )
credentialValue = msg . Params [ 4 ]
2018-02-11 11:30:40 +01:00
} else {
// exactly 4 params
2018-02-03 10:28:02 +01:00
credentialType = "passphrase" // default from the spec
credentialValue = msg . Params [ 3 ]
}
// ensure the credential type is valid
var credentialValid bool
2018-02-11 11:30:40 +01:00
for _ , name := range server . AccountConfig ( ) . Registration . EnabledCredentialTypes {
2018-02-03 10:28:02 +01:00
if credentialType == name {
credentialValid = true
}
}
if credentialType == "certfp" && client . certfp == "" {
2019-04-08 03:36:48 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_INVALID_CREDENTIAL" , account , client . t ( "You must connect with a TLS client certificate to use certfp" ) )
2018-02-03 10:28:02 +01:00
return false
}
if ! credentialValid {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_INVALID_CRED_TYPE" , account , credentialType , client . t ( "Credential type is not supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
var passphrase , certfp string
if credentialType == "certfp" {
certfp = client . certfp
} else if credentialType == "passphrase" {
passphrase = credentialValue
}
2019-01-01 22:45:37 +01:00
throttled , remainingTime := client . loginThrottle . Touch ( )
if throttled {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , "REG_UNSPECIFIED_ERROR" , account , fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime ) )
2019-01-01 22:45:37 +01:00
return false
}
2018-02-11 11:30:40 +01:00
err = server . accounts . Register ( client , account , callbackNamespace , callbackValue , passphrase , certfp )
2018-02-03 10:28:02 +01:00
if err != nil {
2019-04-08 03:36:48 +02:00
msg , code := registrationErrorToMessageAndCode ( err )
rb . Add ( nil , server . name , "FAIL" , "ACC" , code , account , 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 {
2019-02-22 03:37:11 +01:00
messageTemplate := client . t ( "Account created, pending verification; verification code has been sent to %s" )
message := fmt . Sprintf ( messageTemplate , fmt . Sprintf ( "%s:%s" , callbackNamespace , callbackValue ) )
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , RPL_REG_VERIFICATION_REQUIRED , nick , casefoldedAccount , message )
2018-02-03 10:28:02 +01:00
}
2018-02-20 10:20:30 +01:00
return false
}
2019-04-08 03:36:48 +02:00
func registrationErrorToMessageAndCode ( err error ) ( message , code string ) {
2019-02-06 01:03:42 +01:00
// default responses: let's be risk-averse about displaying internal errors
// to the clients, especially for something as sensitive as accounts
2019-04-08 03:36:48 +02:00
code = "REG_UNSPECIFIED_ERROR"
2019-02-06 01:03:42 +01:00
message = ` Could not register `
switch err {
2019-04-08 03:36:48 +02:00
case errAccountBadPassphrase :
code = "REG_INVALID_CREDENTIAL"
message = err . Error ( )
2019-02-06 01:03:42 +01:00
case errAccountAlreadyRegistered , errAccountAlreadyVerified :
message = err . Error ( )
2019-02-15 01:51:55 +01:00
case errAccountCreation , errAccountMustHoldNick , errAccountBadPassphrase , errCertfpAlreadyExists , errFeatureDisabled :
2019-02-06 01:03:42 +01:00
message = err . Error ( )
}
return
}
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 {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , client . server . name , RPL_REG_SUCCESS , client . nick , client . AccountName ( ) , client . t ( "Account created" ) )
2018-02-20 10:20:30 +01:00
}
2019-04-08 02:40:19 +02:00
sendSuccessfulAccountAuth ( client , rb , forNS , false )
2018-02-20 10:20:30 +01:00
}
2019-04-08 02:40:19 +02:00
// sendSuccessfulAccountAuth means that an account auth attempt completed successfully, and is used to dispatch messages.
func sendSuccessfulAccountAuth ( client * Client , rb * ResponseBuffer , forNS , forSASL bool ) {
2019-02-13 08:42:35 +01:00
details := client . Details ( )
2018-02-20 10:20:30 +01:00
if forNS {
2019-02-13 08:42:35 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "You're now logged in as %s" ) , details . accountName ) )
2018-02-20 10:44:44 +01:00
} else {
2019-04-08 02:40:19 +02:00
//TODO(dan): some servers send this numeric even for NickServ logins iirc? to confirm and maybe do too
2019-02-13 08:42:35 +01:00
rb . Add ( nil , client . server . name , RPL_LOGGEDIN , details . nick , details . nickMask , details . accountName , fmt . Sprintf ( client . t ( "You are now logged in as %s" ) , details . accountName ) )
2019-04-08 02:40:19 +02:00
if forSASL {
rb . Add ( nil , client . server . name , RPL_SASLSUCCESS , details . nick , client . t ( "Authentication successful" ) )
}
2018-02-20 10:20:30 +01:00
}
// dispatch account-notify
for friend := range client . Friends ( caps . AccountNotify ) {
2019-02-13 08:42:35 +01:00
friend . Send ( nil , details . nickMask , "ACCOUNT" , details . accountName )
2018-02-20 10:20:30 +01:00
}
2019-02-13 08:42:35 +01:00
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]]" ) , details . nickMask , details . accountName ) )
client . server . logger . Info ( "accounts" , "client" , details . nick , "logged into account" , details . accountName )
2018-02-20 10:20:30 +01:00
}
// ACC VERIFY <accountname> <auth_code>
func accVerifyHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
account := strings . TrimSpace ( msg . Params [ 1 ] )
2019-04-08 02:40:19 +02:00
if len ( msg . Params ) < 3 {
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , msg . Command , client . t ( "Not enough parameters" ) )
return false
}
2018-02-20 10:20:30 +01:00
err := server . accounts . Verify ( client , account , msg . Params [ 2 ] )
var code string
var message string
if err == errAccountVerificationInvalidCode {
2019-04-08 02:40:19 +02:00
code = "ACCOUNT_INVALID_VERIFY_CODE"
2018-02-20 10:20:30 +01:00
message = err . Error ( )
} else if err == errAccountAlreadyVerified {
2019-04-08 02:40:19 +02:00
code = "ACCOUNT_ALREADY_VERIFIED"
2018-02-20 10:20:30 +01:00
message = err . Error ( )
} else if err != nil {
2019-04-08 02:40:19 +02:00
code = "VERIFY_UNSPECIFIED_ERROR"
2018-02-20 10:20:30 +01:00
message = errAccountVerificationFailed . Error ( )
}
if err == nil {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , RPL_VERIFY_SUCCESS , client . Nick ( ) , account , client . t ( "Account verification successful" ) )
sendSuccessfulAccountAuth ( client , rb , false , false )
2018-02-20 10:20:30 +01:00
} else {
2019-04-08 02:40:19 +02:00
rb . Add ( nil , server . name , "FAIL" , "ACC" , code , account , client . t ( message ) )
2018-02-20 10:20:30 +01:00
}
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 {
2019-05-09 20:18:30 +02:00
config := server . Config ( )
2019-05-08 05:24:54 +02:00
details := client . Details ( )
2019-05-09 20:18:30 +02:00
2019-05-08 05:24:54 +02:00
if details . account != "" {
rb . Add ( nil , server . name , ERR_SASLALREADY , details . nick , client . t ( "You're already logged into an account" ) )
return false
}
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 ] == "*" {
2019-05-08 05:24:54 +02:00
rb . Add ( nil , server . name , ERR_SASLABORTED , details . 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
2019-05-09 20:18:30 +02:00
if ! config . Server . Compatibility . SendUnprefixedSasl {
// normal behavior
rb . Add ( nil , server . name , "AUTHENTICATE" , "+" )
} else {
// gross hack: send a raw message to ensure no tags or prefix
rb . Flush ( true )
rb . session . SendRawMessage ( ircmsg . MakeMessage ( nil , "" , "AUTHENTICATE" , "+" ) , true )
}
2018-02-03 10:28:02 +01:00
} else {
2019-05-08 05:24:54 +02:00
rb . Add ( nil , server . name , ERR_SASLFAIL , details . 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 {
2019-05-08 05:24:54 +02:00
rb . Add ( nil , server . name , ERR_SASLTOOLONG , details . 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 {
2019-05-08 05:24:54 +02:00
rb . Add ( nil , server . name , ERR_SASLFAIL , details . 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 {
2019-05-08 05:24:54 +02:00
rb . Add ( nil , server . name , ERR_SASLFAIL , details . 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 {
2019-05-08 05:24:54 +02:00
rb . Add ( nil , server . name , ERR_SASLFAIL , details . 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
// wait 'til SASL is done before emptying the sasl vars
client . saslInProgress = false
client . saslMechanism = ""
client . saslValue = ""
return exiting
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE PLAIN
2018-02-05 15:21:08 +01:00
func authPlainHandler ( server * Server , client * Client , mechanism string , value [ ] byte , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
splitValue := bytes . Split ( value , [ ] byte { '\000' } )
var accountKey , authzid string
2019-01-01 22:45:37 +01:00
nick := client . Nick ( )
2018-02-03 10:28:02 +01:00
if len ( splitValue ) == 3 {
accountKey = string ( splitValue [ 0 ] )
authzid = string ( splitValue [ 1 ] )
if accountKey == "" {
accountKey = authzid
} else if accountKey != authzid {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , client . t ( "SASL authentication failed: authcid and authzid should be the same" ) )
2018-02-03 10:28:02 +01:00
return false
}
} else {
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , client . t ( "SASL authentication failed: Invalid auth blob" ) )
return false
}
throttled , remainingTime := client . loginThrottle . Touch ( )
if throttled {
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
password := string ( splitValue [ 2 ] )
2018-03-02 23:04:24 +01:00
err := server . accounts . AuthenticateByPassphrase ( client , accountKey , password )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-11 11:30:40 +01:00
msg := authErrorToMessage ( server , err )
2019-01-01 22:45:37 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , nick , fmt . Sprintf ( "%s: %s" , client . t ( "SASL authentication failed" ) , client . t ( msg ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-04-08 02:40:19 +02:00
sendSuccessfulAccountAuth ( client , rb , false , true )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
func authErrorToMessage ( server * Server , err error ) ( msg string ) {
if err == errAccountDoesNotExist || err == errAccountUnverified || err == errAccountInvalidCredentials {
msg = err . Error ( )
} else {
2018-12-31 17:33:42 +01:00
server . logger . Error ( "internal" , "sasl authentication failure" , err . Error ( ) )
2018-02-11 11:30:40 +01:00
msg = "Unknown"
}
return
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE EXTERNAL
2018-02-05 15:21:08 +01:00
func authExternalHandler ( server * Server , client * Client , mechanism string , value [ ] byte , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if client . certfp == "" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed, you are not connecting with a certificate" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-11 11:30:40 +01:00
err := server . accounts . AuthenticateByCertFP ( client )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-11 11:30:40 +01:00
msg := authErrorToMessage ( server , err )
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , fmt . Sprintf ( "%s: %s" , client . t ( "SASL authentication failed" ) , client . t ( msg ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-04-08 02:40:19 +02:00
sendSuccessfulAccountAuth ( client , rb , false , true )
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
2019-02-17 20:29:04 +01:00
var awayMessage string
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 0 {
isAway = true
2019-02-17 20:29:04 +01:00
awayMessage = msg . Params [ 0 ]
2018-02-03 10:28:02 +01:00
awayLen := server . Limits ( ) . AwayLen
2019-02-17 20:29:04 +01:00
if len ( awayMessage ) > awayLen {
awayMessage = awayMessage [ : awayLen ]
2018-02-03 10:28:02 +01:00
}
}
2019-04-28 21:10:03 +02:00
client . SetAway ( isAway , awayMessage )
2018-02-03 10:28:02 +01:00
2018-04-23 00:47:10 +02:00
if isAway {
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-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
}
// dispatch away-notify
2019-04-28 20:52:15 +02:00
details := client . Details ( )
2019-04-12 06:08:46 +02:00
for session := range client . Friends ( caps . AwayNotify ) {
2018-04-23 00:47:10 +02:00
if isAway {
2019-04-12 06:08:46 +02:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . account , nil , "AWAY" , awayMessage )
2018-02-03 10:28:02 +01:00
} else {
2019-04-12 06:08:46 +02:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . account , nil , "AWAY" )
2018-02-03 10:28:02 +01:00
}
}
return false
}
// CAP <subcmd> [<caps>]
2018-02-05 15:21:08 +01:00
func capHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
subCommand := strings . ToUpper ( msg . Params [ 0 ] )
2019-02-03 02:00:23 +01:00
toAdd := caps . NewSet ( )
toRemove := caps . NewSet ( )
2018-02-03 10:28:02 +01:00
var capString string
2019-02-03 02:00:23 +01:00
badCaps := false
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 1 {
capString = msg . Params [ 1 ]
2018-06-26 00:08:15 +02:00
strs := strings . Fields ( capString )
2018-02-03 10:28:02 +01:00
for _ , str := range strs {
2019-02-03 02:00:23 +01:00
remove := false
if str [ 0 ] == '-' {
str = str [ 1 : ]
remove = true
}
2018-06-26 00:08:15 +02:00
capab , err := caps . NameToCapability ( str )
2019-02-03 02:00:23 +01:00
if err != nil || ( ! remove && ! SupportedCapabilities . Has ( capab ) ) {
badCaps = true
} else if ! remove {
toAdd . Enable ( capab )
2018-06-26 00:08:15 +02:00
} else {
2019-02-03 02:00:23 +01:00
toRemove . Enable ( capab )
2018-02-03 10:28:02 +01:00
}
}
}
switch subCommand {
case "LS" :
if ! client . registered {
2019-04-12 06:08:46 +02:00
rb . session . capState = caps . NegotiatingState
2018-02-03 10:28:02 +01:00
}
2019-04-27 17:50:16 +02:00
if 1 < len ( msg . Params ) {
num , err := strconv . Atoi ( msg . Params [ 1 ] )
newVersion := caps . Version ( num )
if err == nil && rb . session . capVersion < newVersion {
rb . session . capVersion = newVersion
}
2018-02-03 10:28:02 +01:00
}
// 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.
2019-04-12 06:08:46 +02:00
rb . Add ( nil , server . name , "CAP" , client . nick , subCommand , SupportedCapabilities . String ( rb . session . capVersion , CapValues ) )
2018-02-03 10:28:02 +01:00
case "LIST" :
2019-04-12 06:08:46 +02:00
rb . Add ( nil , server . name , "CAP" , client . nick , subCommand , rb . session . 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 {
2019-04-12 06:08:46 +02:00
rb . session . capState = caps . NegotiatingState
2018-02-03 10:28:02 +01:00
}
// make sure all capabilities actually exist
2019-02-03 02:00:23 +01:00
if badCaps {
2018-06-26 00:08:15 +02:00
rb . Add ( nil , server . name , "CAP" , client . nick , "NAK" , capString )
return false
2018-02-03 10:28:02 +01:00
}
2019-04-12 06:08:46 +02:00
rb . session . capabilities . Union ( toAdd )
rb . session . capabilities . Subtract ( toRemove )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , "CAP" , client . nick , "ACK" , capString )
2018-02-03 10:28:02 +01:00
2018-11-26 11:23:27 +01:00
// if this is the first time the client is requesting a resume token,
// send it to them
2019-02-03 02:00:23 +01:00
if toAdd . Has ( caps . Resume ) {
2019-02-12 06:27:57 +01:00
token := server . resumeManager . GenerateToken ( client )
if token != "" {
2018-11-26 11:23:27 +01:00
rb . Add ( nil , server . name , "RESUME" , "TOKEN" , token )
}
}
2019-05-23 04:16:01 +02:00
// #511: oragono.io/nope is a fake cap to trap bad clients who blindly request
// every offered capability:
if toAdd . Has ( caps . Nope ) {
client . Quit ( client . t ( "Requesting the oragono.io/nope CAP is forbidden" ) , rb . session )
return true
}
2019-04-12 06:08:46 +02:00
// update maxlenrest, just in case they altered the maxline cap
rb . session . SetMaxlenRest ( )
2018-02-03 10:28:02 +01:00
case "END" :
2019-02-05 06:19:03 +01:00
if ! client . registered {
2019-04-12 06:08:46 +02:00
rb . session . 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
}
2019-02-04 11:18:17 +01:00
// CHATHISTORY <target> <preposition> <query> [<limit>]
// e.g., CHATHISTORY #ircv3 AFTER id=ytNBbt565yt4r3err3 10
// CHATHISTORY <target> BETWEEN <query> <query> <direction> [<limit>]
// e.g., CHATHISTORY #ircv3 BETWEEN timestamp=YYYY-MM-DDThh:mm:ss.sssZ timestamp=YYYY-MM-DDThh:mm:ss.sssZ + 100
func chathistoryHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) ( exiting bool ) {
2019-02-04 18:16:28 +01:00
config := server . Config ( )
2019-02-04 11:18:17 +01:00
var items [ ] history . Item
success := false
var hist * history . Buffer
var channel * Channel
defer func ( ) {
2019-05-07 05:17:57 +02:00
// successful responses are sent as a chathistory or history batch
if success && 0 < len ( items ) {
batchType := "chathistory"
if rb . session . capabilities . Has ( caps . EventPlayback ) {
batchType = "history"
}
rb . ForceBatchStart ( batchType , true )
2019-02-04 11:18:17 +01:00
if channel == nil {
client . replayPrivmsgHistory ( rb , items , true )
} else {
2019-05-07 05:17:57 +02:00
channel . replayHistoryItems ( rb , items , false )
2019-02-04 11:18:17 +01:00
}
return
}
2019-05-07 05:17:57 +02:00
// errors are sent either without a batch, or in a draft/labeled-response batch as usual
2019-02-04 11:18:17 +01:00
// TODO: send `WARN CHATHISTORY MAX_MESSAGES_EXCEEDED` when appropriate
2019-02-13 21:29:36 +01:00
if hist == nil {
2019-05-07 05:17:57 +02:00
rb . Add ( nil , server . name , "ERR" , "CHATHISTORY" , "NO_SUCH_CHANNEL" )
2019-02-04 11:18:17 +01:00
} else if len ( items ) == 0 {
2019-05-07 05:17:57 +02:00
rb . Add ( nil , server . name , "ERR" , "CHATHISTORY" , "NO_TEXT_TO_SEND" )
2019-02-13 21:29:36 +01:00
} else if ! success {
2019-05-07 05:17:57 +02:00
rb . Add ( nil , server . name , "ERR" , "CHATHISTORY" , "NEED_MORE_PARAMS" )
2019-02-04 11:18:17 +01:00
}
} ( )
target := msg . Params [ 0 ]
channel = server . channels . Get ( target )
2019-02-13 21:29:36 +01:00
if channel != nil && channel . hasClient ( client ) {
// "If [...] the user does not have permission to view the requested content, [...]
// NO_SUCH_CHANNEL SHOULD be returned"
2019-02-04 11:18:17 +01:00
hist = & channel . history
2019-02-04 18:31:44 +01:00
} else {
targetClient := server . clients . Get ( target )
if targetClient != nil {
myAccount := client . Account ( )
targetAccount := targetClient . Account ( )
if myAccount != "" && targetAccount != "" && myAccount == targetAccount {
hist = targetClient . history
}
}
}
if hist == nil {
return
2019-02-04 11:18:17 +01:00
}
preposition := strings . ToLower ( msg . Params [ 1 ] )
parseQueryParam := func ( param string ) ( msgid string , timestamp time . Time , err error ) {
err = errInvalidParams
pieces := strings . SplitN ( param , "=" , 2 )
if len ( pieces ) < 2 {
return
}
identifier , value := strings . ToLower ( pieces [ 0 ] ) , pieces [ 1 ]
if identifier == "id" {
msgid , err = value , nil
return
} else if identifier == "timestamp" {
timestamp , err = time . Parse ( IRCv3TimestampFormat , value )
return
}
return
}
2019-02-04 18:16:28 +01:00
maxChathistoryLimit := config . History . ChathistoryMax
2019-02-04 11:18:17 +01:00
if maxChathistoryLimit == 0 {
return
}
parseHistoryLimit := func ( paramIndex int ) ( limit int ) {
if len ( msg . Params ) < ( paramIndex + 1 ) {
return maxChathistoryLimit
}
limit , err := strconv . Atoi ( msg . Params [ paramIndex ] )
if err != nil || limit == 0 || limit > maxChathistoryLimit {
limit = maxChathistoryLimit
}
return
}
// TODO: as currently implemented, almost all of thes queries are worst-case O(n)
// in the number of stored history entries. Every one of them can be made O(1)
// if necessary, without too much difficulty. Some ideas:
// * Ensure that the ring buffer is sorted by time, enabling binary search for times
// * Maintain a map from msgid to position in the ring buffer
if preposition == "between" {
if len ( msg . Params ) >= 5 {
startMsgid , startTimestamp , startErr := parseQueryParam ( msg . Params [ 2 ] )
endMsgid , endTimestamp , endErr := parseQueryParam ( msg . Params [ 3 ] )
ascending := msg . Params [ 4 ] == "+"
limit := parseHistoryLimit ( 5 )
if startErr != nil || endErr != nil {
success = false
} else if startMsgid != "" && endMsgid != "" {
inInterval := false
matches := func ( item history . Item ) ( result bool ) {
result = inInterval
if item . HasMsgid ( startMsgid ) {
if ascending {
inInterval = true
} else {
inInterval = false
return false // interval is exclusive
}
} else if item . HasMsgid ( endMsgid ) {
if ascending {
inInterval = false
return false
} else {
inInterval = true
}
}
return
}
items = hist . Match ( matches , ascending , limit )
success = true
} else if ! startTimestamp . IsZero ( ) && ! endTimestamp . IsZero ( ) {
items , _ = hist . Between ( startTimestamp , endTimestamp , ascending , limit )
if ! ascending {
history . Reverse ( items )
}
success = true
}
// else: mismatched params, success = false, fail
}
return
}
// before, after, latest, around
queryParam := msg . Params [ 2 ]
msgid , timestamp , err := parseQueryParam ( queryParam )
limit := parseHistoryLimit ( 3 )
before := false
switch preposition {
case "before" :
before = true
fallthrough
case "after" :
var matches history . Predicate
if err != nil {
break
} else if msgid != "" {
inInterval := false
matches = func ( item history . Item ) ( result bool ) {
result = inInterval
if item . HasMsgid ( msgid ) {
inInterval = true
}
return
}
} else {
matches = func ( item history . Item ) bool {
2019-05-07 05:17:57 +02:00
return before == item . Message . Time . Before ( timestamp )
2019-02-04 11:18:17 +01:00
}
}
items = hist . Match ( matches , ! before , limit )
success = true
case "latest" :
if queryParam == "*" {
items = hist . Latest ( limit )
} else if err != nil {
break
} else {
var matches history . Predicate
if msgid != "" {
shouldStop := false
matches = func ( item history . Item ) bool {
if shouldStop {
return false
}
shouldStop = item . HasMsgid ( msgid )
return ! shouldStop
}
} else {
matches = func ( item history . Item ) bool {
2019-05-07 05:17:57 +02:00
return item . Message . Time . After ( timestamp )
2019-02-04 11:18:17 +01:00
}
}
items = hist . Match ( matches , false , limit )
}
success = true
case "around" :
if err != nil {
break
}
var initialMatcher history . Predicate
if msgid != "" {
inInterval := false
initialMatcher = func ( item history . Item ) ( result bool ) {
if inInterval {
return true
} else {
inInterval = item . HasMsgid ( msgid )
return inInterval
}
}
} else {
initialMatcher = func ( item history . Item ) ( result bool ) {
2019-05-07 05:17:57 +02:00
return item . Message . Time . Before ( timestamp )
2019-02-04 11:18:17 +01:00
}
}
var halfLimit int
halfLimit = ( limit + 1 ) / 2
firstPass := hist . Match ( initialMatcher , false , halfLimit )
if len ( firstPass ) > 0 {
2019-05-07 05:17:57 +02:00
timeWindowStart := firstPass [ 0 ] . Message . Time
2019-02-04 11:18:17 +01:00
items = hist . Match ( func ( item history . Item ) bool {
2019-05-07 05:17:57 +02:00
return item . Message . Time . Equal ( timeWindowStart ) || item . Message . Time . After ( timeWindowStart )
2019-02-04 11:18:17 +01:00
} , true , limit )
}
success = true
}
return
}
2018-02-03 20:48:44 +01:00
// DEBUG <subcmd>
2018-02-05 15:21:08 +01:00
func debugHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-12 05:48:58 +01:00
param := strings . ToUpper ( msg . Params [ 0 ] )
2018-02-03 10:28:02 +01:00
2018-02-11 11:30:40 +01:00
switch param {
2018-02-03 10:28:02 +01:00
case "GCSTATS" :
stats := debug . GCStats {
Pause : make ( [ ] time . Duration , 10 ) ,
PauseQuantiles : make ( [ ] time . Duration , 5 ) ,
}
debug . ReadGCStats ( & stats )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "last GC: %s" , stats . LastGC . Format ( time . RFC1123 ) ) )
rb . Notice ( fmt . Sprintf ( "num GC: %d" , stats . NumGC ) )
rb . Notice ( fmt . Sprintf ( "pause total: %s" , stats . PauseTotal ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles min%%: %s" , stats . PauseQuantiles [ 0 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles 25%%: %s" , stats . PauseQuantiles [ 1 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles 50%%: %s" , stats . PauseQuantiles [ 2 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles 75%%: %s" , stats . PauseQuantiles [ 3 ] ) )
rb . Notice ( fmt . Sprintf ( "pause quantiles max%%: %s" , stats . PauseQuantiles [ 4 ] ) )
2018-02-03 10:28:02 +01:00
case "NUMGOROUTINE" :
count := runtime . NumGoroutine ( )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "num goroutines: %d" , count ) )
2018-02-03 10:28:02 +01:00
case "PROFILEHEAP" :
profFile := "oragono.mprof"
file , err := os . Create ( profFile )
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "error: %s" , err ) )
2018-02-03 10:28:02 +01:00
break
}
defer file . Close ( )
pprof . Lookup ( "heap" ) . WriteTo ( file , 0 )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "written to %s" , profFile ) )
2018-02-03 10:28:02 +01:00
case "STARTCPUPROFILE" :
profFile := "oragono.prof"
file , err := os . Create ( profFile )
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "error: %s" , err ) )
2018-02-03 10:28:02 +01:00
break
}
if err := pprof . StartCPUProfile ( file ) ; err != nil {
defer file . Close ( )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "error: %s" , err ) )
2018-02-03 10:28:02 +01:00
break
}
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "CPU profile writing to %s" , profFile ) )
2018-02-03 10:28:02 +01:00
case "STOPCPUPROFILE" :
pprof . StopCPUProfile ( )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( "CPU profiling stopped" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2019-01-22 11:01:01 +01:00
// helper for parsing the reason args to DLINE and KLINE
func getReasonsFromParams ( params [ ] string , currentArg int ) ( reason , operReason string ) {
reason = "No reason given"
operReason = ""
if len ( params ) > currentArg {
reasons := strings . SplitN ( strings . Join ( params [ currentArg : ] , " " ) , "|" , 2 )
if len ( reasons ) == 1 {
reason = strings . TrimSpace ( reasons [ 0 ] )
} else if len ( reasons ) == 2 {
reason = strings . TrimSpace ( reasons [ 0 ] )
operReason = strings . TrimSpace ( reasons [ 1 ] )
}
}
return
}
func formatBanForListing ( client * Client , key string , info IPBanInfo ) string {
desc := info . Reason
if info . OperReason != "" && info . OperReason != info . Reason {
desc = fmt . Sprintf ( "%s | %s" , info . Reason , info . OperReason )
}
if info . Duration != 0 {
desc = fmt . Sprintf ( "%s [%s]" , desc , info . TimeLeft ( ) )
}
return fmt . Sprintf ( client . t ( "Ban - %[1]s - added by %[2]s - %[3]s" ) , key , info . OperName , desc )
}
2018-02-03 10:28:02 +01:00
// DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
// DLINE LIST
2018-02-05 15:21:08 +01:00
func dlineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_ban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
currentArg := 0
// if they say LIST, we just list the current dlines
if len ( msg . Params ) == currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "list" {
bans := server . dlines . AllBans ( )
if len ( bans ) == 0 {
2018-02-05 15:21:08 +01:00
rb . Notice ( client . t ( "No DLINEs have been set!" ) )
2018-02-03 10:28:02 +01:00
}
for key , info := range bans {
2019-01-22 11:01:01 +01:00
client . Notice ( formatBanForListing ( client , key , info ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// when setting a ban, if they say "ANDKILL" we should also kill all users who match it
var andKill bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "andkill" {
andKill = true
currentArg ++
}
// when setting a ban that covers the oper's current connection, we require them to say
// "DLINE MYSELF" so that we're sure they really mean it.
var dlineMyself bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "myself" {
dlineMyself = true
currentArg ++
}
// duration
duration , err := custime . ParseDuration ( msg . Params [ currentArg ] )
2019-01-22 11:01:01 +01:00
if err != nil {
duration = 0
} else {
2018-02-03 10:28:02 +01:00
currentArg ++
}
// get host
if len ( msg . Params ) < currentArg + 1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
hostString := msg . Params [ currentArg ]
currentArg ++
// check host
2019-01-22 11:01:01 +01:00
hostNet , err := utils . NormalizedNetFromString ( hostString )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Could not parse IP address or CIDR network" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-01-22 11:01:01 +01:00
if ! dlineMyself && hostNet . Contains ( client . IP ( ) ) {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "This ban matches you. To DLINE yourself, you must use the command: /DLINE MYSELF <arguments>" ) )
return false
2018-02-03 10:28:02 +01:00
}
// check remote
if len ( msg . Params ) > currentArg && msg . Params [ currentArg ] == "ON" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Remote servers not yet supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get comment(s)
2019-01-22 11:01:01 +01:00
reason , operReason := getReasonsFromParams ( msg . Params , currentArg )
2018-04-19 08:48:19 +02:00
operName := oper . Name
2018-02-03 10:28:02 +01:00
if operName == "" {
operName = server . name
}
2019-01-22 11:01:01 +01:00
err = server . dlines . AddNetwork ( hostNet , duration , reason , operReason , operName )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Could not successfully save new D-LINE: %s" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
var snoDescription string
2019-01-22 11:01:01 +01:00
hostString = utils . NetToNormalizedString ( hostNet )
if duration != 0 {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added temporary (%[1]s) D-Line for %[2]s" ) , duration . String ( ) , hostString ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added temporary (%s) D-Line for %s" ) , client . nick , operName , duration . String ( ) , hostString )
} else {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added D-Line for %s" ) , hostString ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added D-Line for %s" ) , client . nick , operName , hostString )
}
server . snomasks . Send ( sno . LocalXline , snoDescription )
var killClient bool
if andKill {
var clientsToKill [ ] * Client
var killedClientNicks [ ] string
for _ , mcl := range server . clients . AllClients ( ) {
2019-01-22 11:01:01 +01:00
if hostNet . Contains ( mcl . IP ( ) ) {
2018-02-03 10:28:02 +01:00
clientsToKill = append ( clientsToKill , mcl )
killedClientNicks = append ( killedClientNicks , mcl . nick )
}
}
for _ , mcl := range clientsToKill {
mcl . exitedSnomaskSent = true
2019-04-12 06:08:46 +02:00
mcl . Quit ( fmt . Sprintf ( mcl . t ( "You have been banned from this server (%s)" ) , reason ) , nil )
2018-02-03 10:28:02 +01:00
if mcl == client {
killClient = true
} else {
// if mcl == client, we kill them below
2019-04-12 06:08:46 +02:00
mcl . destroy ( false , nil )
2018-02-03 10:28:02 +01:00
}
}
// 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" {
2019-02-19 08:54:57 +01:00
client . sendHelp ( "HELP" , server . helpIndexManager . GetIndex ( client . Languages ( ) , client . HasMode ( modes . Operator ) ) , rb )
2018-02-03 10:28:02 +01:00
return false
}
helpHandler , exists := Help [ argument ]
2018-04-23 00:47:10 +02:00
if exists && ( ! helpHandler . oper || ( helpHandler . oper && client . HasMode ( modes . Operator ) ) ) {
2018-02-03 10:28:02 +01:00
if helpHandler . textGenerator != nil {
2018-02-05 15:21:08 +01:00
client . sendHelp ( strings . ToUpper ( argument ) , client . t ( helpHandler . textGenerator ( client ) ) , rb )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
client . sendHelp ( strings . ToUpper ( argument ) , client . t ( helpHandler . text ) , rb )
2018-02-03 10:28:02 +01:00
}
} else {
args := msg . Params
args = append ( args , client . t ( "Help not found" ) )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_HELPNOTFOUND , args ... )
2018-02-03 10:28:02 +01:00
}
return false
}
2019-02-04 11:18:17 +01:00
// HISTORY <target> [<limit>]
// e.g., HISTORY #ubuntu 10
// HISTORY me 15
func historyHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-02-04 18:16:28 +01:00
config := server . Config ( )
2019-05-12 09:27:02 +02:00
if ! config . History . Enabled {
rb . Notice ( client . t ( "This command has been disabled by the server administrators" ) )
return false
}
2019-02-04 11:18:17 +01:00
target := msg . Params [ 0 ]
var hist * history . Buffer
channel := server . channels . Get ( target )
2019-02-13 21:29:36 +01:00
if channel != nil && channel . hasClient ( client ) {
2019-02-04 18:31:44 +01:00
hist = & channel . history
} else {
if strings . ToLower ( target ) == "me" {
2019-02-04 11:18:17 +01:00
hist = client . history
} else {
2019-02-04 18:31:44 +01:00
targetClient := server . clients . Get ( target )
if targetClient != nil {
myAccount , targetAccount := client . Account ( ) , targetClient . Account ( )
if myAccount != "" && targetAccount != "" && myAccount == targetAccount {
hist = targetClient . history
}
}
2019-02-04 11:18:17 +01:00
}
2019-02-04 18:31:44 +01:00
}
if hist == nil {
2019-02-13 21:29:36 +01:00
if channel == nil {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , target , client . t ( "No such channel" ) )
} else {
rb . Add ( nil , server . name , ERR_NOTONCHANNEL , client . Nick ( ) , target , client . t ( "You're not on that channel" ) )
}
2019-02-04 18:31:44 +01:00
return false
2019-02-04 11:18:17 +01:00
}
limit := 10
2019-02-04 18:16:28 +01:00
maxChathistoryLimit := config . History . ChathistoryMax
2019-02-04 11:18:17 +01:00
if len ( msg . Params ) > 1 {
providedLimit , err := strconv . Atoi ( msg . Params [ 1 ] )
if providedLimit > maxChathistoryLimit {
providedLimit = maxChathistoryLimit
}
if err == nil && providedLimit != 0 {
limit = providedLimit
}
}
items := hist . Latest ( limit )
if channel != nil {
2019-05-07 05:17:57 +02:00
channel . replayHistoryItems ( rb , items , false )
2019-02-04 11:18:17 +01:00
} else {
client . replayPrivmsgHistory ( rb , items , true )
}
return false
}
2018-02-03 10:28:02 +01:00
// 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
2019-02-19 08:54:57 +01:00
tlines := server . Languages ( ) . Translators ( )
2018-02-03 10:28:02 +01:00
if 0 < len ( tlines ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Translators:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range tlines {
2019-02-03 11:21:07 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , " " + strings . Replace ( line , "\n" , ", " , - 1 ) )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , "" )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ENDOFINFO , client . nick , client . t ( "End of /INFO" ) )
2018-02-03 10:28:02 +01:00
return false
}
// INVITE <nickname> <channel>
2018-02-05 15:21:08 +01:00
func inviteHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname := msg . Params [ 0 ]
channelName := msg . Params [ 1 ]
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
casefoldedChannelName , err := CasefoldChannel ( channelName )
channel := server . channels . Get ( casefoldedChannelName )
if err != nil || channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , channelName , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-05 15:21:08 +01:00
channel . Invite ( target , client , rb )
2018-02-03 10:28:02 +01:00
return false
}
// ISON <nick>{ <nick>}
2018-02-05 15:21:08 +01:00
func isonHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var nicks = msg . Params
2019-05-10 07:44:14 +02:00
ison := make ( [ ] string , 0 , len ( msg . Params ) )
2018-02-03 10:28:02 +01:00
for _ , nick := range nicks {
2019-05-15 03:00:00 +02:00
currentNick := server . getCurrentNick ( nick )
if currentNick != "" {
ison = append ( ison , currentNick )
2018-02-03 10:28:02 +01:00
}
}
2019-05-10 07:44:14 +02:00
rb . Add ( nil , server . name , RPL_ISON , client . nick , strings . Join ( ison , " " ) )
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 ] , "," )
}
2019-02-06 10:55:05 +01:00
config := server . Config ( )
oper := client . Oper ( )
2018-02-03 10:28:02 +01:00
for i , name := range channels {
2019-02-06 10:55:05 +01:00
if config . Channels . MaxChannelsPerClient <= client . NumChannels ( ) && oper == nil {
2019-02-06 21:47:20 +01:00
rb . Add ( nil , server . name , ERR_TOOMANYCHANNELS , client . Nick ( ) , name , client . t ( "You have joined too many channels" ) )
2019-02-06 10:55:05 +01:00
return false
}
2018-02-03 10:28:02 +01:00
var key string
if len ( keys ) > i {
key = keys [ i ]
}
2018-05-25 08:46:36 +02:00
err := server . channels . Join ( client , name , key , false , rb )
2018-02-03 13:03:36 +01:00
if err == errNoSuchChannel {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , name , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2018-05-25 08:46:36 +02:00
// SAJOIN [nick] #channel{,#channel}
func sajoinHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
var target * Client
var channelString string
if strings . HasPrefix ( msg . Params [ 0 ] , "#" ) {
target = client
channelString = msg . Params [ 0 ]
} else {
if len ( msg . Params ) == 1 {
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , "KICK" , client . t ( "Not enough parameters" ) )
return false
} else {
target = server . clients . Get ( msg . Params [ 0 ] )
if target == nil {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . Nick ( ) , msg . Params [ 0 ] , "No such nick" )
return false
}
channelString = msg . Params [ 1 ]
}
}
channels := strings . Split ( channelString , "," )
for _ , chname := range channels {
server . channels . Join ( target , chname , "" , true , rb )
}
return false
}
2018-02-03 10:28:02 +01:00
// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
2018-02-05 15:21:08 +01:00
func kickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
users := strings . Split ( msg . Params [ 1 ] , "," )
if ( len ( channels ) != len ( users ) ) && ( len ( users ) != 1 ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , "KICK" , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
var kicks [ ] [ ] string
for index , channel := range channels {
if len ( users ) == 1 {
kicks = append ( kicks , [ ] string { channel , users [ 0 ] } )
} else {
kicks = append ( kicks , [ ] string { channel , users [ index ] } )
}
}
var comment string
if len ( msg . Params ) > 2 {
comment = msg . Params [ 2 ]
}
for _ , info := range kicks {
chname := info [ 0 ]
nickname := info [ 1 ]
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
if err != nil || channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
continue
}
if comment == "" {
comment = nickname
}
2018-02-05 15:21:08 +01:00
channel . Kick ( client , target , comment , rb )
2018-02-03 10:28:02 +01:00
}
return false
}
// KILL <nickname> <comment>
2018-02-05 15:21:08 +01:00
func killHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname := msg . Params [ 0 ]
comment := "<no reason supplied>"
if len ( msg . Params ) > 1 {
comment = msg . Params [ 1 ]
}
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
quitMsg := fmt . Sprintf ( "Killed (%s (%s))" , client . nick , comment )
server . snomasks . Send ( sno . LocalKills , fmt . Sprintf ( ircfmt . Unescape ( "%s$r was killed by %s $c[grey][$r%s$c[grey]]" ) , target . nick , client . nick , comment ) )
target . exitedSnomaskSent = true
2019-04-12 06:08:46 +02:00
target . Quit ( quitMsg , nil )
target . destroy ( false , nil )
2018-02-03 10:28:02 +01:00
return false
}
// KLINE [ANDKILL] [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]]
// KLINE LIST
2018-02-05 15:21:08 +01:00
func klineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_ban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
currentArg := 0
// if they say LIST, we just list the current klines
if len ( msg . Params ) == currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "list" {
bans := server . klines . AllBans ( )
if len ( bans ) == 0 {
client . Notice ( "No KLINEs have been set!" )
}
for key , info := range bans {
2019-01-22 11:01:01 +01:00
client . Notice ( formatBanForListing ( client , key , info ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// when setting a ban, if they say "ANDKILL" we should also kill all users who match it
var andKill bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "andkill" {
andKill = true
currentArg ++
}
// when setting a ban that covers the oper's current connection, we require them to say
// "KLINE MYSELF" so that we're sure they really mean it.
var klineMyself bool
if len ( msg . Params ) > currentArg + 1 && strings . ToLower ( msg . Params [ currentArg ] ) == "myself" {
klineMyself = true
currentArg ++
}
// duration
duration , err := custime . ParseDuration ( msg . Params [ currentArg ] )
2019-01-22 11:01:01 +01:00
if err != nil {
duration = 0
} else {
2018-02-03 10:28:02 +01:00
currentArg ++
}
// get mask
if len ( msg . Params ) < currentArg + 1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
mask := strings . ToLower ( msg . Params [ currentArg ] )
currentArg ++
// check mask
if ! strings . Contains ( mask , "!" ) && ! strings . Contains ( mask , "@" ) {
mask = mask + "!*@*"
} else if ! strings . Contains ( mask , "@" ) {
mask = mask + "@*"
}
matcher := ircmatch . MakeMatch ( mask )
for _ , clientMask := range client . AllNickmasks ( ) {
if ! klineMyself && matcher . Match ( clientMask ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>" ) )
2018-02-03 10:28:02 +01:00
return false
}
}
// check remote
if len ( msg . Params ) > currentArg && msg . Params [ currentArg ] == "ON" {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Remote servers not yet supported" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get oper name
2018-04-19 08:48:19 +02:00
operName := oper . Name
2018-02-03 10:28:02 +01:00
if operName == "" {
operName = server . name
}
// get comment(s)
2019-01-22 11:01:01 +01:00
reason , operReason := getReasonsFromParams ( msg . Params , currentArg )
2018-02-03 10:28:02 +01:00
2019-01-22 11:01:01 +01:00
err = server . klines . AddMask ( mask , duration , reason , operReason , operName )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Could not successfully save new K-LINE: %s" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
var snoDescription string
2019-01-22 11:01:01 +01:00
if duration != 0 {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added temporary (%[1]s) K-Line for %[2]s" ) , duration . String ( ) , mask ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added temporary (%s) K-Line for %s" ) , client . nick , operName , duration . String ( ) , mask )
} else {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added K-Line for %s" ) , mask ) )
2018-02-03 10:28:02 +01:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added K-Line for %s" ) , client . nick , operName , mask )
}
server . snomasks . Send ( sno . LocalXline , snoDescription )
var killClient bool
if andKill {
var clientsToKill [ ] * Client
var killedClientNicks [ ] string
for _ , mcl := range server . clients . AllClients ( ) {
for _ , clientMask := range mcl . AllNickmasks ( ) {
if matcher . Match ( clientMask ) {
clientsToKill = append ( clientsToKill , mcl )
killedClientNicks = append ( killedClientNicks , mcl . nick )
}
}
}
for _ , mcl := range clientsToKill {
mcl . exitedSnomaskSent = true
2019-04-12 06:08:46 +02:00
mcl . Quit ( fmt . Sprintf ( mcl . t ( "You have been banned from this server (%s)" ) , reason ) , nil )
2018-02-03 10:28:02 +01:00
if mcl == client {
killClient = true
} else {
// if mcl == client, we kill them below
2019-04-12 06:08:46 +02:00
mcl . destroy ( false , nil )
2018-02-03 10:28:02 +01:00
}
}
// 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 {
2019-02-19 08:54:57 +01:00
nick := client . Nick ( )
2018-02-03 10:28:02 +01:00
alreadyDoneLanguages := make ( map [ string ] bool )
var appliedLanguages [ ] string
2019-02-19 08:54:57 +01:00
lm := server . Languages ( )
supportedLanguagesCount := lm . Count ( )
2018-02-03 10:28:02 +01:00
if supportedLanguagesCount < len ( msg . Params ) {
2019-02-19 08:54:57 +01:00
rb . Add ( nil , client . server . name , ERR_TOOMANYLANGUAGES , 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
}
2019-02-19 08:54:57 +01:00
_ , exists := lm . Languages [ value ]
2018-02-03 10:28:02 +01:00
if ! exists {
2019-02-19 08:54:57 +01:00
rb . Add ( nil , client . server . name , ERR_NOLANGUAGE , nick , fmt . Sprintf ( client . t ( "Language %s is not supported by this server" ) , value ) )
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 )
}
2019-02-19 08:54:57 +01:00
var langsToSet [ ] string
if ! ( len ( appliedLanguages ) == 1 && appliedLanguages [ 0 ] == "en" ) {
langsToSet = appliedLanguages
2018-02-03 10:28:02 +01:00
}
2019-02-19 08:54:57 +01:00
client . SetLanguages ( langsToSet )
2018-02-03 10:28:02 +01:00
2019-02-19 08:54:57 +01:00
params := make ( [ ] string , len ( appliedLanguages ) + 2 )
params [ 0 ] = nick
copy ( params [ 1 : ] , appliedLanguages )
params [ len ( params ) - 1 ] = client . t ( "Language preferences have been set" )
2018-02-03 10:28:02 +01:00
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , RPL_YOURLANGUAGESARE , params ... )
2018-02-03 10:28:02 +01:00
return false
}
// LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
2018-02-05 15:21:08 +01:00
func listHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// get channels
var channels [ ] string
for _ , param := range msg . Params {
if 0 < len ( param ) && param [ 0 ] == '#' {
for _ , channame := range strings . Split ( param , "," ) {
if 0 < len ( channame ) && channame [ 0 ] == '#' {
channels = append ( channels , channame )
}
}
}
}
// get elist conditions
var matcher elistMatcher
for _ , param := range msg . Params {
if len ( param ) < 1 {
continue
}
if param [ 0 ] == '<' {
param = param [ 1 : ]
val , err := strconv . Atoi ( param )
if err != nil {
continue
}
matcher . MaxClientsActive = true
matcher . MaxClients = val - 1 // -1 because < means less than the given number
}
if param [ 0 ] == '>' {
param = param [ 1 : ]
val , err := strconv . Atoi ( param )
if err != nil {
continue
}
matcher . MinClientsActive = true
matcher . MinClients = val + 1 // +1 because > means more than the given number
}
}
2018-04-23 00:47:10 +02:00
clientIsOp := client . HasMode ( modes . Operator )
2018-02-03 10:28:02 +01:00
if len ( channels ) == 0 {
for _ , channel := range server . channels . Channels ( ) {
2018-04-23 00:47:10 +02:00
if ! clientIsOp && channel . flags . HasMode ( modes . Secret ) {
2018-02-03 10:28:02 +01:00
continue
}
if matcher . Matches ( channel ) {
2018-02-05 15:21:08 +01:00
client . RplList ( channel , rb )
2018-02-03 10:28:02 +01:00
}
}
} else {
// limit regular users to only listing one channel
2018-04-23 00:47:10 +02:00
if ! clientIsOp {
2018-02-03 10:28:02 +01:00
channels = channels [ : 1 ]
}
for _ , chname := range channels {
casefoldedChname , err := CasefoldChannel ( chname )
channel := server . channels . Get ( casefoldedChname )
2018-04-23 00:47:10 +02:00
if err != nil || channel == nil || ( ! clientIsOp && channel . flags . HasMode ( modes . Secret ) ) {
2018-02-03 10:28:02 +01:00
if len ( chname ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
continue
}
if matcher . Matches ( channel ) {
2018-02-05 15:21:08 +01:00
client . RplList ( channel , rb )
2018-02-03 10:28:02 +01:00
}
}
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_LISTEND , client . nick , client . t ( "End of LIST" ) )
2018-02-03 10:28:02 +01:00
return false
}
// LUSERS [<mask> [<server>]]
2018-02-05 15:21:08 +01:00
func lusersHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
//TODO(vegax87) Fix network statistics and additional parameters
2018-04-20 22:48:15 +02:00
totalCount , invisibleCount , operCount := server . stats . GetStats ( )
rb . Add ( nil , server . name , RPL_LUSERCLIENT , client . nick , fmt . Sprintf ( client . t ( "There are %[1]d users and %[2]d invisible on %[3]d server(s)" ) , totalCount - invisibleCount , invisibleCount , 1 ) )
rb . Add ( nil , server . name , RPL_LUSEROP , client . nick , strconv . Itoa ( operCount ) , client . t ( "IRC Operators online" ) )
rb . Add ( nil , server . name , RPL_LUSERCHANNELS , client . nick , strconv . Itoa ( server . channels . Len ( ) ) , client . t ( "channels formed" ) )
rb . Add ( nil , server . name , RPL_LUSERME , client . nick , fmt . Sprintf ( client . t ( "I have %[1]d clients and %[2]d servers" ) , totalCount , 1 ) )
2018-02-03 10:28:02 +01:00
return false
}
// MODE <target> [<modestring> [<mode arguments>...]]
2018-02-05 15:21:08 +01:00
func modeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
_ , errChan := CasefoldChannel ( msg . Params [ 0 ] )
if errChan == nil {
2018-02-05 15:21:08 +01:00
return cmodeHandler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-05 15:21:08 +01:00
return umodeHandler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-03 20:48:44 +01:00
// MODE <channel> [<modestring> [<mode arguments>...]]
2018-02-05 15:21:08 +01:00
func cmodeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channelName , err := CasefoldChannel ( msg . Params [ 0 ] )
channel := server . channels . Get ( channelName )
if err != nil || channel == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , msg . Params [ 0 ] , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
return false
}
// applied mode changes
2018-02-03 11:21:32 +01:00
applied := make ( modes . ModeChanges , 0 )
2018-02-03 10:28:02 +01:00
if 1 < len ( msg . Params ) {
// parse out real mode changes
params := msg . Params [ 1 : ]
2018-04-23 00:47:10 +02:00
changes , unknown := modes . ParseChannelModeChanges ( params ... )
2018-02-03 10:28:02 +01:00
// alert for unknown mode changes
for char := range unknown {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNMODE , client . nick , string ( char ) , client . t ( "is an unknown mode character to me" ) )
2018-02-03 10:28:02 +01:00
}
if len ( unknown ) == 1 && len ( changes ) == 0 {
return false
}
// apply mode changes
2018-02-05 15:21:08 +01:00
applied = channel . ApplyChannelModeChanges ( client , msg . Command == "SAMODE" , changes , rb )
2018-02-03 10:28:02 +01:00
}
2018-04-04 03:49:40 +02:00
// save changes
var includeFlags uint
2018-02-03 10:28:02 +01:00
for _ , change := range applied {
2018-04-04 03:49:40 +02:00
includeFlags |= IncludeModes
if change . Mode == modes . BanMask || change . Mode == modes . ExceptMask || change . Mode == modes . InviteMask {
includeFlags |= IncludeLists
2018-02-03 10:28:02 +01:00
}
}
2019-03-12 00:24:45 +01:00
if includeFlags != 0 {
channel . MarkDirty ( includeFlags )
2018-02-03 10:28:02 +01:00
}
// send out changes
2019-04-12 06:08:46 +02:00
prefix := client . NickMaskString ( )
2018-02-03 10:28:02 +01:00
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 {
2019-04-12 06:08:46 +02:00
rb . Add ( nil , prefix , "MODE" , args ... )
for _ , session := range client . Sessions ( ) {
if session != rb . session {
session . Send ( nil , prefix , "MODE" , args ... )
}
}
2018-02-05 15:21:08 +01:00
} else {
2019-04-12 06:08:46 +02:00
member . Send ( nil , prefix , "MODE" , args ... )
2018-02-05 15:21:08 +01:00
}
2018-02-03 10:28:02 +01:00
}
} else {
args := append ( [ ] string { client . nick , channel . name } , channel . modeStrings ( client ) ... )
2019-04-12 06:08:46 +02:00
rb . Add ( nil , prefix , RPL_CHANNELMODEIS , args ... )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . nickMaskString , RPL_CHANNELCREATED , client . nick , channel . name , strconv . FormatInt ( channel . createdTime . Unix ( ) , 10 ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// MODE <client> [<modestring> [<mode arguments>...]]
2018-02-05 15:21:08 +01:00
func umodeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname , err := CasefoldName ( msg . Params [ 0 ] )
target := server . clients . Get ( nickname )
if err != nil || target == nil {
if len ( msg . Params [ 0 ] ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , msg . Params [ 0 ] , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
targetNick := target . Nick ( )
hasPrivs := client == target || msg . Command == "SAMODE"
if ! hasPrivs {
if len ( msg . Params ) > 1 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_USERSDONTMATCH , client . nick , client . t ( "Can't change modes for other users" ) )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_USERSDONTMATCH , client . nick , client . t ( "Can't view modes for other users" ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// applied mode changes
2018-02-03 11:21:32 +01:00
applied := make ( modes . ModeChanges , 0 )
2018-02-03 10:28:02 +01:00
if 1 < len ( msg . Params ) {
// parse out real mode changes
params := msg . Params [ 1 : ]
2018-02-03 11:21:32 +01:00
changes , unknown := modes . ParseUserModeChanges ( params ... )
2018-02-03 10:28:02 +01:00
// alert for unknown mode changes
for char := range unknown {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNMODE , client . nick , string ( char ) , client . t ( "is an unknown mode character to me" ) )
2018-02-03 10:28:02 +01:00
}
if len ( unknown ) == 1 && len ( changes ) == 0 {
return false
}
// apply mode changes
2018-04-23 00:47:10 +02:00
applied = ApplyUserModeChanges ( client , changes , msg . Command == "SAMODE" )
2018-02-03 10:28:02 +01:00
}
if len ( applied ) > 0 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . nickMaskString , "MODE" , targetNick , applied . String ( ) )
2018-02-03 10:28:02 +01:00
} else if hasPrivs {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , target . nickMaskString , RPL_UMODEIS , targetNick , target . ModeString ( ) )
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . LocalOperator ) || client . HasMode ( modes . Operator ) {
2018-02-03 10:28:02 +01:00
masks := server . snomasks . String ( client )
if 0 < len ( masks ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , target . nickMaskString , RPL_SNOMASKIS , targetNick , masks , client . t ( "Server notice masks" ) )
2018-02-03 10:28:02 +01:00
}
}
}
return false
}
2019-05-15 03:00:00 +02:00
// get the correct capitalization of a nick (if it's online), otherwise return ""
func ( server * Server ) getCurrentNick ( nick string ) ( result string ) {
if service , isService := OragonoServices [ strings . ToLower ( nick ) ] ; isService {
return service . Name
} else if iclient := server . clients . Get ( nick ) ; iclient != nil {
return iclient . Nick ( )
}
return ""
}
2018-02-03 20:48:44 +01:00
// MONITOR <subcmd> [params...]
2018-02-05 15:21:08 +01:00
func monitorHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-04-12 02:11:45 +02:00
handler , exists := monitorSubcommands [ strings . ToLower ( msg . Params [ 0 ] ) ]
2018-02-03 10:28:02 +01:00
if ! exists {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , "MONITOR" , msg . Params [ 0 ] , client . t ( "Unknown subcommand" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-05 15:21:08 +01:00
return handler ( server , client , msg , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-03 20:48:44 +01:00
// MONITOR - <target>{,<target>}
2018-02-05 15:21:08 +01:00
func monitorRemoveHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) < 2 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
targets := strings . Split ( msg . Params [ 1 ] , "," )
for _ , target := range targets {
cfnick , err := CasefoldName ( target )
if err != nil {
continue
}
server . monitorManager . Remove ( client , cfnick )
}
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR + <target>{,<target>}
2018-02-05 15:21:08 +01:00
func monitorAddHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) < 2 {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
var online [ ] string
var offline [ ] string
2018-07-16 09:46:40 +02:00
limits := server . Limits ( )
2018-02-03 10:28:02 +01:00
targets := strings . Split ( msg . Params [ 1 ] , "," )
for _ , target := range targets {
// check name length
2018-07-16 09:46:40 +02:00
if len ( target ) < 1 || len ( targets ) > limits . NickLen {
2018-02-03 10:28:02 +01:00
continue
}
// add target
casefoldedTarget , err := CasefoldName ( target )
if err != nil {
continue
}
2018-07-16 09:46:40 +02:00
err = server . monitorManager . Add ( client , casefoldedTarget , limits . MonitorEntries )
2018-02-03 13:03:36 +01:00
if err == errMonitorLimitExceeded {
2018-07-16 09:46:40 +02:00
rb . Add ( nil , server . name , ERR_MONLISTFULL , client . Nick ( ) , strconv . Itoa ( limits . MonitorEntries ) , strings . Join ( targets , "," ) )
2018-02-03 10:28:02 +01:00
break
} else if err != nil {
continue
}
2019-05-15 03:00:00 +02:00
currentNick := server . getCurrentNick ( target )
2018-02-03 10:28:02 +01:00
// add to online / offline lists
2019-05-15 03:00:00 +02:00
if currentNick != "" {
online = append ( online , currentNick )
2018-02-03 10:28:02 +01:00
} else {
2019-05-15 03:00:00 +02:00
offline = append ( offline , target )
2018-02-03 10:28:02 +01:00
}
}
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 {
2019-02-17 20:29:04 +01:00
nick := client . Nick ( )
2018-02-03 10:28:02 +01:00
monitorList := server . monitorManager . List ( client )
var nickList [ ] string
for _ , cfnick := range monitorList {
replynick := cfnick
2019-05-15 03:00:00 +02:00
currentNick := server . getCurrentNick ( cfnick )
2018-02-03 10:28:02 +01:00
// report the uncasefolded nick if it's available, i.e., the client is online
2019-05-15 03:00:00 +02:00
if currentNick != "" {
replynick = currentNick
2018-02-03 10:28:02 +01:00
}
nickList = append ( nickList , replynick )
}
for _ , line := range utils . ArgsToStrings ( maxLastArgLength , nickList , "," ) {
2019-02-17 20:29:04 +01:00
rb . Add ( nil , server . name , RPL_MONLIST , nick , line )
2018-02-03 10:28:02 +01:00
}
2019-02-17 20:29:04 +01:00
rb . Add ( nil , server . name , RPL_ENDOFMONLIST , nick , "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 {
2019-05-15 03:00:00 +02:00
currentNick := server . getCurrentNick ( name )
if currentNick != "" {
online = append ( online , currentNick )
2018-02-03 10:28:02 +01:00
} else {
2019-05-15 03:00:00 +02:00
offline = append ( offline , name )
2018-02-03 10:28:02 +01:00
}
}
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 {
2019-02-05 06:19:03 +01:00
if client . registered {
2019-04-12 06:08:46 +02:00
performNickChange ( server , client , client , nil , msg . Params [ 0 ] , rb )
2018-02-27 03:44:03 +01:00
} else {
2019-01-02 05:45:47 +01:00
client . preregNick = msg . Params [ 0 ]
2018-02-03 10:28:02 +01:00
}
2018-02-27 03:44:03 +01:00
return false
2018-02-03 10:28:02 +01:00
}
// NOTICE <target>{,<target>} <message>
2019-03-19 08:35:49 +01:00
// PRIVMSG <target>{,<target>} <message>
// TAGMSG <target>{,<target>}
func messageHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
histType , err := msgCommandToHistType ( server , msg . Command )
if err != nil {
return false
}
cnick := client . Nick ( )
2019-03-07 08:31:46 +01:00
clientOnlyTags := msg . ClientOnlyTags ( )
2019-03-19 08:35:49 +01:00
if histType == history . Tagmsg && len ( clientOnlyTags ) == 0 {
// nothing to do
return false
}
2018-02-03 10:28:02 +01:00
targets := strings . Split ( msg . Params [ 0 ] , "," )
2019-03-19 08:35:49 +01:00
var message string
if len ( msg . Params ) > 1 {
message = msg . Params [ 1 ]
}
// note that error replies are never sent for NOTICE
2018-02-03 10:28:02 +01:00
2019-02-26 03:50:43 +01:00
if client . isTor && isRestrictedCTCPMessage ( message ) {
2019-03-19 08:35:49 +01:00
if histType != history . Notice {
rb . Add ( nil , server . name , "NOTICE" , client . t ( "CTCP messages are disabled over Tor" ) )
}
2019-02-26 03:50:43 +01:00
return false
}
2018-02-03 10:28:02 +01:00
for i , targetString := range targets {
2019-03-19 10:51:33 +01:00
// each target gets distinct msgids
2019-04-12 06:08:46 +02:00
splitMsg := utils . MakeSplitMessage ( message , ! rb . session . capabilities . Has ( caps . MaxLine ) )
2019-03-19 10:51:33 +01:00
2018-02-03 10:28:02 +01:00
// 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
2019-03-19 08:35:49 +01:00
if len ( targetString ) == 0 {
continue
} else if targetString [ 0 ] == '#' {
channel := server . channels . Get ( targetString )
2018-02-03 10:28:02 +01:00
if channel == nil {
2019-03-19 08:35:49 +01:00
if histType != history . Notice {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , cnick , targetString , client . t ( "No such channel" ) )
}
2018-02-03 10:28:02 +01:00
continue
}
2019-03-19 08:35:49 +01:00
channel . SendSplitMessage ( msg . Command , lowestPrefix , clientOnlyTags , client , splitMsg , rb )
2018-02-03 10:28:02 +01:00
} else {
2019-05-21 01:08:57 +02:00
// NOTICE and TAGMSG to services are ignored
if histType == history . Privmsg {
lowercaseTarget := strings . ToLower ( targetString )
if service , isService := OragonoServices [ lowercaseTarget ] ; isService {
2019-03-19 08:35:49 +01:00
servicePrivmsgHandler ( service , server , client , message , rb )
2019-05-21 01:08:57 +02:00
continue
} else if _ , isZNC := zncHandlers [ lowercaseTarget ] ; isZNC {
zncPrivmsgHandler ( client , lowercaseTarget , message , rb )
continue
2019-03-19 08:35:49 +01:00
}
2018-02-03 10:28:02 +01:00
}
2019-03-19 08:35:49 +01:00
user := server . clients . Get ( targetString )
2018-02-03 10:28:02 +01:00
if user == nil {
2019-03-19 08:35:49 +01:00
if histType != history . Notice {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , cnick , targetString , "No such nick" )
}
2018-02-03 10:28:02 +01:00
continue
}
2019-03-19 08:35:49 +01:00
tnick := user . Nick ( )
nickMaskString := client . NickMaskString ( )
accountName := client . AccountName ( )
2018-02-03 10:28:02 +01:00
// restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine
2019-02-26 03:50:43 +01:00
allowedPlusR := ! user . HasMode ( modes . RegisteredOnly ) || client . LoggedIntoAccount ( )
allowedTor := ! user . isTor || ! isRestrictedCTCPMessage ( message )
if allowedPlusR && allowedTor {
2019-04-12 06:08:46 +02:00
for _ , session := range user . Sessions ( ) {
if histType == history . Tagmsg {
// don't send TAGMSG at all if they don't have the tags cap
if session . capabilities . Has ( caps . MessageTags ) {
2019-05-07 05:17:57 +02:00
session . sendFromClientInternal ( false , splitMsg . Time , splitMsg . Msgid , nickMaskString , accountName , clientOnlyTags , msg . Command , tnick )
2019-04-12 06:08:46 +02:00
}
} else {
2019-05-13 06:39:59 +02:00
session . sendSplitMsgFromClientInternal ( false , nickMaskString , accountName , clientOnlyTags , msg . Command , tnick , splitMsg )
2019-04-12 06:08:46 +02:00
}
2019-03-19 08:35:49 +01:00
}
2018-02-03 10:28:02 +01:00
}
2019-04-12 06:08:46 +02:00
// an echo-message may need to be included in the response:
if rb . session . capabilities . Has ( caps . EchoMessage ) {
if histType == history . Tagmsg && rb . session . capabilities . Has ( caps . MessageTags ) {
2019-05-07 05:17:57 +02:00
rb . AddFromClient ( splitMsg . Time , splitMsg . Msgid , nickMaskString , accountName , clientOnlyTags , msg . Command , tnick )
2019-03-19 08:35:49 +01:00
} else {
rb . AddSplitMessageFromClient ( nickMaskString , accountName , clientOnlyTags , msg . Command , tnick , splitMsg )
}
}
2019-04-12 06:08:46 +02:00
// an echo-message may need to go out to other client sessions:
for _ , session := range client . Sessions ( ) {
2019-05-19 10:27:44 +02:00
if session == rb . session {
2019-04-12 06:08:46 +02:00
continue
}
if histType == history . Tagmsg && rb . session . capabilities . Has ( caps . MessageTags ) {
2019-05-07 05:17:57 +02:00
session . sendFromClientInternal ( false , splitMsg . Time , splitMsg . Msgid , nickMaskString , accountName , clientOnlyTags , msg . Command , tnick )
2019-05-19 10:27:44 +02:00
} else if histType != history . Tagmsg {
2019-05-13 06:39:59 +02:00
session . sendSplitMsgFromClientInternal ( false , nickMaskString , accountName , clientOnlyTags , msg . Command , tnick , splitMsg )
2019-04-12 06:08:46 +02:00
}
}
2019-04-28 21:10:03 +02:00
if histType != history . Notice && user . Away ( ) {
2019-03-19 08:35:49 +01:00
//TODO(dan): possibly implement cooldown of away notifications to users
rb . Add ( nil , server . name , RPL_AWAY , cnick , tnick , user . AwayMessage ( ) )
2018-02-03 10:28:02 +01:00
}
2018-11-26 11:23:27 +01:00
2019-05-19 08:14:36 +02:00
item := history . Item {
2019-03-19 08:35:49 +01:00
Type : histType ,
2018-11-26 11:23:27 +01:00
Message : splitMsg ,
2018-12-28 19:45:55 +01:00
Nick : nickMaskString ,
AccountName : accountName ,
2019-05-19 08:14:36 +02:00
}
// add to the target's history:
user . history . Add ( item )
// add this to the client's history as well, recording the target:
item . Params [ 0 ] = tnick
client . history . Add ( item )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2018-02-03 20:48:44 +01:00
// NPC <target> <sourcenick> <message>
2018-02-05 15:21:08 +01:00
func npcHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
fakeSource := msg . Params [ 1 ]
message := msg . Params [ 2 ]
_ , err := CasefoldName ( fakeSource )
if err != nil {
client . Send ( nil , client . server . name , ERR_CANNOTSENDRP , target , client . t ( "Fake source must be a valid nickname" ) )
return false
}
sourceString := fmt . Sprintf ( npcNickMask , fakeSource , client . nick )
2018-02-05 15:21:08 +01:00
sendRoleplayMessage ( server , client , sourceString , target , false , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// NPCA <target> <sourcenick> <message>
2018-02-05 15:21:08 +01:00
func npcaHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
fakeSource := msg . Params [ 1 ]
message := msg . Params [ 2 ]
sourceString := fmt . Sprintf ( npcNickMask , fakeSource , client . nick )
_ , err := CasefoldName ( fakeSource )
if err != nil {
client . Send ( nil , client . server . name , ERR_CANNOTSENDRP , target , client . t ( "Fake source must be a valid nickname" ) )
return false
}
2018-02-05 15:21:08 +01:00
sendRoleplayMessage ( server , client , sourceString , target , true , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
// OPER <name> <password>
2018-02-05 15:21:08 +01:00
func operHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-05-10 07:44:14 +02:00
if client . HasMode ( modes . Operator ) {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , "OPER" , client . t ( "You're already opered-up!" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-04-19 08:48:19 +02:00
authorized := false
oper := server . GetOperator ( msg . Params [ 0 ] )
if oper != nil {
password := [ ] byte ( msg . Params [ 1 ] )
authorized = ( bcrypt . CompareHashAndPassword ( oper . Pass , password ) == nil )
}
if ! authorized {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , server . name , ERR_PASSWDMISMATCH , client . Nick ( ) , client . t ( "Password incorrect" ) )
2019-04-12 06:08:46 +02:00
client . Quit ( client . t ( "Password incorrect" ) , rb . session )
2018-02-03 10:28:02 +01:00
return true
}
2018-04-19 08:48:19 +02:00
oldNickmask := client . NickMaskString ( )
client . SetOper ( oper )
if client . NickMaskString ( ) != oldNickmask {
client . sendChghost ( oldNickmask , oper . Vhost )
2018-02-03 10:28:02 +01:00
}
2018-04-23 00:47:10 +02:00
// set new modes: modes.Operator, plus anything specified in the config
modeChanges := make ( [ ] modes . ModeChange , len ( oper . Modes ) + 1 )
modeChanges [ 0 ] = modes . ModeChange {
Mode : modes . Operator ,
Op : modes . Add ,
2018-02-03 10:28:02 +01:00
}
2018-04-23 00:47:10 +02:00
copy ( modeChanges [ 1 : ] , oper . Modes )
applied := ApplyUserModeChanges ( client , modeChanges , true )
2018-02-03 10:28:02 +01:00
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_YOUREOPER , client . nick , client . t ( "You are now an IRC operator" ) )
rb . Add ( nil , server . name , "MODE" , client . nick , applied . String ( ) )
2018-02-03 10:28:02 +01:00
2018-04-19 08:48:19 +02:00
server . snomasks . Send ( sno . LocalOpers , fmt . Sprintf ( ircfmt . Unescape ( "Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]" ) , client . nickMaskString , oper . Name ) )
2018-03-22 16:04:21 +01:00
// client may now be unthrottled by the fakelag system
2019-04-12 06:08:46 +02:00
for _ , session := range client . Sessions ( ) {
session . resetFakelag ( )
}
2018-03-22 16:04:21 +01:00
2018-02-03 10:28:02 +01:00
return false
}
// PART <channel>{,<channel>} [<reason>]
2018-02-05 15:21:08 +01:00
func partHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
var reason string //TODO(dan): if this isn't supplied here, make sure the param doesn't exist in the PART message sent to other users
if len ( msg . Params ) > 1 {
reason = msg . Params [ 1 ]
}
for _ , chname := range channels {
2018-02-05 15:21:08 +01:00
err := server . channels . Part ( client , chname , reason , rb )
2018-02-03 13:03:36 +01:00
if err == errNoSuchChannel {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , chname , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}
// PASS <password>
2018-02-05 15:21:08 +01:00
func passHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-02-05 06:19:03 +01:00
if client . registered {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_ALREADYREGISTRED , client . nick , client . t ( "You may not reregister" ) )
2018-02-03 10:28:02 +01:00
return false
}
// if no password exists, skip checking
2018-07-16 09:46:40 +02:00
serverPassword := server . Password ( )
if serverPassword == nil {
2018-02-03 10:28:02 +01:00
return false
}
// check the provided password
password := [ ] byte ( msg . Params [ 0 ] )
2018-08-06 04:51:39 +02:00
if bcrypt . CompareHashAndPassword ( serverPassword , password ) != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_PASSWDMISMATCH , client . nick , client . t ( "Password incorrect" ) )
2019-04-12 06:08:46 +02:00
client . Quit ( client . t ( "Password incorrect" ) , rb . session )
2018-02-03 10:28:02 +01:00
return true
}
2019-02-05 06:19:03 +01:00
client . sentPassCommand = 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
}
2019-02-26 03:50:43 +01:00
func isRestrictedCTCPMessage ( message string ) bool {
// block all CTCP privmsgs to Tor clients except for ACTION
// DCC can potentially be used for deanonymization, the others for fingerprinting
return strings . HasPrefix ( message , "\x01" ) && ! strings . HasPrefix ( message , "\x01ACTION" )
}
2018-02-03 10:28:02 +01:00
// 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 ]
}
2019-04-12 06:08:46 +02:00
client . Quit ( reason , rb . session )
2018-02-03 10:28:02 +01:00
return true
}
// REHASH
2018-02-05 15:21:08 +01:00
func rehashHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-02-03 03:12:17 +01:00
server . logger . Info ( "server" , fmt . Sprintf ( "REHASH command used by %s" , client . nick ) )
2018-02-03 10:28:02 +01:00
err := server . rehash ( )
if err == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_REHASHING , client . nick , "ircd.yaml" , client . t ( "Rehashing" ) )
2018-02-03 10:28:02 +01:00
} else {
2019-02-03 03:12:17 +01:00
server . logger . Error ( "server" , fmt . Sprintln ( "Failed to rehash:" , err . Error ( ) ) )
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , "REHASH" , err . Error ( ) )
2018-02-03 10:28:02 +01:00
}
return false
}
// RENAME <oldchan> <newchan> [<reason>]
2018-02-05 15:21:08 +01:00
func renameHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) ( result bool ) {
2018-02-03 10:28:02 +01:00
result = false
2019-03-11 12:03:51 +01:00
oldName , newName := msg . Params [ 0 ] , msg . Params [ 1 ]
if newName == "" {
newName = "<empty>" // intentionally invalid channel name, will error as expected
2018-02-03 10:28:02 +01:00
}
2019-03-11 12:03:51 +01:00
var reason string
2018-02-03 10:28:02 +01:00
if 2 < len ( msg . Params ) {
reason = msg . Params [ 2 ]
}
channel := server . channels . Get ( oldName )
if channel == nil {
2019-03-11 12:03:51 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , oldName , client . t ( "No such channel" ) )
return false
2018-02-03 10:28:02 +01:00
}
2019-03-11 12:03:51 +01:00
if ! ( channel . ClientIsAtLeast ( client , modes . Operator ) || client . HasRoleCapabs ( "chanreg" ) ) {
rb . Add ( nil , server . name , ERR_CHANOPRIVSNEEDED , client . Nick ( ) , oldName , client . t ( "You're not a channel operator" ) )
return false
2018-02-03 10:28:02 +01:00
}
founder := channel . Founder ( )
2018-02-11 11:30:40 +01:00
if founder != "" && founder != client . Account ( ) {
2019-03-11 12:03:51 +01:00
rb . Add ( nil , server . name , ERR_CANNOTRENAME , client . Nick ( ) , oldName , newName , client . t ( "Only channel founders can change registered channels" ) )
2018-02-03 10:28:02 +01:00
return false
}
// perform the channel rename
2019-03-11 12:03:51 +01:00
err := server . channels . Rename ( oldName , newName )
if err == errInvalidChannelName {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , newName , client . t ( err . Error ( ) ) )
} else if err == errChannelNameInUse {
rb . Add ( nil , server . name , ERR_CHANNAMEINUSE , client . Nick ( ) , newName , client . t ( err . Error ( ) ) )
} else if err != nil {
rb . Add ( nil , server . name , ERR_CANNOTRENAME , client . Nick ( ) , oldName , newName , client . t ( "Cannot rename channel" ) )
}
2018-02-03 10:28:02 +01:00
if err != nil {
2019-03-11 12:03:51 +01:00
return false
2018-02-03 10:28:02 +01:00
}
// send RENAME messages
2019-03-11 23:58:28 +01:00
clientPrefix := client . NickMaskString ( )
2018-02-03 10:28:02 +01:00
for _ , mcl := range channel . Members ( ) {
2019-04-12 06:08:46 +02:00
for _ , mSession := range mcl . Sessions ( ) {
targetRb := rb
targetPrefix := clientPrefix
if mSession != rb . session {
targetRb = NewResponseBuffer ( mSession )
targetPrefix = mcl . NickMaskString ( )
2019-03-11 12:03:51 +01:00
}
2019-04-12 06:08:46 +02:00
if mSession . capabilities . Has ( caps . Rename ) {
if reason != "" {
targetRb . Add ( nil , clientPrefix , "RENAME" , oldName , newName , reason )
} else {
targetRb . Add ( nil , clientPrefix , "RENAME" , oldName , newName )
}
2019-03-11 12:03:51 +01:00
} else {
2019-04-12 06:08:46 +02:00
if reason != "" {
targetRb . Add ( nil , targetPrefix , "PART" , oldName , fmt . Sprintf ( mcl . t ( "Channel renamed: %s" ) , reason ) )
} else {
targetRb . Add ( nil , targetPrefix , "PART" , oldName , fmt . Sprintf ( mcl . t ( "Channel renamed" ) ) )
}
if mSession . capabilities . Has ( caps . ExtendedJoin ) {
targetRb . Add ( nil , targetPrefix , "JOIN" , newName , mcl . AccountName ( ) , mcl . Realname ( ) )
} else {
targetRb . Add ( nil , targetPrefix , "JOIN" , newName )
}
channel . SendTopic ( mcl , targetRb , false )
channel . Names ( mcl , targetRb )
2019-03-11 12:03:51 +01:00
}
2019-04-12 06:08:46 +02:00
if mcl != client {
targetRb . Send ( false )
2018-02-03 10:28:02 +01:00
}
}
}
return false
}
2019-02-12 06:27:57 +01:00
// RESUME <token> [timestamp]
2018-02-05 15:21:08 +01:00
func resumeHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-02-12 06:27:57 +01:00
token := msg . Params [ 0 ]
2018-02-03 10:28:02 +01:00
2019-02-05 06:19:03 +01:00
if client . registered {
2019-02-12 06:27:57 +01:00
rb . Add ( nil , server . name , "RESUME" , "ERR" , client . t ( "Cannot resume connection, connection registration has already been completed" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-11-26 11:23:27 +01:00
var timestamp time . Time
2019-02-12 06:27:57 +01:00
if 1 < len ( msg . Params ) {
ts , err := time . Parse ( IRCv3TimestampFormat , msg . Params [ 1 ] )
2018-02-03 10:28:02 +01:00
if err == nil {
2018-11-26 11:23:27 +01:00
timestamp = ts
2018-02-03 10:28:02 +01:00
} else {
2019-02-12 18:53:58 +01:00
rb . Add ( nil , server . name , "RESUME" , "WARN" , client . t ( "Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it" ) )
2018-02-03 10:28:02 +01:00
}
}
client . resumeDetails = & ResumeDetails {
2018-11-26 11:23:27 +01:00
Timestamp : timestamp ,
PresentedToken : token ,
2018-02-03 10:28:02 +01:00
}
return false
}
// SANICK <oldnick> <nickname>
2018-02-05 15:21:08 +01:00
func sanickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
targetNick := strings . TrimSpace ( msg . Params [ 0 ] )
target := server . clients . Get ( targetNick )
if target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , msg . Params [ 0 ] , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-04-12 06:08:46 +02:00
performNickChange ( server , client , target , nil , msg . Params [ 1 ] , rb )
2018-02-27 03:44:03 +01:00
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
}
2019-02-13 14:22:16 +01:00
// SETNAME <realname>
func setnameHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
realname := msg . Params [ 0 ]
client . stateMutex . Lock ( )
client . realname = realname
client . stateMutex . Unlock ( )
2019-04-12 06:08:46 +02:00
details := client . Details ( )
2019-02-13 14:22:16 +01:00
// alert friends
2019-04-12 06:08:46 +02:00
now := time . Now ( ) . UTC ( )
for session := range client . Friends ( caps . SetName ) {
session . sendFromClientInternal ( false , now , "" , details . nickMask , details . account , nil , "SETNAME" , details . realname )
2019-02-13 14:22:16 +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 {
2019-05-12 09:12:50 +02:00
rb . Add ( nil , server . name , RPL_TIME , client . nick , server . name , time . Now ( ) . UTC ( ) . 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 {
2019-02-13 19:22:00 +01:00
channel . SendTopic ( client , rb , true )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// UNDLINE <ip>|<net>
2018-02-05 15:21:08 +01:00
func unDLineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_unban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get host
hostString := msg . Params [ 0 ]
// check host
2019-01-22 11:01:01 +01:00
hostNet , err := utils . NormalizedNetFromString ( hostString )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "Could not parse IP address or CIDR network" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-01-22 11:01:01 +01:00
err = server . dlines . RemoveNetwork ( hostNet )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , fmt . Sprintf ( client . t ( "Could not remove ban [%s]" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-01-22 11:01:01 +01:00
hostString = utils . NetToNormalizedString ( hostNet )
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Removed D-Line for %s" ) , hostString ) )
2018-02-03 10:28:02 +01:00
server . snomasks . Send ( sno . LocalXline , fmt . Sprintf ( ircfmt . Unescape ( "%s$r removed D-Line for %s" ) , client . nick , hostString ) )
return false
}
2018-02-03 20:48:44 +01:00
// UNKLINE <mask>
2018-02-05 15:21:08 +01:00
func unKLineHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
// check oper permissions
2018-04-19 08:48:19 +02:00
oper := client . Oper ( )
if oper == nil || ! oper . Class . Capabilities [ "oper:local_unban" ] {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NOPRIVS , client . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get host
mask := msg . Params [ 0 ]
if ! strings . Contains ( mask , "!" ) && ! strings . Contains ( mask , "@" ) {
mask = mask + "!*@*"
} else if ! strings . Contains ( mask , "@" ) {
mask = mask + "@*"
}
2019-01-22 11:01:01 +01:00
err := server . klines . RemoveMask ( mask )
2018-02-03 10:28:02 +01:00
if err != nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , fmt . Sprintf ( client . t ( "Could not remove ban [%s]" ) , err . Error ( ) ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Removed K-Line for %s" ) , mask ) )
2018-02-03 10:28:02 +01:00
server . snomasks . Send ( sno . LocalXline , fmt . Sprintf ( ircfmt . Unescape ( "%s$r removed K-Line for %s" ) , client . nick , mask ) )
return false
}
// USER <username> * 0 <realname>
2018-02-05 15:21:08 +01:00
func userHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-02-05 06:19:03 +01:00
if client . registered {
2019-03-19 08:35:49 +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
}
2019-02-05 08:40:49 +01:00
err := client . SetNames ( msg . Params [ 0 ] , msg . Params [ 3 ] , false )
2018-11-26 11:23:27 +01:00
if err == errInvalidUsername {
2019-02-05 23:45:09 +01:00
// if client's using a unicode nick or something weird, let's just set 'em up with a stock username instead.
2019-02-05 23:33:15 +01:00
// fixes clients that just use their nick as a username so they can still use the interesting nick
2019-02-05 23:45:09 +01:00
if client . preregNick == msg . Params [ 0 ] {
2019-02-05 23:33:15 +01:00
client . SetNames ( "user" , msg . Params [ 3 ] , false )
} else {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , server . name , ERR_INVALIDUSERNAME , client . Nick ( ) , client . t ( "Malformed username" ) )
2019-02-05 23:33:15 +01:00
}
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// USERHOST <nickname>{ <nickname>}
2018-02-05 15:21:08 +01:00
func userhostHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
returnedNicks := make ( map [ string ] bool )
for i , nickname := range msg . Params {
if i >= 10 {
break
}
casefoldedNickname , err := CasefoldName ( nickname )
target := server . clients . Get ( casefoldedNickname )
if err != nil || target == nil {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , nickname , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
if returnedNicks [ casefoldedNickname ] {
continue
}
// to prevent returning multiple results for a single nick
returnedNicks [ casefoldedNickname ] = true
var isOper , isAway string
2018-04-23 00:47:10 +02:00
if target . HasMode ( modes . Operator ) {
2018-02-03 10:28:02 +01:00
isOper = "*"
}
2019-04-28 21:10:03 +02:00
if target . 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
2019-02-05 06:19: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
2019-04-12 06:08:46 +02:00
if client . HasMode ( modes . TLS ) || client . realIP . IsLoopback ( ) {
2018-02-03 10:28:02 +01:00
secure = true
}
}
}
}
2019-02-05 06:19:03 +01:00
givenPassword := [ ] byte ( msg . Params [ 0 ] )
for _ , info := range server . Config ( ) . Server . WebIRC {
if utils . IPInNets ( client . realIP , info . allowedNets ) {
// confirm password and/or fingerprint
if 0 < len ( info . Password ) && bcrypt . CompareHashAndPassword ( info . Password , givenPassword ) != nil {
continue
}
if 0 < len ( info . Fingerprint ) && client . certfp != info . Fingerprint {
continue
}
2018-02-03 10:28:02 +01:00
2019-02-05 06:19:03 +01:00
proxiedIP := msg . Params [ 3 ]
// see #211; websocket gateways will wrap ipv6 addresses in square brackets
// because IRC parameters can't start with :
if strings . HasPrefix ( proxiedIP , "[" ) && strings . HasSuffix ( proxiedIP , "]" ) {
proxiedIP = proxiedIP [ 1 : len ( proxiedIP ) - 1 ]
2018-02-03 10:28:02 +01:00
}
2019-05-13 07:54:50 +02:00
err , quitMsg := client . ApplyProxiedIP ( rb . session , proxiedIP , secure )
if err != nil {
client . Quit ( quitMsg , rb . session )
return true
} else {
return false
}
2018-02-03 10:28:02 +01:00
}
}
2019-04-12 06:08:46 +02:00
client . Quit ( client . t ( "WEBIRC command is not usable from your address or incorrect password given" ) , rb . session )
2018-02-03 10:28:02 +01:00
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 {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , "WHO" , client . t ( "Mask isn't valid" ) )
2018-02-03 10:28:02 +01:00
return false
}
mask = casefoldedMask
}
//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
//}
2019-04-29 03:09:56 +02:00
isOper := client . HasMode ( modes . Operator )
2018-02-03 10:28:02 +01:00
if mask [ 0 ] == '#' {
// TODO implement wildcard matching
//TODO(dan): ^ only for opers
channel := server . channels . Get ( mask )
2019-04-29 03:09:56 +02:00
if channel != nil {
isJoined := channel . hasClient ( client )
if ! channel . flags . HasMode ( modes . Secret ) || isJoined || isOper {
for _ , member := range channel . Members ( ) {
if ! member . HasMode ( modes . Invisible ) || isJoined || isOper {
client . rplWhoReply ( channel , member , rb )
}
2019-04-12 06:08:46 +02:00
}
}
2018-02-03 10:28:02 +01:00
}
} else {
for mclient := range server . clients . FindAll ( mask ) {
2018-02-05 15:21:08 +01:00
client . rplWhoReply ( nil , mclient , rb )
2018-02-03 10:28:02 +01:00
}
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHO , client . nick , mask , client . t ( "End of WHO list" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// WHOIS [<target>] <mask>{,<mask>}
2018-02-05 15:21:08 +01:00
func whoisHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var masksString string
//var target string
if len ( msg . Params ) > 1 {
//target = msg.Params[0]
masksString = msg . Params [ 1 ]
} else {
masksString = msg . Params [ 0 ]
}
if len ( strings . TrimSpace ( masksString ) ) < 1 {
2018-02-04 12:32:48 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "No masks given" ) )
2018-02-03 10:28:02 +01:00
return false
}
2018-12-30 12:45:23 +01:00
handleService := func ( nick string ) bool {
cfnick , _ := CasefoldName ( nick )
service , ok := OragonoServices [ cfnick ]
if ! ok {
return false
}
clientNick := client . Nick ( )
rb . Add ( nil , client . server . name , RPL_WHOISUSER , clientNick , service . Name , service . Name , "localhost" , "*" , fmt . Sprintf ( client . t ( "Network service, for more info /msg %s HELP" ) , service . Name ) )
// hehe
if client . HasMode ( modes . TLS ) {
rb . Add ( nil , client . server . name , RPL_WHOISSECURE , clientNick , service . Name , client . t ( "is using a secure connection" ) )
}
return true
}
2018-04-23 00:47:10 +02:00
if client . HasMode ( modes . Operator ) {
2018-12-30 12:45:23 +01:00
for _ , mask := range strings . Split ( masksString , "," ) {
matches := server . clients . FindAll ( mask )
if len ( matches ) == 0 && ! handleService ( mask ) {
2018-02-04 12:32:48 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , mask , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
continue
}
for mclient := range matches {
2018-02-04 12:32:48 +01:00
client . getWhoisOf ( mclient , rb )
2018-02-03 10:28:02 +01:00
}
}
} else {
2018-12-30 12:45:23 +01:00
// only get the first request; also require a nick, not a mask
nick := strings . Split ( masksString , "," ) [ 0 ]
mclient := server . clients . Get ( nick )
if mclient != nil {
2018-02-04 12:32:48 +01:00
client . getWhoisOf ( mclient , rb )
2018-12-30 12:45:23 +01:00
} else if ! handleService ( nick ) {
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , masksString , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
}
2018-12-30 12:45:23 +01:00
// fall through, ENDOFWHOIS is always sent
2018-02-03 10:28:02 +01:00
}
2018-02-04 12:32:48 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHOIS , client . nick , masksString , client . t ( "End of /WHOIS list" ) )
2018-02-03 10:28:02 +01:00
return false
}
// WHOWAS <nickname> [<count> [<server>]]
2018-02-05 15:21:08 +01:00
func whowasHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nicknames := strings . Split ( msg . Params [ 0 ] , "," )
2018-05-04 06:24:54 +02:00
// 0 means "all the entries", as does a negative number
var count uint64
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 1 {
2018-05-04 06:24:54 +02:00
count , _ = strconv . ParseUint ( msg . Params [ 1 ] , 10 , 64 )
2018-02-03 10:28:02 +01:00
}
//var target string
//if len(msg.Params) > 2 {
// target = msg.Params[2]
//}
2018-05-04 06:24:54 +02:00
cnick := client . Nick ( )
2018-02-03 10:28:02 +01:00
for _ , nickname := range nicknames {
2018-05-04 06:24:54 +02:00
results := server . whoWas . Find ( nickname , int ( count ) )
2018-02-03 10:28:02 +01:00
if len ( results ) == 0 {
if len ( nickname ) > 0 {
2018-05-04 06:24:54 +02:00
rb . Add ( nil , server . name , ERR_WASNOSUCHNICK , cnick , nickname , client . t ( "There was no such nickname" ) )
2018-02-03 10:28:02 +01:00
}
} else {
for _ , whoWas := range results {
2019-01-01 19:00:16 +01:00
rb . Add ( nil , server . name , RPL_WHOWASUSER , cnick , whoWas . nick , whoWas . username , whoWas . hostname , "*" , whoWas . realname )
2018-02-03 10:28:02 +01:00
}
}
if len ( nickname ) > 0 {
2018-05-04 06:24:54 +02:00
rb . Add ( nil , server . name , RPL_ENDOFWHOWAS , cnick , nickname , client . t ( "End of WHOWAS" ) )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2019-05-21 01:08:57 +02:00
// ZNC <module> [params]
func zncHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
zncModuleHandler ( client , msg . Params [ 0 ] , msg . Params [ 1 : ] , rb )
return false
}