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"
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
2018-06-19 10:03:40 +02:00
"github.com/goshuirc/irc-go/ircfmt"
2019-05-08 10:11:54 +02:00
"github.com/oragono/oragono/irc/modes"
2019-12-29 17:59:49 +01:00
"github.com/oragono/oragono/irc/passwd"
2019-12-19 15:27:54 +01:00
"github.com/oragono/oragono/irc/sno"
2019-05-20 08:56:49 +02:00
"github.com/oragono/oragono/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
}
2019-05-21 08:48:18 +02:00
const (
nsPrefix = "NickServ!NickServ@localhost"
2019-02-18 04:59:13 +01:00
// ZNC's nickserv module will not detect this unless it is:
// 1. sent with prefix `nickserv`
// 2. contains the string "identify"
// 3. contains at least one of several other magic strings ("msg" works)
2019-04-15 00:13:01 +02:00
nsTimeoutNotice = ` This nickname is reserved. Please login within %v (using $b/msg NickServ IDENTIFY <password>$b or SASL), or switch to a different nickname. `
2019-02-18 04:59:13 +01:00
)
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 {
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. ` ,
2019-02-15 01:51:55 +01:00
enabled : servCmdRequiresAuthEnabled ,
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" : {
handler : nsSessionsHandler ,
help : ` Syntax : $ bSESSIONS [ nickname ] $ b
SESSIONS lists information about the sessions currently attached , via
2020-02-21 05:55:42 +01:00
the server ' s multiclient functionality , to your nickname . An administrator
2019-05-08 10:11:54 +02:00
can use this command to list another user ' s sessions . ` ,
helpShort : ` $bSESSIONS$b lists the sessions attached to a nickname. ` ,
enabled : servCmdRequiresBouncerEnabled ,
} ,
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
} ,
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 ]
2. ' timeout ' [ anyone using the nick must authenticate before a deadline ,
or else they will be renamed ]
3. ' strict ' [ you must already be authenticated to use the nick ]
2019-05-20 08:56:49 +02:00
4. ' 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
2019-12-18 23:38:14 +01:00
messages , but may be spammy . Your options are ' always ' , ' never ' , and the default
of ' commands - only ' ( the messages will be replayed in / HISTORY output , but not
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 m arked away when all your sessions are disconnected , and
automatically return from away when you connect again . ` ,
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" } ,
} ,
2019-12-29 17:59:49 +01:00
"cert" : {
handler : nsCertHandler ,
help : ` Syntax : $ bCERT < LIST | ADD | DEL > [ account ] [ certfp ] $ b
CERT examines or modifies the 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
$ 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
example with $ bCERT ADD < account > < fingerprint > $ b . ` ,
helpShort : ` $bCERT$b controls a user account's certificate fingerprints ` ,
enabled : servCmdRequiresAuthEnabled ,
minParams : 1 ,
} ,
2018-04-01 03:22:06 +02:00
}
)
2018-04-01 03:52:37 +02:00
// nsNotice sends the client a notice from NickServ
2018-03-14 17:51:53 +01:00
func nsNotice ( rb * ResponseBuffer , text string ) {
2019-05-19 10:27:44 +02:00
// XXX i can't figure out how to use OragonoServices[servicename].prefix here
// without creating a compile-time initialization loop
2019-05-21 08:48:18 +02:00
rb . Add ( nil , nsPrefix , "NOTICE" , rb . target . Nick ( ) , text )
2019-05-19 10:27:44 +02:00
}
func nsGetHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
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 {
nsNotice ( rb , client . t ( "No such account" ) )
return
} else if err != nil {
nsNotice ( rb , client . t ( "Error loading account data" ) )
return
}
displaySetting ( params [ 0 ] , accountData . Settings , client , rb )
}
func displaySetting ( settingName string , settings AccountSettings , client * Client , rb * ResponseBuffer ) {
config := client . server . Config ( )
switch strings . ToLower ( settingName ) {
case "enforce" :
storedValue := settings . NickEnforcement
serializedStoredValue := nickReservationToString ( storedValue )
nsNotice ( rb , fmt . Sprintf ( client . t ( "Your stored nickname enforcement setting is: %s" ) , serializedStoredValue ) )
serializedActualValue := nickReservationToString ( configuredEnforcementMethod ( config , storedValue ) )
nsNotice ( rb , fmt . Sprintf ( client . t ( "Given current server settings, your nickname is enforced with: %s" ) , serializedActualValue ) )
case "autoreplay-lines" :
if settings . AutoreplayLines == nil {
nsNotice ( rb , fmt . Sprintf ( client . t ( "You will receive the server default of %d lines of autoreplayed history" ) , config . History . AutoreplayOnJoin ) )
} else {
nsNotice ( rb , fmt . Sprintf ( client . t ( "You will receive %d lines of autoreplayed history" ) , * settings . AutoreplayLines ) )
}
2019-12-18 23:38:14 +01:00
case "replay-joins" :
switch settings . ReplayJoins {
case ReplayJoinsCommandsOnly :
nsNotice ( rb , client . t ( "You will see JOINs and PARTs in /HISTORY output, but not in autoreplay" ) )
case ReplayJoinsAlways :
nsNotice ( rb , client . t ( "You will see JOINs and PARTs in /HISTORY output and in autoreplay" ) )
case ReplayJoinsNever :
nsNotice ( rb , client . t ( "You will not see JOINs and PARTs in /HISTORY output or 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 {
2019-12-17 01:50:15 +01:00
nsNotice ( 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 {
nsNotice ( 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-02-21 05:55:42 +01:00
nsNotice ( 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 :
nsNotice ( rb , client . t ( "Multiclient functionality is currently disabled for your account" ) )
case MulticlientAllowedByUser :
nsNotice ( 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 )
2020-02-19 01:38:42 +01:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "Your stored always-on setting is: %s" ) , persistentStatusToString ( stored ) ) )
if actual {
nsNotice ( rb , client . t ( "Given current server settings, your client is always-on" ) )
} else {
nsNotice ( rb , client . t ( "Given current server settings, your client is not always-on" ) )
}
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-02-19 01:38:42 +01:00
nsNotice ( rb , client . t ( "Autoreplay of missed messages is enabled" ) )
} else {
nsNotice ( 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" ) )
}
} else {
nsNotice ( rb , client . t ( "Your account is not configured to receive autoreplayed missed messages" ) )
}
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 )
nsNotice ( rb , fmt . Sprintf ( client . t ( "Your stored auto-away setting is: %s" ) , persistentStatusToString ( stored ) ) )
if actual && alwaysOn {
nsNotice ( rb , client . t ( "Given current server settings, auto-away is enabled for your client" ) )
} else if actual && ! alwaysOn {
nsNotice ( rb , client . t ( "Because your client is not always-on, auto-away is disabled" ) )
} else if ! actual {
nsNotice ( rb , client . t ( "Given current server settings, auto-away is disabled for your client" ) )
}
2020-02-19 01:38:42 +01:00
case "dm-history" :
effectiveValue := historyEnabled ( config . History . Persistent . DirectMessages , settings . DMHistory )
csNotice ( rb , fmt . Sprintf ( client . t ( "Your stored direct message history setting is: %s" ) , historyStatusToString ( settings . DMHistory ) ) )
csNotice ( rb , fmt . Sprintf ( client . t ( "Given current server settings, your direct message history setting is: %s" ) , historyStatusToString ( effectiveValue ) ) )
2019-05-19 10:27:44 +02:00
default :
nsNotice ( rb , client . t ( "No such setting" ) )
}
}
func nsSetHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
var account string
if command == "saset" {
account = params [ 0 ]
params = params [ 1 : ]
} else {
account = client . Account ( )
}
var munger settingsMunger
var finalSettings AccountSettings
var err error
switch strings . ToLower ( params [ 0 ] ) {
case "pass" :
nsNotice ( rb , client . t ( "To change a password, use the PASSWD command. For details, /msg NickServ HELP PASSWD" ) )
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
}
}
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 :
nsNotice ( rb , client . t ( "Successfully changed your account settings" ) )
displaySetting ( params [ 0 ] , finalSettings , client , rb )
case errInvalidParams , errAccountDoesNotExist , errFeatureDisabled , errAccountUnverified , errAccountUpdateFailed :
nsNotice ( rb , client . t ( err . Error ( ) ) )
2020-02-26 07:44:05 +01:00
case errNickAccountMismatch :
nsNotice ( 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
nsNotice ( rb , client . t ( "An error occurred" ) )
}
2018-03-14 17:51:53 +01:00
}
2019-01-04 04:32:07 +01:00
func nsDropHandler ( 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 {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Successfully ungrouped nick %s with your account" ) , nick ) )
} else if err == errAccountNotLoggedIn {
nsNotice ( rb , client . t ( "You're not logged into an account" ) )
} else if err == errAccountCantDropPrimaryNick {
nsNotice ( rb , client . t ( "You can't ungroup your primary nickname (try unregistering your account instead)" ) )
} else {
nsNotice ( rb , client . t ( "Could not ungroup nick" ) )
}
}
2019-01-04 04:32:07 +01:00
func nsGhostHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
nick := params [ 0 ]
2018-04-01 03:22:06 +02:00
ghost := server . clients . Get ( nick )
if ghost == nil {
nsNotice ( rb , client . t ( "No such nick" ) )
return
} else if ghost == client {
nsNotice ( 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 ( ) {
nsNotice ( rb , client . t ( "You can't GHOST an always-on client" ) )
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 {
nsNotice ( rb , client . t ( "You don't own that nick" ) )
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
}
2019-01-04 04:32:07 +01:00
func nsGroupHandler ( 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 {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Successfully grouped nick %s with your account" ) , nick ) )
} else if err == errAccountTooManyNicks {
nsNotice ( rb , client . t ( "You have too many nicks reserved already (you can remove some with /NS DROP)" ) )
} else if err == errNicknameReserved {
nsNotice ( rb , client . t ( "That nickname is already reserved by someone else" ) )
2018-02-12 07:09:30 +01:00
} else {
2018-04-01 03:22:06 +02:00
nsNotice ( rb , client . t ( "Error reserving nickname" ) )
2018-02-12 07:09:30 +01:00
}
}
2019-01-01 22:45:37 +01:00
func nsLoginThrottleCheck ( client * Client , rb * ResponseBuffer ) ( success bool ) {
2020-03-27 22:52:37 +01:00
client . stateMutex . Lock ( )
2019-01-01 22:45:37 +01:00
throttled , remainingTime := client . loginThrottle . Touch ( )
2020-03-27 22:52:37 +01:00
client . stateMutex . Unlock ( )
2019-01-01 22:45:37 +01:00
if throttled {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Please wait at least %v and try again" ) , remainingTime ) )
return false
}
return true
}
2019-01-04 04:32:07 +01:00
func nsIdentifyHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
2019-01-06 02:59:42 +01:00
if client . LoggedIntoAccount ( ) {
nsNotice ( rb , client . t ( "You're already logged into an account" ) )
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 != "" {
2019-01-01 22:45:37 +01:00
if ! nsLoginThrottleCheck ( client , rb ) {
return
}
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-02-26 07:44:05 +01:00
err = server . accounts . AuthenticateByCertFP ( client , rb . session . certfp , "" )
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-03-17 04:37:52 +01:00
if ! fixupNickEqualsAccount ( client , rb , server . Config ( ) ) {
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 {
2019-04-08 02:40:19 +02:00
sendSuccessfulAccountAuth ( client , rb , true , true )
2020-03-17 04:37:52 +01:00
} else if ! nickFixupFailed {
nsNotice ( 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-05-04 02:51:39 +02:00
func nsListHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
if ! client . HasRoleCapabs ( "accreg" ) {
nsNotice ( rb , client . t ( "Insufficient privileges" ) )
return
}
var searchRegex * regexp . Regexp
if len ( params ) > 0 {
var err error
searchRegex , err = regexp . Compile ( params [ 0 ] )
if err != nil {
nsNotice ( rb , client . t ( "Invalid regex" ) )
return
}
}
nsNotice ( rb , ircfmt . Unescape ( client . t ( "*** $bNickServ LIST$b ***" ) ) )
nicks := server . accounts . AllNicks ( )
for _ , nick := range nicks {
if searchRegex == nil || searchRegex . MatchString ( nick ) {
nsNotice ( rb , fmt . Sprintf ( " %s" , nick ) )
}
}
nsNotice ( rb , ircfmt . Unescape ( client . t ( "*** $bEnd of NickServ LIST$b ***" ) ) )
}
2019-01-04 04:32:07 +01:00
func nsInfoHandler ( 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" ) {
nsNotice ( rb , client . t ( "This command has been disabled by the server administrators" ) )
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 == "" {
nsNotice ( rb , client . t ( "That nickname is not registered" ) )
return
}
} else {
accountName = nick
}
} else {
accountName = client . Account ( )
2018-04-01 03:22:06 +02:00
if accountName == "" {
2019-01-04 04:32:07 +01:00
nsNotice ( 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 {
nsNotice ( rb , client . t ( "Account does not exist" ) )
2019-01-04 04:32:07 +01:00
return
2018-04-01 03:22:06 +02:00
}
nsNotice ( rb , fmt . Sprintf ( client . t ( "Account: %s" ) , account . Name ) )
2019-12-17 01:50:15 +01:00
registeredAt := account . RegisteredAt . Format ( time . RFC1123 )
2018-04-01 03:22:06 +02:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "Registered at: %s" ) , registeredAt ) )
// TODO nicer formatting for this
for _ , nick := range account . AdditionalNicks {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Additional grouped nick: %s" ) , nick ) )
}
2019-02-06 10:32:04 +01:00
for _ , channel := range server . accounts . ChannelsForAccount ( accountName ) {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Registered channel: %s" ) , channel ) )
}
2018-04-01 03:22:06 +02:00
}
2019-01-04 04:32:07 +01:00
func nsRegisterHandler ( 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 == "" {
2019-05-29 11:06:40 +02:00
nsNotice ( 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
}
2019-02-06 02:09:36 +01:00
if ! nsLoginThrottleCheck ( client , rb ) {
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 {
nsNotice ( rb , client . t ( "Erroneous nickname" ) )
return
}
account = matches [ 1 ]
}
2018-02-20 10:20:30 +01:00
var callbackNamespace , callbackValue string
noneCallbackAllowed := false
2020-03-16 12:54:50 +01:00
for _ , callback := range config . Accounts . Registration . EnabledCallbacks {
2018-02-20 10:20:30 +01:00
if callback == "*" {
noneCallbackAllowed = true
}
2018-02-03 12:38:28 +01:00
}
2018-02-20 10:20:30 +01:00
// XXX if ACC REGISTER allows registration with the `none` callback, then ignore
// any callback that was passed here (to avoid confusion in the case where the ircd
// has no mail server configured). otherwise, register using the provided callback:
if noneCallbackAllowed {
callbackNamespace = "*"
} else {
2020-03-16 12:54:50 +01:00
callbackNamespace , callbackValue = parseCallback ( email , config . Accounts )
2019-05-29 10:25:20 +02:00
if callbackNamespace == "" || callbackValue == "" {
2018-03-14 17:51:53 +01:00
nsNotice ( rb , client . t ( "Registration requires a valid e-mail address" ) )
2018-02-20 10:20:30 +01:00
return
2018-02-02 14:44:52 +01:00
}
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 == "*" {
err = server . accounts . Verify ( client , account , "" )
2020-03-17 04:37:52 +01:00
if err == nil && fixupNickEqualsAccount ( client , rb , config ) {
2018-02-20 10:20:30 +01:00
sendSuccessfulRegResponse ( client , rb , true )
}
} 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 )
2018-03-14 17:51:53 +01:00
nsNotice ( rb , message )
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
message , _ := registrationErrorToMessageAndCode ( err )
2019-04-08 03:36:48 +02:00
nsNotice ( rb , client . t ( message ) )
2018-02-03 12:38:28 +01:00
}
}
2018-02-02 14:44:52 +01:00
2019-02-05 06:19:03 +01:00
func nsSaregisterHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
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
if err == errAccountAlreadyRegistered || err == errAccountAlreadyVerified {
errMsg = client . t ( "Account already exists" )
} else if err == errAccountBadPassphrase {
errMsg = client . t ( "Passphrase contains forbidden characters or is otherwise invalid" )
} else {
server . logger . Error ( "services" , "unknown error from saregister" , err . Error ( ) )
errMsg = client . t ( "Could not register" )
}
nsNotice ( rb , errMsg )
} else {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Successfully registered account %s" ) , account ) )
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
}
}
2019-01-04 04:32:07 +01:00
func nsUnregisterHandler ( 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 == "" {
2018-06-19 10:03:40 +02:00
nsNotice ( 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 {
nsNotice ( rb , client . t ( "Invalid account name" ) )
return
} else if err != nil {
nsNotice ( rb , client . t ( "Internal error" ) )
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" ) ) {
2018-04-01 03:22:06 +02:00
nsNotice ( 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 {
nsNotice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b" ) ) )
} else {
nsNotice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: unregistering this account will remove its stored privileges.$b" ) ) )
}
2020-05-08 08:47:08 +02:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "To confirm, run this command: %s" ) , fmt . Sprintf ( "/NS UNREGISTER %s %s" , 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 {
nsNotice ( rb , client . t ( err . Error ( ) ) )
} else if err != nil {
nsNotice ( rb , client . t ( "Error while unregistering account" ) )
2018-03-02 23:04:24 +01:00
} else {
2020-03-20 17:34:46 +01:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "Successfully unregistered account %s" ) , accountName ) )
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
}
}
2019-01-04 04:32:07 +01:00
func nsVerifyHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
username , code := params [ 0 ] , params [ 1 ]
2018-04-01 03:22:06 +02:00
err := server . accounts . Verify ( client , username , code )
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 != "" {
nsNotice ( rb , client . t ( errorMessage ) )
return
2018-03-02 23:04:24 +01:00
}
2020-03-17 04:37:52 +01:00
if fixupNickEqualsAccount ( client , rb , server . Config ( ) ) {
2020-03-16 12:54:50 +01:00
sendSuccessfulRegResponse ( client , rb , true )
}
2018-03-02 23:04:24 +01:00
}
2018-11-29 00:21:41 +01:00
2019-01-04 04:32:07 +01:00
func nsPasswdHandler ( 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
2019-01-01 22:45:37 +01:00
hasPrivs := client . HasRoleCapabs ( "accreg" )
2019-01-04 04:32:07 +01:00
switch len ( params ) {
2018-11-29 00:21:41 +01:00
case 2 :
2019-01-01 22:45:37 +01:00
if ! hasPrivs {
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 = ""
}
2018-11-29 00:21:41 +01:00
}
case 3 :
target = client . Account ( )
if target == "" {
2019-12-29 17:59:49 +01:00
errorMessage = ` You're not logged into an account `
2019-01-04 04:32:07 +01:00
} else if params [ 1 ] != params [ 2 ] {
2019-12-29 17:59:49 +01:00
errorMessage = ` Passwords do not match `
2018-11-29 00:21:41 +01:00
} else {
2019-12-29 17:59:49 +01:00
if ! nsLoginThrottleCheck ( client , rb ) {
return
}
accountData , err := server . accounts . LoadAccount ( target )
2018-11-29 00:21:41 +01:00
if err != nil {
2019-12-29 17:59:49 +01:00
errorMessage = ` You're not logged into an account `
2018-11-29 00:21:41 +01:00
} else {
2019-12-29 17:59:49 +01:00
hash := accountData . Credentials . PassphraseHash
if hash != nil && passwd . CompareHashAndPassword ( hash , [ ] byte ( params [ 0 ] ) ) != nil {
errorMessage = ` Password incorrect `
} else {
newPassword = params [ 1 ]
if newPassword == "*" {
newPassword = ""
}
}
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 != "" {
nsNotice ( rb , client . t ( errorMessage ) )
return
}
2019-12-29 17:59:49 +01:00
err := server . accounts . setPassword ( target , newPassword , hasPrivs )
switch err {
case nil :
2018-11-29 00:21:41 +01:00
nsNotice ( rb , client . t ( "Password changed" ) )
2019-12-29 17:59:49 +01:00
case errEmptyCredentials :
nsNotice ( 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 :
nsNotice ( rb , client . t ( "Your account credentials are managed externally and cannot be changed here" ) )
2019-12-29 17:59:49 +01:00
case errCASFailed :
nsNotice ( rb , client . t ( "Try again later" ) )
default :
2018-12-31 17:33:42 +01:00
server . logger . Error ( "internal" , "could not upgrade user password:" , err . Error ( ) )
2018-11-29 00:21:41 +01:00
nsNotice ( rb , client . t ( "Password could not be changed due to server error" ) )
}
}
2019-01-02 16:08:44 +01:00
2019-01-04 04:32:07 +01:00
func nsEnforceHandler ( 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 {
2019-05-19 10:27:44 +02:00
nsGetHandler ( 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 ] )
nsSetHandler ( server , client , "set" , newParams , rb )
2019-01-02 16:08:44 +01:00
}
}
2019-05-08 10:11:54 +02:00
func nsSessionsHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
target := client
if 0 < len ( params ) {
target = server . clients . Get ( params [ 0 ] )
if target == nil {
nsNotice ( rb , client . t ( "No such nick" ) )
return
}
2019-12-18 01:15:26 +01:00
// same permissions check as RPL_WHOISACTUALLY for now:
if target != client && ! client . HasMode ( modes . Operator ) {
nsNotice ( rb , client . t ( "Command restricted" ) )
return
}
2019-05-08 10:11:54 +02:00
}
sessionData , currentIndex := target . AllSessionData ( rb . session )
2019-05-16 00:10:48 +02:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "Nickname %[1]s has %[2]d attached session(s)" ) , target . Nick ( ) , len ( sessionData ) ) )
2019-05-08 10:11:54 +02:00
for i , session := range sessionData {
if currentIndex == i {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Session %d (currently attached session):" ) , i + 1 ) )
} else {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Session %d:" ) , i + 1 ) )
}
nsNotice ( rb , fmt . Sprintf ( client . t ( "IP address: %s" ) , session . ip . String ( ) ) )
nsNotice ( rb , fmt . Sprintf ( client . t ( "Hostname: %s" ) , session . hostname ) )
2019-05-13 08:24:58 +02:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "Created at: %s" ) , session . ctime . Format ( time . RFC1123 ) ) )
nsNotice ( rb , fmt . Sprintf ( client . t ( "Last active: %s" ) , session . atime . Format ( time . RFC1123 ) ) )
2020-02-19 03:42:27 +01:00
if session . certfp != "" {
nsNotice ( rb , fmt . Sprintf ( client . t ( "Certfp: %s" ) , session . certfp ) )
}
2019-05-08 10:11:54 +02:00
}
}
2019-12-29 17:59:49 +01:00
func nsCertHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
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 ]
} else {
nsNotice ( rb , client . t ( "Invalid parameters" ) )
return
}
default :
nsNotice ( rb , client . t ( "Invalid parameters" ) )
return
}
hasPrivs := client . HasRoleCapabs ( "accreg" )
if target != "" && ! hasPrivs {
nsNotice ( rb , client . t ( "Insufficient privileges" ) )
return
} else if target == "" {
target = client . Account ( )
if target == "" {
nsNotice ( rb , client . t ( "You're not logged into an account" ) )
return
}
}
var err error
switch verb {
case "list" :
accountData , err := server . accounts . LoadAccount ( target )
if err == errAccountDoesNotExist {
nsNotice ( rb , client . t ( "Account does not exist" ) )
return
} else if err != nil {
nsNotice ( rb , client . t ( "An error occurred" ) )
return
}
certfps := accountData . Credentials . Certfps
2019-12-30 18:44:07 +01:00
nsNotice ( 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 {
nsNotice ( rb , fmt . Sprintf ( "%d: %s" , i + 1 , certfp ) )
}
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" {
nsNotice ( rb , client . t ( "Certificate fingerprint successfully added" ) )
} else {
nsNotice ( rb , client . t ( "Certificate fingerprint successfully removed" ) )
}
case errNoop :
if verb == "add" {
nsNotice ( rb , client . t ( "That certificate fingerprint was already authorized" ) )
} else {
nsNotice ( rb , client . t ( "Certificate fingerprint not found" ) )
}
case errAccountDoesNotExist :
nsNotice ( rb , client . t ( "Account does not exist" ) )
case errLimitExceeded :
nsNotice ( rb , client . t ( "You already have too many certificate fingerprints" ) )
case utils . ErrInvalidCertfp :
nsNotice ( rb , client . t ( "Invalid certificate fingerprint" ) )
case errCertfpAlreadyExists :
nsNotice ( rb , client . t ( "That certificate fingerprint is already associated with another account" ) )
case errEmptyCredentials :
nsNotice ( 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 :
nsNotice ( rb , client . t ( "Your account credentials are managed externally and cannot be changed here" ) )
2019-12-29 17:59:49 +01:00
case errCASFailed :
nsNotice ( rb , client . t ( "Try again later" ) )
default :
server . logger . Error ( "internal" , "could not modify certificates:" , err . Error ( ) )
nsNotice ( rb , client . t ( "An error occurred" ) )
}
}