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"
2020-05-05 04:29:10 +02:00
"net"
2018-02-03 10:28:02 +01:00
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
2021-06-18 08:41:57 +02:00
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/irc-go/ircutils"
2021-03-18 08:49:12 +01:00
"golang.org/x/crypto/bcrypt"
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/custime"
"github.com/ergochat/ergo/irc/flatip"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/jwt"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
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-10-07 00:04:29 +02:00
func parseCallback ( spec string , config * Config ) ( callbackNamespace string , callbackValue string , err error ) {
// XXX if we don't require verification, ignore any callback that was passed here
// (to avoid confusion in the case where the ircd has no mail server configured)
if ! config . Accounts . Registration . EmailVerification . Enabled {
2018-02-20 10:20:30 +01:00
callbackNamespace = "*"
2020-10-07 00:04:29 +02:00
return
}
callback := strings . ToLower ( spec )
if colonIndex := strings . IndexByte ( callback , ':' ) ; colonIndex != - 1 {
callbackNamespace , callbackValue = callback [ : colonIndex ] , callback [ colonIndex + 1 : ]
2018-02-20 10:20:30 +01:00
} 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
}
2020-10-07 00:04:29 +02:00
if config . Accounts . Registration . EmailVerification . Enabled {
if callbackNamespace != "mailto" {
err = errValidEmailRequired
} else if strings . IndexByte ( callbackValue , '@' ) < 1 {
err = errValidEmailRequired
2018-02-20 10:20:30 +01:00
}
2018-02-03 10:28:02 +01:00
}
2020-10-07 00:04:29 +02:00
2018-02-20 10:20:30 +01:00
return
}
2018-02-03 10:28:02 +01:00
2021-07-07 12:35:30 +02:00
func registrationErrorToMessage ( config * Config , client * Client , err error ) ( message string ) {
if emailError := registrationCallbackErrorText ( config , client , err ) ; emailError != "" {
return emailError
}
2019-02-06 01:03:42 +01:00
switch err {
2022-05-05 07:04:28 +02:00
case errAccountAlreadyRegistered , errAccountAlreadyVerified , errAccountAlreadyUnregistered , errAccountAlreadyLoggedIn , errAccountCreation , errAccountMustHoldNick , errAccountBadPassphrase , errCertfpAlreadyExists , errFeatureDisabled , errAccountBadPassphrase , errNameReserved :
2019-02-06 01:03:42 +01:00
message = err . Error ( )
2020-03-27 22:52:37 +01:00
case errLimitExceeded :
message = ` There have been too many registration attempts recently; try again later `
2020-10-07 15:04:47 +02:00
default :
// default response: let's be risk-averse about displaying internal errors
// to the clients, especially for something as sensitive as accounts
message = ` Could not register `
2019-02-06 01:03:42 +01:00
}
return
}
2021-11-30 07:50:28 +01:00
func announcePendingReg ( client * Client , rb * ResponseBuffer , accountName string ) {
client . server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] attempted to register account $c[grey][$r%s$c[grey]] from IP %s, pending verification" ) , client . Nick ( ) , accountName , rb . session . IP ( ) . String ( ) ) )
}
2018-02-20 10:44:44 +01:00
// helper function to dispatch messages when a client successfully registers
2020-11-29 05:27:11 +01:00
func sendSuccessfulRegResponse ( service * ircService , client * Client , rb * ResponseBuffer ) {
2019-12-19 15:27:54 +01:00
details := client . Details ( )
2020-11-29 05:27:11 +01:00
if service != nil {
service . Notice ( 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
}
2020-07-10 23:09:02 +02: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]] from IP %s" ) , details . nickMask , details . accountName , rb . session . IP ( ) . String ( ) ) )
2020-11-29 05:27:11 +01:00
sendSuccessfulAccountAuth ( service , client , rb , 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.
2020-11-29 05:27:11 +01:00
func sendSuccessfulAccountAuth ( service * ircService , client * Client , rb * ResponseBuffer , forSASL bool ) {
2019-02-13 08:42:35 +01:00
details := client . Details ( )
2018-02-20 10:20:30 +01:00
2020-11-29 05:27:11 +01:00
if service != nil {
service . Notice ( 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
}
2020-11-30 02:20:26 +01:00
if client . Registered ( ) {
// dispatch account-notify
2021-07-24 20:52:03 +02:00
for friend := range client . FriendsMonitors ( caps . AccountNotify ) {
2020-11-30 02:20:26 +01:00
if friend != rb . session {
friend . Send ( nil , details . nickMask , "ACCOUNT" , details . accountName )
}
2020-05-17 17:52:32 +02:00
}
2020-11-30 02:20:26 +01:00
if rb . session . capabilities . Has ( caps . AccountNotify ) {
rb . Add ( nil , details . nickMask , "ACCOUNT" , details . accountName )
}
client . server . sendLoginSnomask ( details . nickMask , details . accountName )
2020-05-17 17:52:32 +02:00
}
2019-02-13 08:42:35 +01:00
2021-01-12 14:40:13 +01:00
// #1479: for Tor clients, replace the hostname with the always-on cloak here
// (for normal clients, this would discard the IP-based cloak, but with Tor
// there's no such concern)
if rb . session . isTor {
config := client . server . Config ( )
if config . Server . Cloaks . EnabledForAlwaysOn {
cloakedHostname := config . Server . Cloaks . ComputeAccountCloak ( details . accountName )
client . setCloakedHostname ( cloakedHostname )
if client . registered {
client . sendChghost ( details . nickMask , client . Hostname ( ) )
}
}
}
2019-02-13 08:42:35 +01:00
client . server . logger . Info ( "accounts" , "client" , details . nick , "logged into account" , details . accountName )
2018-02-20 10:20:30 +01:00
}
2020-11-30 02:20:26 +01:00
func ( server * Server ) sendLoginSnomask ( nickMask , accountName string ) {
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]]" ) , nickMask , accountName ) )
}
2022-05-06 04:34:43 +02:00
// ACCEPT <nicklist>
// nicklist is a comma-delimited list of nicknames; each may be prefixed with -
// to indicate that it should be removed from the list
func acceptHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
for _ , tNick := range strings . Split ( msg . Params [ 0 ] , "," ) {
add := true
if strings . HasPrefix ( tNick , "-" ) {
add = false
tNick = strings . TrimPrefix ( tNick , "-" )
}
target := server . clients . Get ( tNick )
if target == nil {
rb . Add ( nil , server . name , "FAIL" , "ACCEPT" , "INVALID_USER" , utils . SafeErrorParam ( tNick ) , client . t ( "No such user" ) )
continue
}
if add {
server . accepts . Accept ( client , target )
} else {
server . accepts . Unaccept ( client , target )
}
// https://github.com/solanum-ircd/solanum/blob/main/doc/features/modeg.txt
// Charybdis/Solanum define various error numerics that could be sent here,
// but this doesn't seem important to me. One thing to note is that we are not
// imposing an upper bound on the size of the accept list, since in our
// implementation you can only ACCEPT clients who are actually present,
// and an attacker attempting to DoS has much easier resource exhaustion
// strategies available (for example, channel history buffers).
}
return false
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE [<mechanism>|<data>|*]
2021-03-11 02:07:43 +01:00
func authenticateHandler ( server * Server , client * Client , msg ircmsg . Message , 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 == "" {
2021-07-30 20:06:13 +02:00
throttled , remainingTime := client . loginThrottle . Touch ( )
if throttled {
2023-01-08 12:36:04 +01:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . Nick ( ) ,
fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime . Round ( time . Millisecond ) ) )
2021-07-30 20:06:13 +02:00
return false
}
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 ]
2022-04-24 17:57:21 +02:00
// https://ircv3.net/specs/extensions/sasl-3.1:
// "The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks,
// and each chunk is sent as a separate AUTHENTICATE command."
saslMaxArgLength := 400
if len ( rawData ) > saslMaxArgLength {
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
2022-04-24 17:57:21 +02:00
} else if len ( rawData ) == saslMaxArgLength {
2018-02-03 10:28:02 +01:00
// allow 4 'continuation' lines before rejecting for length
2022-04-24 17:57:21 +02:00
if len ( session . sasl . value ) >= saslMaxArgLength * 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 )
2021-07-30 18:20:13 +02:00
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
2021-07-30 20:03:40 +02:00
handler := EnabledSaslMechanisms [ session . sasl . mechanism ]
return handler ( server , client , session , data , rb )
2018-02-03 10:28:02 +01:00
}
2018-02-03 20:48:44 +01:00
// AUTHENTICATE PLAIN
2021-07-30 18:20:13 +02:00
func authPlainHandler ( server * Server , client * Client , session * Session , value [ ] byte , rb * ResponseBuffer ) bool {
defer session . sasl . Clear ( )
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
}
2020-06-12 21:51:48 +02:00
// see #843: strip the device ID for the benefit of clients that don't
// distinguish user/ident from account name
if strudelIndex := strings . IndexByte ( authcid , '@' ) ; strudelIndex != - 1 {
2020-06-16 11:10:09 +02:00
var deviceID string
authcid , deviceID = authcid [ : strudelIndex ] , authcid [ strudelIndex + 1 : ]
2020-06-16 10:57:49 +02:00
if ! client . registered {
2020-06-16 11:10:09 +02:00
rb . session . deviceID = deviceID
2020-06-16 10:57:49 +02:00
}
2020-06-12 21:51:48 +02:00
}
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 {
2021-11-30 21:27:25 +01:00
sendAuthErrorResponse ( client , rb , err )
2018-02-03 10:28:02 +01:00
return false
2020-11-29 05:27:11 +01:00
} else if ! fixupNickEqualsAccount ( client , rb , server . Config ( ) , "" ) {
2020-03-17 04:37:52 +01:00
return false
2018-02-03 10:28:02 +01:00
}
2020-11-29 05:27:11 +01:00
sendSuccessfulAccountAuth ( nil , client , rb , true )
2018-02-03 10:28:02 +01:00
return false
}
2021-11-30 21:27:25 +01:00
func sendAuthErrorResponse ( client * Client , rb * ResponseBuffer , err error ) {
msg := authErrorToMessage ( client . server , err )
rb . Add ( nil , client . server . name , ERR_SASLFAIL , client . nick , fmt . Sprintf ( "%s: %s" , client . t ( "SASL authentication failed" ) , client . t ( msg ) ) )
if err == errAccountUnverified {
2021-12-01 18:16:03 +01:00
rb . Add ( nil , client . server . name , "NOTE" , "AUTHENTICATE" , "VERIFICATION_REQUIRED" , "*" , client . t ( err . Error ( ) ) )
2021-11-30 21:27:25 +01:00
}
}
2018-02-11 11:30:40 +01:00
func authErrorToMessage ( server * Server , err error ) ( msg string ) {
2020-06-12 21:51:48 +02:00
if throttled , ok := err . ( * ThrottleError ) ; ok {
return throttled . Error ( )
}
2019-12-25 21:06:26 +01:00
switch err {
2020-10-27 16:24:17 +01:00
case errAccountDoesNotExist , errAccountUnverified , errAccountInvalidCredentials , errAuthzidAuthcidMismatch , errNickAccountMismatch , errAccountSuspended :
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
2021-07-30 18:20:13 +02:00
func authExternalHandler ( server * Server , client * Client , session * Session , value [ ] byte , rb * ResponseBuffer ) bool {
defer session . sasl . Clear ( )
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-06-12 21:51:48 +02:00
// see #843: strip the device ID for the benefit of clients that don't
// distinguish user/ident from account name
if strudelIndex := strings . IndexByte ( authzid , '@' ) ; strudelIndex != - 1 {
2020-06-16 11:10:09 +02:00
var deviceID string
authzid , deviceID = authzid [ : strudelIndex ] , authzid [ strudelIndex + 1 : ]
2020-06-16 10:57:49 +02:00
if ! client . registered {
2020-06-16 11:10:09 +02:00
rb . session . deviceID = deviceID
2020-06-16 10:57:49 +02:00
}
2020-06-12 21:51:48 +02:00
}
2020-09-23 08:23:35 +02:00
err = server . accounts . AuthenticateByCertificate ( client , rb . session . certfp , rb . session . peerCerts , authzid )
2019-12-25 21:06:26 +01:00
}
if err != nil {
2021-11-30 21:27:25 +01:00
sendAuthErrorResponse ( client , rb , err )
2019-12-25 21:06:26 +01:00
return false
2020-11-29 05:27:11 +01:00
} else if ! fixupNickEqualsAccount ( client , rb , server . Config ( ) , "" ) {
2020-03-17 04:37:52 +01:00
return false
2019-12-25 21:06:26 +01:00
}
2020-11-29 05:27:11 +01:00
sendSuccessfulAccountAuth ( nil , client , rb , true )
2018-02-03 10:28:02 +01:00
return false
}
2021-07-30 18:20:13 +02:00
// AUTHENTICATE SCRAM-SHA-256
func authScramHandler ( server * Server , client * Client , session * Session , value [ ] byte , rb * ResponseBuffer ) bool {
continueAuth := true
defer func ( ) {
if ! continueAuth {
session . sasl . Clear ( )
}
} ( )
// first message? if so, initialize the SCRAM conversation
if session . sasl . scramConv == nil {
session . sasl . scramConv = server . accounts . NewScramConversation ( )
}
// wait for a final AUTHENTICATE + from the client to conclude authentication
if session . sasl . scramConv . Done ( ) {
continueAuth = false
if session . sasl . scramConv . Valid ( ) {
2021-08-03 17:47:00 +02:00
authcid := session . sasl . scramConv . Username ( )
if strudelIndex := strings . IndexByte ( authcid , '@' ) ; strudelIndex != - 1 {
var deviceID string
authcid , deviceID = authcid [ : strudelIndex ] , authcid [ strudelIndex + 1 : ]
if ! client . registered {
rb . session . deviceID = deviceID
}
}
2021-07-30 18:20:13 +02:00
authzid := session . sasl . scramConv . AuthzID ( )
2021-08-03 17:47:00 +02:00
if authzid != "" && authzid != authcid {
2021-07-30 18:20:13 +02:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed: authcid and authzid should be the same" ) )
return false
}
2021-08-03 17:47:00 +02:00
account , err := server . accounts . LoadAccount ( authcid )
2021-07-30 18:20:13 +02:00
if err == nil {
server . accounts . Login ( client , account )
if fixupNickEqualsAccount ( client , rb , server . Config ( ) , "" ) {
sendSuccessfulAccountAuth ( nil , client , rb , true )
}
} else {
2021-08-03 17:47:00 +02:00
server . logger . Error ( "internal" , "SCRAM succeeded but couldn't load account" , authcid , err . Error ( ) )
2021-07-30 18:20:13 +02:00
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed" ) )
}
} else {
rb . Add ( nil , server . name , ERR_SASLFAIL , client . nick , client . t ( "SASL authentication failed" ) )
}
return false
}
response , err := session . sasl . scramConv . Step ( string ( value ) )
if err == nil {
rb . Add ( nil , server . name , "AUTHENTICATE" , base64 . StdEncoding . EncodeToString ( [ ] byte ( response ) ) )
} else {
continueAuth = false
rb . Add ( nil , server . name , ERR_SASLFAIL , client . Nick ( ) , err . Error ( ) )
return false
}
return false
}
2018-02-03 10:28:02 +01:00
// AWAY [<message>]
2021-03-11 02:07:43 +01:00
func awayHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2023-02-05 06:50:14 +01:00
// #1996: `AWAY :` is treated the same as `AWAY`
2019-02-17 20:29:04 +01:00
var awayMessage string
2018-02-03 10:28:02 +01:00
if len ( msg . Params ) > 0 {
2019-02-17 20:29:04 +01:00
awayMessage = msg . Params [ 0 ]
2021-03-18 08:49:12 +01:00
awayMessage = ircutils . TruncateUTF8Safe ( awayMessage , server . Config ( ) . Limits . AwayLen )
2018-02-03 10:28:02 +01:00
}
2023-02-05 06:50:14 +01:00
wasAway , nowAway := rb . session . SetAway ( awayMessage )
2018-02-03 10:28:02 +01:00
2023-02-05 06:50:14 +01:00
if nowAway != "" {
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
}
2023-02-05 06:50:14 +01:00
if client . registered && wasAway != nowAway {
dispatchAwayNotify ( client , nowAway )
} // else: we'll send it (if applicable) after reattach
2020-05-19 20:12:20 +02:00
return false
}
2023-02-05 06:50:14 +01:00
func dispatchAwayNotify ( client * Client , awayMessage string ) {
2018-02-03 10:28:02 +01:00
// dispatch away-notify
2019-04-28 20:52:15 +02:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2021-07-24 20:52:03 +02:00
for session := range client . FriendsMonitors ( caps . AwayNotify ) {
2023-02-05 06:50:14 +01:00
if awayMessage != "" {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . accountName , isBot , nil , "AWAY" , awayMessage )
2018-02-03 10:28:02 +01:00
} else {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . accountName , isBot , nil , "AWAY" )
2018-02-03 10:28:02 +01:00
}
}
}
2019-12-23 21:26:37 +01:00
// BATCH {+,-}reference-tag type [params...]
2021-03-11 02:07:43 +01:00
func batchHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2019-12-23 21:26:37 +01:00
tag := msg . Params [ 0 ]
fail := false
sendErrors := rb . session . batch . command != "NOTICE"
if len ( tag ) == 0 {
fail = true
} else if tag [ 0 ] == '+' {
2020-03-27 15:40:19 +01:00
if len ( msg . Params ) < 3 || msg . Params [ 1 ] != caps . MultilineBatchType {
2019-12-23 21:26:37 +01:00
fail = true
} else {
2020-03-27 15:40:19 +01:00
err := rb . session . StartMultilineBatch ( tag [ 1 : ] , msg . Params [ 2 ] , rb . Label , msg . ClientOnlyTags ( ) )
fail = ( err != nil )
if ! fail {
// suppress ACK for the initial BATCH message (we'll apply the stored label later)
2019-12-23 21:26:37 +01:00
rb . Label = ""
}
}
} else if tag [ 0 ] == '-' {
2020-03-27 15:40:19 +01:00
batch , err := rb . session . EndMultilineBatch ( tag [ 1 : ] )
fail = ( err != nil )
if ! fail {
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 {
2020-03-27 15:40:19 +01:00
rb . session . EndMultilineBatch ( "" )
2019-12-23 21:26:37 +01:00
if sendErrors {
rb . Add ( nil , server . name , "FAIL" , "BATCH" , "MULTILINE_INVALID" , client . t ( "Invalid multiline batch" ) )
}
}
return false
}
2018-02-03 10:28:02 +01:00
// CAP <subcmd> [<caps>]
2021-03-11 02:07:43 +01:00
func capHandler ( server * Server , client * Client , msg ircmsg . Message , 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
2020-12-06 05:06:23 +01:00
} else if rb . session . hideSTS {
supportedCaps = config . Server . supportedCapsWithoutSTS
2019-08-27 06:51:09 +02:00
}
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:
2021-02-13 13:44:13 +01:00
// 1. WeeChat 1.4 won't accept the CAP reply unless it contains the server.name source
2019-11-10 02:31:56 +01:00
// 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.
2021-12-16 07:43:33 +01:00
// :server.name CAP nickname LS * :<tokens>\r\n
// 1 [5 ] 1 [4 ] [2 ]
maxLen := ( MaxLineLen - 2 ) - 1 - len ( server . name ) - 5 - len ( details . nick ) - 1 - len ( subCommand ) - 4
2019-11-10 02:31:56 +01:00
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
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
2021-03-11 02:07:43 +01:00
func chathistoryHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) ( exiting bool ) {
2019-02-04 11:18:17 +01:00
var items [ ] history . Item
2020-02-19 01:38:42 +01:00
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
2021-04-07 11:40:39 +02:00
var listTargets bool
var targets [ ] history . TargetListing
2019-02-04 11:18:17 +01:00
defer func ( ) {
2019-05-07 05:17:57 +02:00
// errors are sent either without a batch, or in a draft/labeled-response batch as usual
2021-04-06 06:46:07 +02:00
if err == utils . ErrInvalidParams {
2020-10-13 04:42:03 +02:00
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "INVALID_PARAMS" , msg . Params [ 0 ] , client . t ( "Invalid parameters" ) )
2021-04-07 11:40:39 +02:00
} else if ! listTargets && sequence == nil {
2021-07-04 07:37:59 +02:00
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "INVALID_TARGET" , msg . Params [ 0 ] , utils . SafeErrorParam ( target ) , client . t ( "Messages could not be retrieved" ) )
2020-02-19 01:38:42 +01:00
} else if err != nil {
rb . Add ( nil , server . name , "FAIL" , "CHATHISTORY" , "MESSAGE_ERROR" , msg . Params [ 0 ] , client . t ( "Messages could not be retrieved" ) )
2020-10-13 04:42:03 +02:00
} else {
// successful responses are sent as a chathistory or history batch
2021-04-07 11:40:39 +02:00
if listTargets {
batchID := rb . StartNestedBatch ( "draft/chathistory-targets" )
2021-04-06 06:46:07 +02:00
defer rb . EndNestedBatch ( batchID )
2021-04-07 11:40:39 +02:00
for _ , target := range targets {
name := server . UnfoldName ( target . CfName )
rb . Add ( nil , server . name , "CHATHISTORY" , "TARGETS" , name ,
target . Time . Format ( IRCv3TimestampFormat ) )
2021-04-06 06:46:07 +02:00
}
} else if channel != nil {
2021-11-01 06:24:14 +01:00
channel . replayHistoryItems ( rb , items , true )
2020-10-13 04:42:03 +02:00
} else {
2021-11-01 06:24:14 +01:00
client . replayPrivmsgHistory ( rb , items , target , true )
2020-10-13 04:42:03 +02:00
}
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 ]
2021-04-07 11:40:39 +02:00
listTargets = ( preposition == "targets" )
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" {
2021-11-01 06:24:14 +01:00
msgid , err = history . NormalizeMsgid ( value ) , nil
2019-02-04 11:18:17 +01:00
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
roundUp := func ( endpoint time . Time ) ( result time . Time ) {
return endpoint . Truncate ( time . Millisecond ) . Add ( time . Millisecond )
}
2021-04-06 06:46:07 +02:00
paramPos := 2
2020-02-19 01:38:42 +01:00
var start , end history . Selector
var limit int
2019-02-04 11:18:17 +01:00
switch preposition {
2021-04-07 11:40:39 +02:00
case "targets" :
2021-04-06 06:46:07 +02:00
// use the same selector parsing as BETWEEN,
// except that we have no target so we have one fewer parameter
paramPos = 1
fallthrough
2020-02-19 01:38:42 +01:00
case "between" :
2021-04-06 06:46:07 +02:00
start . Msgid , start . Time , err = parseQueryParam ( msg . Params [ paramPos ] )
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
}
2021-04-06 06:46:07 +02:00
end . Msgid , end . Time , err = parseQueryParam ( msg . Params [ paramPos + 1 ] )
2020-02-19 01:38:42 +01:00
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
}
}
2021-04-06 06:46:07 +02:00
limit = parseHistoryLimit ( paramPos + 2 )
2020-02-19 01:38:42 +01:00
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 :
2021-04-06 06:46:07 +02:00
err = utils . ErrInvalidParams
2020-02-19 01:38:42 +01:00
return
2019-02-04 11:18:17 +01:00
}
2021-04-07 11:40:39 +02:00
if listTargets {
targets , err = client . listTargets ( start , end , limit )
2020-02-19 01:38:42 +01:00
} else {
2021-11-01 06:23:07 +01:00
channel , sequence , err = server . GetHistorySequence ( nil , client , target )
2021-04-07 11:40:39 +02:00
if err != nil || sequence == nil {
return
}
if preposition == "around" {
items , err = sequence . Around ( start , limit )
} else {
items , err = sequence . Between ( start , end , limit )
}
2020-02-19 01:38:42 +01:00
}
2019-02-04 11:18:17 +01:00
return
}
2018-02-03 20:48:44 +01:00
// DEBUG <subcmd>
2021-03-11 02:07:43 +01:00
func debugHandler ( server * Server , client * Client , msg ircmsg . Message , 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" :
2021-05-25 06:34:38 +02:00
profFile := server . Config ( ) . getOutputPath ( "ergo.mprof" )
2018-02-03 10:28:02 +01:00
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" :
2021-05-25 06:34:38 +02:00
profFile := server . Config ( ) . getOutputPath ( "ergo.prof" )
2018-02-03 10:28:02 +01:00
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" :
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 {
2020-05-08 08:47:08 +02:00
rb . Notice ( fmt . Sprintf ( client . t ( "To confirm, run this command: %s" ) , fmt . Sprintf ( "/DEBUG CRASHSERVER %s" , code ) ) )
2020-02-21 18:41:04 +01:00
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
}
2021-03-11 02:07:43 +01:00
func defconHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-07-08 11:32:14 +02:00
if len ( msg . Params ) > 0 {
level , err := strconv . Atoi ( msg . Params [ 0 ] )
if err == nil && 1 <= level && level <= 5 {
server . SetDefcon ( uint32 ( level ) )
server . snomasks . Send ( sno . LocalAnnouncements , fmt . Sprintf ( "%s [%s] set DEFCON level to %d" , client . Nick ( ) , client . Oper ( ) . Name , level ) )
} else {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , msg . Command , client . t ( "Invalid DEFCON parameter" ) )
return false
}
}
rb . Notice ( fmt . Sprintf ( client . t ( "Current DEFCON level is %d" ) , server . Defcon ( ) ) )
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 )
}
2022-08-05 16:09:54 +02:00
desc = fmt . Sprintf ( "%s [%s] added on [%s]" , desc , info . TimeLeft ( ) , info . TimeCreated . UTC ( ) . Format ( time . RFC1123 ) )
2021-01-22 15:38:40 +01:00
banType := "Ban"
if info . RequireSASL {
banType = "SASL required"
}
return fmt . Sprintf ( client . t ( "%[1]s - %[2]s - added by %[3]s - %[4]s" ) , banType , key , info . OperName , desc )
2019-01-22 11:01:01 +01:00
}
2018-02-03 10:28:02 +01:00
// DLINE [ANDKILL] [MYSELF] [duration] <ip>/<net> [ON <server>] [reason [| oper reason]]
// DLINE LIST
2021-03-11 02:07:43 +01:00
func dlineHandler ( server * Server , client * Client , msg ircmsg . Message , 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 ( )
2021-01-15 15:26:34 +01:00
if ! oper . HasRoleCapab ( "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
}
2020-07-10 23:09:02 +02:00
if ! dlineMyself && hostNet . Contains ( rb . session . IP ( ) ) {
2019-01-22 11:01:01 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . nick , msg . Command , client . t ( "This ban matches you. To DLINE yourself, you must use the command: /DLINE MYSELF <arguments>" ) )
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
}
2021-01-22 15:38:40 +01:00
err = server . dlines . AddNetwork ( flatip . FromNetIPNet ( hostNet ) , duration , false , 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 {
2020-07-10 23:09:02 +02:00
var sessionsToKill [ ] * Session
2018-02-03 10:28:02 +01:00
var killedClientNicks [ ] string
for _ , mcl := range server . clients . AllClients ( ) {
2020-07-10 23:09:02 +02:00
nickKilled := false
for _ , session := range mcl . Sessions ( ) {
if hostNet . Contains ( session . IP ( ) ) {
sessionsToKill = append ( sessionsToKill , session )
if ! nickKilled {
killedClientNicks = append ( killedClientNicks , mcl . Nick ( ) )
nickKilled = true
}
}
2018-02-03 10:28:02 +01:00
}
}
2020-07-10 23:09:02 +02:00
for _ , session := range sessionsToKill {
mcl := session . client
mcl . Quit ( fmt . Sprintf ( mcl . t ( "You have been banned from this server (%s)" ) , reason ) , session )
if session == rb . session {
2018-02-03 10:28:02 +01:00
killClient = true
} else {
// if mcl == client, we kill them below
2020-07-10 23:09:02 +02:00
mcl . destroy ( session )
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
}
2020-04-15 10:14:17 +02:00
// EXTJWT <target> [service_name]
2021-03-11 02:07:43 +01:00
func extjwtHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-04-15 10:14:17 +02:00
accountName := client . AccountName ( )
if accountName == "*" {
accountName = ""
}
claims := jwt . MapClaims {
"iss" : server . name ,
"sub" : client . Nick ( ) ,
"account" : accountName ,
"umodes" : [ ] string { } ,
}
if msg . Params [ 0 ] != "*" {
channel := server . channels . Get ( msg . Params [ 0 ] )
if channel == nil {
rb . Add ( nil , server . name , "FAIL" , "EXTJWT" , "NO_SUCH_CHANNEL" , client . t ( "No such channel" ) )
return false
}
claims [ "channel" ] = channel . Name ( )
claims [ "joined" ] = 0
claims [ "cmodes" ] = [ ] string { }
2021-01-21 03:13:18 +01:00
if present , joinTimeSecs , cModes := channel . ClientStatus ( client ) ; present {
claims [ "joined" ] = joinTimeSecs
2020-06-15 20:16:02 +02:00
var modeStrings [ ] string
for _ , cMode := range cModes {
modeStrings = append ( modeStrings , string ( cMode ) )
}
claims [ "cmodes" ] = modeStrings
2020-04-15 10:14:17 +02:00
}
}
2020-06-15 20:16:02 +02:00
config := server . Config ( )
var serviceName string
var sConfig jwt . JwtServiceConfig
2020-04-15 10:14:17 +02:00
if 1 < len ( msg . Params ) {
2020-06-15 20:16:02 +02:00
serviceName = strings . ToLower ( msg . Params [ 1 ] )
sConfig = config . Extjwt . Services [ serviceName ]
} else {
serviceName = "*"
sConfig = config . Extjwt . Default
}
2020-04-15 10:14:17 +02:00
2020-06-15 20:16:02 +02:00
if ! sConfig . Enabled ( ) {
rb . Add ( nil , server . name , "FAIL" , "EXTJWT" , "NO_SUCH_SERVICE" , client . t ( "No such service" ) )
return false
2020-04-15 10:14:17 +02:00
}
2020-06-15 20:16:02 +02:00
tokenString , err := sConfig . Sign ( claims )
2020-04-15 10:14:17 +02:00
if err == nil {
2022-04-24 17:57:21 +02:00
maxTokenLength := maxLastArgLength
2020-04-15 10:14:17 +02:00
for maxTokenLength < len ( tokenString ) {
2020-06-15 20:16:02 +02:00
rb . Add ( nil , server . name , "EXTJWT" , msg . Params [ 0 ] , serviceName , "*" , tokenString [ : maxTokenLength ] )
2020-04-15 10:14:17 +02:00
tokenString = tokenString [ maxTokenLength : ]
}
2020-06-15 20:16:02 +02:00
rb . Add ( nil , server . name , "EXTJWT" , msg . Params [ 0 ] , serviceName , tokenString )
2020-04-15 10:14:17 +02:00
} else {
rb . Add ( nil , server . name , "FAIL" , "EXTJWT" , "UNKNOWN_ERROR" , client . t ( "Could not generate EXTJWT token" ) )
}
return false
}
2018-02-03 20:48:44 +01:00
// HELP [<query>]
2020-12-29 11:20:18 +01:00
// HELPOP [<query>]
2021-03-11 02:07:43 +01:00
func helpHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-12-29 11:20:18 +01:00
if len ( msg . Params ) == 0 {
2018-02-03 10:28:02 +01:00
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
}
2020-12-29 11:20:18 +01:00
argument := strings . ToLower ( strings . TrimSpace ( msg . Params [ 0 ] ) )
2018-02-03 10:28:02 +01:00
// 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 {
2020-12-29 11:20:18 +01:00
client . sendHelp ( argument , helpHandler . textGenerator ( client ) , rb )
2018-02-03 10:28:02 +01:00
} else {
2020-12-29 11:20:18 +01:00
client . sendHelp ( argument , client . t ( helpHandler . text ) , rb )
2018-02-03 10:28:02 +01:00
}
} else {
2020-12-29 19:19:46 +01:00
rb . Add ( nil , server . name , ERR_HELPNOTFOUND , client . Nick ( ) , strings . ToUpper ( utils . SafeErrorParam ( argument ) ) , client . t ( "Help not found" ) )
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
2021-04-19 14:54:40 +02:00
// HISTORY alice 15
2020-02-19 01:38:42 +01:00
// HISTORY #darwin 1h
2021-03-11 02:07:43 +01:00
func historyHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
2020-05-12 18:05:40 +02:00
items , channel , err := easySelectHistory ( server , client , msg . Params )
2019-02-04 18:31:44 +01:00
2020-05-12 18:05:40 +02:00
if err == errNoSuchChannel {
rb . Add ( nil , server . name , ERR_NOSUCHCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( msg . Params [ 0 ] ) , client . t ( "No such channel" ) )
return false
} else if err != nil {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , msg . Command , client . t ( "Could not retrieve history" ) )
2019-02-04 18:31:44 +01:00
return false
2019-02-04 11:18:17 +01:00
}
2020-05-12 18:05:40 +02:00
if len ( items ) != 0 {
2020-02-19 01:38:42 +01:00
if channel != nil {
2021-11-01 06:24:14 +01:00
channel . replayHistoryItems ( rb , items , true )
2020-02-19 01:38:42 +01:00
} else {
2021-11-01 06:24:14 +01:00
client . replayPrivmsgHistory ( rb , items , "" , true )
2020-02-19 01:38:42 +01:00
}
}
2019-02-04 11:18:17 +01:00
return false
}
2018-02-03 10:28:02 +01:00
// INFO
2021-03-11 02:07:43 +01:00
func infoHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-06-01 19:17:26 +02:00
nick := client . Nick ( )
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 {
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , line )
2018-02-03 10:28:02 +01:00
}
2021-05-25 06:34:38 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , fmt . Sprintf ( client . t ( "This is Ergo version %s." ) , SemVer ) )
2020-06-01 19:17:26 +02:00
if Commit != "" {
rb . Add ( nil , server . name , RPL_INFO , nick , fmt . Sprintf ( client . t ( "It was built from git hash %s." ) , Commit ) )
}
2020-08-24 03:59:19 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , fmt . Sprintf ( client . t ( "It was compiled using %s." ) , runtime . Version ( ) ) )
2022-02-08 13:38:11 +01:00
rb . Add ( nil , server . name , RPL_INFO , nick , fmt . Sprintf ( client . t ( "This server has been running since %s." ) , server . ctime . Format ( time . RFC1123 ) ) )
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , "" )
2021-05-25 06:34:38 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , client . t ( "Ergo is released under the MIT license." ) )
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , "" )
rb . Add ( nil , server . name , RPL_INFO , nick , client . t ( "Core Developers:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range infoString2 {
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , line )
2018-02-03 10:28:02 +01:00
}
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , client . t ( "Former Core Developers:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range infoString3 {
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , line )
2018-02-03 10:28:02 +01:00
}
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , client . t ( "For a more complete list of contributors, see our changelog:" ) )
2021-05-25 06:34:38 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , " https://github.com/ergochat/ergo/blob/master/CHANGELOG.md" )
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , 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 ) {
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , client . t ( "Translators:" ) )
2018-02-03 10:28:02 +01:00
for _ , line := range tlines {
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , " " + strings . Replace ( line , "\n" , ", " , - 1 ) )
2018-02-03 10:28:02 +01:00
}
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_INFO , nick , "" )
2018-02-03 10:28:02 +01:00
}
2020-06-01 19:17:26 +02:00
rb . Add ( nil , server . name , RPL_ENDOFINFO , nick , client . t ( "End of /INFO" ) )
2018-02-03 10:28:02 +01:00
return false
}
// INVITE <nickname> <channel>
2020-10-26 03:16:19 +01:00
// UNINVITE <nickname> <channel>
2021-03-11 02:07:43 +01:00
func inviteHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-10-26 03:16:19 +01:00
invite := msg . Command == "INVITE"
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 {
2020-03-18 11:13:57 +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
}
2020-10-26 03:16:19 +01:00
if invite {
channel . Invite ( target , client , rb )
} else {
channel . Uninvite ( target , client , rb )
}
2018-02-03 10:28:02 +01:00
return false
}
// ISON <nick>{ <nick>}
2021-03-11 02:07:43 +01:00
func isonHandler ( server * Server , client * Client , msg ircmsg . Message , 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>}]
2021-03-11 02:07:43 +01:00
func joinHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-12-01 20:23:47 +01:00
// #1417: allow `JOIN 0` with a confirmation code
2018-02-03 10:28:02 +01:00
if msg . Params [ 0 ] == "0" {
2020-12-01 20:23:47 +01:00
expectedCode := utils . ConfirmationCode ( "" , rb . session . ctime )
if len ( msg . Params ) == 1 || msg . Params [ 1 ] != expectedCode {
rb . Notice ( fmt . Sprintf ( client . t ( "Warning: /JOIN 0 will remove you from all channels. To confirm, type: /JOIN 0 %s" ) , expectedCode ) )
} else {
for _ , channel := range client . Channels ( ) {
channel . Part ( client , "" , rb )
}
}
2018-02-03 10:28:02 +01:00
return false
}
// handle regular JOINs
channels := strings . Split ( msg . Params [ 0 ] , "," )
var keys [ ] string
if len ( msg . Params ) > 1 {
keys = strings . Split ( msg . Params [ 1 ] , "," )
}
for i , name := range channels {
2019-12-03 03:13:09 +01:00
if name == "" {
continue // #679
}
2018-02-03 10:28:02 +01:00
var key string
if len ( keys ) > i {
key = keys [ i ]
}
2020-12-14 11:00:21 +01:00
err , forward := server . channels . Join ( client , name , key , false , rb )
2019-12-17 19:21:26 +01:00
if err != nil {
2020-12-14 11:00:21 +01:00
if forward != "" {
rb . Add ( nil , server . name , ERR_LINKCHANNEL , client . Nick ( ) , utils . SafeErrorParam ( name ) , forward , client . t ( "Forwarding to another channel" ) )
name = forward
err , _ = server . channels . Join ( client , name , key , false , rb )
}
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 ) {
2020-07-01 01:24:56 +02:00
var code , errMsg , forbiddingMode string
2019-12-17 19:21:26 +01:00
switch err {
case errInsufficientPrivs :
2020-07-01 01:24:56 +02:00
code , errMsg = ERR_NOSUCHCHANNEL , ` Only server operators can create new channels `
2019-12-17 19:21:26 +01:00
case errConfusableIdentifier :
2020-07-01 01:24:56 +02:00
code , errMsg = ERR_NOSUCHCHANNEL , ` That channel name is too close to the name of another channel `
2019-12-17 19:21:26 +01:00
case errChannelPurged :
2020-07-01 01:24:56 +02:00
code , errMsg = ERR_NOSUCHCHANNEL , err . Error ( )
case errTooManyChannels :
code , errMsg = ERR_TOOMANYCHANNELS , ` You have joined too many channels `
case errLimitExceeded :
code , forbiddingMode = ERR_CHANNELISFULL , "l"
case errWrongChannelKey :
code , forbiddingMode = ERR_BADCHANNELKEY , "k"
case errInviteOnly :
code , forbiddingMode = ERR_INVITEONLYCHAN , "i"
case errBanned :
code , forbiddingMode = ERR_BANNEDFROMCHAN , "b"
case errRegisteredOnly :
code , errMsg = ERR_NEEDREGGEDNICK , ` You must be registered to join that channel `
2019-12-17 19:21:26 +01:00
default :
2020-07-01 01:24:56 +02:00
code , errMsg = ERR_NOSUCHCHANNEL , ` No such channel `
}
if forbiddingMode != "" {
errMsg = fmt . Sprintf ( client . t ( "Cannot join channel (+%s)" ) , forbiddingMode )
} else {
errMsg = client . t ( errMsg )
2019-12-17 19:21:26 +01:00
}
2020-07-01 01:24:56 +02:00
rb . Add ( nil , client . server . name , code , client . Nick ( ) , utils . SafeErrorParam ( name ) , errMsg )
2019-12-17 19:21:26 +01:00
}
2018-05-25 08:46:36 +02:00
// SAJOIN [nick] #channel{,#channel}
2021-03-11 02:07:43 +01:00
func sajoinHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-05-25 08:46:36 +02:00
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 ]
}
}
2021-01-15 15:26:34 +01:00
message := fmt . Sprintf ( "Operator %s ran SAJOIN %s" , client . Oper ( ) . Name , strings . Join ( msg . Params , " " ) )
server . snomasks . Send ( sno . LocalOpers , message )
server . logger . Info ( "opers" , message )
2018-05-25 08:46:36 +02:00
channels := strings . Split ( channelString , "," )
for _ , chname := range channels {
2020-12-14 11:00:21 +01:00
err , _ := server . channels . Join ( target , chname , "" , true , rb )
2019-12-17 19:21:26 +01:00
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>]
2021-07-10 17:36:39 +02:00
// RFC 2812 requires the number of channels to be either 1 or equal to
// the number of users.
// Addditionally, we support multiple channels and a single user.
2021-03-11 02:07:43 +01:00
func kickHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
channels := strings . Split ( msg . Params [ 0 ] , "," )
users := strings . Split ( msg . Params [ 1 ] , "," )
2021-07-10 11:11:02 +02:00
if ( len ( channels ) != len ( users ) ) && ( len ( users ) != 1 ) && ( len ( channels ) != 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
}
2021-07-10 17:36:39 +02:00
var kicks [ ] kickCmd
if len ( users ) == 1 {
kicks = make ( [ ] kickCmd , 0 , len ( channels ) )
// Single user, possibly multiple channels
user := users [ 0 ]
for _ , channel := range channels {
if channel == "" {
continue // #679
}
kicks = append ( kicks , kickCmd { channel , user } )
2021-07-10 11:11:02 +02:00
}
2021-07-10 17:36:39 +02:00
} else {
// Multiple users, either a single channel or as many channels
// as users.
kicks = make ( [ ] kickCmd , 0 , len ( users ) )
channel := channels [ 0 ]
for index , user := range users {
if len ( channels ) > 1 {
channel = channels [ index ]
}
if channel == "" {
continue // #679
}
kicks = append ( kicks , kickCmd { channel , user } )
2019-12-03 03:13:09 +01:00
}
2018-02-03 10:28:02 +01:00
}
var comment string
if len ( msg . Params ) > 2 {
comment = msg . Params [ 2 ]
}
2021-08-13 19:33:56 +02:00
if comment == "" {
comment = client . Nick ( )
}
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
}
2022-02-08 01:06:31 +01:00
channel . Kick ( client , target , comment , rb , false )
2018-02-03 10:28:02 +01:00
}
return false
}
// KILL <nickname> <comment>
2021-03-11 02:07:43 +01:00
func killHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
nickname := msg . Params [ 0 ]
2023-02-27 09:22:38 +01:00
var comment string
2018-02-03 10:28:02 +01:00
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 {
2020-07-10 23:09:02 +02:00
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
2020-07-10 23:09:02 +02:00
} else if target . AlwaysOn ( ) {
rb . Add ( nil , client . server . name , ERR_UNKNOWNERROR , client . Nick ( ) , "KILL" , fmt . Sprintf ( client . t ( "Client %s is always-on and cannot be fully removed by /KILL; consider /NS SUSPEND instead" ) , target . Nick ( ) ) )
2018-02-03 10:28:02 +01:00
}
2023-02-27 09:22:38 +01:00
quitMsg := "Killed"
if comment != "" {
quitMsg = fmt . Sprintf ( "Killed by %s: %s" , client . Nick ( ) , comment )
}
2018-02-03 10:28:02 +01:00
2023-02-27 09:22:38 +01:00
var snoLine string
if comment == "" {
snoLine = fmt . Sprintf ( ircfmt . Unescape ( "%s was killed by %s" ) , target . Nick ( ) , client . Nick ( ) )
} else {
snoLine = fmt . Sprintf ( ircfmt . Unescape ( "%s was killed by %s $c[grey][$r%s$c[grey]]" ) , target . Nick ( ) , client . Nick ( ) , comment )
}
server . snomasks . Send ( sno . LocalKills , snoLine )
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
2021-03-11 02:07:43 +01:00
func klineHandler ( server * Server , client * Client , msg ircmsg . Message , 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 ( )
2021-01-15 15:26:34 +01:00
if ! oper . HasRoleCapab ( "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
}
2020-05-13 09:41:00 +02:00
matcher , err := utils . CompileGlob ( mask , false )
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
for _ , clientMask := range client . AllNickmasks ( ) {
2020-05-13 09:41:00 +02:00
if ! klineMyself && matcher . MatchString ( 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 ( ) {
2020-05-13 09:41:00 +02:00
if matcher . MatchString ( clientMask ) {
2018-02-03 10:28:02 +01:00
clientsToKill = append ( clientsToKill , mcl )
killedClientNicks = append ( killedClientNicks , mcl . nick )
2020-11-10 17:17:17 +01:00
break
2018-02-03 10:28:02 +01:00
}
}
}
for _ , mcl := range clientsToKill {
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>}
2021-03-11 02:07:43 +01:00
func languageHandler ( server * Server , client * Client , msg ircmsg . Message , 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>}]
2021-03-11 02:07:43 +01:00
func listHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-04-26 08:19:10 +02:00
config := server . Config ( )
2020-04-27 06:58:48 +02:00
if time . Since ( client . ctime ) < config . Channels . ListDelay && client . Account ( ) == "" && ! client . HasMode ( modes . Operator ) {
2020-04-26 08:19:10 +02:00
remaining := time . Until ( client . ctime . Add ( config . Channels . ListDelay ) )
2023-01-08 12:36:04 +01:00
rb . Notice ( fmt . Sprintf ( client . t ( "This server requires that you wait %v after connecting before you can use /LIST. You have %v left." ) , config . Channels . ListDelay , remaining . Round ( time . Millisecond ) ) )
2020-04-26 09:08:44 +02:00
rb . Add ( nil , server . name , RPL_LISTEND , client . Nick ( ) , client . t ( "End of LIST" ) )
2020-04-26 08:19:10 +02:00
return false
}
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
}
}
2020-06-29 21:41:29 +02:00
nick := client . Nick ( )
rplList := func ( channel * Channel ) {
2021-02-04 21:26:03 +01:00
members , name , topic := channel . listData ( )
rb . Add ( nil , client . server . name , RPL_LIST , nick , name , strconv . Itoa ( members ) , topic )
2020-06-29 21:41:29 +02:00
}
2021-02-07 04:45:34 +01:00
clientIsOp := client . HasRoleCapabs ( "sajoin" )
2018-02-03 10:28:02 +01:00
if len ( channels ) == 0 {
2023-01-04 11:06:21 +01:00
for _ , channel := range server . channels . ListableChannels ( ) {
2022-03-01 02:31:16 +01:00
if ! clientIsOp && channel . flags . HasMode ( modes . Secret ) && ! channel . hasClient ( client ) {
2018-02-03 10:28:02 +01:00
continue
}
if matcher . Matches ( channel ) {
2020-06-29 21:41:29 +02:00
rplList ( channel )
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 )
2022-03-01 02:31:16 +01:00
if channel == nil || ( ! clientIsOp && channel . flags . HasMode ( modes . Secret ) && ! channel . hasClient ( client ) ) {
2018-02-03 10:28:02 +01:00
continue
}
if matcher . Matches ( channel ) {
2020-06-29 21:41:29 +02:00
rplList ( channel )
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>]]
2021-03-11 02:07:43 +01:00
func lusersHandler ( server * Server , client * Client , msg ircmsg . Message , 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>...]]
2021-03-11 02:07:43 +01:00
func modeHandler ( server * Server , client * Client , msg ircmsg . Message , 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>...]]
2021-03-11 02:07:43 +01:00
func cmodeHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
}
2021-10-04 01:35:22 +02:00
isSamode := msg . Command == "SAMODE"
if isSamode {
message := fmt . Sprintf ( "Operator %s ran SAMODE %s" , client . Oper ( ) . Name , strings . Join ( msg . Params , " " ) )
server . snomasks . Send ( sno . LocalOpers , message )
server . logger . Info ( "opers" , message )
}
2019-10-13 12:07:30 +02:00
// process mode changes, include list operations (an empty set of changes does a list)
2021-10-04 01:35:22 +02:00
applied := channel . ApplyChannelModeChanges ( client , isSamode , changes , rb )
2020-04-23 04:51:19 +02:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
announceCmodeChanges ( channel , applied , details . nickMask , details . accountName , details . account , isBot , rb )
2018-02-03 10:28:02 +01:00
2020-03-25 17:08:08 +01:00
return false
}
2018-02-03 10:28:02 +01:00
2021-03-17 19:36:52 +01:00
func announceCmodeChanges ( channel * Channel , applied modes . ModeChanges , source , accountName , account string , isBot bool , rb * ResponseBuffer ) {
2018-02-03 10:28:02 +01:00
// send out changes
if len ( applied ) > 0 {
2020-04-23 04:51:19 +02:00
message := utils . MakeMessage ( "" )
changeStrings := applied . Strings ( )
for _ , changeString := range changeStrings {
message . Split = append ( message . Split , utils . MessagePair { Message : changeString } )
}
args := append ( [ ] string { channel . name } , changeStrings ... )
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( message . Time , message . Msgid , source , accountName , isBot , nil , "MODE" , args ... )
2018-02-03 10:28:02 +01:00
for _ , member := range channel . Members ( ) {
2020-03-25 17:08:08 +01:00
for _ , session := range member . Sessions ( ) {
if session != rb . session {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , source , accountName , isBot , nil , "MODE" , args ... )
2020-03-25 17:08:08 +01:00
}
2018-02-05 15:21:08 +01:00
}
2018-02-03 10:28:02 +01:00
}
2020-04-23 04:51:19 +02:00
channel . AddHistoryItem ( history . Item {
Type : history . Mode ,
Nick : source ,
AccountName : accountName ,
Message : message ,
2021-03-17 19:36:52 +01:00
IsBot : isBot ,
2020-05-12 18:05:40 +02:00
} , account )
2018-02-03 10:28:02 +01:00
}
}
2018-02-03 20:48:44 +01:00
// MODE <client> [<modestring> [<mode arguments>...]]
2021-03-11 02:07:43 +01:00
func umodeHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
2021-01-15 15:26:34 +01:00
if msg . Command == "SAMODE" {
message := fmt . Sprintf ( "Operator %s ran SAMODE %s" , client . Oper ( ) . Name , strings . Join ( msg . Params , " " ) )
server . snomasks . Send ( sno . LocalOpers , message )
server . logger . Info ( "opers" , message )
}
2018-02-03 10:28:02 +01:00
// 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 {
2020-03-25 17:08:08 +01:00
args := append ( [ ] string { targetNick } , applied . Strings ( ) ... )
rb . Add ( nil , cDetails . nickMask , "MODE" , args ... )
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 ( ) )
2021-02-07 04:45:34 +01:00
if target . HasMode ( modes . Operator ) {
2019-07-02 14:45:14 +02:00
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 ) {
2022-12-02 13:04:19 +01:00
if service , isService := ErgoServices [ strings . ToLower ( nick ) ] ; isService {
2019-05-15 03:00:00 +02:00
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...]
2021-03-11 02:07:43 +01:00
func monitorHandler ( server * Server , client * Client , msg ircmsg . Message , 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>}
2021-03-11 02:07:43 +01:00
func monitorRemoveHandler ( server * Server , client * Client , msg ircmsg . Message , 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 {
2020-06-22 05:51:31 +02:00
server . monitorManager . Remove ( rb . session , target )
2018-02-03 10:28:02 +01:00
}
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR + <target>{,<target>}
2021-03-11 02:07:43 +01:00
func monitorAddHandler ( server * Server , client * Client , msg ircmsg . Message , 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
2020-06-22 05:51:31 +02:00
err := server . monitorManager . Add ( rb . session , target , 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
2021-03-11 02:07:43 +01:00
func monitorClearHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-06-22 05:51:31 +02:00
server . monitorManager . RemoveAll ( rb . session )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// MONITOR L
2021-03-11 02:07:43 +01:00
func monitorListHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2019-02-17 20:29:04 +01:00
nick := client . Nick ( )
2020-06-22 05:51:31 +02:00
monitorList := server . monitorManager . List ( rb . session )
2018-02-03 10:28:02 +01:00
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 )
}
2020-12-30 06:41:34 +01:00
for _ , line := range utils . BuildTokenLines ( 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
2021-03-11 02:07:43 +01:00
func monitorStatusHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
var online [ ] string
var offline [ ] string
2020-06-22 05:51:31 +02:00
monitorList := server . monitorManager . List ( rb . session )
2018-02-03 10:28:02 +01:00
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 {
2020-12-30 06:41:34 +01:00
for _ , line := range utils . BuildTokenLines ( 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 {
2020-12-30 06:41:34 +01:00
for _ , line := range utils . BuildTokenLines ( 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
2021-03-11 02:07:43 +01:00
func motdHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-05 15:21:08 +01:00
server . MOTD ( client , rb )
return false
}
2018-02-25 11:02:42 +01:00
// NAMES [<channel>{,<channel>} [target]]
2021-03-11 02:07:43 +01:00
func namesHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-05 15:21:08 +01:00
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 {
2021-02-07 04:45:34 +01:00
if ! channel . flags . HasMode ( modes . Secret ) || channel . hasClient ( client ) || client . HasRoleCapabs ( "sajoin" ) {
2019-05-29 20:51:09 +02:00
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>
2021-03-11 02:07:43 +01:00
func nickHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2022-04-24 07:39:45 +02:00
newNick := msg . Params [ 0 ]
2019-02-05 06:19:03 +01:00
if client . registered {
2020-10-19 16:52:38 +02:00
if client . account == "" && server . Config ( ) . Accounts . NickReservation . ForbidAnonNickChanges {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , client . Nick ( ) , client . t ( "You may not change your nickname" ) )
return false
}
2022-04-24 07:39:45 +02:00
performNickChange ( server , client , client , nil , newNick , rb )
2018-02-27 03:44:03 +01:00
} else {
2022-04-24 07:39:45 +02:00
if newNick == "" {
// #1933: this would leave (*Client).preregNick at its zero value of "",
// which is the same condition as NICK not having been sent yet ---
// so we need to send an error immediately
rb . Add ( nil , server . name , ERR_NONICKNAMEGIVEN , "*" , client . t ( "No nickname given" ) )
return false
}
client . preregNick = newNick
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
}
2021-03-05 04:29:34 +01:00
// check whether a PRIVMSG or NOTICE is too long to be relayed without truncation
func validateLineLen ( msgType history . ItemType , source , target , payload string ) ( ok bool ) {
// :source PRIVMSG #target :payload\r\n
// 1: initial colon on prefix
// 1: space between prefix and command
// 1: space between command and target (first parameter)
// 1: space between target and payload (second parameter)
// 1: colon to send the payload as a trailing (we force trailing for PRIVMSG and NOTICE)
// 2: final \r\n
limit := MaxLineLen - 7
limit -= len ( source )
switch msgType {
case history . Privmsg :
limit -= 7
case history . Notice :
limit -= 6
default :
return true
}
limit -= len ( payload )
return limit >= 0
}
// check validateLineLen for an entire SplitMessage (which may consist of multiple lines)
func validateSplitMessageLen ( msgType history . ItemType , source , target string , message utils . SplitMessage ) ( ok bool ) {
if message . Is512 ( ) {
return validateLineLen ( msgType , source , target , message . Message )
} else {
for _ , messagePair := range message . Split {
if ! validateLineLen ( msgType , source , target , messagePair . Message ) {
return false
}
}
return true
}
}
2019-12-23 21:26:37 +01:00
// helper to store a batched PRIVMSG in the session object
2021-03-11 02:07:43 +01:00
func absorbBatchedMessage ( server * Server , client * Client , msg ircmsg . Message , batchTag string , histType history . ItemType , rb * ResponseBuffer ) {
2020-05-14 18:58:49 +02:00
var errorCode , errorMessage string
defer func ( ) {
if errorCode != "" {
if histType != history . Notice {
rb . Add ( nil , server . name , "FAIL" , "BATCH" , errorCode , errorMessage )
}
rb . session . EndMultilineBatch ( "" )
2020-03-27 15:40:19 +01:00
}
2020-05-14 18:58:49 +02:00
} ( )
if batchTag != rb . session . batch . label {
errorCode , errorMessage = "MULTILINE_INVALID" , client . t ( "Incorrect batch tag sent" )
2020-03-27 15:40:19 +01:00
return
2020-05-14 18:58:49 +02:00
} else if len ( msg . Params ) < 2 {
errorCode , errorMessage = "MULTILINE_INVALID" , client . t ( "Invalid multiline batch" )
2019-12-23 21:26:37 +01:00
return
}
rb . session . batch . command = msg . Command
isConcat , _ := msg . GetTag ( caps . MultilineConcatTag )
2020-05-14 18:58:49 +02:00
if isConcat && len ( msg . Params [ 1 ] ) == 0 {
errorCode , errorMessage = "MULTILINE_INVALID" , client . t ( "Cannot send a blank line with the multiline concat tag" )
return
}
2020-05-15 04:16:34 +02:00
if ! isConcat && len ( rb . session . batch . message . Split ) != 0 {
rb . session . batch . lenBytes ++ // bill for the newline
}
2019-12-23 21:26:37 +01:00
rb . session . batch . message . Append ( msg . Params [ 1 ] , isConcat )
2020-05-15 04:16:34 +02:00
rb . session . batch . lenBytes += len ( msg . Params [ 1 ] )
2019-12-23 21:26:37 +01:00
config := server . Config ( )
2020-05-15 04:16:34 +02:00
if config . Limits . Multiline . MaxBytes < rb . session . batch . lenBytes {
2020-05-14 18:58:49 +02:00
errorCode , errorMessage = "MULTILINE_MAX_BYTES" , strconv . Itoa ( config . Limits . Multiline . MaxBytes )
2019-12-23 21:26:37 +01:00
} else if config . Limits . Multiline . MaxLines != 0 && config . Limits . Multiline . MaxLines < rb . session . batch . message . LenLines ( ) {
2020-05-14 18:58:49 +02:00
errorCode , errorMessage = "MULTILINE_MAX_LINES" , strconv . Itoa ( config . Limits . Multiline . MaxLines )
2019-12-23 21:26:37 +01:00
}
}
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>}
2021-03-11 02:07:43 +01:00
func messageHandler ( server * Server , client * Client , msg ircmsg . Message , 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-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 == "" {
2021-03-05 04:29:34 +01:00
rb . Add ( nil , server . name , ERR_NOTEXTTOSEND , client . Nick ( ) , client . t ( "No text to send" ) )
2019-12-23 21:26:37 +01:00
return false
}
2019-03-19 08:35:49 +01:00
2020-06-29 10:32:39 +02:00
isCTCP := utils . IsRestrictedCTCPMessage ( message )
if histType == history . Privmsg && ! isCTCP {
client . UpdateActive ( rb . session )
}
if rb . session . isTor && isCTCP {
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
}
2020-06-08 02:19:28 +02:00
2020-06-08 07:17:45 +02:00
config := server . Config ( )
2020-09-09 10:01:46 +02:00
if config . isRelaymsgIdentifier ( targetString ) {
if histType == history . Privmsg {
rb . Add ( nil , server . name , ERR_NOSUCHNICK , client . Nick ( ) , targetString , client . t ( "Relayed users cannot receive private messages" ) )
2020-06-08 02:19:28 +02:00
}
2020-09-09 10:01:46 +02:00
// TAGMSG/NOTICEs are intentionally silently dropped
continue
2020-06-08 02:19:28 +02:00
}
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 )
2021-04-20 12:38:20 +02:00
} else if target [ 0 ] == '$' && len ( target ) > 2 && client . Oper ( ) . HasRoleCapab ( "massmessage" ) {
details := client . Details ( )
2021-04-20 13:04:24 +02:00
matcher , err := utils . CompileGlob ( target [ 2 : ] , false )
2021-04-20 12:38:20 +02:00
if err != nil {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . nick , command , client . t ( "Erroneous target" ) )
return
}
nickMaskString := details . nickMask
accountName := details . accountName
isBot := client . HasMode ( modes . Bot )
for _ , tClient := range server . clients . AllClients ( ) {
2021-04-20 13:05:05 +02:00
if ( target [ 1 ] == '$' && matcher . MatchString ( tClient . server . name ) ) || // $$servername
2021-04-20 13:04:24 +02:00
( target [ 1 ] == '#' && matcher . MatchString ( tClient . Hostname ( ) ) ) { // $#hostname
2021-04-20 12:38:20 +02:00
2021-04-20 13:04:24 +02:00
tnick := tClient . Nick ( )
2021-04-20 12:38:20 +02:00
for _ , session := range tClient . Sessions ( ) {
session . sendSplitMsgFromClientInternal ( false , nickMaskString , accountName , isBot , nil , command , tnick , message )
}
}
}
2019-12-23 21:26:37 +01:00
} else {
2020-05-25 16:47:09 +02:00
lowercaseTarget := strings . ToLower ( target )
2022-12-02 13:04:19 +01:00
service , isService := ErgoServices [ lowercaseTarget ]
2020-05-25 16:47:09 +02:00
_ , isZNC := zncHandlers [ lowercaseTarget ]
2020-07-24 08:37:36 +02:00
if isService || isZNC {
2020-07-24 08:55:46 +02:00
details := client . Details ( )
rb . addEchoMessage ( tags , details . nickMask , details . accountName , command , target , message )
2020-07-24 08:37:36 +02:00
if histType != history . Privmsg {
return // NOTICE and TAGMSG to services are ignored
}
2020-05-25 16:47:09 +02:00
if isService {
2019-12-23 21:26:37 +01:00
servicePrivmsgHandler ( service , server , client , message . Message , rb )
2020-05-25 16:47:09 +02:00
} else if isZNC {
2019-12-23 21:26:37 +01:00
zncPrivmsgHandler ( client , lowercaseTarget , message . Message , rb )
2018-02-03 10:28:02 +01:00
}
2020-05-25 16:47:09 +02:00
return
}
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-06-08 20:43:58 +02:00
// Restrict CTCP message for target user with +T
if user . modes . HasMode ( modes . UserNoCTCP ) && message . IsRestrictedCTCPMessage ( ) {
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 ( )
2020-07-09 10:33:09 +02:00
if details . account == "" && server . Defcon ( ) <= 3 {
rb . Add ( nil , server . name , ERR_NEEDREGGEDNICK , client . Nick ( ) , tnick , client . t ( "Direct messages from unregistered users are temporarily restricted" ) )
return
}
2021-03-04 04:36:29 +01:00
// restrict messages appropriately when +R is set
2022-05-06 04:34:43 +02:00
if details . account == "" && user . HasMode ( modes . RegisteredOnly ) && ! server . accepts . MaySendTo ( client , user ) {
2021-03-04 04:36:29 +01:00
rb . Add ( nil , server . name , ERR_NEEDREGGEDNICK , client . Nick ( ) , tnick , client . t ( "You must be registered to send a direct message to this user" ) )
return
}
2022-05-06 04:34:43 +02:00
if client . HasMode ( modes . RegisteredOnly ) && tDetails . account == "" {
// #1688: auto-ACCEPT on DM
server . accepts . Accept ( client , user )
}
2021-03-05 04:29:34 +01:00
if ! client . server . Config ( ) . Server . Compatibility . allowTruncation {
if ! validateSplitMessageLen ( histType , client . NickMaskString ( ) , tnick , message ) {
rb . Add ( nil , server . name , ERR_INPUTTOOLONG , client . Nick ( ) , client . t ( "Line too long to be relayed without truncation" ) )
return
}
}
2020-02-19 01:38:42 +01:00
nickMaskString := details . nickMask
accountName := details . accountName
2020-02-20 07:15:11 +01:00
var deliverySessions [ ] * Session
2021-03-04 04:36:29 +01:00
deliverySessions = append ( deliverySessions , user . Sessions ( ) ... )
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
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
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 {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , nickMaskString , accountName , isBot , 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
}
2021-03-17 19:36:52 +01:00
session . sendSplitMsgFromClientInternal ( false , nickMaskString , accountName , isBot , 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:
2020-07-24 08:55:46 +02:00
rb . addEchoMessage ( tags , nickMaskString , accountName , command , tnick , message )
2022-07-18 06:54:01 +02:00
if histType == history . Privmsg {
2019-12-23 21:26:37 +01:00
//TODO(dan): possibly implement cooldown of away notifications to users
2020-07-17 07:55:13 +02:00
if away , awayMessage := user . Away ( ) ; away {
rb . Add ( nil , server . name , RPL_AWAY , client . Nick ( ) , tnick , awayMessage )
}
2019-12-23 21:26:37 +01:00
}
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 {
2020-11-30 04:12:06 +01:00
Type : histType ,
Message : message ,
Tags : tags ,
2019-12-23 21:26:37 +01:00
}
2020-11-30 04:12:06 +01:00
client . addHistoryItem ( user , item , & details , & tDetails , config )
2018-02-03 10:28:02 +01:00
}
}
2020-07-10 00:36:45 +02:00
func itemIsStorable ( item * history . Item , config * Config ) bool {
switch item . Type {
case history . Tagmsg :
if config . History . TagmsgStorage . Default {
for _ , blacklistedTag := range config . History . TagmsgStorage . Blacklist {
if _ , ok := item . Tags [ blacklistedTag ] ; ok {
return false
}
}
return true
} else {
for _ , whitelistedTag := range config . History . TagmsgStorage . Whitelist {
if _ , ok := item . Tags [ whitelistedTag ] ; ok {
return true
}
}
return false
}
case history . Privmsg , history . Notice :
// don't store CTCP other than ACTION
return ! item . Message . IsRestrictedCTCPMessage ( )
default :
return true
}
}
2018-02-03 20:48:44 +01:00
// NPC <target> <sourcenick> <message>
2021-03-11 02:07:43 +01:00
func npcHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
fakeSource := msg . Params [ 1 ]
2020-03-19 22:09:52 +01:00
message := msg . Params [ 2 : ]
2018-02-03 10:28:02 +01:00
2020-09-16 18:03:06 +02:00
sendRoleplayMessage ( server , client , fakeSource , target , false , 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>
2021-03-11 02:07:43 +01:00
func npcaHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
fakeSource := msg . Params [ 1 ]
2020-03-19 22:09:52 +01:00
message := msg . Params [ 2 : ]
2018-02-03 10:28:02 +01:00
2020-09-16 18:03:06 +02:00
sendRoleplayMessage ( server , client , fakeSource , target , false , true , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
2019-12-18 21:44:06 +01:00
// OPER <name> [password]
2021-03-11 02:07:43 +01:00
func operHandler ( server * Server , client * Client , msg ircmsg . Message , 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
2020-04-23 04:11:44 +02:00
var checkPassed , checkFailed , passwordFailed bool
2018-04-19 08:48:19 +02:00
oper := server . GetOperator ( msg . Params [ 0 ] )
if oper != nil {
2020-06-21 21:46:08 +02:00
if oper . Certfp != "" {
if oper . Certfp == rb . session . certfp {
2019-12-20 00:30:19 +01:00
checkPassed = true
} else {
checkFailed = true
}
}
if ! checkFailed && oper . Pass != nil {
2020-04-23 04:11:44 +02:00
if len ( msg . Params ) == 1 {
2019-12-20 00:30:19 +01:00
checkFailed = true
2020-04-23 04:11:44 +02:00
} else if bcrypt . CompareHashAndPassword ( oper . Pass , [ ] byte ( msg . Params [ 1 ] ) ) != nil {
checkFailed = true
passwordFailed = true
2019-12-20 00:30:19 +01:00
} 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" ) )
2020-04-23 04:11:44 +02:00
// #951: only disconnect them if we actually tried to check a password for them
if passwordFailed {
client . Quit ( client . t ( "Password incorrect" ) , rb . session )
return true
} else {
return false
}
2018-02-03 10:28:02 +01:00
}
2020-04-23 04:11:44 +02:00
if oper != nil {
applyOper ( client , oper , rb )
}
2019-12-19 12:33:43 +01:00
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
2021-01-15 15:26:34 +01:00
client . server . logger . Info ( "opers" , details . nick , "opered up as" , oper . Name )
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" ) )
2020-03-25 17:08:08 +01:00
args := append ( [ ] string { details . nick } , applied . Strings ( ) ... )
rb . Broadcast ( nil , client . server . name , "MODE" , args ... )
2020-02-21 12:10:35 +01:00
} 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
2021-03-11 02:07:43 +01:00
func deoperHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2021-02-07 04:45:34 +01:00
if client . Oper ( ) == nil {
rb . Notice ( client . t ( "Insufficient oper privs" ) )
return false
}
2020-02-21 12:10:35 +01:00
// 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>]
2021-03-11 02:07:43 +01:00
func partHandler ( server * Server , client * Client , msg ircmsg . Message , 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>
2021-03-11 02:07:43 +01:00
func passHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
2020-06-12 21:51:48 +02:00
// only give them one try to run the PASS command (if a server password is set,
// then all code paths end with this variable being set):
2020-05-18 00:06:20 +02:00
if rb . session . passStatus != serverPassUnsent {
return false
}
password := msg . Params [ 0 ]
config := server . Config ( )
if config . Accounts . LoginViaPassCommand {
colonIndex := strings . IndexByte ( password , ':' )
if colonIndex != - 1 && client . Account ( ) == "" {
2020-06-12 21:51:48 +02:00
account , accountPass := password [ : colonIndex ] , password [ colonIndex + 1 : ]
if strudelIndex := strings . IndexByte ( account , '@' ) ; strudelIndex != - 1 {
account , rb . session . deviceID = account [ : strudelIndex ] , account [ strudelIndex + 1 : ]
}
err := server . accounts . AuthenticateByPassphrase ( client , account , accountPass )
if err == nil {
2020-11-29 05:27:11 +01:00
sendSuccessfulAccountAuth ( nil , client , rb , true )
2020-06-12 21:51:48 +02:00
// login-via-pass-command entails that we do not need to check
// an actual server password (either no password or skip-server-password)
rb . session . passStatus = serverPassSuccessful
return false
2020-05-18 00:06:20 +02:00
}
}
}
2020-05-18 09:35:58 +02:00
// if login-via-PASS failed for any reason, proceed to try and interpret the
// provided password as the server password
2020-05-18 00:06:20 +02:00
serverPassword := config . Server . passwordBytes
2018-02-03 10:28:02 +01:00
// if no password exists, skip checking
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
2020-05-18 00:06:20 +02:00
if bcrypt . CompareHashAndPassword ( serverPassword , [ ] byte ( password ) ) == nil {
rb . session . passStatus = serverPassSuccessful
} else {
rb . session . passStatus = serverPassFailed
}
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
2020-05-18 09:35:58 +02:00
// note in particular that with skip-server-password, you can give the wrong server
// password here, then successfully SASL and be admitted
2018-02-03 10:28:02 +01:00
return false
}
2022-07-15 06:04:35 +02:00
// PERSISTENCE <subcommand> [params...]
func persistenceHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
account := client . Account ( )
if account == "" {
rb . Add ( nil , server . name , "FAIL" , "PERSISTENCE" , "ACCOUNT_REQUIRED" , client . t ( "You're not logged into an account" ) )
return false
}
switch strings . ToUpper ( msg . Params [ 0 ] ) {
case "GET" :
2022-08-04 03:37:10 +02:00
reportPersistenceStatus ( client , rb , false )
2022-07-15 06:04:35 +02:00
case "SET" :
if len ( msg . Params ) == 1 {
goto fail
}
var desiredSetting PersistentStatus
switch strings . ToUpper ( msg . Params [ 1 ] ) {
case "DEFAULT" :
desiredSetting = PersistentUnspecified
case "OFF" :
desiredSetting = PersistentDisabled
case "ON" :
desiredSetting = PersistentMandatory
default :
goto fail
}
2022-08-04 03:37:10 +02:00
broadcast := false
2022-07-15 06:04:35 +02:00
_ , err := server . accounts . ModifyAccountSettings ( account ,
func ( input AccountSettings ) ( output AccountSettings , err error ) {
output = input
output . AlwaysOn = desiredSetting
2022-08-04 03:37:10 +02:00
broadcast = output . AlwaysOn != input . AlwaysOn
2022-07-15 06:04:35 +02:00
return
} )
if err != nil {
server . logger . Error ( "internal" , "couldn't modify persistence setting" , err . Error ( ) )
rb . Add ( nil , server . name , "FAIL" , "PERSISTENCE" , "UNKNOWN_ERROR" , client . t ( "An error occurred" ) )
return false
}
2022-08-04 03:37:10 +02:00
reportPersistenceStatus ( client , rb , broadcast )
2022-07-15 06:04:35 +02:00
default :
goto fail
}
return false
fail :
rb . Add ( nil , server . name , "FAIL" , "PERSISTENCE" , "INVALID_PARAMS" , client . t ( "Invalid parameters" ) )
return false
}
2022-08-04 03:37:10 +02:00
func reportPersistenceStatus ( client * Client , rb * ResponseBuffer , broadcast bool ) {
2022-07-15 06:04:35 +02:00
settings := client . AccountSettings ( )
serverSetting := client . server . Config ( ) . Accounts . Multiclient . AlwaysOn
effectiveSetting := persistenceEnabled ( serverSetting , settings . AlwaysOn )
toString := func ( setting PersistentStatus ) string {
switch setting {
case PersistentUnspecified :
return "DEFAULT"
case PersistentDisabled :
return "OFF"
case PersistentMandatory :
return "ON"
default :
return "*" // impossible
}
}
2022-08-04 03:37:10 +02:00
storedSettingStr := toString ( settings . AlwaysOn )
2022-07-15 06:04:35 +02:00
effectiveSettingStr := "OFF"
if effectiveSetting {
effectiveSettingStr = "ON"
}
2022-08-04 03:37:10 +02:00
rb . Add ( nil , client . server . name , "PERSISTENCE" , "STATUS" , storedSettingStr , effectiveSettingStr )
if broadcast {
for _ , session := range client . Sessions ( ) {
2022-08-08 07:06:19 +02:00
if session != rb . session && session . capabilities . Has ( caps . Persistence ) {
2022-08-04 03:37:10 +02:00
session . Send ( nil , client . server . name , "PERSISTENCE" , "STATUS" , storedSettingStr , effectiveSettingStr )
}
}
}
2022-07-15 06:04:35 +02:00
}
2018-02-03 20:48:44 +01:00
// PING [params...]
2021-03-11 02:07:43 +01:00
func pingHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-08-26 00:06:31 +02:00
rb . Add ( nil , server . name , "PONG" , server . name , msg . Params [ 0 ] )
2018-02-03 10:28:02 +01:00
return false
}
2018-02-03 20:48:44 +01:00
// PONG [params...]
2021-03-11 02:07:43 +01:00
func pongHandler ( server * Server , client * Client , msg ircmsg . Message , 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>]
2021-03-11 02:07:43 +01:00
func quitHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
2021-07-07 13:37:46 +02:00
// REGISTER < account | * > < email | * > <password>
2021-03-11 02:07:43 +01:00
func registerHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) ( exiting bool ) {
2021-02-28 23:14:10 +01:00
accountName := client . Nick ( )
if accountName == "*" {
accountName = client . preregNick
}
2021-07-07 13:37:46 +02:00
switch msg . Params [ 0 ] {
case "*" , accountName :
// ok
default :
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "ACCOUNTNAME_MUST_BE_NICK" , utils . SafeErrorParam ( msg . Params [ 0 ] ) , client . t ( "You may only register your nickname as your account name" ) )
return
}
2021-02-28 23:14:10 +01:00
// check that accountName is valid as a non-final parameter;
// this is necessary for us to be valid and it will prevent us from emitting invalid error lines
2021-04-25 23:16:34 +02:00
nickErrorParam := utils . SafeErrorParam ( accountName )
if accountName == "*" || accountName != nickErrorParam {
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "INVALID_USERNAME" , nickErrorParam , client . t ( "Username invalid or not given" ) )
2021-02-28 23:14:10 +01:00
return
}
2020-10-07 00:04:29 +02:00
config := server . Config ( )
if ! config . Accounts . Registration . Enabled {
2021-02-28 23:14:10 +01:00
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "DISALLOWED" , accountName , client . t ( "Account registration is disabled" ) )
2020-10-07 00:04:29 +02:00
return
}
if ! client . registered && ! config . Accounts . Registration . AllowBeforeConnect {
2021-02-28 23:14:10 +01:00
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "COMPLETE_CONNECTION_REQUIRED" , accountName , client . t ( "You must complete the connection before registering your account" ) )
2020-10-07 00:04:29 +02:00
return
}
if client . registerCmdSent || client . Account ( ) != "" {
2021-02-28 23:14:10 +01:00
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "ALREADY_REGISTERED" , accountName , client . t ( "You have already registered or attempted to register" ) )
2020-10-07 00:04:29 +02:00
return
}
2021-07-07 13:37:46 +02:00
callbackNamespace , callbackValue , err := parseCallback ( msg . Params [ 1 ] , config )
2020-10-07 00:04:29 +02:00
if err != nil {
2021-02-28 23:14:10 +01:00
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "INVALID_EMAIL" , accountName , client . t ( "A valid e-mail address is required" ) )
2020-10-07 00:04:29 +02:00
return
}
2021-07-07 13:37:46 +02:00
err = server . accounts . Register ( client , accountName , callbackNamespace , callbackValue , msg . Params [ 2 ] , rb . session . certfp )
2020-10-07 00:04:29 +02:00
switch err {
case nil :
if callbackNamespace == "*" {
2022-05-04 22:41:01 +02:00
err := server . accounts . Verify ( client , accountName , "" , true )
2020-10-07 00:04:29 +02:00
if err == nil {
if client . registered {
2020-11-29 05:27:11 +01:00
if ! fixupNickEqualsAccount ( client , rb , config , "" ) {
2020-10-07 00:04:29 +02:00
err = errNickAccountMismatch
}
}
if err == nil {
rb . Add ( nil , server . name , "REGISTER" , "SUCCESS" , accountName , client . t ( "Account successfully registered" ) )
2020-11-29 05:27:11 +01:00
sendSuccessfulRegResponse ( nil , client , rb )
2020-10-07 00:04:29 +02:00
}
}
if err != nil {
server . logger . Error ( "internal" , "accounts" , "failed autoverification" , accountName , err . Error ( ) )
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "UNKNOWN_ERROR" , client . t ( "An error occurred" ) )
}
} else {
rb . Add ( nil , server . name , "REGISTER" , "VERIFICATION_REQUIRED" , accountName , fmt . Sprintf ( client . t ( "Account created, pending verification; verification code has been sent to %s" ) , callbackValue ) )
client . registerCmdSent = true
2021-11-30 07:50:28 +01:00
announcePendingReg ( client , rb , accountName )
2020-10-07 00:04:29 +02:00
}
case errAccountAlreadyRegistered , errAccountAlreadyUnregistered , errAccountMustHoldNick :
2021-02-28 23:14:10 +01:00
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "USERNAME_EXISTS" , accountName , client . t ( "Username is already registered or otherwise unavailable" ) )
2020-10-07 00:04:29 +02:00
case errAccountBadPassphrase :
2021-02-28 23:14:10 +01:00
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "INVALID_PASSWORD" , accountName , client . t ( "Password was invalid" ) )
2020-10-07 00:04:29 +02:00
default :
2021-07-07 12:35:30 +02:00
if emailError := registrationCallbackErrorText ( config , client , err ) ; emailError != "" {
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "UNACCEPTABLE_EMAIL" , accountName , emailError )
} else {
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , "UNKNOWN_ERROR" , accountName , client . t ( "Could not register" ) )
}
2020-10-07 00:04:29 +02:00
}
return
}
// VERIFY <account> <code>
2021-03-11 02:07:43 +01:00
func verifyHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) ( exiting bool ) {
2020-10-07 00:04:29 +02:00
config := server . Config ( )
if ! config . Accounts . Registration . Enabled {
rb . Add ( nil , server . name , "FAIL" , "VERIFY" , "DISALLOWED" , client . t ( "Account registration is disabled" ) )
return
}
if ! client . registered && ! config . Accounts . Registration . AllowBeforeConnect {
rb . Add ( nil , server . name , "FAIL" , "VERIFY" , "DISALLOWED" , client . t ( "You must complete the connection before verifying your account" ) )
return
}
if client . Account ( ) != "" {
rb . Add ( nil , server . name , "FAIL" , "VERIFY" , "ALREADY_REGISTERED" , client . t ( "You have already registered or attempted to register" ) )
return
}
accountName , verificationCode := msg . Params [ 0 ] , msg . Params [ 1 ]
2022-05-04 22:41:01 +02:00
err := server . accounts . Verify ( client , accountName , verificationCode , false )
2020-10-07 00:04:29 +02:00
if err == nil && client . registered {
2020-11-29 05:27:11 +01:00
if ! fixupNickEqualsAccount ( client , rb , config , "" ) {
2020-10-07 00:04:29 +02:00
err = errNickAccountMismatch
}
}
switch err {
case nil :
rb . Add ( nil , server . name , "VERIFY" , "SUCCESS" , accountName , client . t ( "Account successfully registered" ) )
2020-11-29 05:27:11 +01:00
sendSuccessfulRegResponse ( nil , client , rb )
2020-10-07 00:04:29 +02:00
case errAccountVerificationInvalidCode :
rb . Add ( nil , server . name , "FAIL" , "VERIFY" , "INVALID_CODE" , client . t ( "Invalid verification code" ) )
default :
rb . Add ( nil , server . name , "FAIL" , "VERIFY" , "UNKNOWN_ERROR" , client . t ( "Failed to verify account" ) )
}
if err != nil && ! client . registered {
// XXX pre-registration clients are exempt from fakelag;
// slow the client down to stop them spamming verify attempts
time . Sleep ( time . Second )
}
return
}
2022-03-30 21:35:28 +02:00
// MARKREAD <target> [timestamp]
func markReadHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) ( exiting bool ) {
if len ( msg . Params ) == 0 {
rb . Add ( nil , server . name , "FAIL" , "MARKREAD" , "NEED_MORE_PARAMS" , client . t ( "Missing parameters" ) )
return
}
target := msg . Params [ 0 ]
cftarget , err := CasefoldTarget ( target )
if err != nil {
rb . Add ( nil , server . name , "FAIL" , "MARKREAD" , "INVALID_PARAMS" , utils . SafeErrorParam ( target ) , client . t ( "Invalid target" ) )
return
}
unfoldedTarget := server . UnfoldName ( cftarget )
// "MARKREAD client get command": MARKREAD <target>
if len ( msg . Params ) == 1 {
rb . Add ( nil , client . server . name , "MARKREAD" , unfoldedTarget , client . GetReadMarker ( cftarget ) )
return
}
// "MARKREAD client set command": MARKREAD <target> <timestamp>
2022-05-20 07:32:39 +02:00
readTimestamp := strings . TrimPrefix ( msg . Params [ 1 ] , "timestamp=" )
2022-03-30 21:35:28 +02:00
readTime , err := time . Parse ( IRCv3TimestampFormat , readTimestamp )
if err != nil {
rb . Add ( nil , server . name , "FAIL" , "MARKREAD" , "INVALID_PARAMS" , utils . SafeErrorParam ( readTimestamp ) , client . t ( "Invalid timestamp" ) )
return
}
result := client . SetReadMarker ( cftarget , readTime )
2022-05-20 07:32:39 +02:00
readTimestamp = fmt . Sprintf ( "timestamp=%s" , result . Format ( IRCv3TimestampFormat ) )
2022-03-30 21:35:28 +02:00
// inform the originating session whether it was a success or a no-op:
rb . Add ( nil , server . name , "MARKREAD" , unfoldedTarget , readTimestamp )
if result . Equal ( readTime ) {
// successful update (i.e. it moved the stored timestamp forward):
// inform other sessions
for _ , session := range client . Sessions ( ) {
2022-05-20 07:58:14 +02:00
if session != rb . session && session . capabilities . Has ( caps . ReadMarker ) {
2022-03-30 21:35:28 +02:00
session . Send ( nil , server . name , "MARKREAD" , unfoldedTarget , readTimestamp )
}
}
}
return
}
2018-02-03 10:28:02 +01:00
// REHASH
2021-03-11 02:07:43 +01:00
func rehashHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-05-18 17:25:49 +02:00
nick := client . Nick ( )
server . logger . Info ( "server" , "REHASH command used by" , nick )
2018-02-03 10:28:02 +01:00
err := server . rehash ( )
if err == nil {
2020-05-18 17:25:49 +02:00
// we used to send RPL_REHASHING here but i don't think it really makes sense
// in the labeled-response world, since the intent is "rehash in progress" but
// it won't display until the rehash is actually complete
// TODO all operators should get a notice of some kind here
rb . Notice ( client . t ( "Rehash complete" ) )
2018-02-03 10:28:02 +01:00
} else {
2023-01-12 12:58:18 +01:00
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , nick , "REHASH" , ircutils . SanitizeText ( err . Error ( ) , 350 ) )
2018-02-03 10:28:02 +01:00
}
return false
}
2020-06-08 02:19:28 +02:00
// RELAYMSG <channel> <spoofed nick> :<message>
2021-03-11 02:07:43 +01:00
func relaymsgHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) ( result bool ) {
2020-06-08 07:17:45 +02:00
config := server . Config ( )
2020-09-09 10:01:46 +02:00
if ! config . Server . Relaymsg . Enabled {
rb . Add ( nil , server . name , "FAIL" , "RELAYMSG" , "NOT_ENABLED" , client . t ( "RELAYMSG has been disabled" ) )
2020-06-08 07:17:45 +02:00
return false
}
2020-06-08 02:19:28 +02: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" ) )
return false
}
2020-10-01 15:42:08 +02:00
allowedToRelay := client . HasRoleCapabs ( "relaymsg" ) || ( config . Server . Relaymsg . AvailableToChanops && channel . ClientIsAtLeast ( client , modes . ChannelOperator ) )
2020-06-08 07:17:45 +02:00
if ! allowedToRelay {
2020-12-07 03:01:44 +01:00
rb . Add ( nil , server . name , "FAIL" , "RELAYMSG" , "PRIVS_NEEDED" , client . t ( "You cannot relay messages to this channel" ) )
2020-06-08 02:19:28 +02:00
return false
}
rawMessage := msg . Params [ 2 ]
if strings . TrimSpace ( rawMessage ) == "" {
rb . Add ( nil , server . name , "FAIL" , "RELAYMSG" , "BLANK_MSG" , client . t ( "The message must not be blank" ) )
return false
}
message := utils . MakeMessage ( rawMessage )
nick := msg . Params [ 1 ]
2021-11-17 00:39:38 +01:00
cfnick , err := CasefoldName ( nick )
2020-06-08 02:19:28 +02:00
if err != nil {
rb . Add ( nil , server . name , "FAIL" , "RELAYMSG" , "INVALID_NICK" , client . t ( "Invalid nickname" ) )
return false
}
2020-09-09 10:01:46 +02:00
if ! config . isRelaymsgIdentifier ( nick ) {
rb . Add ( nil , server . name , "FAIL" , "RELAYMSG" , "INVALID_NICK" , fmt . Sprintf ( client . t ( "Relayed nicknames MUST contain a relaymsg separator from this set: %s" ) , config . Server . Relaymsg . Separators ) )
2020-06-08 02:19:28 +02:00
return false
}
2021-11-17 00:39:38 +01:00
if channel . relayNickMuted ( cfnick ) {
2021-03-11 07:21:03 +01:00
rb . Add ( nil , server . name , "FAIL" , "RELAYMSG" , "BANNED" , fmt . Sprintf ( client . t ( "%s is banned from relaying to the channel" ) , nick ) )
return false
}
2020-06-08 02:19:28 +02:00
2021-05-27 17:43:21 +02:00
details := client . Details ( )
2021-05-27 08:00:59 +02:00
// #1647: we need to publish a full NUH. send ~u (or the configured alternative)
// as the user/ident, and send the relayer's hostname as the hostname:
ident := config . Server . CoerceIdent
if ident == "" {
ident = "~u"
}
2021-05-27 17:43:21 +02:00
// #1661: if the bot has its own account, use the account cloak,
// otherwise fall back to the hostname (which may be IP-derived)
hostname := details . hostname
if details . accountName != "" {
hostname = config . Server . Cloaks . ComputeAccountCloak ( details . accountName )
}
2021-05-27 08:00:59 +02:00
nuh := fmt . Sprintf ( "%s!%s@%s" , nick , ident , hostname )
2020-06-08 07:22:41 +02:00
channel . AddHistoryItem ( history . Item {
Type : history . Privmsg ,
Message : message ,
2021-05-27 08:00:59 +02:00
Nick : nuh ,
2020-06-08 07:22:41 +02:00
} , "" )
2020-06-08 02:19:28 +02:00
2021-05-27 08:00:59 +02:00
// 3 possibilities for tags:
// no tags, the relaymsg tag only, or the relaymsg tag together with all client-only tags
relayTag := map [ string ] string {
2021-05-27 17:43:21 +02:00
caps . RelaymsgTagName : details . nick ,
2020-12-22 03:59:46 +01:00
}
2021-05-27 08:00:59 +02:00
clientOnlyTags := msg . ClientOnlyTags ( )
var fullTags map [ string ] string
if len ( clientOnlyTags ) == 0 {
fullTags = relayTag
} else {
fullTags = make ( map [ string ] string , 1 + len ( clientOnlyTags ) )
2021-05-27 17:43:21 +02:00
fullTags [ caps . RelaymsgTagName ] = details . nick
2021-05-27 08:00:59 +02:00
for t , v := range clientOnlyTags {
fullTags [ t ] = v
}
2020-09-09 10:01:46 +02:00
}
2021-05-27 08:00:59 +02:00
// actually send the message
channelName := channel . Name ( )
2020-06-08 02:19:28 +02:00
for _ , member := range channel . Members ( ) {
for _ , session := range member . Sessions ( ) {
2020-06-08 07:25:59 +02:00
var tagsToUse map [ string ] string
2021-05-27 08:00:59 +02:00
if session . capabilities . Has ( caps . MessageTags ) {
tagsToUse = fullTags
} else if session . capabilities . Has ( caps . Relaymsg ) {
tagsToUse = relayTag
2020-06-08 02:19:28 +02:00
}
2020-09-09 10:01:46 +02:00
if session == rb . session {
2021-05-27 08:00:59 +02:00
rb . AddSplitMessageFromClient ( nuh , "*" , false , tagsToUse , "PRIVMSG" , channelName , message )
2020-09-09 10:01:46 +02:00
} else {
2021-05-27 08:00:59 +02:00
session . sendSplitMsgFromClientInternal ( false , nuh , "*" , false , tagsToUse , "PRIVMSG" , channelName , message )
2020-09-09 10:01:46 +02:00
}
2020-06-08 02:19:28 +02:00
}
}
return false
}
2018-02-03 10:28:02 +01:00
// RENAME <oldchan> <newchan> [<reason>]
2021-03-11 02:07:43 +01:00
func renameHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
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
}
2020-08-04 16:13:29 +02:00
oldName = channel . Name ( )
2020-03-19 22:09:52 +01:00
if ! ( channel . ClientIsAtLeast ( client , modes . ChannelOperator ) || client . HasRoleCapabs ( "chanreg" ) ) {
2019-03-11 12:03:51 +01:00
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 ( ) {
2020-08-04 16:13:29 +02:00
rb . Add ( nil , server . name , "FAIL" , "RENAME" , "CANNOT_RENAME" , oldName , utils . SafeErrorParam ( newName ) , client . t ( "Only channel founders can change registered channels" ) )
2018-02-03 10:28:02 +01:00
return false
}
2020-04-13 04:38:39 +02:00
config := server . Config ( )
2021-01-21 03:13:18 +01:00
status , _ , _ := channel . historyStatus ( config )
2020-04-13 04:38:39 +02:00
if status == HistoryPersistent {
2020-08-04 16:13:29 +02:00
rb . Add ( nil , server . name , "FAIL" , "RENAME" , "CANNOT_RENAME" , oldName , utils . SafeErrorParam ( newName ) , client . t ( "Channels with persistent history cannot be renamed" ) )
2020-04-13 04:38:39 +02:00
return false
}
2018-02-03 10:28:02 +01:00
// 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 ( ) ) )
2020-12-15 05:01:41 +01:00
} else if err == errChannelNameInUse || err == errConfusableIdentifier {
2020-08-04 16:13:29 +02:00
rb . Add ( nil , server . name , "FAIL" , "RENAME" , "CHANNEL_NAME_IN_USE" , oldName , utils . SafeErrorParam ( newName ) , client . t ( err . Error ( ) ) )
2019-03-11 12:03:51 +01:00
} else if err != nil {
2020-08-04 16:13:29 +02:00
rb . Add ( nil , server . name , "FAIL" , "RENAME" , "CANNOT_RENAME" , 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
}
2020-09-09 17:46:05 +02:00
if mSession . capabilities . Has ( caps . ChannelRename ) {
2019-04-12 06:08:46 +02:00
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
}
// SANICK <oldnick> <nickname>
2021-03-11 02:07:43 +01:00
func sanickHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-09-23 08:30:34 +02:00
targetNick := msg . Params [ 0 ]
2018-02-03 10:28:02 +01:00
target := server . clients . Get ( targetNick )
if target == nil {
2020-09-23 08:30:34 +02:00
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "NO_SUCH_NICKNAME" , utils . SafeErrorParam ( targetNick ) , 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>
2021-03-11 02:07:43 +01:00
func sceneHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-03 10:28:02 +01:00
target := msg . Params [ 0 ]
2020-03-19 22:09:52 +01:00
message := msg . Params [ 1 : ]
2018-02-03 10:28:02 +01:00
2020-09-16 18:03:06 +02:00
sendRoleplayMessage ( server , client , "" , target , true , false , message , rb )
2018-02-03 10:28:02 +01:00
return false
}
2019-02-13 14:22:16 +01:00
// SETNAME <realname>
2021-03-11 02:07:43 +01:00
func setnameHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2019-02-13 14:22:16 +01:00
realname := msg . Params [ 0 ]
2020-10-26 23:06:06 +01:00
if len ( msg . Params ) != 1 {
2020-10-26 20:31:51 +01:00
// workaround for clients that turn unknown commands into raw IRC lines,
// so you can do `/setname Jane Doe` in the client and get the expected result
realname = strings . Join ( msg . Params , " " )
}
2020-07-06 10:08:04 +02:00
if realname == "" {
rb . Add ( nil , server . name , "FAIL" , "SETNAME" , "INVALID_REALNAME" , client . t ( "Realname is not valid" ) )
return false
}
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 ( )
2021-07-24 20:52:03 +02:00
friends := client . FriendsMonitors ( caps . SetName )
2020-10-26 20:31:51 +01:00
delete ( friends , rb . session )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2020-10-26 20:31:51 +01:00
for session := range friends {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , now , "" , details . nickMask , details . accountName , isBot , nil , "SETNAME" , details . realname )
2019-02-13 14:22:16 +01:00
}
2020-10-26 20:31:51 +01:00
// respond to the user unconditionally, even if they don't have the cap
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( now , "" , details . nickMask , details . accountName , isBot , nil , "SETNAME" , details . realname )
2019-02-13 14:22:16 +01:00
return false
}
2020-06-02 15:57:51 +02:00
// SUMMON [parameters]
2021-03-11 02:07:43 +01:00
func summonHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-06-02 15:57:51 +02:00
rb . Add ( nil , server . name , ERR_SUMMONDISABLED , client . Nick ( ) , client . t ( "SUMMON has been disabled" ) )
return false
}
2018-02-03 20:48:44 +01:00
// TIME
2021-03-11 02:07:43 +01:00
func timeHandler ( server * Server , client * Client , msg ircmsg . Message , 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>]
2021-03-11 02:07:43 +01:00
func topicHandler ( server * Server , client * Client , msg ircmsg . Message , 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>
2021-03-11 02:07:43 +01:00
func unDLineHandler ( server * Server , client * Client , msg ircmsg . Message , 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 ( )
2021-01-15 15:26:34 +01:00
if ! oper . HasRoleCapab ( "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
}
// get host
hostString := msg . Params [ 0 ]
// check host
2021-01-19 14:49:45 +01:00
hostNet , err := flatip . ParseToNormalizedNet ( 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
}
2021-01-19 14:49:45 +01:00
hostString = hostNet . String ( )
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>
2021-03-11 02:07:43 +01:00
func unKLineHandler ( server * Server , client * Client , msg ircmsg . Message , 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 ( )
2021-01-15 15:26:34 +01:00
if ! oper . HasRoleCapab ( "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
}
// 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>
2021-03-11 02:07:43 +01:00
func userHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
2020-05-26 16:56:24 +02:00
username , realname := msg . Params [ 0 ] , msg . Params [ 3 ]
if len ( realname ) == 0 {
2021-08-13 21:10:39 +02:00
rb . Add ( nil , server . name , ERR_NEEDMOREPARAMS , client . Nick ( ) , "USER" , client . t ( "Not enough parameters" ) )
2020-05-26 16:56:24 +02:00
return false
}
2020-06-12 21:51:48 +02:00
// #843: we accept either: `USER user:pass@clientid` or `USER user@clientid`
if strudelIndex := strings . IndexByte ( username , '@' ) ; strudelIndex != - 1 {
username , rb . session . deviceID = username [ : strudelIndex ] , username [ strudelIndex + 1 : ]
if colonIndex := strings . IndexByte ( username , ':' ) ; colonIndex != - 1 {
var password string
username , password = username [ : colonIndex ] , username [ colonIndex + 1 : ]
err := server . accounts . AuthenticateByPassphrase ( client , username , password )
if err == nil {
2020-11-29 05:27:11 +01:00
sendSuccessfulAccountAuth ( nil , client , rb , true )
2020-06-12 21:51:48 +02:00
} else {
// this is wrong, but send something for debugging that will show up in a raw transcript
rb . Add ( nil , server . name , ERR_SASLFAIL , client . Nick ( ) , client . t ( "SASL authentication failed" ) )
}
}
}
2020-05-26 16:56:24 +02:00
err := client . SetNames ( username , realname , 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
2020-05-26 16:56:24 +02:00
if client . preregNick == username {
client . SetNames ( "user" , realname , false )
2019-02-05 23:33:15 +01:00
} 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
}
2020-10-09 18:29:09 +02:00
// does `target` have an operator status that is visible to `client`?
func operStatusVisible ( client , target * Client , hasPrivs bool ) bool {
targetOper := target . Oper ( )
if targetOper == nil {
return false
}
if client == target || hasPrivs {
return true
}
return ! targetOper . Hidden
}
2018-02-03 20:48:44 +01:00
// USERHOST <nickname>{ <nickname>}
2021-03-11 02:07:43 +01:00
func userhostHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2021-02-07 04:45:34 +01:00
hasPrivs := client . HasMode ( modes . Operator )
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
2022-04-24 17:57:21 +02:00
tl . Initialize ( maxLastArgLength , " " )
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
2020-10-09 18:29:09 +02:00
if operStatusVisible ( client , target , hasPrivs ) {
2018-02-03 10:28:02 +01:00
isOper = "*"
}
2020-07-17 07:55:13 +02:00
if away , _ := target . Away ( ) ; 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
}
2020-06-02 15:57:51 +02:00
// USERS [parameters]
2021-03-11 02:07:43 +01:00
func usersHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-06-02 15:57:51 +02:00
rb . Add ( nil , server . name , ERR_USERSDISABLED , client . Nick ( ) , client . t ( "USERS has been disabled" ) )
return false
}
2018-02-03 20:48:44 +01:00
// VERSION
2021-03-11 02:07:43 +01:00
func versionHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2018-02-05 15:21:08 +01:00
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]
2021-03-11 02:07:43 +01:00
func webircHandler ( server * Server , client * Client , msg ircmsg . Message , 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-06-21 21:46:08 +02:00
if info . Certfp != "" && info . Certfp != rb . session . certfp {
2019-02-05 06:19:03 +01:00
continue
}
2018-02-03 10:28:02 +01:00
2020-05-05 04:29:10 +02:00
err , quitMsg := client . ApplyProxiedIP ( rb . session , net . ParseIP ( 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
}
2020-07-12 04:49:17 +02:00
type whoxFields uint32 // bitset to hold the WHOX field values, 'a' through 'z'
2020-07-11 17:45:02 +02:00
2020-07-12 04:49:17 +02:00
func ( fields whoxFields ) Add ( field rune ) ( result whoxFields ) {
index := int ( field ) - int ( 'a' )
if 0 <= index && index < 26 {
return fields | ( 1 << index )
} else {
return fields
}
}
2020-07-11 17:45:02 +02:00
2020-07-12 04:49:17 +02:00
func ( fields whoxFields ) Has ( field rune ) bool {
index := int ( field ) - int ( 'a' )
if 0 <= index && index < 26 {
return ( fields & ( 1 << index ) ) != 0
2020-07-11 17:45:02 +02:00
} else {
return false
}
}
// rplWhoReply returns the WHO(X) reply between one user and another channel/user.
// who format:
// <channel> <user> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] :<hopcount> <real name>
// whox format:
// <type> <channel> <user> <ip> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] <hops> <idle> <account> <rank> :<real name>
2021-02-07 04:45:34 +01:00
func ( client * Client ) rplWhoReply ( channel * Channel , target * Client , rb * ResponseBuffer , canSeeIPs , canSeeOpers , includeRFlag , isWhox bool , fields whoxFields , whoType string ) {
2020-07-11 17:45:02 +02:00
params := [ ] string { client . Nick ( ) }
details := target . Details ( )
if fields . Has ( 't' ) {
params = append ( params , whoType )
}
if fields . Has ( 'c' ) {
fChannel := "*"
if channel != nil {
fChannel = channel . name
}
params = append ( params , fChannel )
}
if fields . Has ( 'u' ) {
params = append ( params , details . username )
}
if fields . Has ( 'i' ) {
fIP := "255.255.255.255"
2021-02-07 04:45:34 +01:00
if canSeeIPs || client == target {
2020-07-11 17:45:02 +02:00
// you can only see a target's IP if they're you or you're an oper
2022-01-09 23:24:24 +01:00
ip , _ := target . getWhoisActually ( )
2022-07-15 03:53:36 +02:00
fIP = utils . IPStringToHostname ( ip . String ( ) )
2020-07-11 17:45:02 +02:00
}
params = append ( params , fIP )
}
if fields . Has ( 'h' ) {
params = append ( params , details . hostname )
}
if fields . Has ( 's' ) {
params = append ( params , target . server . name )
}
if fields . Has ( 'n' ) {
params = append ( params , details . nick )
}
if fields . Has ( 'f' ) { // "flags" (away + oper state + channel status prefix + bot)
var flags strings . Builder
2020-07-17 07:55:13 +02:00
if away , _ := target . Away ( ) ; away {
2020-07-11 17:45:02 +02:00
flags . WriteRune ( 'G' ) // Gone
} else {
flags . WriteRune ( 'H' ) // Here
}
2021-02-07 04:45:34 +01:00
if target . HasMode ( modes . Operator ) && operStatusVisible ( client , target , canSeeOpers ) {
2020-07-11 17:45:02 +02:00
flags . WriteRune ( '*' )
}
if channel != nil {
2020-07-12 22:40:11 +02:00
flags . WriteString ( channel . ClientPrefixes ( target , rb . session . capabilities . Has ( caps . MultiPrefix ) ) )
2020-07-11 17:45:02 +02:00
}
if target . HasMode ( modes . Bot ) {
flags . WriteRune ( 'B' )
}
2020-10-29 19:25:28 +01:00
if includeRFlag && details . account != "" {
flags . WriteRune ( 'r' )
}
2020-07-11 17:45:02 +02:00
params = append ( params , flags . String ( ) )
}
if fields . Has ( 'd' ) { // server hops from us to target
params = append ( params , "0" )
}
if fields . Has ( 'l' ) {
params = append ( params , fmt . Sprintf ( "%d" , target . IdleSeconds ( ) ) )
}
if fields . Has ( 'a' ) {
fAccount := "0"
if details . accountName != "*" {
// WHOX uses "0" to mean "no account"
fAccount = details . accountName
}
params = append ( params , fAccount )
}
2021-12-12 09:05:56 +01:00
if fields . Has ( 'o' ) {
// channel oplevel, not implemented
params = append ( params , "*" )
2020-07-11 17:45:02 +02:00
}
if fields . Has ( 'r' ) {
params = append ( params , details . realname )
}
numeric := RPL_WHOSPCRPL
if ! isWhox {
numeric = RPL_WHOREPLY
// if this isn't WHOX, stick hops + realname at the end
params = append ( params , "0 " + details . realname )
}
rb . Add ( nil , client . server . name , numeric , params ... )
}
2022-12-02 13:04:19 +01:00
func serviceWhoReply ( client * Client , service * ircService , rb * ResponseBuffer , isWhox bool , fields whoxFields , whoType string ) {
params := [ ] string { client . Nick ( ) }
if fields . Has ( 't' ) {
params = append ( params , whoType )
}
if fields . Has ( 'c' ) {
params = append ( params , "*" )
}
if fields . Has ( 'u' ) {
params = append ( params , service . Name )
}
if fields . Has ( 'i' ) {
params = append ( params , "127.0.0.1" )
}
if fields . Has ( 'h' ) {
params = append ( params , "localhost" )
}
if fields . Has ( 's' ) {
params = append ( params , client . server . name )
}
if fields . Has ( 'n' ) {
params = append ( params , service . Name )
}
if fields . Has ( 'f' ) { // "flags" (away + oper state + channel status prefix + bot)
params = append ( params , "H" )
}
if fields . Has ( 'd' ) { // server hops from us to target
params = append ( params , "0" )
}
if fields . Has ( 'l' ) { // idle seconds
params = append ( params , "0" )
}
if fields . Has ( 'a' ) { // account, services are considered not to have one
params = append ( params , "0" )
}
if fields . Has ( 'o' ) { // channel oplevel, not implemented
params = append ( params , "*" )
}
if fields . Has ( 'r' ) {
params = append ( params , service . Realname ( client ) )
}
numeric := RPL_WHOSPCRPL
if ! isWhox {
numeric = RPL_WHOREPLY
// if this isn't WHOX, stick hops + realname at the end
params = append ( params , "0 " + service . Realname ( client ) )
}
rb . Add ( nil , client . server . name , numeric , params ... )
}
2020-07-11 17:45:02 +02:00
// WHO <mask> [<filter>%<fields>,<type>]
2021-03-11 02:07:43 +01:00
func whoHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2021-11-14 19:41:27 +01:00
origMask := utils . SafeErrorParam ( msg . Params [ 0 ] )
if origMask != msg . Params [ 0 ] {
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
2021-11-14 19:41:27 +01:00
}
2022-08-23 05:03:17 +02:00
// https://modern.ircdocs.horse/#who-message
// "1. A channel name, in which case the channel members are listed."
// "2. An exact nickname, in which case a single user is returned."
// "3. A mask pattern, in which case all visible users whose nickname matches are listed."
var isChannel bool
var isBareNick bool
2021-11-14 19:41:27 +01:00
mask := origMask
var err error
2022-08-23 05:03:17 +02:00
if origMask [ 0 ] == '#' {
mask , err = CasefoldChannel ( origMask )
isChannel = true
} else if ! strings . ContainsAny ( origMask , protocolBreakingNameCharacters ) {
isBareNick = true
2019-10-23 17:32:32 +02:00
} else {
2022-08-23 05:03:17 +02:00
mask , err = CanonicalizeMaskWildcard ( origMask )
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
}
2020-10-29 19:25:28 +01:00
// include the r flag only if nick and account are synonymous
config := server . Config ( )
includeRFlag := config . Accounts . NickReservation . Enabled &&
config . Accounts . NickReservation . Method == NickEnforcementStrict &&
! config . Accounts . NickReservation . AllowCustomEnforcement &&
config . Accounts . NickReservation . ForceNickEqualsAccount
2020-07-11 17:45:02 +02:00
sFields := "cuhsnf"
whoType := "0"
isWhox := false
if len ( msg . Params ) > 1 && strings . Contains ( msg . Params [ 1 ] , "%" ) {
isWhox = true
whoxData := msg . Params [ 1 ]
fieldStart := strings . Index ( whoxData , "%" )
sFields = whoxData [ fieldStart + 1 : ]
typeIndex := strings . Index ( sFields , "," )
if typeIndex > - 1 && typeIndex < ( len ( sFields ) - 1 ) { // make sure there's , and a value after it
whoType = sFields [ typeIndex + 1 : ]
sFields = strings . ToLower ( sFields [ : typeIndex ] )
}
}
2020-07-12 04:49:17 +02:00
var fields whoxFields
2020-07-11 17:45:02 +02:00
for _ , field := range sFields {
2020-07-12 04:49:17 +02:00
fields = fields . Add ( field )
2020-07-11 17:45:02 +02:00
}
2021-11-14 20:03:02 +01:00
// successfully parsed query, ensure we send the success response:
defer func ( ) {
rb . Add ( nil , server . name , RPL_ENDOFWHO , client . Nick ( ) , origMask , client . t ( "End of WHO list" ) )
} ( )
// XXX #1730: https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.1
// 'If the "o" parameter is passed only operators are returned according to
// the name mask supplied.'
// see discussion on #1730, we just return no results in this case.
if len ( msg . Params ) > 1 && msg . Params [ 1 ] == "o" {
return false
}
2018-02-03 10:28:02 +01:00
2021-02-07 04:45:34 +01:00
oper := client . Oper ( )
hasPrivs := oper . HasRoleCapab ( "sajoin" )
canSeeIPs := oper . HasRoleCapab ( "ban" )
2022-08-23 05:03:17 +02:00
if isChannel {
2018-02-03 10:28:02 +01:00
channel := server . channels . Get ( mask )
2019-04-29 03:09:56 +02:00
if channel != nil {
isJoined := channel . hasClient ( client )
2021-02-07 04:45:34 +01:00
if ! channel . flags . HasMode ( modes . Secret ) || isJoined || hasPrivs {
2020-10-02 14:13:52 +02:00
var members [ ] * Client
2021-02-07 04:45:34 +01:00
if hasPrivs {
2020-10-02 14:13:52 +02:00
members = channel . Members ( )
} else {
members = channel . auditoriumFriends ( client )
}
for _ , member := range members {
2021-02-07 04:45:34 +01:00
if ! member . HasMode ( modes . Invisible ) || isJoined || hasPrivs {
client . rplWhoReply ( channel , member , rb , canSeeIPs , oper != nil , includeRFlag , isWhox , fields , whoType )
2019-04-29 03:09:56 +02:00
}
2019-04-12 06:08:46 +02:00
}
}
2018-02-03 10:28:02 +01:00
}
2022-08-23 05:03:17 +02:00
} else if isBareNick {
2022-12-02 13:04:19 +01:00
if mclient := server . clients . Get ( mask ) ; mclient != nil {
2022-08-23 05:03:17 +02:00
client . rplWhoReply ( nil , mclient , rb , canSeeIPs , oper != nil , includeRFlag , isWhox , fields , whoType )
2022-12-02 13:04:19 +01:00
} else if service , ok := ErgoServices [ strings . ToLower ( mask ) ] ; ok {
serviceWhoReply ( client , service , rb , isWhox , fields , whoType )
2022-08-23 05:03:17 +02:00
}
2018-02-03 10:28:02 +01:00
} else {
2020-05-08 03:55:47 +02:00
// Construct set of channels the client is in.
2020-08-04 07:10:22 +02:00
userChannels := make ( ChannelSet )
2020-05-08 03:55:47 +02:00
for _ , channel := range client . Channels ( ) {
2022-03-30 06:44:51 +02:00
userChannels . Add ( channel )
2020-05-08 03:55:47 +02:00
}
// Another client is a friend if they share at least one channel, or they are the same client.
isFriend := func ( otherClient * Client ) bool {
if client == otherClient {
return true
}
for _ , channel := range otherClient . Channels ( ) {
2020-10-02 14:13:52 +02:00
if channel . flags . HasMode ( modes . Auditorium ) {
return false // TODO this should respect +v etc.
}
2022-03-30 06:44:51 +02:00
if userChannels . Has ( channel ) {
2020-05-08 03:55:47 +02:00
return true
}
}
return false
}
2018-02-03 10:28:02 +01:00
for mclient := range server . clients . FindAll ( mask ) {
2021-02-07 04:45:34 +01:00
if hasPrivs || ! mclient . HasMode ( modes . Invisible ) || isFriend ( mclient ) {
client . rplWhoReply ( nil , mclient , rb , canSeeIPs , oper != nil , includeRFlag , isWhox , fields , whoType )
2020-05-07 02:16:22 +02:00
}
2018-02-03 10:28:02 +01:00
}
}
return false
}
2018-02-03 20:48:44 +01:00
// WHOIS [<target>] <mask>{,<mask>}
2021-03-11 02:07:43 +01:00
func whoisHandler ( server * Server , client * Client , msg ircmsg . Message , 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 )
2022-12-02 13:04:19 +01:00
service , ok := ErgoServices [ cfnick ]
2020-12-07 08:15:44 +01:00
hostname := "localhost"
config := server . Config ( )
if config . Server . OverrideServicesHostname != "" {
hostname = config . Server . OverrideServicesHostname
}
2018-12-30 12:45:23 +01:00
if ! ok {
return false
}
clientNick := client . Nick ( )
2022-12-02 13:04:19 +01:00
rb . Add ( nil , client . server . name , RPL_WHOISUSER , clientNick , service . Name , service . Name , hostname , "*" , service . Realname ( client ) )
2020-05-28 19:16:17 +02:00
// #1080:
rb . Add ( nil , client . server . name , RPL_WHOISOPERATOR , clientNick , service . Name , client . t ( "is a network service" ) )
2018-12-30 12:45:23 +01:00
// 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
}
2021-02-07 04:45:34 +01:00
hasPrivs := client . HasRoleCapabs ( "samode" )
2020-10-09 14:03:26 +02:00
if hasPrivs {
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 {
2020-10-09 14:03:26 +02:00
client . getWhoisOf ( mclient , hasPrivs , 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 {
2020-10-09 14:03:26 +02:00
client . getWhoisOf ( mclient , hasPrivs , 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>]]
2021-03-11 02:07:43 +01:00
func whowasHandler ( server * Server , client * Client , msg ircmsg . Message , 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
}
2018-05-04 06:24:54 +02:00
cnick := client . Nick ( )
2021-06-20 19:26:30 +02:00
canSeeIP := client . Oper ( ) . HasRoleCapab ( "ban" )
2018-02-03 10:28:02 +01:00
for _ , nickname := range nicknames {
2019-12-05 13:52:58 +01:00
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 )
2021-06-20 19:26:30 +02:00
if canSeeIP {
2021-06-20 20:13:18 +02:00
rb . Add ( nil , server . name , RPL_WHOWASIP , cnick , whoWas . nick , fmt . Sprintf ( client . t ( "was connecting from %s" ) , utils . IPStringToHostname ( whoWas . ip . String ( ) ) ) )
2021-06-20 19:26:30 +02:00
}
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]
2021-03-11 02:07:43 +01:00
func zncHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-07-20 10:28:17 +02:00
params := msg . Params [ 1 : ]
// #1205: compatibility with Palaver, which sends `ZNC *playback :play ...`
if len ( params ) == 1 && strings . IndexByte ( params [ 0 ] , ' ' ) != - 1 {
params = strings . Fields ( params [ 0 ] )
}
zncModuleHandler ( client , msg . Params [ 0 ] , params , rb )
2019-05-21 01:08:57 +02:00
return false
}
2020-05-08 11:44:40 +02:00
// fake handler for unknown commands
2021-03-11 02:07:43 +01:00
func unknownCommandHandler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2021-09-06 02:14:13 +02:00
var message string
if strings . HasPrefix ( msg . Command , "/" ) {
2022-01-02 23:32:14 +01:00
message = fmt . Sprintf ( client . t ( "Unknown command; if you are using /QUOTE, the correct syntax is /QUOTE %[1]s, not /QUOTE %[2]s" ) ,
2021-09-06 02:14:13 +02:00
strings . TrimPrefix ( msg . Command , "/" ) , msg . Command )
} else {
message = client . t ( "Unknown command" )
}
rb . Add ( nil , server . name , ERR_UNKNOWNCOMMAND , client . Nick ( ) , utils . SafeErrorParam ( msg . Command ) , message )
2020-05-08 11:44:40 +02:00
return false
}
2020-06-22 20:54:43 +02:00
// fake handler for invalid utf8
2021-03-11 02:07:43 +01:00
func invalidUtf8Handler ( server * Server , client * Client , msg ircmsg . Message , rb * ResponseBuffer ) bool {
2020-06-23 00:53:54 +02:00
rb . Add ( nil , server . name , "FAIL" , utils . SafeErrorParam ( msg . Command ) , "INVALID_UTF8" , client . t ( "Message rejected for containing invalid UTF-8" ) )
2020-06-22 20:54:43 +02:00
return false
}