2017-03-11 13:01:40 +01:00
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
2018-02-02 14:44:52 +01:00
"fmt"
2020-05-04 02:51:39 +02:00
"regexp"
2020-10-27 16:24:17 +01:00
"sort"
2019-05-19 10:27:44 +02:00
"strconv"
"strings"
2019-05-13 08:24:58 +02:00
"time"
2018-04-01 03:22:06 +02:00
2021-06-18 08:41:57 +02:00
"github.com/ergochat/irc-go/ircfmt"
2019-05-08 10:11:54 +02:00
2022-05-13 21:35:11 +02:00
"github.com/ergochat/ergo/irc/caps"
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/custime"
"github.com/ergochat/ergo/irc/passwd"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
2017-03-11 13:01:40 +01:00
)
2018-04-19 08:48:19 +02:00
// "enabled" callbacks for specific nickserv commands
2019-01-04 04:32:07 +01:00
func servCmdRequiresAccreg ( config * Config ) bool {
return config . Accounts . Registration . Enabled
2018-04-19 08:48:19 +02:00
}
2019-01-04 04:32:07 +01:00
func servCmdRequiresAuthEnabled ( config * Config ) bool {
return config . Accounts . AuthenticationEnabled
2018-04-19 08:48:19 +02:00
}
2019-02-15 01:51:55 +01:00
func servCmdRequiresNickRes ( config * Config ) bool {
2019-01-04 04:32:07 +01:00
return config . Accounts . AuthenticationEnabled && config . Accounts . NickReservation . Enabled
2018-08-06 11:02:47 +02:00
}
2019-05-08 10:11:54 +02:00
func servCmdRequiresBouncerEnabled ( config * Config ) bool {
2020-02-21 05:55:42 +01:00
return config . Accounts . Multiclient . Enabled
2019-05-08 10:11:54 +02:00
}
2021-08-26 04:32:55 +02:00
func servCmdRequiresEmailReset ( config * Config ) bool {
return config . Accounts . Registration . EmailVerification . Enabled &&
config . Accounts . Registration . EmailVerification . PasswordReset . Enabled
}
2019-12-30 18:44:07 +01:00
const nickservHelp = ` NickServ lets you register, log in to, and manage an account. `
2018-04-01 03:22:06 +02:00
var (
2018-04-19 08:48:19 +02:00
nickservCommands = map [ string ] * serviceCommand {
2020-09-19 19:19:41 +02:00
"clients" : {
handler : nsClientsHandler ,
help : ` Syntax : $ bCLIENTS LIST [ nickname ] $ b
CLIENTS LIST shows information about the clients currently attached , via
the server ' s multiclient functionality , to your nickname . An administrator
can use this command to list another user ' s clients .
Syntax : $ bCLIENTS LOGOUT [ nickname ] [ client_id / all ] $ b
2021-08-04 03:54:37 +02:00
CLIENTS LOGOUT detaches a single client , or all clients currently attached
to your nickname . An administrator can use this command to logout another
user ' s clients . ` ,
2020-09-19 19:19:41 +02:00
helpShort : ` $bCLIENTS$b can list and logout the sessions attached to a nickname. ` ,
enabled : servCmdRequiresBouncerEnabled ,
minParams : 1 ,
} ,
2018-04-01 03:22:06 +02:00
"drop" : {
handler : nsDropHandler ,
help : ` Syntax : $ bDROP [ nickname ] $ b
DROP de - links the given ( or your current ) nickname from your user account . ` ,
2018-04-19 08:48:19 +02:00
helpShort : ` $bDROP$b de-links your current (or the given) nickname from your user account. ` ,
2019-02-15 01:51:55 +01:00
enabled : servCmdRequiresNickRes ,
2018-04-19 08:48:19 +02:00
authRequired : true ,
2018-04-01 03:22:06 +02:00
} ,
2019-01-02 16:08:44 +01:00
"enforce" : {
2019-05-19 10:27:44 +02:00
hidden : true ,
2019-01-02 16:08:44 +01:00
handler : nsEnforceHandler ,
help : ` Syntax : $ bENFORCE [ method ] $ b
2019-05-19 10:27:44 +02:00
ENFORCE is an alias for $ bGET enforce $ b and $ bSET enforce $ b . See the help
entry for $ bSET $ b for more information . ` ,
2019-01-02 16:08:44 +01:00
authRequired : true ,
2020-03-31 20:28:26 +02:00
enabled : servCmdRequiresNickRes ,
2019-01-02 16:08:44 +01:00
} ,
2018-04-01 03:22:06 +02:00
"ghost" : {
handler : nsGhostHandler ,
help : ` Syntax : $ bGHOST < nickname > $ b
GHOST disconnects the given user from the network if they ' re logged in with the
same user account , letting you reclaim your nickname . ` ,
2018-04-19 08:48:19 +02:00
helpShort : ` $bGHOST$b reclaims your nickname. ` ,
2020-01-01 03:18:41 +01:00
enabled : servCmdRequiresNickRes ,
2018-04-19 08:48:19 +02:00
authRequired : true ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-04-01 03:22:06 +02:00
} ,
"group" : {
handler : nsGroupHandler ,
help : ` Syntax : $ bGROUP $ b
2019-02-22 03:53:01 +01:00
GROUP links your current nickname with your logged - in account , so other people
will not be able to use it . ` ,
2018-04-19 08:48:19 +02:00
helpShort : ` $bGROUP$b links your current nickname to your user account. ` ,
2019-02-15 01:51:55 +01:00
enabled : servCmdRequiresNickRes ,
2018-04-19 08:48:19 +02:00
authRequired : true ,
2018-04-01 03:22:06 +02:00
} ,
"identify" : {
handler : nsIdentifyHandler ,
help : ` Syntax : $ bIDENTIFY < username > [ password ] $ b
IDENTIFY lets you login to the given username using either password auth , or
certfp ( your client certificate ) if a password is not given . ` ,
helpShort : ` $bIDENTIFY$b lets you login to your account. ` ,
2019-12-29 17:59:49 +01:00
enabled : servCmdRequiresAuthEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-04-01 03:22:06 +02:00
} ,
2020-05-04 02:51:39 +02:00
"list" : {
handler : nsListHandler ,
help : ` Syntax : $ bLIST [ regex ] $ b
LIST returns the list of registered nicknames , which match the given regex .
If no regex is provided , all registered nicknames are returned . ` ,
helpShort : ` $bLIST$b searches the list of registered nicknames. ` ,
enabled : servCmdRequiresAuthEnabled ,
capabs : [ ] string { "accreg" } ,
minParams : 0 ,
} ,
2018-04-01 03:22:06 +02:00
"info" : {
handler : nsInfoHandler ,
help : ` Syntax : $ bINFO [ username ] $ b
INFO gives you information about the given ( or your own ) user account . ` ,
helpShort : ` $bINFO$b gives you information on a user account. ` ,
} ,
"register" : {
handler : nsRegisterHandler ,
2018-04-01 03:29:13 +02:00
// TODO: "email" is an oversimplification here; it's actually any callback, e.g.,
// person@example.com, mailto:person@example.com, tel:16505551234.
2019-05-29 10:25:20 +02:00
help : ` Syntax : $ bREGISTER < password > [ email ] $ b
2018-04-01 03:22:06 +02:00
2019-05-29 10:25:20 +02:00
REGISTER lets you register your current nickname as a user account . If the
server allows anonymous registration , you can omit the e - mail address .
2018-04-01 03:22:06 +02:00
2019-05-29 10:25:20 +02:00
If you are currently logged in with a TLS client certificate and wish to use
it instead of a password to log in , send * as the password . ` ,
2018-04-01 03:22:06 +02:00
helpShort : ` $bREGISTER$b lets you register a user account. ` ,
2018-04-19 08:48:19 +02:00
enabled : servCmdRequiresAccreg ,
2019-05-29 10:25:20 +02:00
minParams : 1 ,
maxParams : 2 ,
2018-04-01 03:22:06 +02:00
} ,
"sadrop" : {
handler : nsDropHandler ,
help : ` Syntax : $ bSADROP < nickname > $ b
2018-04-19 08:48:19 +02:00
SADROP forcibly de - links the given nickname from the attached user account . ` ,
helpShort : ` $bSADROP$b forcibly de-links the given nickname from its user account. ` ,
2018-08-15 04:48:06 +02:00
capabs : [ ] string { "accreg" } ,
2019-02-15 01:51:55 +01:00
enabled : servCmdRequiresNickRes ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-04-01 03:22:06 +02:00
} ,
2019-02-05 06:19:03 +01:00
"saregister" : {
handler : nsSaregisterHandler ,
2020-02-11 12:35:17 +01:00
help : ` Syntax : $ bSAREGISTER < username > [ password ] $ b
2019-02-05 06:19:03 +01:00
SAREGISTER registers an account on someone else ' s behalf .
This is for use in configurations that require SASL for all connections ;
an administrator can set use this command to set up user accounts . ` ,
helpShort : ` $bSAREGISTER$b registers an account on someone else's behalf. ` ,
2022-05-04 22:41:01 +02:00
enabled : servCmdRequiresAuthEnabled , // deliberate
capabs : [ ] string { "accreg" } ,
minParams : 1 ,
} ,
"saverify" : {
handler : nsSaverifyHandler ,
help : ` Syntax : $ bSAVERIFY < username > $ b
SAVERIFY manually verifies an account that is pending verification . ` ,
2022-12-24 19:13:38 +01:00
helpShort : ` $bSAVERIFY$b manually verifies an account that is pending verification. ` ,
2022-05-04 22:41:01 +02:00
enabled : servCmdRequiresAuthEnabled , // deliberate
2019-02-05 06:19:03 +01:00
capabs : [ ] string { "accreg" } ,
2020-02-11 12:35:17 +01:00
minParams : 1 ,
2019-02-05 06:19:03 +01:00
} ,
2019-05-08 10:11:54 +02:00
"sessions" : {
2020-09-19 19:19:41 +02:00
hidden : true ,
handler : nsClientsHandler ,
2019-05-08 10:11:54 +02:00
help : ` Syntax : $ bSESSIONS [ nickname ] $ b
2020-09-19 19:19:41 +02:00
SESSIONS is an alias for $ bCLIENTS LIST $ b . See the help entry for $ bCLIENTS $ b
for more information . ` ,
enabled : servCmdRequiresBouncerEnabled ,
2019-05-08 10:11:54 +02:00
} ,
2018-04-01 03:22:06 +02:00
"unregister" : {
handler : nsUnregisterHandler ,
2018-06-19 10:03:40 +02:00
help : ` Syntax : $ bUNREGISTER < username > [ code ] $ b
2018-04-01 03:22:06 +02:00
2018-06-19 10:03:40 +02:00
UNREGISTER lets you delete your user account ( or someone else ' s , if you ' re an
IRC operator with the correct permissions ) . To prevent accidental
unregistrations , a verification code is required ; invoking the command without
a code will display the necessary code . ` ,
2018-04-01 03:22:06 +02:00
helpShort : ` $bUNREGISTER$b lets you delete your user account. ` ,
2019-02-15 01:51:55 +01:00
enabled : servCmdRequiresAuthEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 1 ,
2018-04-01 03:22:06 +02:00
} ,
2020-03-20 17:34:46 +01:00
"erase" : {
handler : nsUnregisterHandler ,
help : ` Syntax : $ bERASE < username > [ code ] $ b
ERASE deletes all records of an account , allowing it to be re - registered .
This should be used with caution , because it violates an expectation that
account names are permanent identifiers . Typically , UNREGISTER should be
used instead . A confirmation code is required ; invoking the command
without a code will display the necessary code . ` ,
helpShort : ` $bERASE$b erases all records of an account, allowing reuse. ` ,
enabled : servCmdRequiresAuthEnabled ,
capabs : [ ] string { "accreg" } ,
minParams : 1 ,
} ,
2018-04-01 03:22:06 +02:00
"verify" : {
handler : nsVerifyHandler ,
help : ` Syntax : $ bVERIFY < username > < code > $ b
VERIFY lets you complete an account registration , if the server requires email
or other verification . ` ,
helpShort : ` $bVERIFY$b lets you complete account registration. ` ,
2018-04-19 08:48:19 +02:00
enabled : servCmdRequiresAccreg ,
2019-01-04 04:32:07 +01:00
minParams : 2 ,
2018-04-01 03:22:06 +02:00
} ,
2018-11-29 00:21:41 +01:00
"passwd" : {
handler : nsPasswdHandler ,
help : ` Syntax : $ bPASSWD < current > < new > < new_again > $ b
Or : $ bPASSWD < username > < new > $ b
PASSWD lets you change your account password . You must supply your current
password and confirm the new one by typing it twice . If you ' re an IRC operator
with the correct permissions , you can use PASSWD to reset someone else ' s
2019-12-29 17:59:49 +01:00
password by supplying their username and then the desired password . To
indicate an empty password , use * instead . ` ,
2018-11-29 00:21:41 +01:00
helpShort : ` $bPASSWD$b lets you change your password. ` ,
enabled : servCmdRequiresAuthEnabled ,
2019-01-04 04:32:07 +01:00
minParams : 2 ,
2018-11-29 00:21:41 +01:00
} ,
2021-03-02 04:40:39 +01:00
"password" : {
aliasOf : "passwd" ,
} ,
2019-05-19 10:27:44 +02:00
"get" : {
handler : nsGetHandler ,
help : ` Syntax : $ bGET < setting > $ b
GET queries the current values of your account settings . For more information
on the settings and their possible values , see HELP SET . ` ,
helpShort : ` $bGET$b queries the current values of your account settings ` ,
authRequired : true ,
2020-03-31 20:28:26 +02:00
enabled : servCmdRequiresAuthEnabled ,
2019-05-19 10:27:44 +02:00
minParams : 1 ,
} ,
"saget" : {
handler : nsGetHandler ,
help : ` Syntax : $ bSAGET < account > < setting > $ b
SAGET queries the values of someone else ' s account settings . For more
information on the settings and their possible values , see HELP SET . ` ,
helpShort : ` $bSAGET$b queries the current values of another user's account settings ` ,
2020-03-31 20:28:26 +02:00
enabled : servCmdRequiresAuthEnabled ,
2019-05-19 10:27:44 +02:00
minParams : 2 ,
capabs : [ ] string { "accreg" } ,
} ,
"set" : {
2019-05-20 08:56:49 +02:00
handler : nsSetHandler ,
helpShort : ` $bSET$b modifies your account settings ` ,
// these are broken out as separate strings so they can be translated separately
helpStrings : [ ] string {
` Syntax $ bSET < setting > < value > $ b
2019-05-19 10:27:44 +02:00
2020-02-19 01:38:42 +01:00
SET modifies your account settings . The following settings are available : ` ,
2019-05-19 10:27:44 +02:00
2019-05-20 08:56:49 +02:00
` $ bENFORCE $ b
2019-05-19 10:27:44 +02:00
' enforce ' lets you specify a custom enforcement mechanism for your registered
nicknames . Your options are :
1. ' none ' [ no enforcement , overriding the server default ]
2020-06-01 17:54:38 +02:00
2. ' strict ' [ you must already be authenticated to use the nick ]
3. ' default ' [ use the server default ] ` ,
2019-05-19 10:27:44 +02:00
2020-02-21 05:55:42 +01:00
` $ bMULTICLIENT $ b
If ' multiclient ' is enabled and you are already logged in and using a nick , a
2019-05-19 10:27:44 +02:00
second client of yours that authenticates with SASL and requests the same nick
is allowed to attach to the nick as well ( this is comparable to the behavior
of IRC "bouncers" like ZNC ) . Your options are ' on ' ( allow this behavior ) ,
2019-05-20 08:56:49 +02:00
' off ' ( disallow it ) , and ' default ' ( use the server default value ) . ` ,
2019-05-19 10:27:44 +02:00
2019-05-20 08:56:49 +02:00
` $ bAUTOREPLAY - LINES $ b
2019-05-19 10:27:44 +02:00
' autoreplay - lines ' controls the number of lines of channel history that will
be replayed to you automatically when joining a channel . Your options are any
positive number , 0 to disable the feature , and ' default ' to use the server
2019-05-20 08:56:49 +02:00
default . ` ,
2019-05-19 10:27:44 +02:00
2019-12-18 23:38:14 +01:00
` $ bREPLAY - JOINS $ b
' replay - joins ' controls whether replayed channel history will include
2019-05-19 10:27:44 +02:00
lines for join and part . This provides more information about the context of
2021-11-01 09:36:06 +01:00
messages , but may be spammy . Your options are ' always ' and the default of
' commands - only ' ( the messages will be replayed in CHATHISTORY output , but not
2019-12-18 23:38:14 +01:00
during autoreplay ) . ` ,
2020-02-19 01:38:42 +01:00
` $ bALWAYS - ON $ b
' always - on ' controls whether your nickname / identity will remain active
even while you are disconnected from the server . Your options are ' true ' ,
' false ' , and ' default ' ( use the server default value ) . ` ,
` $ bAUTOREPLAY - MISSED $ b
' autoreplay - missed ' is only effective for always - on clients . If enabled ,
if you have at most one active session , the server will remember the time
you disconnect and then replay missed messages to you when you reconnect .
Your options are ' on ' and ' off ' . ` ,
` $ bDM - HISTORY $ b
' dm - history ' is only effective for always - on clients . It lets you control
how the history of your direct messages is stored . Your options are :
1. ' off ' [ no history ]
2. ' ephemeral ' [ a limited amount of temporary history , not stored on disk ]
3. ' on ' [ history stored in a permanent database , if available ]
4. ' default ' [ use the server default ] ` ,
2020-05-19 20:12:20 +02:00
` $ bAUTO - AWAY $ b
' auto - away ' is only effective for always - on clients . If enabled , you will
automatically be marked away when all your sessions are disconnected , and
automatically return from away when you connect again . ` ,
2021-08-26 04:32:55 +02:00
` $ bEMAIL $ b
' email ' controls the e - mail address associated with your account ( if the
server operator allows it , this address can be used for password resets ) .
As an additional security measure , if you have a password set , you must
provide it as an additional argument to $ bSET $ b , for example ,
SET EMAIL test @ example . com hunter2 ` ,
2019-05-20 08:56:49 +02:00
} ,
2019-05-19 10:27:44 +02:00
authRequired : true ,
2020-03-31 20:28:26 +02:00
enabled : servCmdRequiresAuthEnabled ,
2019-05-19 10:27:44 +02:00
minParams : 2 ,
} ,
"saset" : {
2019-05-20 08:56:49 +02:00
handler : nsSetHandler ,
help : ` Syntax : $ bSASET < account > < setting > < value > $ b
SASET modifies the values of someone else ' s account settings . For more
information on the settings and their possible values , see HELP SET . ` ,
2019-05-19 10:27:44 +02:00
helpShort : ` $bSASET$b modifies another user's account settings ` ,
2020-03-31 20:28:26 +02:00
enabled : servCmdRequiresAuthEnabled ,
2019-05-19 10:27:44 +02:00
minParams : 3 ,
capabs : [ ] string { "accreg" } ,
} ,
2021-08-26 04:32:55 +02:00
"sendpass" : {
handler : nsSendpassHandler ,
help : ` Syntax : $ bSENDPASS < account > $ b
SENDPASS sends a password reset email to the email address associated with
the target account . The reset code in the email can then be used with the
$ bRESETPASS $ b command . ` ,
helpShort : ` $bSENDPASS$b initiates an email-based password reset ` ,
enabled : servCmdRequiresEmailReset ,
minParams : 1 ,
} ,
"resetpass" : {
handler : nsResetpassHandler ,
help : ` Syntax : $ bRESETPASS < account > < code > < password > $ b
RESETPASS resets an account password , using a reset code that was emailed as
the result of a previous $ bSENDPASS $ b command . ` ,
helpShort : ` $bRESETPASS$b completes an email-based password reset ` ,
enabled : servCmdRequiresEmailReset ,
minParams : 3 ,
} ,
2019-12-29 17:59:49 +01:00
"cert" : {
handler : nsCertHandler ,
help : ` Syntax : $ bCERT < LIST | ADD | DEL > [ account ] [ certfp ] $ b
2021-11-14 01:58:56 +01:00
CERT examines or modifies the SHA - 256 TLS certificate fingerprints that can
be used to log into an account . Specifically , $ bCERT LIST $ b lists the
authorized fingerprints , $ bCERT ADD < fingerprint > $ b adds a new fingerprint , and
2019-12-29 17:59:49 +01:00
$ bCERT DEL < fingerprint > $ b removes a fingerprint . If you ' re an IRC operator
with the correct permissions , you can act on another user ' s account , for
2021-11-14 01:58:56 +01:00
example with $ bCERT ADD < account > < fingerprint > $ b . See the operator manual
for instructions on how to compute the fingerprint . ` ,
2019-12-29 17:59:49 +01:00
helpShort : ` $bCERT$b controls a user account's certificate fingerprints ` ,
enabled : servCmdRequiresAuthEnabled ,
minParams : 1 ,
} ,
2020-07-10 23:09:02 +02:00
"suspend" : {
handler : nsSuspendHandler ,
2020-10-27 16:24:17 +01:00
help : ` Syntax : $ bSUSPEND ADD < nickname > [ DURATION duration ] [ reason ] $ b
$ bSUSPEND DEL < nickname > $ b
$ bSUSPEND LIST $ b
Suspending an account disables it ( preventing new logins ) and disconnects
all associated clients . You can specify a time limit or a reason for
the suspension . The $ bDEL $ b subcommand reverses a suspension , and the $ bLIST $ b
command lists all current suspensions . ` ,
2020-10-29 01:32:55 +01:00
helpShort : ` $bSUSPEND$b manages account suspensions ` ,
2020-07-10 23:09:02 +02:00
minParams : 1 ,
2021-11-18 12:41:32 +01:00
capabs : [ ] string { "ban" } ,
2020-07-10 23:09:02 +02:00
} ,
2020-11-11 01:59:12 +01:00
"rename" : {
handler : nsRenameHandler ,
help : ` Syntax : $ bRENAME < account > < newname > $ b
RENAME allows a server administrator to change the name of an account .
Currently , you can only change the canonical casefolding of an account
( e . g . , you can change "Alice" to "alice" , but not "Alice" to "Amanda" ) . ` ,
helpShort : ` $bRENAME$b renames an account ` ,
minParams : 2 ,
capabs : [ ] string { "accreg" } ,
} ,
2021-08-26 04:32:55 +02:00
"verifyemail" : {
handler : nsVerifyEmailHandler ,
authRequired : true ,
minParams : 1 ,
hidden : true ,
} ,
2018-04-01 03:22:06 +02:00
}
)
2020-11-29 05:27:11 +01:00
func nsGetHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-05-19 10:27:44 +02:00
var account string
if command == "saget" {
account = params [ 0 ]
params = params [ 1 : ]
} else {
account = client . Account ( )
}
accountData , err := server . accounts . LoadAccount ( account )
if err == errAccountDoesNotExist {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such account" ) )
2019-05-19 10:27:44 +02:00
return
} else if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Error loading account data" ) )
2019-05-19 10:27:44 +02:00
return
}
2020-11-29 05:27:11 +01:00
displaySetting ( service , params [ 0 ] , accountData . Settings , client , rb )
2019-05-19 10:27:44 +02:00
}
2020-11-29 05:27:11 +01:00
func displaySetting ( service * ircService , settingName string , settings AccountSettings , client * Client , rb * ResponseBuffer ) {
2019-05-19 10:27:44 +02:00
config := client . server . Config ( )
switch strings . ToLower ( settingName ) {
case "enforce" :
storedValue := settings . NickEnforcement
serializedStoredValue := nickReservationToString ( storedValue )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Your stored nickname enforcement setting is: %s" ) , serializedStoredValue ) )
2019-05-19 10:27:44 +02:00
serializedActualValue := nickReservationToString ( configuredEnforcementMethod ( config , storedValue ) )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Given current server settings, your nickname is enforced with: %s" ) , serializedActualValue ) )
2019-05-19 10:27:44 +02:00
case "autoreplay-lines" :
if settings . AutoreplayLines == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "You will receive the server default of %d lines of autoreplayed history" ) , config . History . AutoreplayOnJoin ) )
2019-05-19 10:27:44 +02:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "You will receive %d lines of autoreplayed history" ) , * settings . AutoreplayLines ) )
2019-05-19 10:27:44 +02:00
}
2019-12-18 23:38:14 +01:00
case "replay-joins" :
switch settings . ReplayJoins {
case ReplayJoinsCommandsOnly :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You will see JOINs and PARTs in /HISTORY output, but not in autoreplay" ) )
2019-12-18 23:38:14 +01:00
case ReplayJoinsAlways :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You will see JOINs and PARTs in /HISTORY output and in autoreplay" ) )
2019-05-19 10:27:44 +02:00
}
2020-02-21 05:55:42 +01:00
case "multiclient" :
if ! config . Accounts . Multiclient . Enabled {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "This feature has been disabled by the server administrators" ) )
2019-05-19 10:27:44 +02:00
} else {
switch settings . AllowBouncer {
2020-02-21 05:55:42 +01:00
case MulticlientAllowedServerDefault :
if config . Accounts . Multiclient . AllowedByDefault {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Multiclient functionality is currently enabled for your account, but you can opt out" ) )
2019-05-19 10:27:44 +02:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Multiclient functionality is currently disabled for your account, but you can opt in" ) )
2019-05-19 10:27:44 +02:00
}
2020-02-21 05:55:42 +01:00
case MulticlientDisallowedByUser :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Multiclient functionality is currently disabled for your account" ) )
2020-02-21 05:55:42 +01:00
case MulticlientAllowedByUser :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Multiclient functionality is currently enabled for your account" ) )
2019-05-19 10:27:44 +02:00
}
}
2020-02-19 01:38:42 +01:00
case "always-on" :
stored := settings . AlwaysOn
2020-03-02 05:13:21 +01:00
actual := persistenceEnabled ( config . Accounts . Multiclient . AlwaysOn , stored )
2021-03-11 06:43:07 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Your stored always-on setting is: %s" ) , userPersistentStatusToString ( stored ) ) )
2020-02-19 01:38:42 +01:00
if actual {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Given current server settings, your client is always-on" ) )
2020-02-19 01:38:42 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Given current server settings, your client is not always-on" ) )
2020-02-19 01:38:42 +01:00
}
case "autoreplay-missed" :
stored := settings . AutoreplayMissed
if stored {
2020-03-02 05:13:21 +01:00
alwaysOn := persistenceEnabled ( config . Accounts . Multiclient . AlwaysOn , settings . AlwaysOn )
if alwaysOn {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Autoreplay of missed messages is enabled" ) )
2020-02-19 01:38:42 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You have enabled autoreplay of missed messages, but you can't receive them because your client isn't set to always-on" ) )
2020-02-19 01:38:42 +01:00
}
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Your account is not configured to receive autoreplayed missed messages" ) )
2020-02-19 01:38:42 +01:00
}
2020-05-19 20:12:20 +02:00
case "auto-away" :
stored := settings . AutoAway
alwaysOn := persistenceEnabled ( config . Accounts . Multiclient . AlwaysOn , settings . AlwaysOn )
actual := persistenceEnabled ( config . Accounts . Multiclient . AutoAway , settings . AutoAway )
2021-03-11 06:43:07 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Your stored auto-away setting is: %s" ) , userPersistentStatusToString ( stored ) ) )
2020-05-19 20:12:20 +02:00
if actual && alwaysOn {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Given current server settings, auto-away is enabled for your client" ) )
2020-05-19 20:12:20 +02:00
} else if actual && ! alwaysOn {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Because your client is not always-on, auto-away is disabled" ) )
2020-05-19 20:12:20 +02:00
} else if ! actual {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Given current server settings, auto-away is disabled for your client" ) )
2020-05-19 20:12:20 +02:00
}
2020-02-19 01:38:42 +01:00
case "dm-history" :
effectiveValue := historyEnabled ( config . History . Persistent . DirectMessages , settings . DMHistory )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Your stored direct message history setting is: %s" ) , historyStatusToString ( settings . DMHistory ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Given current server settings, your direct message history setting is: %s" ) , historyStatusToString ( effectiveValue ) ) )
2021-08-26 04:32:55 +02:00
case "email" :
if settings . Email != "" {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Your stored e-mail address is: %s" ) , settings . Email ) )
} else {
service . Notice ( rb , client . t ( "You have no stored e-mail address" ) )
}
2019-05-19 10:27:44 +02:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such setting" ) )
2019-05-19 10:27:44 +02:00
}
}
2021-03-11 06:43:07 +01:00
func userPersistentStatusToString ( status PersistentStatus ) string {
// #1544: "mandatory" as a user setting should display as "enabled"
result := persistentStatusToString ( status )
if result == "mandatory" {
result = "enabled"
}
return result
}
2020-11-29 05:27:11 +01:00
func nsSetHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2021-08-26 04:32:55 +02:00
var privileged bool
2019-05-19 10:27:44 +02:00
var account string
if command == "saset" {
2021-08-26 04:32:55 +02:00
privileged = true
2019-05-19 10:27:44 +02:00
account = params [ 0 ]
params = params [ 1 : ]
} else {
account = client . Account ( )
}
2021-08-26 04:32:55 +02:00
key := strings . ToLower ( params [ 0 ] )
// unprivileged NS SET EMAIL is different because it requires a confirmation
if ! privileged && key == "email" {
nsSetEmailHandler ( service , client , params , rb )
return
}
2019-05-19 10:27:44 +02:00
var munger settingsMunger
var finalSettings AccountSettings
var err error
2021-08-26 04:32:55 +02:00
switch key {
2020-07-21 07:05:13 +02:00
case "pass" , "password" :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "To change a password, use the PASSWD command. For details, /msg NickServ HELP PASSWD" ) )
2019-05-19 10:27:44 +02:00
return
case "enforce" :
var method NickEnforcementMethod
method , err = nickReservationFromString ( params [ 1 ] )
if err != nil {
err = errInvalidParams
break
}
// updating enforcement settings is special-cased, because it requires
// an update to server.accounts.accountToMethod
finalSettings , err = server . accounts . SetEnforcementStatus ( account , method )
if err == nil {
finalSettings . NickEnforcement = method // success
}
case "autoreplay-lines" :
var newValue * int
if strings . ToLower ( params [ 1 ] ) != "default" {
val , err_ := strconv . Atoi ( params [ 1 ] )
if err_ != nil || val < 0 {
err = errInvalidParams
break
}
newValue = new ( int )
* newValue = val
}
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . AutoreplayLines = newValue
return
}
2020-02-21 05:55:42 +01:00
case "multiclient" :
var newValue MulticlientAllowedSetting
2019-05-19 10:27:44 +02:00
if strings . ToLower ( params [ 1 ] ) == "default" {
2020-02-21 05:55:42 +01:00
newValue = MulticlientAllowedServerDefault
2019-05-19 10:27:44 +02:00
} else {
var enabled bool
2019-05-20 08:56:49 +02:00
enabled , err = utils . StringToBool ( params [ 1 ] )
2019-05-19 10:27:44 +02:00
if enabled {
2020-02-21 05:55:42 +01:00
newValue = MulticlientAllowedByUser
2019-05-19 10:27:44 +02:00
} else {
2020-02-21 05:55:42 +01:00
newValue = MulticlientDisallowedByUser
2019-05-19 10:27:44 +02:00
}
}
if err == nil {
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . AllowBouncer = newValue
return
}
}
2019-12-18 23:38:14 +01:00
case "replay-joins" :
var newValue ReplayJoinsSetting
newValue , err = replayJoinsSettingFromString ( params [ 1 ] )
2019-05-19 10:27:44 +02:00
if err == nil {
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
2019-12-18 23:38:14 +01:00
out . ReplayJoins = newValue
2019-05-19 10:27:44 +02:00
return
}
}
2020-02-19 01:38:42 +01:00
case "always-on" :
2020-02-26 08:00:38 +01:00
// #821: it's problematic to alter the value of always-on if you're not
// the (actual or potential) always-on client yourself. make an exception
// for `saset` to give operators an escape hatch (any consistency problems
// can probably be fixed by restarting the server):
if command != "saset" {
details := client . Details ( )
if details . nick != details . accountName {
err = errNickAccountMismatch
}
}
if err == nil {
2020-02-26 07:44:05 +01:00
var newValue PersistentStatus
newValue , err = persistentStatusFromString ( params [ 1 ] )
// "opt-in" and "opt-out" don't make sense as user preferences
if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut {
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . AlwaysOn = newValue
return
}
2020-02-19 01:38:42 +01:00
}
}
case "autoreplay-missed" :
var newValue bool
newValue , err = utils . StringToBool ( params [ 1 ] )
if err == nil {
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . AutoreplayMissed = newValue
return
}
}
2020-05-19 20:12:20 +02:00
case "auto-away" :
var newValue PersistentStatus
newValue , err = persistentStatusFromString ( params [ 1 ] )
// "opt-in" and "opt-out" don't make sense as user preferences
if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut {
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . AutoAway = newValue
return
}
}
2020-02-19 01:38:42 +01:00
case "dm-history" :
var newValue HistoryStatus
newValue , err = historyStatusFromString ( params [ 1 ] )
if err == nil {
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . DMHistory = newValue
return
}
}
2021-08-26 04:32:55 +02:00
case "email" :
newValue := params [ 1 ]
munger = func ( in AccountSettings ) ( out AccountSettings , err error ) {
out = in
out . Email = newValue
return
}
2019-05-19 10:27:44 +02:00
default :
err = errInvalidParams
}
if munger != nil {
finalSettings , err = server . accounts . ModifyAccountSettings ( account , munger )
}
switch err {
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully changed your account settings" ) )
2021-08-26 04:32:55 +02:00
displaySetting ( service , key , finalSettings , client , rb )
2019-05-19 10:27:44 +02:00
case errInvalidParams , errAccountDoesNotExist , errFeatureDisabled , errAccountUnverified , errAccountUpdateFailed :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( err . Error ( ) ) )
2020-02-26 07:44:05 +01:00
case errNickAccountMismatch :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Your nickname must match your account name %s exactly to modify this setting. Try changing it with /NICK, or logging out and back in with the correct nickname." ) , client . AccountName ( ) ) )
2019-05-19 10:27:44 +02:00
default :
// unknown error
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2019-05-19 10:27:44 +02:00
}
2018-03-14 17:51:53 +01:00
}
2021-08-26 04:32:55 +02:00
// handle unprivileged NS SET EMAIL, which sends a confirmation code
func nsSetEmailHandler ( service * ircService , client * Client , params [ ] string , rb * ResponseBuffer ) {
config := client . server . Config ( )
if ! config . Accounts . Registration . EmailVerification . Enabled {
rb . Notice ( client . t ( "E-mail verification is disabled" ) )
return
}
if ! nsLoginThrottleCheck ( service , client , rb ) {
return
}
var password string
if len ( params ) > 2 {
password = params [ 2 ]
}
account := client . Account ( )
errorMessage := nsConfirmPassword ( client . server , account , password )
if errorMessage != "" {
service . Notice ( rb , client . t ( errorMessage ) )
return
}
err := client . server . accounts . NsSetEmail ( client , params [ 1 ] )
switch err {
case nil :
service . Notice ( rb , client . t ( "Check your e-mail for instructions on how to confirm your change of address" ) )
case errLimitExceeded :
service . Notice ( rb , client . t ( "Try again later" ) )
default :
// if appropriate, show the client the error from the attempted email sending
if rErr := registrationCallbackErrorText ( config , client , err ) ; rErr != "" {
service . Notice ( rb , rErr )
} else {
service . Notice ( rb , client . t ( "An error occurred" ) )
}
}
}
func nsVerifyEmailHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
err := server . accounts . NsVerifyEmail ( client , params [ 0 ] )
switch err {
case nil :
service . Notice ( rb , client . t ( "Successfully changed your account settings" ) )
displaySetting ( service , "email" , client . AccountSettings ( ) , client , rb )
case errAccountVerificationInvalidCode :
service . Notice ( rb , client . t ( err . Error ( ) ) )
default :
service . Notice ( rb , client . t ( "An error occurred" ) )
}
}
2020-11-29 05:27:11 +01:00
func nsDropHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2018-04-01 03:22:06 +02:00
sadrop := command == "sadrop"
2019-01-04 04:32:07 +01:00
var nick string
if len ( params ) > 0 {
nick = params [ 0 ]
} else {
nick = client . NickCasefolded ( )
}
2018-04-01 03:22:06 +02:00
err := server . accounts . SetNickReserved ( client , nick , sadrop , false )
if err == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully ungrouped nick %s with your account" ) , nick ) )
2018-04-01 03:22:06 +02:00
} else if err == errAccountNotLoggedIn {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You're not logged into an account" ) )
2018-04-01 03:22:06 +02:00
} else if err == errAccountCantDropPrimaryNick {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You can't ungroup your primary nickname (try unregistering your account instead)" ) )
2018-04-01 03:22:06 +02:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Could not ungroup nick" ) )
2018-04-01 03:22:06 +02:00
}
}
2020-11-29 05:27:11 +01:00
func nsGhostHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-04 04:32:07 +01:00
nick := params [ 0 ]
2018-04-01 03:22:06 +02:00
ghost := server . clients . Get ( nick )
if ghost == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such nick" ) )
2018-04-01 03:22:06 +02:00
return
} else if ghost == client {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You can't GHOST yourself (try /QUIT instead)" ) )
2018-02-12 07:09:30 +01:00
return
2020-02-19 01:38:42 +01:00
} else if ghost . AlwaysOn ( ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You can't GHOST an always-on client" ) )
2020-02-19 01:38:42 +01:00
return
2018-02-12 07:09:30 +01:00
}
2018-04-09 18:29:19 +02:00
authorized := false
account := client . Account ( )
if account != "" {
// the user must either own the nick, or the target client
authorized = ( server . accounts . NickToAccount ( nick ) == account ) || ( ghost . Account ( ) == account )
}
if ! authorized {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You don't own that nick" ) )
2018-04-09 18:29:19 +02:00
return
}
2019-04-12 06:08:46 +02:00
ghost . Quit ( fmt . Sprintf ( ghost . t ( "GHOSTed by %s" ) , client . Nick ( ) ) , nil )
2019-05-22 03:40:25 +02:00
ghost . destroy ( nil )
2018-04-01 03:22:06 +02:00
}
2020-11-29 05:27:11 +01:00
func nsGroupHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-31 00:59:49 +01:00
nick := client . Nick ( )
2018-04-01 03:22:06 +02:00
err := server . accounts . SetNickReserved ( client , nick , false , true )
if err == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully grouped nick %s with your account" ) , nick ) )
2018-04-01 03:22:06 +02:00
} else if err == errAccountTooManyNicks {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You have too many nicks reserved already (you can remove some with /NS DROP)" ) )
2018-04-01 03:22:06 +02:00
} else if err == errNicknameReserved {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "That nickname is already reserved by someone else" ) )
2018-02-12 07:09:30 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Error reserving nickname" ) )
2018-02-12 07:09:30 +01:00
}
}
2020-11-29 05:27:11 +01:00
func nsLoginThrottleCheck ( service * ircService , client * Client , rb * ResponseBuffer ) ( success bool ) {
2020-06-12 21:51:48 +02:00
throttled , remainingTime := client . checkLoginThrottle ( )
2019-01-01 22:45:37 +01:00
if throttled {
2023-01-08 12:36:04 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime . Round ( time . Millisecond ) ) )
2019-01-01 22:45:37 +01:00
}
2020-06-12 21:51:48 +02:00
return ! throttled
2019-01-01 22:45:37 +01:00
}
2020-11-29 05:27:11 +01:00
func nsIdentifyHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-06 02:59:42 +01:00
if client . LoggedIntoAccount ( ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You're already logged into an account" ) )
2019-01-06 02:59:42 +01:00
return
}
2020-02-26 07:44:05 +01:00
var err error
2018-04-01 03:22:06 +02:00
loginSuccessful := false
2019-02-13 08:17:56 +01:00
var username , passphrase string
if len ( params ) == 1 {
2020-02-19 03:42:27 +01:00
if rb . session . certfp != "" {
2019-02-13 08:17:56 +01:00
username = params [ 0 ]
} else {
// XXX undocumented compatibility mode with other nickservs, allowing
// /msg NickServ identify passphrase
username = client . NickCasefolded ( )
passphrase = params [ 0 ]
}
} else {
username = params [ 0 ]
2019-01-04 04:32:07 +01:00
passphrase = params [ 1 ]
}
2018-04-01 03:22:06 +02:00
// try passphrase
2019-01-04 04:32:07 +01:00
if passphrase != "" {
2020-02-26 07:44:05 +01:00
err = server . accounts . AuthenticateByPassphrase ( client , username , passphrase )
2018-04-01 03:22:06 +02:00
loginSuccessful = ( err == nil )
}
// try certfp
2020-02-19 03:42:27 +01:00
if ! loginSuccessful && rb . session . certfp != "" {
2020-09-23 08:23:35 +02:00
err = server . accounts . AuthenticateByCertificate ( client , rb . session . certfp , rb . session . peerCerts , "" )
2018-04-01 03:22:06 +02:00
loginSuccessful = ( err == nil )
}
2020-03-17 04:37:52 +01:00
nickFixupFailed := false
2020-03-16 12:54:50 +01:00
if loginSuccessful {
2020-11-29 05:27:11 +01:00
if ! fixupNickEqualsAccount ( client , rb , server . Config ( ) , service . prefix ) {
2020-03-16 12:54:50 +01:00
loginSuccessful = false
2020-03-17 04:37:52 +01:00
// fixupNickEqualsAccount sends its own error message, don't send another
nickFixupFailed = true
2020-03-16 12:54:50 +01:00
}
}
2018-04-01 03:22:06 +02:00
if loginSuccessful {
2020-11-29 05:27:11 +01:00
sendSuccessfulAccountAuth ( service , client , rb , true )
2020-03-17 04:37:52 +01:00
} else if ! nickFixupFailed {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Authentication failed: %s" ) , authErrorToMessage ( server , err ) ) )
2018-04-01 03:22:06 +02:00
}
2018-02-20 10:20:30 +01:00
}
2020-11-29 05:27:11 +01:00
func nsListHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-05-04 02:51:39 +02:00
if ! client . HasRoleCapabs ( "accreg" ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
2020-05-04 02:51:39 +02:00
return
}
var searchRegex * regexp . Regexp
if len ( params ) > 0 {
var err error
searchRegex , err = regexp . Compile ( params [ 0 ] )
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid regex" ) )
2020-05-04 02:51:39 +02:00
return
}
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "*** $bNickServ LIST$b ***" ) ) )
2020-05-04 02:51:39 +02:00
nicks := server . accounts . AllNicks ( )
for _ , nick := range nicks {
if searchRegex == nil || searchRegex . MatchString ( nick ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( " %s" , nick ) )
2020-05-04 02:51:39 +02:00
}
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "*** $bEnd of NickServ LIST$b ***" ) ) )
2020-05-04 02:51:39 +02:00
}
2020-11-29 05:27:11 +01:00
func nsInfoHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-12-29 17:59:49 +01:00
if ! server . Config ( ) . Accounts . AuthenticationEnabled && ! client . HasRoleCapabs ( "accreg" ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "This command has been disabled by the server administrators" ) )
2019-12-29 17:59:49 +01:00
return
}
2019-01-04 04:32:07 +01:00
var accountName string
if len ( params ) > 0 {
nick := params [ 0 ]
2020-03-16 12:54:50 +01:00
if server . Config ( ) . Accounts . NickReservation . Enabled {
2019-01-04 04:32:07 +01:00
accountName = server . accounts . NickToAccount ( nick )
if accountName == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "That nickname is not registered" ) )
2019-01-04 04:32:07 +01:00
return
}
} else {
accountName = nick
}
} else {
accountName = client . Account ( )
2018-04-01 03:22:06 +02:00
if accountName == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You're not logged into an account" ) )
2018-04-01 03:22:06 +02:00
return
}
}
account , err := server . accounts . LoadAccount ( accountName )
if err != nil || ! account . Verified {
2022-05-05 07:04:28 +02:00
if server . accounts . accountWasUnregistered ( accountName ) {
service . Notice ( rb , client . t ( "Name reserved due to a prior registration" ) )
} else {
service . Notice ( rb , client . t ( "Account does not exist" ) )
}
2019-01-04 04:32:07 +01:00
return
2018-04-01 03:22:06 +02:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Account: %s" ) , account . Name ) )
2019-12-17 01:50:15 +01:00
registeredAt := account . RegisteredAt . Format ( time . RFC1123 )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Registered at: %s" ) , registeredAt ) )
2021-06-05 01:44:00 +02:00
if account . Name == client . AccountName ( ) || client . HasRoleCapabs ( "accreg" ) {
2021-08-26 04:32:55 +02:00
if account . Settings . Email != "" {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Email address: %s" ) , account . Settings . Email ) )
2021-06-05 01:44:00 +02:00
}
}
2018-04-01 03:22:06 +02:00
// TODO nicer formatting for this
for _ , nick := range account . AdditionalNicks {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Additional grouped nick: %s" ) , nick ) )
2018-04-01 03:22:06 +02:00
}
2021-04-08 05:23:09 +02:00
listRegisteredChannels ( service , accountName , rb )
2020-10-27 16:24:17 +01:00
if account . Suspended != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , suspensionToString ( client , * account . Suspended ) )
2020-10-27 16:24:17 +01:00
}
2018-04-01 03:22:06 +02:00
}
2021-04-08 05:23:09 +02:00
func listRegisteredChannels ( service * ircService , accountName string , rb * ResponseBuffer ) {
client := rb . session . client
channels := client . server . accounts . ChannelsForAccount ( accountName )
2021-04-19 21:49:56 +02:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Account %s has %d registered channel(s)." ) , accountName , len ( channels ) ) )
2021-04-08 05:23:09 +02:00
for _ , channel := range rb . session . client . server . accounts . ChannelsForAccount ( accountName ) {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Registered channel: %s" ) , channel ) )
}
}
2020-11-29 05:27:11 +01:00
func nsRegisterHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-05-29 10:25:20 +02:00
details := client . Details ( )
passphrase := params [ 0 ]
var email string
if 1 < len ( params ) {
email = params [ 1 ]
2019-01-04 04:32:07 +01:00
}
2018-04-01 03:22:06 +02:00
2020-02-19 03:42:27 +01:00
certfp := rb . session . certfp
2019-05-29 10:25:20 +02:00
if passphrase == "*" {
if certfp == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You must be connected with TLS and a client certificate to do this" ) )
2019-05-29 10:25:20 +02:00
return
} else {
passphrase = ""
}
2018-02-20 10:20:30 +01:00
}
2020-07-06 09:54:51 +02:00
if passphrase != "" {
cfPassphrase , err := Casefold ( passphrase )
if err == nil && cfPassphrase == details . nickCasefolded {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Usage: REGISTER <passphrase> [email]" ) ) // #1179
2020-07-06 09:54:51 +02:00
return
}
}
2020-11-29 05:27:11 +01:00
if ! nsLoginThrottleCheck ( service , client , rb ) {
2019-02-06 02:09:36 +01:00
return
}
2020-03-16 12:54:50 +01:00
config := server . Config ( )
account := details . nick
2020-03-17 04:25:50 +01:00
if config . Accounts . NickReservation . ForceGuestFormat {
2020-03-16 12:54:50 +01:00
matches := config . Accounts . NickReservation . guestRegexp . FindStringSubmatch ( account )
if matches == nil || len ( matches ) < 2 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Erroneous nickname" ) )
2020-03-16 12:54:50 +01:00
return
}
account = matches [ 1 ]
}
2020-10-07 00:04:29 +02:00
callbackNamespace , callbackValue , validationErr := parseCallback ( email , config )
if validationErr != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Registration requires a valid e-mail address" ) )
2020-10-07 00:04:29 +02:00
return
2018-02-03 12:38:28 +01:00
}
2018-02-02 14:44:52 +01:00
2020-02-19 03:42:27 +01:00
err := server . accounts . Register ( client , account , callbackNamespace , callbackValue , passphrase , rb . session . certfp )
2018-02-11 11:30:40 +01:00
if err == nil {
2018-02-20 10:20:30 +01:00
if callbackNamespace == "*" {
2022-05-04 22:41:01 +02:00
err = server . accounts . Verify ( client , account , "" , true )
2020-11-29 05:27:11 +01:00
if err == nil && fixupNickEqualsAccount ( client , rb , config , service . prefix ) {
sendSuccessfulRegResponse ( service , client , rb )
2018-02-20 10:20:30 +01:00
}
} else {
2019-02-22 03:37:11 +01:00
messageTemplate := client . t ( "Account created, pending verification; verification code has been sent to %s" )
2020-05-06 07:15:00 +02:00
message := fmt . Sprintf ( messageTemplate , callbackValue )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , message )
2021-11-30 07:50:28 +01:00
announcePendingReg ( client , rb , account )
2018-02-20 10:20:30 +01:00
}
2020-03-27 22:52:37 +01:00
} else {
// details could not be stored and relevant numerics have been dispatched, abort
2021-07-07 12:35:30 +02:00
message := registrationErrorToMessage ( config , client , err )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( message ) )
2018-02-03 12:38:28 +01:00
}
}
2018-02-02 14:44:52 +01:00
2020-11-29 05:27:11 +01:00
func nsSaregisterHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2022-05-13 21:35:11 +02:00
registerCap := rb . session . capabilities . Has ( caps . AccountRegistration )
2020-02-11 12:35:17 +01:00
var account , passphrase string
account = params [ 0 ]
if 1 < len ( params ) && params [ 1 ] != "*" {
passphrase = params [ 1 ]
2019-02-05 06:19:03 +01:00
}
2020-02-11 12:35:17 +01:00
err := server . accounts . SARegister ( account , passphrase )
2019-02-05 06:19:03 +01:00
if err != nil {
var errMsg string
2022-05-13 21:35:11 +02:00
var failCode string
2019-02-05 06:19:03 +01:00
if err == errAccountAlreadyRegistered || err == errAccountAlreadyVerified {
errMsg = client . t ( "Account already exists" )
2022-05-13 21:35:11 +02:00
failCode = "USERNAME_EXISTS"
2022-05-05 07:04:28 +02:00
} else if err == errNameReserved {
errMsg = client . t ( err . Error ( ) )
2022-05-13 21:35:11 +02:00
failCode = "USERNAME_EXISTS"
2019-02-05 06:19:03 +01:00
} else if err == errAccountBadPassphrase {
errMsg = client . t ( "Passphrase contains forbidden characters or is otherwise invalid" )
2022-05-13 21:35:11 +02:00
failCode = "INVALID_PASSWORD"
2019-02-05 06:19:03 +01:00
} else {
server . logger . Error ( "services" , "unknown error from saregister" , err . Error ( ) )
2022-05-13 21:35:11 +02:00
errMsg = fmt . Sprintf ( client . t ( "Could not register: %v" ) , err )
failCode = "UNKNOWN_ERROR"
2019-02-05 06:19:03 +01:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , errMsg )
2022-05-13 21:35:11 +02:00
if registerCap {
rb . Add ( nil , server . name , "FAIL" , "REGISTER" , failCode , utils . SafeErrorParam ( account ) , err . Error ( ) )
}
2019-02-05 06:19:03 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully registered account %s" ) , account ) )
2022-05-13 21:35:11 +02:00
if registerCap {
rb . Add ( nil , server . name , "REGISTER" , "SUCCESS" , account , client . t ( "Account successfully registered" ) )
}
2019-12-19 15:27:54 +01:00
server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Operator $c[grey][$r%s$c[grey]] registered account $c[grey][$r%s$c[grey]] with SAREGISTER" ) , client . Oper ( ) . Name , account ) )
2019-02-05 06:19:03 +01:00
}
}
2022-05-04 22:41:01 +02:00
func nsSaverifyHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
account := params [ 0 ]
err := server . accounts . Verify ( nil , account , "" , true )
if err == nil {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully verified account %s" ) , account ) )
} else {
service . Notice ( rb , fmt . Sprintf ( client . t ( "Failed to verify account %s: %v" ) , account , err . Error ( ) ) )
}
}
2020-11-29 05:27:11 +01:00
func nsUnregisterHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-03-20 17:34:46 +01:00
erase := command == "erase"
2019-01-04 04:32:07 +01:00
username := params [ 0 ]
var verificationCode string
if len ( params ) > 1 {
verificationCode = params [ 1 ]
2018-03-02 23:04:24 +01:00
}
2018-04-01 03:22:06 +02:00
if username == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You must specify an account" ) )
2018-03-02 23:04:24 +01:00
return
2018-04-01 03:22:06 +02:00
}
2018-06-19 10:03:40 +02:00
2020-03-20 17:34:46 +01:00
var accountName string
var registeredAt time . Time
if erase {
// account may not be in a loadable state, e.g., if it was unregistered
accountName = username
2020-04-24 21:41:58 +02:00
// make the confirmation code nondeterministic for ERASE
registeredAt = server . ctime
2020-03-20 17:34:46 +01:00
} else {
account , err := server . accounts . LoadAccount ( username )
if err == errAccountDoesNotExist {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid account name" ) )
2020-03-20 17:34:46 +01:00
return
} else if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Internal error" ) )
2020-03-20 17:34:46 +01:00
return
}
accountName = account . Name
registeredAt = account . RegisteredAt
2018-03-02 23:04:24 +01:00
}
2018-06-19 10:03:40 +02:00
2020-03-20 17:34:46 +01:00
if ! ( accountName == client . AccountName ( ) || client . HasRoleCapabs ( "accreg" ) ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient oper privs" ) )
2018-03-02 23:04:24 +01:00
return
}
2020-03-20 17:34:46 +01:00
expectedCode := utils . ConfirmationCode ( accountName , registeredAt )
2018-06-19 10:03:40 +02:00
if expectedCode != verificationCode {
2020-03-20 17:34:46 +01:00
if erase {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b" ) ) )
2020-03-20 17:34:46 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: unregistering this account will remove its stored privileges.$b" ) ) )
2021-04-08 11:16:23 +02:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bNote that an unregistered account name remains reserved and cannot be re-registered.$b" ) ) )
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bIf you are having problems with your account, contact an administrator.$b" ) ) )
2022-01-05 19:27:17 +01:00
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bUnregistering your account will unregister all channels you founded.$b" ) ) )
service . Notice ( rb , ircfmt . Unescape ( client . t ( "$bTo prevent this, transfer your channels first with CS TRANSFER.$b" ) ) )
2020-03-20 17:34:46 +01:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "To confirm, run this command: %s" ) , fmt . Sprintf ( "/NS %s %s %s" , strings . ToUpper ( command ) , accountName , expectedCode ) ) )
2018-06-19 10:03:40 +02:00
return
}
2020-03-20 17:34:46 +01:00
err := server . accounts . Unregister ( accountName , erase )
2018-04-01 03:22:06 +02:00
if err == errAccountDoesNotExist {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( err . Error ( ) ) )
2018-04-01 03:22:06 +02:00
} else if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Error while unregistering account" ) )
2018-03-02 23:04:24 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully unregistered account %s" ) , accountName ) )
2020-03-20 17:34:46 +01:00
server . logger . Info ( "accounts" , "client" , client . Nick ( ) , "unregistered account" , accountName )
client . server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]" ) , client . NickMaskString ( ) , accountName ) )
2018-03-02 23:04:24 +01:00
}
}
2020-11-29 05:27:11 +01:00
func nsVerifyHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-04 04:32:07 +01:00
username , code := params [ 0 ] , params [ 1 ]
2022-05-04 22:41:01 +02:00
err := server . accounts . Verify ( client , username , code , false )
2018-03-02 23:04:24 +01:00
2018-04-01 03:22:06 +02:00
var errorMessage string
2020-03-27 22:52:37 +01:00
if err != nil {
switch err {
case errAccountAlreadyLoggedIn , errAccountVerificationInvalidCode , errAccountAlreadyVerified :
errorMessage = err . Error ( )
default :
errorMessage = errAccountVerificationFailed . Error ( )
}
2018-03-02 23:04:24 +01:00
}
2018-04-01 03:22:06 +02:00
if errorMessage != "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( errorMessage ) )
2018-04-01 03:22:06 +02:00
return
2018-03-02 23:04:24 +01:00
}
2020-11-29 05:27:11 +01:00
if fixupNickEqualsAccount ( client , rb , server . Config ( ) , service . prefix ) {
sendSuccessfulRegResponse ( service , client , rb )
2020-03-16 12:54:50 +01:00
}
2018-03-02 23:04:24 +01:00
}
2018-11-29 00:21:41 +01:00
2021-08-26 04:32:55 +02:00
func nsConfirmPassword ( server * Server , account , passphrase string ) ( errorMessage string ) {
accountData , err := server . accounts . LoadAccount ( account )
if err != nil {
errorMessage = ` You're not logged into an account `
} else {
hash := accountData . Credentials . PassphraseHash
2021-11-05 01:10:56 +01:00
if hash != nil {
if passphrase == "" {
errorMessage = ` You must supply a password `
} else if passwd . CompareHashAndPassword ( hash , [ ] byte ( passphrase ) ) != nil {
errorMessage = ` Password incorrect `
}
2021-08-26 04:32:55 +02:00
}
}
return
}
2020-11-29 05:27:11 +01:00
func nsPasswdHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2018-11-29 00:21:41 +01:00
var target string
var newPassword string
var errorMessage string
2021-01-15 15:26:34 +01:00
var oper * Oper
2019-01-01 22:45:37 +01:00
2019-01-04 04:32:07 +01:00
switch len ( params ) {
2018-11-29 00:21:41 +01:00
case 2 :
2021-01-15 15:26:34 +01:00
oper = client . Oper ( )
if ! oper . HasRoleCapab ( "accreg" ) {
2019-12-29 17:59:49 +01:00
errorMessage = ` Insufficient privileges `
2018-11-29 00:21:41 +01:00
} else {
2019-01-04 04:32:07 +01:00
target , newPassword = params [ 0 ] , params [ 1 ]
2019-12-29 17:59:49 +01:00
if newPassword == "*" {
newPassword = ""
}
2021-01-15 15:26:34 +01:00
message := fmt . Sprintf ( "Operator %s ran NS PASSWD for account %s" , oper . Name , target )
server . snomasks . Send ( sno . LocalOpers , message )
server . logger . Info ( "opers" , message )
2018-11-29 00:21:41 +01:00
}
case 3 :
target = client . Account ( )
2021-08-26 04:32:55 +02:00
newPassword = params [ 1 ]
if newPassword == "*" {
newPassword = ""
}
2021-12-30 18:15:30 +01:00
2021-12-30 18:59:14 +01:00
checkPassword := params [ 2 ]
2021-12-30 18:15:30 +01:00
if checkPassword == "*" {
2021-12-30 18:59:14 +01:00
checkPassword = "" // #1883
2021-12-30 18:15:30 +01:00
}
2018-11-29 00:21:41 +01:00
if target == "" {
2019-12-29 17:59:49 +01:00
errorMessage = ` You're not logged into an account `
2021-12-30 18:15:30 +01:00
} else if newPassword != checkPassword {
2019-12-29 17:59:49 +01:00
errorMessage = ` Passwords do not match `
2018-11-29 00:21:41 +01:00
} else {
2020-11-29 05:27:11 +01:00
if ! nsLoginThrottleCheck ( service , client , rb ) {
2019-12-29 17:59:49 +01:00
return
}
2021-08-26 04:32:55 +02:00
errorMessage = nsConfirmPassword ( server , target , params [ 0 ] )
2018-11-29 00:21:41 +01:00
}
default :
2019-05-20 08:56:49 +02:00
errorMessage = ` Invalid parameters `
2018-11-29 00:21:41 +01:00
}
if errorMessage != "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( errorMessage ) )
2018-11-29 00:21:41 +01:00
return
}
2021-01-15 15:26:34 +01:00
err := server . accounts . setPassword ( target , newPassword , oper != nil )
2019-12-29 17:59:49 +01:00
switch err {
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Password changed" ) )
2019-12-29 17:59:49 +01:00
case errEmptyCredentials :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You can't delete your password unless you add a certificate fingerprint" ) )
2020-02-11 12:35:17 +01:00
case errCredsExternallyManaged :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Your account credentials are managed externally and cannot be changed here" ) )
2019-12-29 17:59:49 +01:00
case errCASFailed :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Try again later" ) )
2021-07-06 19:28:25 +02:00
case errAccountDoesNotExist :
service . Notice ( rb , client . t ( "Account does not exist" ) )
2019-12-29 17:59:49 +01:00
default :
2018-12-31 17:33:42 +01:00
server . logger . Error ( "internal" , "could not upgrade user password:" , err . Error ( ) )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Password could not be changed due to server error" ) )
2018-11-29 00:21:41 +01:00
}
}
2019-01-02 16:08:44 +01:00
2020-11-29 05:27:11 +01:00
func nsEnforceHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-05-19 10:27:44 +02:00
newParams := [ ] string { "enforce" }
2019-01-04 04:32:07 +01:00
if len ( params ) == 0 {
2020-11-29 05:27:11 +01:00
nsGetHandler ( service , server , client , "get" , newParams , rb )
2019-01-02 16:08:44 +01:00
} else {
2019-05-19 10:27:44 +02:00
newParams = append ( newParams , params [ 0 ] )
2020-11-29 05:27:11 +01:00
nsSetHandler ( service , server , client , "set" , newParams , rb )
2019-01-02 16:08:44 +01:00
}
}
2019-05-08 10:11:54 +02:00
2020-11-29 05:27:11 +01:00
func nsClientsHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-09-19 19:19:41 +02:00
var verb string
if command == "sessions" {
// Legacy "SESSIONS" command is an alias for CLIENTS LIST.
verb = "list"
} else if len ( params ) > 0 {
verb = strings . ToLower ( params [ 0 ] )
params = params [ 1 : ]
}
2019-05-08 10:11:54 +02:00
2020-09-19 19:19:41 +02:00
switch verb {
case "list" :
2020-11-29 05:27:11 +01:00
nsClientsListHandler ( service , server , client , params , rb )
2020-09-19 19:19:41 +02:00
case "logout" :
2020-11-29 05:27:11 +01:00
nsClientsLogoutHandler ( service , server , client , params , rb )
2020-09-19 19:19:41 +02:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2020-09-19 19:19:41 +02:00
}
}
2020-11-29 05:27:11 +01:00
func nsClientsListHandler ( service * ircService , server * Server , client * Client , params [ ] string , rb * ResponseBuffer ) {
2020-09-19 19:19:41 +02:00
target := client
2021-01-15 14:24:42 +01:00
hasPrivs := client . HasRoleCapabs ( "ban" )
2019-05-08 10:11:54 +02:00
if 0 < len ( params ) {
target = server . clients . Get ( params [ 0 ] )
if target == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such nick" ) )
2019-05-08 10:11:54 +02:00
return
}
2020-09-24 08:44:12 +02:00
if target != client && ! hasPrivs {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Command restricted" ) )
2019-12-18 01:15:26 +01:00
return
}
2019-05-08 10:11:54 +02:00
}
2020-09-24 14:35:03 +02:00
sessionData , currentIndex := target . AllSessionData ( rb . session , hasPrivs )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Nickname %[1]s has %[2]d attached clients(s)" ) , target . Nick ( ) , len ( sessionData ) ) )
2019-05-08 10:11:54 +02:00
for i , session := range sessionData {
if currentIndex == i {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Client %d (currently attached client):" ) , session . sessionID ) )
2019-05-08 10:11:54 +02:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Client %d:" ) , session . sessionID ) )
2019-05-08 10:11:54 +02:00
}
2020-06-12 21:51:48 +02:00
if session . deviceID != "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Device ID: %s" ) , session . deviceID ) )
2020-06-12 21:51:48 +02:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "IP address: %s" ) , session . ip . String ( ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Hostname: %s" ) , session . hostname ) )
2020-09-24 08:44:12 +02:00
if hasPrivs {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Connection: %s" ) , session . connInfo ) )
2020-09-24 08:44:12 +02:00
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Created at: %s" ) , session . ctime . Format ( time . RFC1123 ) ) )
service . Notice ( rb , fmt . Sprintf ( client . t ( "Last active: %s" ) , session . atime . Format ( time . RFC1123 ) ) )
2020-02-19 03:42:27 +01:00
if session . certfp != "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Certfp: %s" ) , session . certfp ) )
2020-02-19 03:42:27 +01:00
}
2021-03-18 09:24:45 +01:00
for _ , capStr := range session . caps {
if capStr != "" {
service . Notice ( rb , fmt . Sprintf ( client . t ( "IRCv3 CAPs: %s" ) , capStr ) )
}
}
2019-05-08 10:11:54 +02:00
}
}
2019-12-29 17:59:49 +01:00
2020-11-29 05:27:11 +01:00
func nsClientsLogoutHandler ( service * ircService , server * Server , client * Client , params [ ] string , rb * ResponseBuffer ) {
2020-09-19 19:19:41 +02:00
if len ( params ) < 1 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Missing client ID to logout (or \"all\")" ) )
2020-09-19 19:19:41 +02:00
return
}
target := client
if len ( params ) >= 2 {
// CLIENTS LOGOUT [nickname] [client ID]
target = server . clients . Get ( params [ 0 ] )
if target == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such nick" ) )
2020-09-19 19:19:41 +02:00
return
}
2021-01-15 14:24:42 +01:00
// User must have "kill" privileges to logout other user sessions.
2020-09-19 19:19:41 +02:00
if target != client {
oper := client . Oper ( )
2021-02-02 22:45:38 +01:00
if ! oper . HasRoleCapab ( "kill" ) {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient oper privs" ) )
2020-09-19 19:19:41 +02:00
return
}
}
params = params [ 1 : ]
}
var sessionToDestroy * Session // target.destroy(nil) will logout all sessions
if strings . ToLower ( params [ 0 ] ) != "all" {
sessionID , err := strconv . ParseInt ( params [ 0 ] , 10 , 64 )
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Client ID to logout should be an integer (or \"all\")" ) )
2020-09-19 19:19:41 +02:00
return
}
// Find the client ID that the user requested to logout.
sessions := target . Sessions ( )
for _ , session := range sessions {
if session . sessionID == sessionID {
sessionToDestroy = session
}
}
if sessionToDestroy == nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Specified client ID does not exist" ) )
2020-09-19 19:19:41 +02:00
return
}
}
target . destroy ( sessionToDestroy )
if ( sessionToDestroy != nil && rb . session != sessionToDestroy ) || client != target {
if sessionToDestroy != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully logged out session" ) )
2020-09-19 19:19:41 +02:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully logged out all sessions" ) )
2020-09-19 19:19:41 +02:00
}
}
}
2020-11-29 05:27:11 +01:00
func nsCertHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-12-29 17:59:49 +01:00
verb := strings . ToLower ( params [ 0 ] )
params = params [ 1 : ]
var target , certfp string
switch verb {
case "list" :
if 1 <= len ( params ) {
target = params [ 0 ]
}
case "add" , "del" :
if 2 <= len ( params ) {
target , certfp = params [ 0 ] , params [ 1 ]
} else if len ( params ) == 1 {
certfp = params [ 0 ]
2020-06-22 00:42:45 +02:00
} else if len ( params ) == 0 && verb == "add" && rb . session . certfp != "" {
certfp = rb . session . certfp // #1059
2019-12-29 17:59:49 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2019-12-29 17:59:49 +01:00
return
}
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2019-12-29 17:59:49 +01:00
return
}
hasPrivs := client . HasRoleCapabs ( "accreg" )
if target != "" && ! hasPrivs {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Insufficient privileges" ) )
2019-12-29 17:59:49 +01:00
return
} else if target == "" {
target = client . Account ( )
if target == "" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You're not logged into an account" ) )
2019-12-29 17:59:49 +01:00
return
}
}
var err error
switch verb {
case "list" :
accountData , err := server . accounts . LoadAccount ( target )
if err == errAccountDoesNotExist {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Account does not exist" ) )
2019-12-29 17:59:49 +01:00
return
} else if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2019-12-29 17:59:49 +01:00
return
}
certfps := accountData . Credentials . Certfps
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "There are %[1]d certificate fingerprint(s) authorized for account %[2]s." ) , len ( certfps ) , accountData . Name ) )
2019-12-29 17:59:49 +01:00
for i , certfp := range certfps {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( "%d: %s" , i + 1 , certfp ) )
2019-12-29 17:59:49 +01:00
}
return
case "add" :
err = server . accounts . addRemoveCertfp ( target , certfp , true , hasPrivs )
case "del" :
err = server . accounts . addRemoveCertfp ( target , certfp , false , hasPrivs )
}
switch err {
case nil :
if verb == "add" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Certificate fingerprint successfully added" ) )
2019-12-29 17:59:49 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Certificate fingerprint successfully removed" ) )
2019-12-29 17:59:49 +01:00
}
case errNoop :
if verb == "add" {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "That certificate fingerprint was already authorized" ) )
2019-12-29 17:59:49 +01:00
} else {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Certificate fingerprint not found" ) )
2019-12-29 17:59:49 +01:00
}
case errAccountDoesNotExist :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Account does not exist" ) )
2019-12-29 17:59:49 +01:00
case errLimitExceeded :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You already have too many certificate fingerprints" ) )
2019-12-29 17:59:49 +01:00
case utils . ErrInvalidCertfp :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid certificate fingerprint" ) )
2019-12-29 17:59:49 +01:00
case errCertfpAlreadyExists :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "That certificate fingerprint is already associated with another account" ) )
2019-12-29 17:59:49 +01:00
case errEmptyCredentials :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "You can't remove all your certificate fingerprints unless you add a password" ) )
2020-02-11 12:35:17 +01:00
case errCredsExternallyManaged :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Your account credentials are managed externally and cannot be changed here" ) )
2019-12-29 17:59:49 +01:00
case errCASFailed :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Try again later" ) )
2019-12-29 17:59:49 +01:00
default :
server . logger . Error ( "internal" , "could not modify certificates:" , err . Error ( ) )
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2019-12-29 17:59:49 +01:00
}
}
2020-07-10 23:09:02 +02:00
2020-11-29 05:27:11 +01:00
func nsSuspendHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-10-27 16:24:17 +01:00
subCmd := strings . ToLower ( params [ 0 ] )
params = params [ 1 : ]
switch subCmd {
case "add" :
2020-11-29 05:27:11 +01:00
nsSuspendAddHandler ( service , server , client , command , params , rb )
2020-10-27 16:24:17 +01:00
case "del" , "delete" , "remove" :
2020-11-29 05:27:11 +01:00
nsSuspendRemoveHandler ( service , server , client , command , params , rb )
2020-10-27 16:24:17 +01:00
case "list" :
2020-11-29 05:27:11 +01:00
nsSuspendListHandler ( service , server , client , command , params , rb )
2020-10-27 16:24:17 +01:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2020-10-27 16:24:17 +01:00
}
}
2020-11-29 05:27:11 +01:00
func nsSuspendAddHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-10-27 16:24:17 +01:00
if len ( params ) == 0 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2020-10-27 16:24:17 +01:00
return
}
account := params [ 0 ]
params = params [ 1 : ]
var duration time . Duration
if 2 <= len ( params ) && strings . ToLower ( params [ 0 ] ) == "duration" {
var err error
cDuration , err := custime . ParseDuration ( params [ 1 ] )
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid time duration for NS SUSPEND" ) )
2020-10-27 16:24:17 +01:00
return
}
duration = time . Duration ( cDuration )
params = params [ 2 : ]
}
var reason string
if len ( params ) != 0 {
reason = strings . Join ( params , " " )
}
name := client . Oper ( ) . Name
err := server . accounts . Suspend ( account , duration , name , reason )
2020-07-10 23:09:02 +02:00
switch err {
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully suspended account %s" ) , account ) )
2020-07-10 23:09:02 +02:00
case errAccountDoesNotExist :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such account" ) )
2020-07-10 23:09:02 +02:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2020-07-10 23:09:02 +02:00
}
}
2020-11-29 05:27:11 +01:00
func nsSuspendRemoveHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-10-27 16:24:17 +01:00
if len ( params ) == 0 {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Invalid parameters" ) )
2020-10-27 16:24:17 +01:00
return
}
2020-07-10 23:09:02 +02:00
err := server . accounts . Unsuspend ( params [ 0 ] )
switch err {
case nil :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Successfully un-suspended account %s" ) , params [ 0 ] ) )
2020-07-10 23:09:02 +02:00
case errAccountDoesNotExist :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "No such account" ) )
2020-10-27 16:24:17 +01:00
case errNoop :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Account was not suspended" ) )
2020-07-10 23:09:02 +02:00
default :
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "An error occurred" ) )
2020-07-10 23:09:02 +02:00
}
}
2020-10-27 16:24:17 +01:00
// sort in reverse order of creation time
type ByCreationTime [ ] AccountSuspension
func ( a ByCreationTime ) Len ( ) int { return len ( a ) }
func ( a ByCreationTime ) Swap ( i , j int ) { a [ i ] , a [ j ] = a [ j ] , a [ i ] }
func ( a ByCreationTime ) Less ( i , j int ) bool { return a [ i ] . TimeCreated . After ( a [ j ] . TimeCreated ) }
2020-11-29 05:27:11 +01:00
func nsSuspendListHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2021-01-19 14:49:45 +01:00
listAccountSuspensions ( client , rb , service . prefix )
}
func listAccountSuspensions ( client * Client , rb * ResponseBuffer , source string ) {
suspensions := client . server . accounts . ListSuspended ( )
2020-10-27 16:24:17 +01:00
sort . Sort ( ByCreationTime ( suspensions ) )
2021-01-19 14:49:45 +01:00
nick := client . Nick ( )
rb . Add ( nil , source , "NOTICE" , nick , fmt . Sprintf ( client . t ( "There are %d active account suspensions." ) , len ( suspensions ) ) )
2020-10-27 16:24:17 +01:00
for _ , suspension := range suspensions {
2021-01-19 14:49:45 +01:00
rb . Add ( nil , source , "NOTICE" , nick , suspensionToString ( client , suspension ) )
2020-10-27 16:24:17 +01:00
}
}
func suspensionToString ( client * Client , suspension AccountSuspension ) ( result string ) {
duration := client . t ( "indefinite" )
if suspension . Duration != time . Duration ( 0 ) {
duration = suspension . Duration . String ( )
}
ts := suspension . TimeCreated . Format ( time . RFC1123 )
reason := client . t ( "No reason given." )
if suspension . Reason != "" {
reason = fmt . Sprintf ( client . t ( "Reason: %s" ) , suspension . Reason )
}
2020-10-29 01:32:55 +01:00
return fmt . Sprintf ( client . t ( "Account %[1]s suspended at %[2]s. Duration: %[3]s. %[4]s" ) , suspension . AccountName , ts , duration , reason )
2020-10-27 16:24:17 +01:00
}
2020-11-11 01:59:12 +01:00
2021-08-26 04:32:55 +02:00
func nsSendpassHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
if ! nsLoginThrottleCheck ( service , client , rb ) {
return
}
account := params [ 0 ]
var message string
err := server . accounts . NsSendpass ( client , account )
switch err {
case nil :
server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( "Client %s sent a password reset for account %s" , client . Nick ( ) , account ) )
message = ` Successfully sent password reset email `
case errAccountDoesNotExist , errAccountUnverified , errAccountSuspended :
message = err . Error ( )
case errValidEmailRequired :
message = ` That account is not associated with an email address `
case errLimitExceeded :
message = ` Try again later `
default :
server . logger . Error ( "services" , "error in NS SENDPASS" , err . Error ( ) )
message = ` An error occurred `
}
rb . Notice ( client . t ( message ) )
}
func nsResetpassHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
if ! nsLoginThrottleCheck ( service , client , rb ) {
return
}
var message string
err := server . accounts . NsResetpass ( client , params [ 0 ] , params [ 1 ] , params [ 2 ] )
switch err {
case nil :
message = ` Successfully reset account password `
case errAccountDoesNotExist , errAccountUnverified , errAccountSuspended , errAccountBadPassphrase :
message = err . Error ( )
case errAccountInvalidCredentials :
message = ` Code did not match `
default :
server . logger . Error ( "services" , "error in NS RESETPASS" , err . Error ( ) )
message = ` An error occurred `
}
rb . Notice ( client . t ( message ) )
}
2020-11-29 05:27:11 +01:00
func nsRenameHandler ( service * ircService , server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2020-11-11 01:59:12 +01:00
oldName , newName := params [ 0 ] , params [ 1 ]
err := server . accounts . Rename ( oldName , newName )
if err != nil {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Couldn't rename account: %s" ) , client . t ( err . Error ( ) ) ) )
2020-11-11 01:59:12 +01:00
return
}
2020-11-29 05:27:11 +01:00
service . Notice ( rb , client . t ( "Successfully renamed account" ) )
2020-11-11 01:59:12 +01:00
if server . Config ( ) . Accounts . NickReservation . ForceNickEqualsAccount {
if curClient := server . clients . Get ( oldName ) ; curClient != nil {
renameErr := performNickChange ( client . server , client , curClient , nil , newName , rb )
if renameErr != nil && renameErr != errNoop {
2020-11-29 05:27:11 +01:00
service . Notice ( rb , fmt . Sprintf ( client . t ( "Warning: could not rename affected client: %v" ) , err ) )
2020-11-11 01:59:12 +01:00
}
}
}
}