mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-26 22:09:24 +01:00
838 lines
27 KiB
Go
838 lines
27 KiB
Go
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
|
|
// released under the MIT license
|
|
|
|
package irc
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/goshuirc/irc-go/ircfmt"
|
|
|
|
"github.com/oragono/oragono/irc/modes"
|
|
"github.com/oragono/oragono/irc/utils"
|
|
)
|
|
|
|
// "enabled" callbacks for specific nickserv commands
|
|
func servCmdRequiresAccreg(config *Config) bool {
|
|
return config.Accounts.Registration.Enabled
|
|
}
|
|
|
|
func servCmdRequiresAuthEnabled(config *Config) bool {
|
|
return config.Accounts.AuthenticationEnabled
|
|
}
|
|
|
|
func servCmdRequiresNickRes(config *Config) bool {
|
|
return config.Accounts.AuthenticationEnabled && config.Accounts.NickReservation.Enabled
|
|
}
|
|
|
|
func servCmdRequiresBouncerEnabled(config *Config) bool {
|
|
return config.Accounts.Bouncer.Enabled
|
|
}
|
|
|
|
const (
|
|
nsPrefix = "NickServ!NickServ@localhost"
|
|
// 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)
|
|
nsTimeoutNotice = `This nickname is reserved. Please login within %v (using $b/msg NickServ IDENTIFY <password>$b or SASL), or switch to a different nickname.`
|
|
)
|
|
|
|
const nickservHelp = `NickServ lets you register and login to an account.
|
|
|
|
To see in-depth help for a specific NickServ command, try:
|
|
$b/NS HELP <command>$b
|
|
|
|
Here are the commands you can use:
|
|
%s`
|
|
|
|
var (
|
|
nickservCommands = map[string]*serviceCommand{
|
|
"drop": {
|
|
handler: nsDropHandler,
|
|
help: `Syntax: $bDROP [nickname]$b
|
|
|
|
DROP de-links the given (or your current) nickname from your user account.`,
|
|
helpShort: `$bDROP$b de-links your current (or the given) nickname from your user account.`,
|
|
enabled: servCmdRequiresNickRes,
|
|
authRequired: true,
|
|
},
|
|
"enforce": {
|
|
hidden: true,
|
|
handler: nsEnforceHandler,
|
|
help: `Syntax: $bENFORCE [method]$b
|
|
|
|
ENFORCE is an alias for $bGET enforce$b and $bSET enforce$b. See the help
|
|
entry for $bSET$b for more information.`,
|
|
authRequired: true,
|
|
enabled: servCmdRequiresAccreg,
|
|
},
|
|
"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.`,
|
|
helpShort: `$bGHOST$b reclaims your nickname.`,
|
|
authRequired: true,
|
|
minParams: 1,
|
|
},
|
|
"group": {
|
|
handler: nsGroupHandler,
|
|
help: `Syntax: $bGROUP$b
|
|
|
|
GROUP links your current nickname with your logged-in account, so other people
|
|
will not be able to use it.`,
|
|
helpShort: `$bGROUP$b links your current nickname to your user account.`,
|
|
enabled: servCmdRequiresNickRes,
|
|
authRequired: true,
|
|
},
|
|
"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.`,
|
|
minParams: 1,
|
|
},
|
|
"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,
|
|
// TODO: "email" is an oversimplification here; it's actually any callback, e.g.,
|
|
// person@example.com, mailto:person@example.com, tel:16505551234.
|
|
help: `Syntax: $bREGISTER <username> <email> [password]$b
|
|
|
|
REGISTER lets you register a user account. If the server allows anonymous
|
|
registration, you can send an asterisk (*) as the email address.
|
|
|
|
If the password is left out, your account will be registered to your TLS client
|
|
certificate (and you will need to use that certificate to login in future).`,
|
|
helpShort: `$bREGISTER$b lets you register a user account.`,
|
|
enabled: servCmdRequiresAccreg,
|
|
minParams: 2,
|
|
},
|
|
"sadrop": {
|
|
handler: nsDropHandler,
|
|
help: `Syntax: $bSADROP <nickname>$b
|
|
|
|
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.`,
|
|
capabs: []string{"accreg"},
|
|
enabled: servCmdRequiresNickRes,
|
|
minParams: 1,
|
|
},
|
|
"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.`,
|
|
enabled: servCmdRequiresAuthEnabled,
|
|
capabs: []string{"accreg"},
|
|
minParams: 2,
|
|
},
|
|
"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,
|
|
},
|
|
"unregister": {
|
|
handler: nsUnregisterHandler,
|
|
help: `Syntax: $bUNREGISTER <username> [code]$b
|
|
|
|
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.`,
|
|
helpShort: `$bUNREGISTER$b lets you delete your user account.`,
|
|
enabled: servCmdRequiresAuthEnabled,
|
|
minParams: 1,
|
|
},
|
|
"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.`,
|
|
enabled: servCmdRequiresAccreg,
|
|
minParams: 2,
|
|
},
|
|
"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
|
|
password by supplying their username and then the desired password.`,
|
|
helpShort: `$bPASSWD$b lets you change your password.`,
|
|
enabled: servCmdRequiresAuthEnabled,
|
|
minParams: 2,
|
|
},
|
|
"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": {
|
|
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
|
|
|
|
Set modifies your account settings. The following settings are available:`,
|
|
|
|
`$bENFORCE$b
|
|
'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]
|
|
4. 'default' [use the server default]`,
|
|
|
|
`$bBOUNCER$b
|
|
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),
|
|
'off' (disallow it), and 'default' (use the server default value).`,
|
|
|
|
`$bAUTOREPLAY-LINES$b
|
|
'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
|
|
default.`,
|
|
|
|
`$bAUTOREPLAY-JOINS$b
|
|
'autoreplay-joins' controls whether autoreplayed channel history will include
|
|
lines for join and part. This provides more information about the context of
|
|
messages, but may be spammy. Your options are 'on' and 'off'.`,
|
|
},
|
|
authRequired: true,
|
|
enabled: servCmdRequiresAccreg,
|
|
minParams: 2,
|
|
},
|
|
"saset": {
|
|
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.`,
|
|
helpShort: `$bSASET$b modifies another user's account settings`,
|
|
enabled: servCmdRequiresAccreg,
|
|
minParams: 3,
|
|
capabs: []string{"accreg"},
|
|
},
|
|
}
|
|
)
|
|
|
|
// nsNotice sends the client a notice from NickServ
|
|
func nsNotice(rb *ResponseBuffer, text string) {
|
|
// XXX i can't figure out how to use OragonoServices[servicename].prefix here
|
|
// without creating a compile-time initialization loop
|
|
rb.Add(nil, nsPrefix, "NOTICE", rb.target.Nick(), text)
|
|
}
|
|
|
|
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))
|
|
}
|
|
case "autoreplay-joins":
|
|
if settings.AutoreplayJoins {
|
|
nsNotice(rb, client.t("You will see JOINs and PARTs in autoreplayed history lines"))
|
|
} else {
|
|
nsNotice(rb, client.t("You will not see JOINs and PARTs in autoreplayed history lines"))
|
|
}
|
|
case "bouncer":
|
|
if !config.Accounts.Bouncer.Enabled {
|
|
nsNotice(rb, fmt.Sprintf(client.t("This feature has been disabled by the server administrators")))
|
|
} else {
|
|
switch settings.AllowBouncer {
|
|
case BouncerAllowedServerDefault:
|
|
if config.Accounts.Bouncer.AllowedByDefault {
|
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently enabled for your account, but you can opt out")))
|
|
} else {
|
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently disabled for your account, but you can opt in")))
|
|
}
|
|
case BouncerDisallowedByUser:
|
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently disabled for your account")))
|
|
case BouncerAllowedByUser:
|
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently enabled for your account")))
|
|
}
|
|
}
|
|
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
|
|
enabled, err = utils.StringToBool(params[1])
|
|
if enabled {
|
|
newValue = BouncerAllowedByUser
|
|
} else {
|
|
newValue = BouncerDisallowedByUser
|
|
}
|
|
}
|
|
if err == nil {
|
|
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
|
out = in
|
|
out.AllowBouncer = newValue
|
|
return
|
|
}
|
|
}
|
|
case "autoreplay-joins":
|
|
var newValue bool
|
|
newValue, err = utils.StringToBool(params[1])
|
|
if err == nil {
|
|
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
|
out = in
|
|
out.AutoreplayJoins = newValue
|
|
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"))
|
|
}
|
|
}
|
|
|
|
func nsDropHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
sadrop := command == "sadrop"
|
|
var nick string
|
|
if len(params) > 0 {
|
|
nick = params[0]
|
|
} else {
|
|
nick = client.NickCasefolded()
|
|
}
|
|
|
|
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"))
|
|
}
|
|
}
|
|
|
|
func nsGhostHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
nick := params[0]
|
|
|
|
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)"))
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
ghost.Quit(fmt.Sprintf(ghost.t("GHOSTed by %s"), client.Nick()), nil)
|
|
ghost.destroy(false, nil)
|
|
}
|
|
|
|
func nsGroupHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
nick := client.Nick()
|
|
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"))
|
|
} else {
|
|
nsNotice(rb, client.t("Error reserving nickname"))
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func nsIdentifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
if client.LoggedIntoAccount() {
|
|
nsNotice(rb, client.t("You're already logged into an account"))
|
|
return
|
|
}
|
|
|
|
loginSuccessful := false
|
|
|
|
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]
|
|
passphrase = params[1]
|
|
}
|
|
|
|
// try passphrase
|
|
if passphrase != "" {
|
|
if !nsLoginThrottleCheck(client, rb) {
|
|
return
|
|
}
|
|
err := server.accounts.AuthenticateByPassphrase(client, username, passphrase)
|
|
loginSuccessful = (err == nil)
|
|
}
|
|
|
|
// try certfp
|
|
if !loginSuccessful && client.certfp != "" {
|
|
err := server.accounts.AuthenticateByCertFP(client)
|
|
loginSuccessful = (err == nil)
|
|
}
|
|
|
|
if loginSuccessful {
|
|
sendSuccessfulAccountAuth(client, rb, true, true)
|
|
} else {
|
|
nsNotice(rb, client.t("Could not login with your TLS certificate or supplied username/password"))
|
|
}
|
|
}
|
|
|
|
func nsInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
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()
|
|
if accountName == "" {
|
|
nsNotice(rb, client.t("You're not logged into an account"))
|
|
return
|
|
}
|
|
}
|
|
|
|
account, err := server.accounts.LoadAccount(accountName)
|
|
if err != nil || !account.Verified {
|
|
nsNotice(rb, client.t("Account does not exist"))
|
|
return
|
|
}
|
|
|
|
nsNotice(rb, fmt.Sprintf(client.t("Account: %s"), account.Name))
|
|
registeredAt := account.RegisteredAt.Format("Jan 02, 2006 15:04:05Z")
|
|
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))
|
|
}
|
|
for _, channel := range server.accounts.ChannelsForAccount(accountName) {
|
|
nsNotice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel))
|
|
}
|
|
}
|
|
|
|
func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
// get params
|
|
account, email := params[0], params[1]
|
|
var passphrase string
|
|
if len(params) > 2 {
|
|
passphrase = params[2]
|
|
}
|
|
|
|
certfp := client.certfp
|
|
if passphrase == "" && certfp == "" {
|
|
nsNotice(rb, client.t("You need to either supply a passphrase or be connected via TLS with a client cert"))
|
|
return
|
|
}
|
|
|
|
if client.LoggedIntoAccount() {
|
|
nsNotice(rb, client.t("You're already logged into an account"))
|
|
return
|
|
}
|
|
|
|
if !nsLoginThrottleCheck(client, rb) {
|
|
return
|
|
}
|
|
|
|
// band-aid to let users know if they mix up the order of registration params
|
|
if email == "*" {
|
|
nsNotice(rb, client.t("Registering your account with no email address"))
|
|
} else {
|
|
nsNotice(rb, fmt.Sprintf(client.t("Registering your account with email address %s"), email))
|
|
}
|
|
|
|
config := server.AccountConfig()
|
|
var callbackNamespace, callbackValue string
|
|
noneCallbackAllowed := false
|
|
for _, callback := range config.Registration.EnabledCallbacks {
|
|
if callback == "*" {
|
|
noneCallbackAllowed = true
|
|
}
|
|
}
|
|
// 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)
|
|
if callbackNamespace == "" {
|
|
nsNotice(rb, client.t("Registration requires a valid e-mail address"))
|
|
return
|
|
}
|
|
}
|
|
|
|
err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, client.certfp)
|
|
if err == nil {
|
|
if callbackNamespace == "*" {
|
|
err = server.accounts.Verify(client, account, "")
|
|
if err == nil {
|
|
sendSuccessfulRegResponse(client, rb, true)
|
|
}
|
|
} else {
|
|
messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s")
|
|
message := fmt.Sprintf(messageTemplate, fmt.Sprintf("%s:%s", callbackNamespace, callbackValue))
|
|
nsNotice(rb, message)
|
|
}
|
|
}
|
|
|
|
// details could not be stored and relevant numerics have been dispatched, abort
|
|
message, _ := registrationErrorToMessageAndCode(err)
|
|
if err != nil {
|
|
nsNotice(rb, client.t(message))
|
|
return
|
|
}
|
|
}
|
|
|
|
func nsSaregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
account, passphrase := params[0], params[1]
|
|
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))
|
|
}
|
|
}
|
|
|
|
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]
|
|
}
|
|
|
|
if username == "" {
|
|
nsNotice(rb, client.t("You must specify an account"))
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
cfname, _ := CasefoldName(username)
|
|
if !(cfname == client.Account() || client.HasRoleCapabs("accreg")) {
|
|
nsNotice(rb, client.t("Insufficient oper privs"))
|
|
return
|
|
}
|
|
|
|
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")))
|
|
nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), cfname, expectedCode))
|
|
return
|
|
}
|
|
|
|
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"))
|
|
} else {
|
|
nsNotice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), cfname))
|
|
server.logger.Info("accounts", "client", client.Nick(), "unregistered account", cfname)
|
|
}
|
|
}
|
|
|
|
func nsVerifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
username, code := params[0], params[1]
|
|
err := server.accounts.Verify(client, username, code)
|
|
|
|
var errorMessage string
|
|
if err == errAccountVerificationInvalidCode || err == errAccountAlreadyVerified {
|
|
errorMessage = err.Error()
|
|
} else if err != nil {
|
|
errorMessage = errAccountVerificationFailed.Error()
|
|
}
|
|
|
|
if errorMessage != "" {
|
|
nsNotice(rb, client.t(errorMessage))
|
|
return
|
|
}
|
|
|
|
sendSuccessfulRegResponse(client, rb, true)
|
|
}
|
|
|
|
func nsPasswdHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
var target string
|
|
var newPassword string
|
|
var errorMessage string
|
|
|
|
hasPrivs := client.HasRoleCapabs("accreg")
|
|
if !hasPrivs && !nsLoginThrottleCheck(client, rb) {
|
|
return
|
|
}
|
|
|
|
switch len(params) {
|
|
case 2:
|
|
if !hasPrivs {
|
|
errorMessage = "Insufficient privileges"
|
|
} else {
|
|
target, newPassword = params[0], params[1]
|
|
}
|
|
case 3:
|
|
target = client.Account()
|
|
if target == "" {
|
|
errorMessage = "You're not logged into an account"
|
|
} else if params[1] != params[2] {
|
|
errorMessage = "Passwords do not match"
|
|
} else {
|
|
// check that they correctly supplied the preexisting password
|
|
_, err := server.accounts.checkPassphrase(target, params[0])
|
|
if err != nil {
|
|
errorMessage = "Password incorrect"
|
|
} else {
|
|
newPassword = params[1]
|
|
}
|
|
}
|
|
default:
|
|
errorMessage = `Invalid parameters`
|
|
}
|
|
|
|
if errorMessage != "" {
|
|
nsNotice(rb, client.t(errorMessage))
|
|
return
|
|
}
|
|
|
|
err := server.accounts.setPassword(target, newPassword)
|
|
if err == nil {
|
|
nsNotice(rb, client.t("Password changed"))
|
|
} else {
|
|
server.logger.Error("internal", "could not upgrade user password:", err.Error())
|
|
nsNotice(rb, client.t("Password could not be changed due to server error"))
|
|
}
|
|
}
|
|
|
|
func nsEnforceHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
newParams := []string{"enforce"}
|
|
if len(params) == 0 {
|
|
nsGetHandler(server, client, "get", newParams, rb)
|
|
} else {
|
|
newParams = append(newParams, params[0])
|
|
nsSetHandler(server, client, "set", newParams, rb)
|
|
}
|
|
}
|
|
|
|
func nsSessionsHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
target := client
|
|
|
|
if 0 < len(params) {
|
|
// same permissions check as RPL_WHOISACTUALLY for now:
|
|
if !client.HasMode(modes.Operator) {
|
|
nsNotice(rb, client.t("Command restricted"))
|
|
return
|
|
}
|
|
target = server.clients.Get(params[0])
|
|
if target == nil {
|
|
nsNotice(rb, client.t("No such nick"))
|
|
return
|
|
}
|
|
}
|
|
|
|
sessionData, currentIndex := target.AllSessionData(rb.session)
|
|
nsNotice(rb, fmt.Sprintf(client.t("Nickname %[1]s has %[2]d attached session(s)"), target.Nick(), len(sessionData)))
|
|
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))
|
|
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)))
|
|
}
|
|
}
|