2018-02-03 10:28:02 +01:00
// Copyright (c) 2012-2014 Jeremy Latt
2018-02-03 10:48:30 +01:00
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2018 Daniel Oaks <daniel@danieloaks.net>
// Copyright (c) 2017-2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2018-02-03 10:28:02 +01:00
// released under the MIT license
package irc
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmatch"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/custime"
2018-11-26 11:23:27 +01:00
"github.com/oragono/oragono/irc/history"
2018-02-03 11:21:32 +01:00
"github.com/oragono/oragono/irc/modes"
2018-02-03 10:28:02 +01:00
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
2018-04-19 08:48:19 +02:00
"golang.org/x/crypto/bcrypt"
2018-02-03 10:28:02 +01:00
)
2018-02-20 10:44:44 +01:00
// helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234
2020-03-16 12:54:50 +01:00
func parseCallback ( spec string , config AccountConfig ) ( callbackNamespace string , callbackValue string ) {
2018-02-20 10:20:30 +01:00
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
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 ) {
2019-12-19 15:27:54 +01:00
details := client . Details ( )
2018-02-20 10:20:30 +01:00
if forNS {
2019-06-28 22:40:27 +02:00
nsNotice ( rb , client . t ( "Account created" ) )
2018-02-20 10:44:44 +01:00
} else {
2019-12-19 15:27:54 +01:00
rb . Add ( nil , client . server . name , RPL_REG_SUCCESS , details . nick , details . accountName , client . t ( "Account created" ) )
2018-02-20 10:20:30 +01:00
}
2019-12-19 15:27:54 +01:00
client . server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] registered account $c[grey][$r%s$c[grey]]" ) , details . nickMask , details . accountName ) )
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-06-28 22:40:27 +02:00
nsNotice ( rb , 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
}
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 {
2020-02-19 03:42:27 +01:00
session := rb . session
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-08-27 06:51:09 +02:00
if client . isSTSOnly {
rb . Add ( nil , server . name , ERR_SASLFAIL , details . nick , client . t ( "SASL authentication failed" ) )
return false
}
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
2020-03-16 12:54:50 +01:00
if ! config . Accounts . 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" ) )
2020-02-19 03:42:27 +01:00
session . sasl . Clear ( )
2018-02-03 10:28:02 +01:00
return false
}
// start new sasl session
2020-02-19 03:42:27 +01:00
if session . sasl . mechanism == "" {
2018-02-03 10:28:02 +01:00
mechanism := strings . ToUpper ( msg . Params [ 0 ] )
_ , mechanismIsEnabled := EnabledSaslMechanisms [ mechanism ]
if mechanismIsEnabled {
2020-02-19 03:42:27 +01:00
session . sasl . mechanism = 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" ) )
2020-02-19 03:42:27 +01:00
session . sasl . Clear ( )
2018-02-03 10:28:02 +01:00
return false
} else if len ( rawData ) == 400 {
// allow 4 'continuation' lines before rejecting for length
2020-02-19 03:42:27 +01:00
if len ( session . sasl . value ) >= 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" ) )
2020-02-19 03:42:27 +01:00
session . sasl . Clear ( )
2018-02-03 10:28:02 +01:00
return false
}
2020-02-19 03:42:27 +01:00
session . sasl . value += rawData
2018-02-03 10:28:02 +01:00
return false
}
if rawData != "+" {
2020-02-19 03:42:27 +01:00
session . sasl . value += rawData
2018-02-03 10:28:02 +01:00
}
var data [ ] byte
var err error
2020-02-19 03:42:27 +01:00
if session . sasl . value != "+" {
data , err = base64 . StdEncoding . DecodeString ( session . sasl . value )
2018-02-03 10:28:02 +01:00
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" ) )
2020-02-19 03:42:27 +01:00
session . sasl . Clear ( )
2018-02-03 10:28:02 +01:00
return false
}
}
// call actual handler
2020-02-19 03:42:27 +01:00
handler , handlerExists := EnabledSaslMechanisms [ session . sasl . mechanism ]
2018-02-03 10:28:02 +01:00
// 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" ) )
2020-02-19 03:42:27 +01:00
session . sasl . Clear ( )
2018-02-03 10:28:02 +01:00
return false
}
// let the SASL handler do its thing
2020-02-19 03:42:27 +01:00
exiting := handler ( server , client , session . sasl . mechanism , data , rb )
session . sasl . Clear ( )
2018-02-03 10:28:02 +01:00
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' } )
2019-12-24 18:46:31 +01:00
// PLAIN has separate "authorization ID" (which user you want to become)
// and "authentication ID" (whose password you want to use). the first is optional:
// [authzid] \x00 authcid \x00 password
var authzid , authcid string
2019-01-01 22:45:37 +01:00
2018-02-03 10:28:02 +01:00
if len ( splitValue ) == 3 {
2019-12-24 18:46:31 +01:00
authzid , authcid = string ( splitValue [ 0 ] ) , string ( splitValue [ 1 ] )
2018-02-03 10:28:02 +01:00
2019-12-24 18:46:31 +01:00
if authzid != "" && authcid != authzid {
rb . Add ( nil , server . name , ERR_SASLFAIL , client . Nick ( ) , client . t ( "SASL authentication failed: authcid and authzid should be the same" ) )
2018-02-03 10:28:02 +01:00
return false
}
} else {
2019-12-24 18:46:31 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . Nick ( ) , client . t ( "SASL authentication failed: Invalid auth blob" ) )
2019-01-01 22:45:37 +01:00
return false
}
throttled , remainingTime := client . loginThrottle . Touch ( )
if throttled {
2019-12-24 18:46:31 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . 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 ] )
2019-12-24 18:46:31 +01:00
err := server . accounts . AuthenticateByPassphrase ( client , authcid , 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-12-24 18:46:31 +01:00
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
2020-03-17 04:37:52 +01:00
} else if ! fixupNickEqualsAccount ( client , rb , server . Config ( ) ) {
return false
2018-02-03 10:28:02 +01:00
}
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 ) {
2019-12-25 21:06:26 +01:00
switch err {
2020-03-17 04:37:52 +01:00
case errAccountDoesNotExist , errAccountUnverified , errAccountInvalidCredentials , errAuthzidAuthcidMismatch , errNickAccountMismatch :
2019-12-25 21:06:26 +01:00
return err . Error ( )
default :
// don't expose arbitrary error messages to the user
2018-12-31 17:33:42 +01:00
server . logger . Error ( "internal" , "sasl authentication failure" , err . Error ( ) )
2019-12-25 21:06:26 +01:00
return "Unknown"
2018-02-11 11:30:40 +01:00
}
}
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 {
2020-02-19 03:42:27 +01:00
if rb . session . 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
}
2019-12-25 18:43:02 +01:00
// EXTERNAL doesn't carry an authentication ID (this is determined from the
// certificate), but does carry an optional authorization ID.
2019-12-25 21:06:26 +01:00
var authzid string
var err error
2019-12-25 18:43:02 +01:00
if len ( value ) != 0 {
2019-12-25 21:06:26 +01:00
authzid , err = CasefoldName ( string ( value ) )
if err != nil {
err = errAuthzidAuthcidMismatch
2019-12-25 18:43:02 +01:00
}
}
2019-12-25 21:06:26 +01:00
if err == nil {
2020-02-19 03:42:27 +01:00
err = server . accounts . AuthenticateByCertFP ( client , rb . session . certfp , authzid )
2019-12-25 21:06:26 +01:00
}
if err != nil {
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 ) ) )
return false
2020-03-17 04:37:52 +01:00
} else if ! fixupNickEqualsAccount ( client , rb , server . Config ( ) ) {
return false
2019-12-25 21:06:26 +01:00
}
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 ]
2019-05-23 01:07:12 +02:00
awayLen := server . Config ( ) . 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
}
2019-12-23 21:26:37 +01:00
// BATCH {+,-}reference-tag type [params...]
func batchHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
tag := msg . Params [ 0 ]
fail := false
sendErrors := rb . session . batch . command != "NOTICE"
if len ( tag ) == 0 {
fail = true
} else if tag [ 0 ] == '+' {
if rb . session . batch . label != "" || msg . Params [ 1 ] != caps . MultilineBatchType {
fail = true
} else {
rb . session . batch . label = tag [ 1 : ]
rb . session . batch . tags = msg . ClientOnlyTags ( )
if len ( msg . Params ) == 2 {
fail = true
} else {
rb . session . batch . target = msg . Params [ 2 ]
// save the response label for later
rb . session . batch . responseLabel = rb . Label
rb . Label = ""
}
}
} else if tag [ 0 ] == '-' {
if rb . session . batch . label == "" || rb . session . batch . label != tag [ 1 : ] {
fail = true
} else if rb . session . batch . message . LenLines ( ) == 0 {
fail = true
} else {
batch := rb . session . batch
rb . session . batch = MultilineBatch { }
2020-01-20 06:37:13 +01:00
// time tag should correspond to the time when the message was completed
batch . message . SetTime ( )
2019-12-23 21:26:37 +01:00
histType , err := msgCommandToHistType ( batch . command )
if err != nil {
histType = history . Privmsg
2019-12-27 04:54:00 +01:00
batch . command = "PRIVMSG"
2019-12-23 21:26:37 +01:00
}
2020-01-20 06:37:13 +01:00
// XXX changing the label inside a handler is a bit dodgy, but it works here
// because there's no way we could have triggered a flush up to this point
2019-12-23 21:26:37 +01:00
rb . Label = batch . responseLabel
2019-12-27 04:54:00 +01:00
dispatchMessageToTarget ( client , batch . tags , histType , batch . command , batch . target , batch . message , rb )
2019-12-23 21:26:37 +01:00
}
}
if fail {
rb . session . batch = MultilineBatch { }
if sendErrors {
rb . Add ( nil , server . name , "FAIL" , "BATCH" , "MULTILINE_INVALID" , client . t ( "Invalid multiline batch" ) )
}
}
return false
}
2019-05-22 03:40:25 +02:00
// BRB [message]
func brbHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
success , duration := client . brbTimer . Enable ( )
if ! success {
rb . Add ( nil , server . name , "FAIL" , "BRB" , "CANNOT_BRB" , client . t ( "Your client does not support BRB" ) )
return false
} else {
rb . Add ( nil , server . name , "BRB" , strconv . Itoa ( int ( duration . Seconds ( ) ) ) )
}
var message string
if 0 < len ( msg . Params ) {
message = msg . Params [ 0 ]
} else {
message = client . t ( "I'll be right back" )
}
if len ( client . Sessions ( ) ) == 1 {
// true BRB
client . SetAway ( true , message )
}
return true
}
2018-02-03 10:28:02 +01:00
// CAP <subcmd> [<caps>]
2018-02-05 15:21:08 +01:00
func capHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2019-05-24 19:31:43 +02:00
details := client . Details ( )
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-08-27 06:51:09 +02:00
config := server . Config ( )
supportedCaps := config . Server . supportedCaps
if client . isSTSOnly {
supportedCaps = stsOnlyCaps
}
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-08-27 06:51:09 +02:00
if err != nil || ( ! remove && ! supportedCaps . Has ( capab ) ) {
2019-02-03 02:00:23 +01:00
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
}
}
}
2019-08-27 06:51:09 +02:00
sendCapLines := func ( cset * caps . Set , values caps . Values ) {
version := rb . session . capVersion
2019-11-10 02:31:56 +01:00
// we're working around two bugs:
// 1. weechat 1.4 won't accept the CAP reply unless it contains the server.name source
// 2. old versions of Kiwi and The Lounge can't parse multiline CAP LS 302 (#661),
// so try as hard as possible to get the response to fit on one line.
// :server.name CAP * LS * :<tokens>
// 1 7 4
maxLen := 510 - 1 - len ( server . name ) - 7 - len ( subCommand ) - 4
capLines := cset . Strings ( version , values , maxLen )
2019-08-27 06:51:09 +02:00
for i , capStr := range capLines {
2019-09-08 12:22:34 +02:00
if version >= caps . Cap302 && i < len ( capLines ) - 1 {
2019-08-27 06:51:09 +02:00
rb . Add ( nil , server . name , "CAP" , details . nick , subCommand , "*" , capStr )
} else {
rb . Add ( nil , server . name , "CAP" , details . nick , subCommand , capStr )
}
}
}
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
}
2019-08-27 06:51:09 +02:00
sendCapLines ( supportedCaps , config . Server . capValues )
2018-02-03 10:28:02 +01:00
case "LIST" :
2019-08-27 06:51:09 +02:00
// values not sent on LIST
sendCapLines ( & rb . session . capabilities , nil )
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-05-24 19:31:43 +02:00
// #511, #521: oragono.io/nope is a fake cap to trap bad clients who blindly request
// every offered capability. during registration, requesting it produces a quit,
// otherwise just a CAP NAK
if badCaps || ( toAdd . Has ( caps . Nope ) && client . registered ) {
rb . Add ( nil , server . name , "CAP" , details . nick , "NAK" , capString )
2018-06-26 00:08:15 +02:00
return false
2019-05-24 19:31:43 +02:00
} else if toAdd . Has ( caps . Nope ) && ! client . registered {
2019-05-29 11:50:33 +02:00
client . Quit ( fmt . Sprintf ( client . t ( "Requesting the %s client capability is forbidden" ) , caps . Nope . Name ( ) ) , rb . session )
2019-05-24 19:31:43 +02:00
return true
2018-02-03 10:28:02 +01:00
}
2019-05-24 19:31:43 +02:00
2019-04-12 06:08:46 +02:00
rb . session . capabilities . Union ( toAdd )
rb . session . capabilities . Subtract ( toRemove )
2019-05-24 19:31:43 +02:00
rb . Add ( nil , server . name , "CAP" , details . 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-05-22 03:40:25 +02:00
token , id := server . resumeManager . GenerateToken ( client )
2019-02-12 06:27:57 +01:00
if token != "" {
2018-11-26 11:23:27 +01:00
rb . Add ( nil , server . name , "RESUME" , "TOKEN" , token )
2019-05-22 03:40:25 +02:00
rb . session . SetResumeID ( id )
2018-11-26 11:23:27 +01:00
}
}
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 :
2019-05-24 19:31:43 +02:00
rb . Add ( nil , server . name , ERR_INVALIDCAPCMD , details . 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 ) {
var items [ ] history . Item
2020-02-19 01:38:42 +01:00
unknown_command := false
var target string
2019-02-04 11:18:17 +01:00
var channel * Channel
2020-02-19 01:38:42 +01:00
var sequence history . Sequence
var err error
2019-02-04 11:18:17 +01:00
defer func ( ) {
2019-05-07 05:17:57 +02:00
// successful responses are sent as a chathistory or history batch
2020-02-21 05:47:13 +01:00
if err == nil {
2020-02-19 01:38:42 +01:00
if channel != nil {
2019-05-07 05:17:57 +02:00
channel . replayHistoryItems ( rb , items , false )
2020-02-19 01:38:42 +01:00
} else {
client . replayPrivmsgHistory ( rb , items , target , true )
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
2020-02-19 01:38:42 +01:00
if unknown_command {
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "UNKNOWN_COMMAND" , utils . SafeErrorParam ( msg . Params [ 0 ] ) , client . t ( "Unknown command" ) )
} else if err == utils . ErrInvalidParams {
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "INVALID_PARAMETERS" , msg . Params [ 0 ] , client . t ( "Invalid parameters" ) )
} else if err != nil {
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "MESSAGE_ERROR" , msg . Params [ 0 ] , client . t ( "Messages could not be retrieved" ) )
} else if sequence == nil {
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "NO_SUCH_CHANNEL" , utils . SafeErrorParam ( msg . Params [ 1 ] ) , client . t ( "No such channel" ) )
2019-02-04 11:18:17 +01:00
}
} ( )
2020-02-19 01:38:42 +01:00
config := server . Config ( )
maxChathistoryLimit := config . History . ChathistoryMax
if maxChathistoryLimit == 0 {
2019-02-04 18:31:44 +01:00
return
2019-02-04 11:18:17 +01:00
}
2020-02-25 03:45:21 +01:00
preposition := strings . ToLower ( msg . Params [ 0 ] )
target = msg . Params [ 1 ]
2019-02-04 11:18:17 +01:00
parseQueryParam := func ( param string ) ( msgid string , timestamp time . Time , err error ) {
2020-02-25 03:45:21 +01:00
if param == "*" && ( preposition == "before" || preposition == "between" ) {
// XXX compatibility with kiwi, which as of February 2020 is
// using BEFORE * as a synonym for LATEST *
return
}
2020-02-19 01:38:42 +01:00
err = utils . ErrInvalidParams
2019-02-04 11:18:17 +01:00
pieces := strings . SplitN ( param , "=" , 2 )
if len ( pieces ) < 2 {
return
}
identifier , value := strings . ToLower ( pieces [ 0 ] ) , pieces [ 1 ]
2020-02-19 01:38:42 +01:00
if identifier == "msgid" {
2019-02-04 11:18:17 +01:00
msgid , err = value , nil
return
} else if identifier == "timestamp" {
timestamp , err = time . Parse ( IRCv3TimestampFormat , value )
return
}
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
}
2020-02-19 01:38:42 +01:00
channel , sequence , err = server . GetHistorySequence ( nil , client , target )
if err != nil || sequence == nil {
2019-02-04 11:18:17 +01:00
return
}
2020-02-19 01:38:42 +01:00
roundUp := func ( endpoint time . Time ) ( result time . Time ) {
return endpoint . Truncate ( time . Millisecond ) . Add ( time . Millisecond )
}
var start , end history . Selector
var limit int
2019-02-04 11:18:17 +01:00
switch preposition {
2020-02-19 01:38:42 +01:00
case "between" :
start . Msgid , start . Time , err = parseQueryParam ( msg . Params [ 2 ] )
2019-02-04 11:18:17 +01:00
if err != nil {
2020-02-19 01:38:42 +01:00
return
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
end . Msgid , end . Time , err = parseQueryParam ( msg . Params [ 3 ] )
if err != nil {
return
}
// XXX preserve the ordering of the two parameters, since we might be going backwards,
// but round up the chronologically first one, whichever it is, to make it exclusive
if ! start . Time . IsZero ( ) && ! end . Time . IsZero ( ) {
if start . Time . Before ( end . Time ) {
start . Time = roundUp ( start . Time )
2019-02-04 11:18:17 +01:00
} else {
2020-02-19 01:38:42 +01:00
end . Time = roundUp ( end . Time )
2019-02-04 11:18:17 +01:00
}
}
2020-02-19 01:38:42 +01:00
limit = parseHistoryLimit ( 4 )
case "before" , "after" , "around" :
start . Msgid , start . Time , err = parseQueryParam ( msg . Params [ 2 ] )
2019-02-04 11:18:17 +01:00
if err != nil {
2020-02-19 01:38:42 +01:00
return
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
if preposition == "after" && ! start . Time . IsZero ( ) {
start . Time = roundUp ( start . Time )
}
if preposition == "before" {
end = start
start = history . Selector { }
}
limit = parseHistoryLimit ( 3 )
case "latest" :
if msg . Params [ 2 ] != "*" {
end . Msgid , end . Time , err = parseQueryParam ( msg . Params [ 2 ] )
if err != nil {
return
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
if ! end . Time . IsZero ( ) {
end . Time = roundUp ( end . Time )
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
start . Time = time . Now ( ) . UTC ( )
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
limit = parseHistoryLimit ( 3 )
default :
unknown_command = true
return
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
if preposition == "around" {
items , err = sequence . Around ( start , limit )
} else {
items , _ , err = sequence . Between ( start , end , limit )
}
2019-02-04 11:18:17 +01:00
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" ) )
2020-02-21 18:41:04 +01:00
case "CRASHSERVER" :
if ! client . HasRoleCapabs ( "oper:rehash" ) {
rb . Notice ( client . t ( "You must have rehash permissions in order to execute DEBUG CRASHSERVER" ) )
return false
}
2020-02-23 04:32:19 +01:00
code := utils . ConfirmationCode ( server . name , server . ctime )
2020-02-21 18:41:04 +01:00
if len ( msg . Params ) == 1 || msg . Params [ 1 ] != code {
rb . Notice ( fmt . Sprintf ( client . t ( "To crash the server, issue the following command: /DEBUG CRASHSERVER %s" ) , code ) )
return false
}
2020-02-21 19:07:22 +01:00
server . logger . Error ( "server" , fmt . Sprintf ( "DEBUG CRASHSERVER executed by operator %s" , client . Oper ( ) . Name ) )
2020-02-21 18:41:04 +01:00
go func ( ) {
// intentional nil dereference on a new goroutine, bypassing recover-from-errors
var i , j * int
* i = * j
} ( )
default :
rb . Notice ( client . t ( "Unrecognized DEBUG subcommand" ) )
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 {
2019-05-27 20:33:59 +02:00
mcl . SetExitedSnomaskSent ( )
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-05-22 03:40:25 +02:00
mcl . destroy ( 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 {
2019-07-02 18:36:32 +02:00
client . sendHelp ( strings . ToUpper ( argument ) , 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
2020-02-19 01:38:42 +01:00
// HISTORY #darwin 1h
2019-02-04 11:18:17 +01:00
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 ]
2020-02-19 01:38:42 +01:00
if strings . ToLower ( target ) == "me" {
target = "*"
2019-02-04 18:31:44 +01:00
}
2020-02-19 01:38:42 +01:00
channel , sequence , err := server . GetHistorySequence ( nil , client , target )
2019-02-04 18:31:44 +01:00
2020-02-19 01:38:42 +01:00
if sequence == nil || err != nil {
// whatever
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( target ) , client . t ( "No such channel" ) )
2019-02-04 18:31:44 +01:00
return false
2019-02-04 11:18:17 +01:00
}
2020-02-19 01:38:42 +01:00
var duration time . Duration
2019-02-04 18:16:28 +01:00
maxChathistoryLimit := config . History . ChathistoryMax
2020-02-19 01:38:42 +01:00
limit := 100
if maxChathistoryLimit < limit {
limit = maxChathistoryLimit
}
2019-02-04 11:18:17 +01:00
if len ( msg . Params ) > 1 {
providedLimit , err := strconv . Atoi ( msg . Params [ 1 ] )
if err == nil && providedLimit != 0 {
limit = providedLimit
2020-02-19 01:38:42 +01:00
if maxChathistoryLimit < limit {
limit = maxChathistoryLimit
}
} else if err != nil {
duration , err = time . ParseDuration ( msg . Params [ 1 ] )
if err == nil {
limit = maxChathistoryLimit
}
2019-02-04 11:18:17 +01:00
}
}
2020-02-19 01:38:42 +01:00
var items [ ] history . Item
if duration == 0 {
items , _ , err = sequence . Between ( history . Selector { } , history . Selector { } , limit )
2019-02-04 11:18:17 +01:00
} else {
2020-02-19 01:38:42 +01:00
now := time . Now ( ) . UTC ( )
start := history . Selector { Time : now }
end := history . Selector { Time : now . Add ( - duration ) }
items , _ , err = sequence . Between ( start , end , limit )
2019-02-04 11:18:17 +01:00
}
2020-02-21 05:47:13 +01:00
if err == nil && len ( items ) != 0 {
2020-02-19 01:38:42 +01:00
if channel != nil {
channel . replayHistoryItems ( rb , items , false )
} else {
client . replayPrivmsgHistory ( rb , items , "" , true )
}
}
2019-02-04 11:18:17 +01:00
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 ( "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
}
2020-02-26 08:39:37 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "Former Core 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
}
2020-02-26 08:39:37 +01:00
rb . Add ( nil , server . name , RPL_INFO , client . nick , client . t ( "For a more complete list of contributors, see our changelog:" ) )
rb . Add ( nil , server . name , RPL_INFO , client . nick , " https://github.com/oragono/oragono/blob/master/CHANGELOG.md" )
rb . Add ( nil , server . name , RPL_INFO , client . nick , "" )
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 ]
2019-12-05 12:52:07 +01:00
target := server . clients . Get ( nickname )
if target == nil {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . Nick ( ) , utils . SafeErrorParam ( nickname ) , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-12-05 13:41:09 +01:00
channel := server . channels . Get ( channelName )
if channel == nil {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , utils . SafeErrorParam ( 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-12-03 03:13:09 +01:00
if name == "" {
continue // #679
}
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 )
2019-12-17 19:21:26 +01:00
if err != nil {
sendJoinError ( client , name , rb , err )
2018-02-03 10:28:02 +01:00
}
}
return false
}
2019-12-17 19:21:26 +01:00
func sendJoinError ( client * Client , name string , rb * ResponseBuffer , err error ) {
var errMsg string
switch err {
case errInsufficientPrivs :
errMsg = ` Only server operators can create new channels `
case errConfusableIdentifier :
errMsg = ` That channel name is too close to the name of another channel `
case errChannelPurged :
errMsg = err . Error ( )
default :
errMsg = ` No such channel `
}
rb . Add ( nil , client . server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( name ) , client . t ( errMsg ) )
}
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 {
2019-12-17 19:21:26 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , "SAJOIN" , client . t ( "Not enough parameters" ) )
2018-05-25 08:46:36 +02:00
return false
} else {
target = server . clients . Get ( msg . Params [ 0 ] )
if target == nil {
2019-12-05 12:52:07 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . Nick ( ) , utils . SafeErrorParam ( msg . Params [ 0 ] ) , "No such nick" )
2018-05-25 08:46:36 +02:00
return false
}
channelString = msg . Params [ 1 ]
}
}
channels := strings . Split ( channelString , "," )
for _ , chname := range channels {
2019-12-17 19:21:26 +01:00
err := server . channels . Join ( target , chname , "" , true , rb )
if err != nil {
sendJoinError ( client , chname , rb , err )
}
2018-05-25 08:46:36 +02:00
}
return false
}
2018-02-03 10:28:02 +01:00
// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
2018-02-05 15:21:08 +01:00
func kickHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
users := strings . Split ( msg . Params [ 1 ] , "," )
if ( len ( channels ) != len ( users ) ) && ( len ( users ) != 1 ) {
2018-02-05 15:21:08 +01:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . nick , "KICK" , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-12-03 03:13:09 +01:00
type kickCmd struct {
channel string
nick string
}
kicks := make ( [ ] kickCmd , 0 , len ( channels ) )
2018-02-03 10:28:02 +01:00
for index , channel := range channels {
2019-12-03 03:13:09 +01:00
if channel == "" {
continue // #679
}
2018-02-03 10:28:02 +01:00
if len ( users ) == 1 {
2019-12-03 03:13:09 +01:00
kicks = append ( kicks , kickCmd { channel , users [ 0 ] } )
2018-02-03 10:28:02 +01:00
} else {
2019-12-03 03:13:09 +01:00
kicks = append ( kicks , kickCmd { channel , users [ index ] } )
2018-02-03 10:28:02 +01:00
}
}
var comment string
if len ( msg . Params ) > 2 {
comment = msg . Params [ 2 ]
}
2019-12-03 03:13:09 +01:00
for _ , kick := range kicks {
channel := server . channels . Get ( kick . channel )
if channel == nil {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , utils . SafeErrorParam ( kick . channel ) , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
continue
}
2019-12-03 03:13:09 +01:00
target := server . clients . Get ( kick . nick )
if target == nil {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . nick , utils . SafeErrorParam ( kick . nick ) , client . t ( "No such nick" ) )
2018-02-03 10:28:02 +01:00
continue
}
if comment == "" {
2019-12-03 03:13:09 +01:00
comment = kick . nick
2018-02-03 10:28:02 +01:00
}
2019-12-17 01:50:15 +01:00
channel . Kick ( client , target , comment , rb , false )
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 ]
}
2019-12-05 12:52:07 +01:00
target := server . clients . Get ( nickname )
if target == nil {
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . nick , utils . SafeErrorParam ( 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 ) )
2019-05-27 20:33:59 +02:00
target . SetExitedSnomaskSent ( )
2018-02-03 10:28:02 +01:00
2019-04-12 06:08:46 +02:00
target . Quit ( quitMsg , nil )
2019-05-22 03:40:25 +02:00
target . destroy ( 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 {
2019-07-14 22:17:37 +02:00
details := client . Details ( )
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" ] {
2019-07-14 22:17:37 +02:00
rb . Add ( nil , server . name , ERR_NOPRIVS , details . 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 {
2019-07-14 22:17:37 +02:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , details . nick , msg . Command , client . t ( "Not enough parameters" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-07-14 22:17:37 +02:00
mask := msg . Params [ currentArg ]
2018-02-03 10:28:02 +01:00
currentArg ++
// check mask
2019-07-14 22:17:37 +02:00
mask , err = CanonicalizeMaskWildcard ( mask )
if err != nil {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . nick , msg . Command , client . t ( "Erroneous nickname" ) )
return false
2018-02-03 10:28:02 +01:00
}
matcher := ircmatch . MakeMatch ( mask )
for _ , clientMask := range client . AllNickmasks ( ) {
if ! klineMyself && matcher . Match ( clientMask ) {
2019-07-14 22:17:37 +02:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . 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" {
2019-07-14 22:17:37 +02:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . 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 ) )
2019-07-14 22:17:37 +02:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added temporary (%s) K-Line for %s" ) , details . nick , operName , duration . String ( ) , mask )
2018-02-03 10:28:02 +01:00
} else {
2018-02-05 15:21:08 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "Added K-Line for %s" ) , mask ) )
2019-07-14 22:17:37 +02:00
snoDescription = fmt . Sprintf ( ircfmt . Unescape ( "%s [%s]$r added K-Line for %s" ) , details . nick , operName , mask )
2018-02-03 10:28:02 +01:00
}
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 {
2019-05-27 20:33:59 +02:00
mcl . SetExitedSnomaskSent ( )
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-05-22 03:40:25 +02:00
mcl . destroy ( nil )
2018-02-03 10:28:02 +01:00
}
}
// send snomask
sort . Strings ( killedClientNicks )
2019-07-14 22:17:37 +02:00
server . snomasks . Send ( sno . LocalKills , fmt . Sprintf ( ircfmt . Unescape ( "%s [%s] killed %d clients with a KLINE $c[grey][$r%s$c[grey]]" ) , details . nick , operName , len ( killedClientNicks ) , strings . Join ( killedClientNicks , ", " ) ) )
2018-02-03 10:28:02 +01:00
}
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 {
2019-12-03 03:13:09 +01:00
channel := server . channels . Get ( chname )
if channel == nil || ( ! clientIsOp && channel . flags . HasMode ( modes . Secret ) ) {
2018-02-03 10:28:02 +01:00
if len ( chname ) > 0 {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , utils . SafeErrorParam ( 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 {
2019-07-01 15:21:38 +02:00
server . Lusers ( client , rb )
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 {
2019-12-05 13:41:09 +01:00
if 0 < len ( msg . Params [ 0 ] ) && msg . Params [ 0 ] [ 0 ] == '#' {
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 {
2019-12-05 13:41:09 +01:00
channel := server . channels . Get ( msg . Params [ 0 ] )
2018-02-03 10:28:02 +01:00
2019-12-05 13:41:09 +01:00
if channel == nil {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , utils . SafeErrorParam ( msg . Params [ 0 ] ) , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
return false
}
2019-10-13 12:07:30 +02:00
var changes modes . ModeChanges
2018-02-03 10:28:02 +01:00
if 1 < len ( msg . Params ) {
// parse out real mode changes
params := msg . Params [ 1 : ]
2019-10-13 12:07:30 +02:00
var unknown map [ rune ] bool
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
}
}
2019-10-13 12:07:30 +02:00
// process mode changes, include list operations (an empty set of changes does a list)
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
if len ( applied ) > 0 {
2019-10-13 12:07:30 +02:00
prefix := client . NickMaskString ( )
2018-02-03 10:28:02 +01:00
//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 ( ) , " " ) ... )
2019-12-17 01:50:15 +01:00
rb . Add ( nil , prefix , "MODE" , args ... )
for _ , session := range client . Sessions ( ) {
if session != rb . session {
session . Send ( nil , prefix , "MODE" , args ... )
}
}
2018-02-03 10:28:02 +01:00
for _ , member := range channel . Members ( ) {
2019-12-17 01:50:15 +01:00
if member != client {
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
}
}
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 {
2019-07-02 14:45:14 +02:00
cDetails := client . Details ( )
2019-12-05 13:41:09 +01:00
target := server . clients . Get ( msg . Params [ 0 ] )
if target == nil {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , cDetails . nick , utils . SafeErrorParam ( 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 {
2019-07-02 14:45:14 +02:00
rb . Add ( nil , server . name , ERR_USERSDONTMATCH , cDetails . nick , client . t ( "Can't change modes for other users" ) )
2018-02-03 10:28:02 +01:00
} else {
2019-07-02 14:45:14 +02:00
rb . Add ( nil , server . name , ERR_USERSDONTMATCH , cDetails . 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 {
2019-07-02 14:45:14 +02:00
rb . Add ( nil , server . name , ERR_UNKNOWNMODE , cDetails . 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
2020-03-17 18:19:27 +01:00
applied = ApplyUserModeChanges ( target , changes , msg . Command == "SAMODE" , nil )
2018-02-03 10:28:02 +01:00
}
if len ( applied ) > 0 {
2019-07-02 14:45:14 +02:00
rb . Add ( nil , cDetails . nickMask , "MODE" , targetNick , applied . String ( ) )
2018-02-03 10:28:02 +01:00
} else if hasPrivs {
2019-07-02 14:45:14 +02:00
rb . Add ( nil , server . name , RPL_UMODEIS , targetNick , target . ModeString ( ) )
if target . HasMode ( modes . LocalOperator ) || target . HasMode ( modes . Operator ) {
masks := server . snomasks . String ( target )
2018-02-03 10:28:02 +01:00
if 0 < len ( masks ) {
2019-07-02 14:45:14 +02:00
rb . Add ( nil , server . name , 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
2019-05-23 01:07:12 +02:00
limits := server . Config ( ) . 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
2019-05-29 20:51:09 +02:00
// implement the modern behavior: https://modern.ircdocs.horse/#names-message
// "Servers MAY only return information about the first <channel> and silently ignore the others."
// "If no parameter is given for this command, servers SHOULD return one RPL_ENDOFNAMES numeric
// with the <channel> parameter set to an asterix character"
2018-02-05 15:21:08 +01:00
if len ( channels ) == 0 {
2019-05-29 20:24:23 +02:00
rb . Add ( nil , server . name , RPL_ENDOFNAMES , client . Nick ( ) , "*" , client . t ( "End of NAMES list" ) )
2018-02-05 15:21:08 +01:00
return false
}
2019-05-29 20:51:09 +02:00
chname := channels [ 0 ]
success := false
channel := server . channels . Get ( chname )
if channel != nil {
if ! channel . flags . HasMode ( modes . Secret ) || channel . hasClient ( client ) || client . HasMode ( modes . Operator ) {
channel . Names ( client , rb )
success = true
2018-02-05 15:21:08 +01:00
}
}
2019-05-29 20:51:09 +02:00
if ! success { // channel.Names() sends this numeric itself on success
rb . Add ( nil , server . name , RPL_ENDOFNAMES , client . Nick ( ) , chname , client . t ( "End of NAMES list" ) )
}
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
}
2019-12-23 21:26:37 +01:00
// helper to store a batched PRIVMSG in the session object
func absorbBatchedMessage ( server * Server , client * Client , msg ircmsg . IrcMessage , batchTag string , histType history . ItemType , rb * ResponseBuffer ) {
// sanity checks. batch tag correctness was already checked and is redundant here
// as a defensive measure. TAGMSG is checked without an error message: "don't eat paste"
if batchTag != rb . session . batch . label || histType == history . Tagmsg || len ( msg . Params ) == 1 || msg . Params [ 1 ] == "" {
return
}
rb . session . batch . command = msg . Command
isConcat , _ := msg . GetTag ( caps . MultilineConcatTag )
rb . session . batch . message . Append ( msg . Params [ 1 ] , isConcat )
config := server . Config ( )
if config . Limits . Multiline . MaxBytes < rb . session . batch . message . LenBytes ( ) {
if histType != history . Notice {
rb . Add ( nil , server . name , "FAIL" , "BATCH" , "MULTILINE_MAX_BYTES" , strconv . Itoa ( config . Limits . Multiline . MaxBytes ) )
}
rb . session . batch = MultilineBatch { }
} else if config . Limits . Multiline . MaxLines != 0 && config . Limits . Multiline . MaxLines < rb . session . batch . message . LenLines ( ) {
if histType != history . Notice {
rb . Add ( nil , server . name , "FAIL" , "BATCH" , "MULTILINE_MAX_LINES" , strconv . Itoa ( config . Limits . Multiline . MaxLines ) )
}
rb . session . batch = MultilineBatch { }
}
}
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 {
2019-12-23 21:26:37 +01:00
histType , err := msgCommandToHistType ( msg . Command )
2019-03-19 08:35:49 +01:00
if err != nil {
return false
}
2019-12-23 21:26:37 +01:00
if isBatched , batchTag := msg . GetTag ( "batch" ) ; isBatched {
absorbBatchedMessage ( server , client , msg , batchTag , histType , rb )
return false
}
2019-03-19 08:35:49 +01:00
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 ]
}
2019-12-23 21:26:37 +01:00
if histType != history . Tagmsg && message == "" {
rb . Add ( nil , server . name , ERR_NOTEXTTOSEND , cnick , client . t ( "No text to send" ) )
return false
}
2019-03-19 08:35:49 +01:00
2020-02-19 01:38:42 +01:00
if rb . session . isTor && utils . IsRestrictedCTCPMessage ( message ) {
2019-12-23 21:26:37 +01:00
// note that error replies are never sent for NOTICE
2019-03-19 08:35:49 +01:00
if histType != history . Notice {
2019-12-23 21:26:37 +01:00
rb . Notice ( client . t ( "CTCP messages are disabled over Tor" ) )
2019-03-19 08:35:49 +01:00
}
2019-02-26 03:50:43 +01:00
return false
}
2018-02-03 10:28:02 +01:00
for i , targetString := range targets {
// max of four targets per privmsg
2019-12-23 21:26:37 +01:00
if i == maxTargets {
2018-02-03 10:28:02 +01:00
break
}
2019-12-23 21:26:37 +01:00
// each target gets distinct msgids
2020-01-19 05:47:05 +01:00
splitMsg := utils . MakeMessage ( message )
2019-12-27 04:54:00 +01:00
dispatchMessageToTarget ( client , clientOnlyTags , histType , msg . Command , targetString , splitMsg , rb )
2019-12-23 21:26:37 +01:00
}
return false
}
2018-02-03 10:28:02 +01:00
2019-12-27 04:54:00 +01:00
func dispatchMessageToTarget ( client * Client , tags map [ string ] string , histType history . ItemType , command , target string , message utils . SplitMessage , rb * ResponseBuffer ) {
2019-12-23 21:26:37 +01:00
server := client . server
prefixes , target := modes . SplitChannelMembershipPrefixes ( target )
lowestPrefix := modes . GetLowestChannelModePrefix ( prefixes )
if len ( target ) == 0 {
return
} else if target [ 0 ] == '#' {
channel := server . channels . Get ( target )
if channel == nil {
if histType != history . Notice {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( target ) , client . t ( "No such channel" ) )
2018-02-03 10:28:02 +01:00
}
2019-12-23 21:26:37 +01:00
return
}
channel . SendSplitMessage ( command , lowestPrefix , tags , client , message , rb )
} else {
// NOTICE and TAGMSG to services are ignored
if histType == history . Privmsg {
lowercaseTarget := strings . ToLower ( target )
if service , isService := OragonoServices [ lowercaseTarget ] ; isService {
servicePrivmsgHandler ( service , server , client , message . Message , rb )
return
} else if _ , isZNC := zncHandlers [ lowercaseTarget ] ; isZNC {
zncPrivmsgHandler ( client , lowercaseTarget , message . Message , rb )
return
2018-02-03 10:28:02 +01:00
}
2019-12-23 21:26:37 +01:00
}
2018-02-03 10:28:02 +01:00
2019-12-23 21:26:37 +01:00
user := server . clients . Get ( target )
if user == nil {
if histType != history . Notice {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . Nick ( ) , target , "No such nick" )
2018-02-03 10:28:02 +01:00
}
2019-12-23 21:26:37 +01:00
return
}
2020-02-19 01:38:42 +01:00
tDetails := user . Details ( )
tnick := tDetails . nick
2019-12-23 21:26:37 +01:00
2020-02-19 01:38:42 +01:00
details := client . Details ( )
nickMaskString := details . nickMask
accountName := details . accountName
2020-02-20 07:15:11 +01:00
var deliverySessions [ ] * Session
2019-12-23 21:26:37 +01:00
// restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine
2020-02-20 07:45:17 +01:00
allowedPlusR := details . account != "" || ! user . HasMode ( modes . RegisteredOnly )
2020-02-19 01:38:42 +01:00
if allowedPlusR {
2020-02-20 07:15:11 +01:00
deliverySessions = append ( deliverySessions , user . Sessions ( ) ... )
2019-12-23 21:26:37 +01:00
}
2020-02-20 07:15:11 +01:00
// all sessions of the sender, except the originating session, get a copy as well:
if client != user {
for _ , session := range client . Sessions ( ) {
if session != rb . session {
deliverySessions = append ( deliverySessions , session )
2019-03-19 08:35:49 +01:00
}
}
2019-12-23 21:26:37 +01:00
}
2020-02-20 07:15:11 +01:00
for _ , session := range deliverySessions {
2020-01-26 02:44:15 +01:00
hasTagsCap := session . capabilities . Has ( caps . MessageTags )
2020-02-20 07:15:11 +01:00
// don't send TAGMSG at all if they don't have the tags cap
2020-01-26 02:44:15 +01:00
if histType == history . Tagmsg && hasTagsCap {
2019-12-23 21:26:37 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , nickMaskString , accountName , tags , command , tnick )
2020-02-20 07:15:11 +01:00
} else if histType != history . Tagmsg && ! ( session . isTor && message . IsRestrictedCTCPMessage ( ) ) {
2020-01-26 02:44:15 +01:00
tagsToSend := tags
if ! hasTagsCap {
tagsToSend = nil
}
session . sendSplitMsgFromClientInternal ( false , nickMaskString , accountName , tagsToSend , command , tnick , message )
2019-05-19 08:14:36 +02:00
}
2018-02-03 10:28:02 +01:00
}
2020-02-20 07:15:11 +01:00
// the originating session may get an echo message:
if rb . session . capabilities . Has ( caps . EchoMessage ) {
2020-02-20 07:35:03 +01:00
hasTagsCap := rb . session . capabilities . Has ( caps . MessageTags )
if histType == history . Tagmsg && hasTagsCap {
2020-02-20 07:15:11 +01:00
rb . AddFromClient ( message . Time , message . Msgid , nickMaskString , accountName , tags , command , tnick )
} else {
2020-02-20 07:35:03 +01:00
tagsToSend := tags
if ! hasTagsCap {
tagsToSend = nil
}
rb . AddSplitMessageFromClient ( nickMaskString , accountName , tagsToSend , command , tnick , message )
2020-02-20 07:15:11 +01:00
}
}
2019-12-23 21:26:37 +01:00
if histType != history . Notice && user . Away ( ) {
//TODO(dan): possibly implement cooldown of away notifications to users
rb . Add ( nil , server . name , RPL_AWAY , client . Nick ( ) , tnick , user . AwayMessage ( ) )
}
2020-02-19 01:38:42 +01:00
config := server . Config ( )
if ! config . History . Enabled {
return
}
2019-12-23 21:26:37 +01:00
item := history . Item {
Type : histType ,
Message : message ,
Nick : nickMaskString ,
AccountName : accountName ,
2020-02-19 01:38:42 +01:00
Tags : tags ,
}
if ! item . IsStorable ( ) {
return
}
targetedItem := item
targetedItem . Params [ 0 ] = tnick
2020-02-24 20:09:00 +01:00
cStatus , _ := client . historyStatus ( config )
tStatus , _ := user . historyStatus ( config )
2020-02-19 01:38:42 +01:00
// add to ephemeral history
2020-02-24 20:09:00 +01:00
if cStatus == HistoryEphemeral {
2020-02-19 01:38:42 +01:00
targetedItem . CfCorrespondent = tDetails . nickCasefolded
client . history . Add ( targetedItem )
}
2020-02-24 20:09:00 +01:00
if tStatus == HistoryEphemeral && client != user {
2020-02-19 01:38:42 +01:00
item . CfCorrespondent = details . nickCasefolded
user . history . Add ( item )
}
2020-02-28 11:41:08 +01:00
if cStatus == HistoryPersistent || tStatus == HistoryPersistent {
targetedItem . CfCorrespondent = ""
server . historyDB . AddDirectMessage ( details . nickCasefolded , details . account , tDetails . nickCasefolded , tDetails . account , targetedItem )
2019-12-23 21:26:37 +01:00
}
2018-02-03 10:28:02 +01:00
}
}
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
}
2019-12-18 21:44:06 +01:00
// 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
}
2019-12-20 00:30:19 +01:00
// must pass at least one check, and all enabled checks
var checkPassed , checkFailed bool
2018-04-19 08:48:19 +02:00
oper := server . GetOperator ( msg . Params [ 0 ] )
if oper != nil {
2019-12-20 00:30:19 +01:00
if oper . Fingerprint != "" {
2020-02-19 03:42:27 +01:00
if oper . Fingerprint == rb . session . certfp {
2019-12-20 00:30:19 +01:00
checkPassed = true
} else {
checkFailed = true
}
}
if ! checkFailed && oper . Pass != nil {
if len ( msg . Params ) == 1 || bcrypt . CompareHashAndPassword ( oper . Pass , [ ] byte ( msg . Params [ 1 ] ) ) != nil {
checkFailed = true
} else {
checkPassed = true
2019-12-19 12:33:43 +01:00
}
2019-12-18 21:44:06 +01:00
}
2018-04-19 08:48:19 +02:00
}
2019-12-20 00:30:19 +01:00
if ! checkPassed || checkFailed {
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
}
2019-12-19 12:33:43 +01:00
applyOper ( client , oper , rb )
return false
}
2020-02-21 12:10:35 +01:00
// adds or removes operator status
// XXX: to add oper, this calls into ApplyUserModeChanges, but to remove oper,
// ApplyUserModeChanges calls into this, because the commands are asymmetric
// (/OPER to add, /MODE self -o to remove)
2019-12-19 12:33:43 +01:00
func applyOper ( client * Client , oper * Oper , rb * ResponseBuffer ) {
details := client . Details ( )
2018-04-19 08:48:19 +02:00
client . SetOper ( oper )
2020-02-21 12:10:35 +01:00
newDetails := client . Details ( )
if details . nickMask != newDetails . nickMask {
client . sendChghost ( details . nickMask , newDetails . hostname )
2018-02-03 10:28:02 +01:00
}
2020-02-21 12:10:35 +01:00
if oper != nil {
// 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 ,
}
copy ( modeChanges [ 1 : ] , oper . Modes )
2020-03-17 18:19:27 +01:00
applied := ApplyUserModeChanges ( client , modeChanges , true , oper )
2020-02-21 12:10:35 +01:00
client . server . snomasks . Send ( sno . LocalOpers , fmt . Sprintf ( ircfmt . Unescape ( "Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]" ) , newDetails . nickMask , oper . Name ) )
2018-02-03 10:28:02 +01:00
2020-02-21 12:10:35 +01:00
rb . Broadcast ( nil , client . server . name , RPL_YOUREOPER , details . nick , client . t ( "You are now an IRC operator" ) )
rb . Broadcast ( nil , client . server . name , "MODE" , details . nick , applied . String ( ) )
} else {
client . server . snomasks . Send ( sno . LocalOpers , fmt . Sprintf ( ircfmt . Unescape ( "Client deopered $c[grey][$r%s$c[grey]]" ) , newDetails . nickMask ) )
}
2018-03-22 16:04:21 +01:00
2019-04-12 06:08:46 +02:00
for _ , session := range client . Sessions ( ) {
2019-12-19 12:33:43 +01:00
// client may now be unthrottled by the fakelag system
2019-04-12 06:08:46 +02:00
session . resetFakelag ( )
}
2018-02-03 10:28:02 +01:00
}
2020-02-21 12:10:35 +01:00
// DEOPER
func deoperHandler ( server * Server , client * Client , msg ircmsg . IrcMessage , rb * ResponseBuffer ) bool {
// pretend they sent /MODE $nick -o
fakeModeMsg := ircmsg . MakeMessage ( nil , "" , "MODE" , client . Nick ( ) , "-o" )
return umodeHandler ( server , client , fakeModeMsg , rb )
}
2018-02-03 10:28:02 +01:00
// 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 ] , "," )
2019-12-03 03:13:09 +01:00
var reason string
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 1 {
reason = msg . Params [ 1 ]
}
for _ , chname := range channels {
2019-12-03 03:13:09 +01:00
if chname == "" {
continue // #679
}
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 {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , utils . SafeErrorParam ( 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
2019-05-23 01:07:12 +02:00
serverPassword := server . Config ( ) . Server . passwordBytes
2018-07-16 09:46:40 +02:00
if serverPassword == nil {
2018-02-03 10:28:02 +01:00
return false
}
// check the provided password
password := [ ] byte ( msg . Params [ 0 ] )
2020-02-19 03:42:27 +01:00
rb . session . sentPassCommand = bcrypt . CompareHashAndPassword ( serverPassword , password ) == nil
2018-02-03 10:28:02 +01:00
2019-05-23 02:25:57 +02:00
// if they failed the check, we'll bounce them later when they try to complete registration
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
}
// 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 ]
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-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( oldName ) , client . t ( "No such channel" ) )
2019-03-11 12:03:51 +01:00
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 {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( newName ) , client . t ( err . Error ( ) ) )
2019-03-11 12:03:51 +01:00
} else if err == errChannelNameInUse {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_CHANNAMEINUSE , client . Nick ( ) , utils . SafeErrorParam ( newName ) , client . t ( err . Error ( ) ) )
2019-03-11 12:03:51 +01:00
} else if err != nil {
2019-12-03 03:13:09 +01:00
rb . Add ( nil , server . name , ERR_CANNOTRENAME , client . Nick ( ) , oldName , utils . SafeErrorParam ( newName ) , client . t ( "Cannot rename channel" ) )
2019-03-11 12:03:51 +01:00
}
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-05-22 22:10:56 +02:00
mDetails := mcl . Details ( )
2019-04-12 06:08:46 +02:00
for _ , mSession := range mcl . Sessions ( ) {
targetRb := rb
targetPrefix := clientPrefix
if mSession != rb . session {
targetRb = NewResponseBuffer ( mSession )
2019-05-22 22:10:56 +02:00
targetPrefix = mDetails . nickMask
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 {
2019-12-17 01:50:15 +01:00
targetRb . Add ( nil , targetPrefix , "PART" , oldName , mcl . t ( "Channel renamed" ) )
2019-04-12 06:08:46 +02:00
}
if mSession . capabilities . Has ( caps . ExtendedJoin ) {
2019-05-22 22:10:56 +02:00
targetRb . Add ( nil , targetPrefix , "JOIN" , newName , mDetails . accountName , mDetails . realname )
2019-04-12 06:08:46 +02:00
} 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-05-22 03:40:25 +02:00
details := ResumeDetails {
PresentedToken : msg . Params [ 0 ] ,
}
2018-02-03 10:28:02 +01:00
2019-02-05 06:19:03 +01:00
if client . registered {
2019-05-22 03:40:25 +02:00
rb . Add ( nil , server . name , "FAIL" , "RESUME" , "REGISTRATION_IS_COMPLETED" , client . t ( "Cannot resume connection, connection registration has already been completed" ) )
2018-02-03 10:28:02 +01:00
return false
}
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 {
2019-05-22 03:40:25 +02:00
details . Timestamp = ts
2018-02-03 10:28:02 +01:00
} else {
2019-05-22 03:40:25 +02:00
rb . Add ( nil , server . name , "WARN" , "RESUME" , "HISTORY_LOST" , client . t ( "Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it" ) )
2018-02-03 10:28:02 +01:00
}
}
2019-05-22 03:40:25 +02:00
rb . session . resumeDetails = & details
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 ]
2020-02-19 01:38:42 +01:00
client . SetRealname ( realname )
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 {
2019-12-05 13:41:09 +01:00
channel := server . channels . Get ( msg . Params [ 0 ] )
if channel == nil {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . nick , utils . SafeErrorParam ( 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 {
2019-07-14 22:17:37 +02:00
details := client . Details ( )
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" ] {
2019-07-14 22:17:37 +02:00
rb . Add ( nil , server . name , ERR_NOPRIVS , details . nick , msg . Command , client . t ( "Insufficient oper privs" ) )
2018-02-03 10:28:02 +01:00
return false
}
// get host
mask := msg . Params [ 0 ]
2019-07-14 22:17:37 +02:00
mask , err := CanonicalizeMaskWildcard ( mask )
if err != nil {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . nick , msg . Command , client . t ( "Erroneous nickname" ) )
return false
2018-02-03 10:28:02 +01:00
}
2019-07-14 22:17:37 +02:00
err = server . klines . RemoveMask ( mask )
2018-02-03 10:28:02 +01:00
if err != nil {
2019-07-14 22:17:37 +02:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . 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 ) )
2019-07-14 22:17:37 +02:00
server . snomasks . Send ( sno . LocalXline , fmt . Sprintf ( ircfmt . Unescape ( "%s$r removed K-Line for %s" ) , details . nick , mask ) )
2018-02-03 10:28:02 +01:00
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 {
2019-12-05 12:52:07 +01:00
returnedClients := make ( ClientSet )
2018-02-03 10:28:02 +01:00
2019-12-05 13:28:20 +01:00
var tl utils . TokenLineBuilder
tl . Initialize ( 400 , " " )
2018-02-03 10:28:02 +01:00
for i , nickname := range msg . Params {
if i >= 10 {
break
}
2019-12-05 12:52:07 +01:00
target := server . clients . Get ( nickname )
if target == nil {
2018-02-03 10:28:02 +01:00
continue
}
// to prevent returning multiple results for a single nick
2019-12-05 12:52:07 +01:00
if returnedClients . Has ( target ) {
continue
}
returnedClients . Add ( target )
2018-02-03 10:28:02 +01:00
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 = "+"
}
2019-12-05 13:28:20 +01:00
details := target . Details ( )
tl . Add ( fmt . Sprintf ( "%s%s=%s%s@%s" , details . nick , isOper , isAway , details . username , details . hostname ) )
}
lines := tl . Lines ( )
if lines == nil {
lines = [ ] string { "" }
}
nick := client . Nick ( )
for _ , line := range lines {
rb . Add ( nil , client . server . name , RPL_USERHOST , nick , line )
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 )
2019-07-01 15:21:38 +02:00
server . RplISupport ( client , 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
}
2020-02-19 03:42:27 +01:00
if info . Fingerprint != "" && info . Fingerprint != rb . session . certfp {
2019-02-05 06:19:03 +01:00
continue
}
2018-02-03 10:28:02 +01:00
2020-03-18 10:38:11 +01:00
err , quitMsg := client . ApplyProxiedIP ( rb . session , msg . Params [ 3 ] , secure )
2019-05-13 07:54:50 +02:00
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 {
2019-10-23 17:32:32 +02:00
mask := msg . Params [ 0 ]
var err error
if mask == "" {
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
2019-10-23 17:32:32 +02:00
} else if mask [ 0 ] == '#' {
mask , err = CasefoldChannel ( msg . Params [ 0 ] )
} else {
2019-12-18 13:01:26 +01:00
mask , err = CanonicalizeMaskWildcard ( mask )
2018-02-03 10:28:02 +01:00
}
2019-10-23 17:32:32 +02:00
if err != nil {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , "WHO" , client . t ( "Mask isn't valid" ) )
return false
2018-02-03 10:28:02 +01:00
}
//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 ]
}
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 ) {
2019-12-05 12:52:07 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . Nick ( ) , utils . SafeErrorParam ( 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 ) {
2019-12-05 12:52:07 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . Nick ( ) , utils . SafeErrorParam ( 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
}
2019-12-05 12:52:07 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHOIS , client . nick , utils . SafeErrorParam ( 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
2019-12-05 13:52:58 +01:00
var count int
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 1 {
2019-12-05 13:52:58 +01:00
count , _ = strconv . Atoi ( msg . Params [ 1 ] )
if count < 0 {
count = 0
}
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 {
2019-12-05 13:52:58 +01:00
if len ( nickname ) == 0 {
continue
}
results := server . whoWas . Find ( nickname , count )
2018-02-03 10:28:02 +01:00
if len ( results ) == 0 {
2019-12-05 13:52:58 +01:00
rb . Add ( nil , server . name , ERR_WASNOSUCHNICK , cnick , utils . SafeErrorParam ( 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
}
}
2019-12-05 13:52:58 +01:00
rb . Add ( nil , server . name , RPL_ENDOFWHOWAS , cnick , utils . SafeErrorParam ( 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
}