2016-06-15 13:50:56 +02:00
// Copyright (c) 2012-2014 Jeremy Latt
2017-03-27 14:15:02 +02:00
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2016-06-15 13:50:56 +02:00
// released under the MIT license
2014-03-17 20:11:35 +01:00
package irc
2016-10-11 15:51:46 +02:00
import (
2018-02-18 10:46:14 +01:00
"crypto/rand"
2016-11-16 03:02:22 +01:00
"fmt"
2016-10-11 15:51:46 +02:00
"strings"
2017-11-22 10:41:11 +01:00
"github.com/goshuirc/irc-go/ircfmt"
2019-05-07 05:17:57 +02:00
"github.com/oragono/oragono/irc/history"
2021-03-17 19:36:52 +01:00
"github.com/oragono/oragono/irc/modes"
2017-11-22 10:41:11 +01:00
"github.com/oragono/oragono/irc/sno"
2019-05-07 05:17:57 +02:00
"github.com/oragono/oragono/irc/utils"
2016-10-11 15:51:46 +02:00
)
2016-06-19 02:01:30 +02:00
2017-03-11 13:01:40 +01:00
var (
2019-05-24 19:09:56 +02:00
restrictedNicknames = [ ] string {
2020-05-18 11:28:48 +02:00
"=scene=" , // used for rp commands
2020-05-28 19:16:17 +02:00
"Global" , // global announcements on some networks
// common services not implemented by us:
"MemoServ" , "BotServ" , "OperServ" ,
2017-03-11 13:01:40 +01:00
}
2019-05-24 19:09:56 +02:00
2020-08-23 04:43:21 +02:00
restrictedCasefoldedNicks = make ( utils . StringSet )
restrictedSkeletons = make ( utils . StringSet )
2017-03-11 13:01:40 +01:00
)
2020-04-23 07:38:12 +02:00
func performNickChange ( server * Server , client * Client , target * Client , session * Session , nickname string , rb * ResponseBuffer ) error {
2020-03-16 12:54:50 +01:00
details := target . Details ( )
hadNick := details . nick != "*"
origNickMask := details . nickMask
2020-09-23 08:30:34 +02:00
isSanick := client != target
2014-03-17 20:11:35 +01:00
2020-10-07 00:04:29 +02:00
assignedNickname , err , back := client . server . clients . SetNick ( target , session , nickname , false )
2018-02-03 13:03:36 +01:00
if err == errNicknameInUse {
2020-09-23 08:30:34 +02:00
if ! isSanick {
rb . Add ( nil , server . name , ERR_NICKNAMEINUSE , details . nick , utils . SafeErrorParam ( nickname ) , client . t ( "Nickname is already in use" ) )
} else {
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "NICKNAME_IN_USE" , utils . SafeErrorParam ( nickname ) , client . t ( "Nickname is already in use" ) )
}
2018-02-11 11:30:40 +01:00
} else if err == errNicknameReserved {
2020-09-23 08:30:34 +02:00
if ! isSanick {
rb . Add ( nil , server . name , ERR_NICKNAMEINUSE , details . nick , utils . SafeErrorParam ( nickname ) , client . t ( "Nickname is reserved by a different account" ) )
} else {
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "NICKNAME_RESERVED" , utils . SafeErrorParam ( nickname ) , client . t ( "Nickname is reserved by a different account" ) )
}
2019-05-24 19:09:56 +02:00
} else if err == errNicknameInvalid {
2020-09-23 08:30:34 +02:00
if ! isSanick {
rb . Add ( nil , server . name , ERR_ERRONEUSNICKNAME , details . nick , utils . SafeErrorParam ( nickname ) , client . t ( "Erroneous nickname" ) )
} else {
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "NICKNAME_INVALID" , utils . SafeErrorParam ( nickname ) , client . t ( "Erroneous nickname" ) )
}
2020-03-16 12:54:50 +01:00
} else if err == errNickAccountMismatch {
// this used to use ERR_NICKNAMEINUSE, but it displayed poorly in some clients;
// ERR_UNKNOWNERROR at least has a better chance of displaying our error text
2020-09-23 08:30:34 +02:00
if ! isSanick {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . nick , "NICK" , client . t ( "You must use your account name as your nickname" ) )
} else {
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "UNKNOWN_ERROR" , utils . SafeErrorParam ( nickname ) , client . t ( "This user's nickname and account name need to be equal" ) )
}
2020-03-16 12:54:50 +01:00
} else if err == errNickMissing {
2020-09-23 08:30:34 +02:00
if ! isSanick {
rb . Add ( nil , server . name , ERR_NONICKNAMEGIVEN , details . nick , client . t ( "No nickname given" ) )
} else {
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "NICKNAME_INVALID" , utils . SafeErrorParam ( nickname ) , client . t ( "No nickname given" ) )
}
2020-05-17 19:39:37 +02:00
} else if err == errNoop {
2020-09-23 08:30:34 +02:00
if ! isSanick {
// no message
} else {
rb . Add ( nil , server . name , "NOTE" , "SANICK" , "NOOP" , utils . SafeErrorParam ( nickname ) , client . t ( "Client already had the desired nickname" ) )
}
2016-11-16 03:02:22 +01:00
} else if err != nil {
2020-09-23 08:30:34 +02:00
client . server . logger . Error ( "internal" , "couldn't change nick" , nickname , err . Error ( ) )
if ! isSanick {
rb . Add ( nil , server . name , ERR_UNKNOWNERROR , details . nick , "NICK" , client . t ( "Could not set or change nickname" ) )
} else {
rb . Add ( nil , server . name , "FAIL" , "SANICK" , "UNKNOWN_ERROR" , utils . SafeErrorParam ( nickname ) , client . t ( "Could not set or change nickname" ) )
}
2019-05-24 19:09:56 +02:00
}
if err != nil {
2020-04-23 07:38:12 +02:00
return err
2016-11-15 18:05:33 +01:00
}
2017-11-22 10:41:11 +01:00
2021-03-18 00:01:38 +01:00
isBot := ! isSanick && client . HasMode ( modes . Bot )
2020-01-19 05:47:05 +01:00
message := utils . MakeMessage ( "" )
2019-05-07 05:17:57 +02:00
histItem := history . Item {
Type : history . Nick ,
Nick : origNickMask ,
AccountName : details . accountName ,
Message : message ,
2021-03-18 00:01:38 +01:00
IsBot : isBot ,
2019-05-07 05:17:57 +02:00
}
2020-02-19 01:38:42 +01:00
histItem . Params [ 0 ] = assignedNickname
2019-05-07 05:17:57 +02:00
2020-02-19 01:38:42 +01:00
client . server . logger . Debug ( "nick" , fmt . Sprintf ( "%s changed nickname to %s [%s]" , origNickMask , assignedNickname , client . NickCasefolded ( ) ) )
2017-11-22 10:41:11 +01:00
if hadNick {
2019-05-12 09:25:02 +02:00
if client == target {
2020-02-19 01:38:42 +01:00
target . server . snomasks . Send ( sno . LocalNicks , fmt . Sprintf ( ircfmt . Unescape ( "$%s$r changed nickname to %s" ) , details . nick , assignedNickname ) )
2019-05-12 09:25:02 +02:00
} else {
2020-02-19 01:38:42 +01:00
target . server . snomasks . Send ( sno . LocalNicks , fmt . Sprintf ( ircfmt . Unescape ( "Operator %s changed nickname of $%s$r to %s" ) , client . Nick ( ) , details . nick , assignedNickname ) )
2019-05-12 09:25:02 +02:00
}
2019-05-07 05:17:57 +02:00
target . server . whoWas . Append ( details . WhoWas )
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( message . Time , message . Msgid , origNickMask , details . accountName , isBot , nil , "NICK" , assignedNickname )
2019-04-12 06:08:46 +02:00
for session := range target . Friends ( ) {
if session != rb . session {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , origNickMask , details . accountName , isBot , nil , "NICK" , assignedNickname )
2019-02-21 04:20:23 +01:00
}
2017-11-22 10:41:11 +01:00
}
}
2020-05-19 20:12:20 +02:00
if back {
dispatchAwayNotify ( session . client , false , "" )
}
2020-09-22 02:30:42 +02:00
for _ , channel := range target . Channels ( ) {
2020-05-12 18:05:40 +02:00
channel . AddHistoryItem ( histItem , details . account )
2019-05-07 05:17:57 +02:00
}
2020-08-06 09:16:58 +02:00
newCfnick := target . NickCasefolded ( )
if newCfnick != details . nickCasefolded {
client . server . monitorManager . AlertAbout ( details . nick , details . nickCasefolded , false )
client . server . monitorManager . AlertAbout ( assignedNickname , newCfnick , true )
}
2020-04-23 07:38:12 +02:00
return nil
2014-03-17 20:11:35 +01:00
}
2018-02-18 10:46:14 +01:00
func ( server * Server ) RandomlyRename ( client * Client ) {
2020-03-16 12:54:50 +01:00
format := server . Config ( ) . Accounts . NickReservation . GuestFormat
2018-02-18 10:46:14 +01:00
buf := make ( [ ] byte , 8 )
rand . Read ( buf )
2020-03-16 12:54:50 +01:00
nick := strings . Replace ( format , "*" , utils . B32Encoder . EncodeToString ( buf ) , - 1 )
2019-04-12 06:08:46 +02:00
sessions := client . Sessions ( )
if len ( sessions ) == 0 {
2020-04-06 06:06:06 +02:00
// this can happen if they are anonymous and BRB (in general, an always-on
// client has title to its nickname and will never be the victim of
// a call to RandomlyRename)
client . destroy ( nil )
2019-04-12 06:08:46 +02:00
return
}
// XXX arbitrarily pick the first session to receive error messages;
// all other sessions receive a `NICK` line same as a friend would
rb := NewResponseBuffer ( sessions [ 0 ] )
performNickChange ( server , client , client , nil , nick , rb )
2018-12-28 19:45:55 +01:00
rb . Send ( false )
2018-02-18 10:46:14 +01:00
// technically performNickChange can fail to change the nick,
// but if they're still delinquent, the timer will get them later
}
2020-03-17 04:37:52 +01:00
// if force-nick-equals-account is set, account name and nickname must be equal,
// so we need to re-NICK automatically on every login event (IDENTIFY,
// VERIFY, and a REGISTER that auto-verifies). if we can't get the nick
// then we log them out (they will be able to reattach with SASL)
2020-11-29 05:27:11 +01:00
func fixupNickEqualsAccount ( client * Client , rb * ResponseBuffer , config * Config , source string ) ( success bool ) {
2020-03-17 04:37:52 +01:00
if ! config . Accounts . NickReservation . ForceNickEqualsAccount {
return true
}
if ! client . registered {
return true
}
2020-08-30 03:42:27 +02:00
err := performNickChange ( client . server , client , client , rb . session , client . AccountName ( ) , rb )
if err != nil && err != errNoop {
2020-03-17 04:37:52 +01:00
client . server . accounts . Logout ( client )
2020-11-29 05:27:11 +01:00
if source == "" {
source = client . server . name
}
rb . Add ( nil , source , "NOTICE" , client . t ( "A client is already using that account; try logging out and logging back in with SASL" ) )
2020-03-17 04:37:52 +01:00
return false
}
return true
}