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"
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 {
return config . Accounts . Bouncer . Enabled
}
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 ,
2019-05-19 10:27:44 +02:00
enabled : servCmdRequiresAccreg ,
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. ` ,
2019-12-29 17:59:49 +01:00
enabled : servCmdRequiresAuthEnabled ,
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
} ,
"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 ,
help : ` Syntax : $ bSAREGISTER < username > < password > $ b
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" } ,
minParams : 2 ,
} ,
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
the server ' s bouncer functionality , to your nickname . An administrator
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
} ,
"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 ,
enabled : servCmdRequiresAccreg ,
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 ` ,
enabled : servCmdRequiresAccreg ,
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
2019-05-20 08:56:49 +02: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
2019-05-20 08:56:49 +02:00
` $ bBOUNCER $ b
2019-05-19 10:27:44 +02:00
If ' bouncer ' is enabled and you are already logged in and using a nick , a
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 ) . ` ,
2019-05-20 08:56:49 +02:00
} ,
2019-05-19 10:27:44 +02:00
authRequired : true ,
enabled : servCmdRequiresAccreg ,
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 ` ,
enabled : servCmdRequiresAccreg ,
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
}
case "bouncer" :
if ! config . Accounts . Bouncer . 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 {
case BouncerAllowedServerDefault :
if config . Accounts . Bouncer . AllowedByDefault {
2019-12-17 01:50:15 +01:00
nsNotice ( rb , client . t ( "Bouncer functionality is currently enabled for your account, but you can opt out" ) )
2019-05-19 10:27:44 +02:00
} else {
2019-12-17 01:50:15 +01:00
nsNotice ( rb , client . t ( "Bouncer functionality is currently disabled for your account, but you can opt in" ) )
2019-05-19 10:27:44 +02:00
}
case BouncerDisallowedByUser :
2019-12-17 01:50:15 +01:00
nsNotice ( rb , client . t ( "Bouncer functionality is currently disabled for your account" ) )
2019-05-19 10:27:44 +02:00
case BouncerAllowedByUser :
2019-12-17 01:50:15 +01:00
nsNotice ( rb , client . t ( "Bouncer functionality is currently enabled for your account" ) )
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
}
case "bouncer" :
var newValue BouncerAllowedSetting
if strings . ToLower ( params [ 1 ] ) == "default" {
newValue = BouncerAllowedServerDefault
} 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 {
newValue = BouncerAllowedByUser
} else {
newValue = BouncerDisallowedByUser
}
}
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
}
}
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 ( ) ) )
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
}
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 ) {
throttled , remainingTime := client . loginThrottle . Touch ( )
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
}
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 {
if client . certfp != "" {
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
}
2018-04-01 03:22:06 +02:00
err := server . accounts . AuthenticateByPassphrase ( client , username , passphrase )
loginSuccessful = ( err == nil )
}
// try certfp
if ! loginSuccessful && client . certfp != "" {
2019-12-25 21:06:26 +01:00
err := server . accounts . AuthenticateByCertFP ( client , "" )
2018-04-01 03:22:06 +02:00
loginSuccessful = ( err == nil )
}
if loginSuccessful {
2019-04-08 02:40:19 +02:00
sendSuccessfulAccountAuth ( client , rb , true , true )
2018-04-01 03:22:06 +02:00
} else {
nsNotice ( rb , client . t ( "Could not login with your TLS certificate or supplied username/password" ) )
}
2018-02-20 10:20:30 +01:00
}
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 ]
if server . AccountConfig ( ) . NickReservation . Enabled {
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 ( )
account := details . nick
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
2018-02-20 10:20:30 +01:00
certfp := client . 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-05-29 10:25:20 +02:00
if details . account != "" {
2018-08-15 04:50:20 +02:00
nsNotice ( rb , client . t ( "You're already logged into an account" ) )
return
2018-02-03 12:38:28 +01:00
}
2018-02-02 14:44:52 +01:00
2019-02-06 02:09:36 +01:00
if ! nsLoginThrottleCheck ( client , rb ) {
return
}
2018-02-20 10:20:30 +01:00
config := server . AccountConfig ( )
var callbackNamespace , callbackValue string
noneCallbackAllowed := false
2018-03-02 23:04:24 +01:00
for _ , callback := range config . 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 {
callbackNamespace , callbackValue = parseCallback ( email , config )
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
2018-02-20 10:20:30 +01:00
err := server . accounts . Register ( client , account , callbackNamespace , callbackValue , passphrase , client . 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 , "" )
if err == nil {
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" )
message := fmt . Sprintf ( messageTemplate , fmt . Sprintf ( "%s:%s" , callbackNamespace , callbackValue ) )
2018-03-14 17:51:53 +01:00
nsNotice ( rb , message )
2018-02-20 10:20:30 +01:00
}
2018-02-11 11:30:40 +01:00
}
2018-02-02 14:44:52 +01:00
2018-02-03 12:38:28 +01:00
// details could not be stored and relevant numerics have been dispatched, abort
2019-04-08 03:36:48 +02:00
message , _ := registrationErrorToMessageAndCode ( err )
2018-02-03 12:38:28 +01:00
if err != nil {
2019-04-08 03:36:48 +02:00
nsNotice ( rb , client . t ( message ) )
2018-02-03 12:38:28 +01:00
return
}
}
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 ) {
account , passphrase := params [ 0 ] , params [ 1 ]
2019-12-29 17:59:49 +01:00
if passphrase == "*" {
passphrase = ""
}
2019-02-05 06:19:03 +01:00
err := server . accounts . Register ( nil , account , "admin" , "" , passphrase , "" )
if err == nil {
err = server . accounts . Verify ( nil , account , "" )
}
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 ) {
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
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" ) )
2018-03-02 23:04:24 +01:00
return
}
2018-06-19 10:03:40 +02:00
cfname , _ := CasefoldName ( username )
if ! ( cfname == client . Account ( ) || 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
}
2018-06-19 10:03:40 +02:00
expectedCode := unregisterConfirmationCode ( account . Name , account . RegisteredAt )
if expectedCode != verificationCode {
nsNotice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: unregistering this account will remove its stored privileges.$b" ) ) )
2019-02-22 03:37:11 +01:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s" ) , cfname , expectedCode ) )
2018-06-19 10:03:40 +02:00
return
}
2018-04-01 03:22:06 +02:00
err = server . accounts . Unregister ( cfname )
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 {
2018-04-01 03:22:06 +02:00
nsNotice ( rb , fmt . Sprintf ( client . t ( "Successfully unregistered account %s" ) , cfname ) )
2019-02-13 08:42:35 +01:00
server . logger . Info ( "accounts" , "client" , client . Nick ( ) , "unregistered account" , cfname )
2018-03-02 23:04:24 +01:00
}
2019-12-19 15:27:54 +01:00
client . server . snomasks . Send ( sno . LocalAccounts , fmt . Sprintf ( ircfmt . Unescape ( "Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]" ) , client . NickMaskString ( ) , account . Name ) )
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
if err == errAccountVerificationInvalidCode || err == errAccountAlreadyVerified {
errorMessage = err . Error ( )
} else if err != nil {
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
}
2018-04-01 03:22:06 +02: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" ) )
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 ) ) )
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" ) )
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" ) )
}
}