3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-23 04:19:25 +01:00

Merge pull request #1308 from slingamn/register_command.2

fix #1075
This commit is contained in:
Shivaram Lingamneni 2020-10-07 06:17:07 -07:00 committed by GitHub
commit 6bbedadfd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 285 additions and 118 deletions

View File

@ -313,6 +313,9 @@ accounts:
# the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER` # the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER`
enabled: true enabled: true
# can users use the REGISTER command to register before fully connecting?
allow-before-connect: true
# global throttle on new account creation # global throttle on new account creation
throttling: throttling:
enabled: true enabled: true
@ -327,28 +330,26 @@ accounts:
# length of time a user has to verify their account before it can be re-registered # length of time a user has to verify their account before it can be re-registered
verify-timeout: "32h" verify-timeout: "32h"
# callbacks to allow # options for email verification of account registrations
enabled-callbacks: email-verification:
- none # no verification needed, will instantly register successfully enabled: false
sender: "admin@my.network"
# example configuration for sending verification emails require-tls: true
# callbacks: helo-domain: "my.network" # defaults to server name if unset
# mailto: # options to enable DKIM signing of outgoing emails (recommended, but
# sender: "admin@my.network" # requires creating a DNS entry for the public key):
# require-tls: true # dkim:
# helo-domain: "my.network" # defaults to server name if unset # domain: "my.network"
# dkim: # selector: "20200229"
# domain: "my.network" # key-file: "dkim.pem"
# selector: "20200229" # to use an MTA/smarthost instead of sending email directly:
# key-file: "dkim.pem" # mta:
# # to use an MTA/smarthost instead of sending email directly: # server: localhost
# # mta: # port: 25
# # server: localhost # username: "admin"
# # port: 25 # password: "hunter2"
# # username: "admin" blacklist-regexes:
# # password: "hunter2" # - ".*@mailinator.com"
# blacklist-regexes:
# # - ".*@mailinator.com"
# throttle account login attempts (to prevent either password guessing, or DoS # throttle account login attempts (to prevent either password guessing, or DoS
# attacks on the server aimed at forcing repeated expensive bcrypt computations) # attacks on the server aimed at forcing repeated expensive bcrypt computations)

View File

@ -341,6 +341,9 @@ accounts:
# the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER` # the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER`
enabled: true enabled: true
# can users use the REGISTER command to register before fully connecting?
allow-before-connect: true
# global throttle on new account creation # global throttle on new account creation
throttling: throttling:
enabled: true enabled: true
@ -355,28 +358,26 @@ accounts:
# length of time a user has to verify their account before it can be re-registered # length of time a user has to verify their account before it can be re-registered
verify-timeout: "32h" verify-timeout: "32h"
# callbacks to allow # options for email verification of account registrations
enabled-callbacks: email-verification:
- none # no verification needed, will instantly register successfully enabled: false
sender: "admin@my.network"
# example configuration for sending verification emails require-tls: true
# callbacks: helo-domain: "my.network" # defaults to server name if unset
# mailto: # options to enable DKIM signing of outgoing emails (recommended, but
# sender: "admin@my.network" # requires creating a DNS entry for the public key):
# require-tls: true # dkim:
# helo-domain: "my.network" # defaults to server name if unset # domain: "my.network"
# dkim: # selector: "20200229"
# domain: "my.network" # key-file: "dkim.pem"
# selector: "20200229" # to use an MTA/smarthost instead of sending email directly:
# key-file: "dkim.pem" # mta:
# # to use an MTA/smarthost instead of sending email directly: # server: localhost
# # mta: # port: 25
# # server: localhost # username: "admin"
# # port: 25 # password: "hunter2"
# # username: "admin" blacklist-regexes:
# # password: "hunter2" # - ".*@mailinator.com"
# blacklist-regexes:
# # - ".*@mailinator.com"
# throttle account login attempts (to prevent either password guessing, or DoS # throttle account login attempts (to prevent either password guessing, or DoS
# attacks on the server aimed at forcing repeated expensive bcrypt computations) # attacks on the server aimed at forcing repeated expensive bcrypt computations)

View File

@ -177,6 +177,12 @@ CAPDEFS = [
url="https://github.com/ircv3/ircv3-specifications/pull/393", url="https://github.com/ircv3/ircv3-specifications/pull/393",
standard="proposed IRCv3", standard="proposed IRCv3",
), ),
CapDef(
identifier="Register",
name="draft/register",
url="https://gist.github.com/edk0/bf3b50fc219fd1bed1aa15d98bfb6495",
standard="proposed IRCv3",
),
] ]
def validate_defs(): def validate_defs():

View File

@ -403,17 +403,18 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
return errLimitExceeded return errLimitExceeded
} }
// if nick reservation is enabled, you can only register your current nickname // if nick reservation is enabled, don't let people reserve nicknames
// as an account; this prevents "land-grab" situations where someone else // that they would not be eligible to take, e.g.,
// registers your nick out from under you and then NS GHOSTs you // 1. a nickname that someone else is currently holding
// n.b. client is nil during a SAREGISTER // 2. a nickname confusable with an existing reserved nickname
// n.b. if ForceGuestFormat, then there's no concern, because you can't // this has a lot of weird edge cases because of force-guest-format
// register a guest nickname anyway, and the actual registration system // and the possibility of registering a nickname on an "unregistered connection"
// will prevent any double-register // (i.e., pre-handshake).
if client != nil && config.Accounts.NickReservation.Enabled && if client != nil && config.Accounts.NickReservation.Enabled {
!config.Accounts.NickReservation.ForceGuestFormat && _, nickAcquireError, _ := am.server.clients.SetNick(client, nil, account, true)
client.NickCasefolded() != casefoldedAccount { if !(nickAcquireError == nil || nickAcquireError == errNoop) {
return errAccountMustHoldNick return errAccountMustHoldNick
}
} }
// can't register a guest nickname // can't register a guest nickname
@ -763,7 +764,7 @@ func (am *AccountManager) dispatchCallback(client *Client, account string, callb
} }
func (am *AccountManager) dispatchMailtoCallback(client *Client, account string, callbackValue string) (code string, err error) { func (am *AccountManager) dispatchMailtoCallback(client *Client, account string, callbackValue string) (code string, err error) {
config := am.server.Config().Accounts.Registration.Callbacks.Mailto config := am.server.Config().Accounts.Registration.EmailVerification
code = utils.GenerateSecretToken() code = utils.GenerateSecretToken()
subject := config.VerifyMessageSubject subject := config.VerifyMessageSubject

View File

@ -7,7 +7,7 @@ package caps
const ( const (
// number of recognized capabilities: // number of recognized capabilities:
numCapabs = 27 numCapabs = 28
// length of the uint64 array that represents the bitset: // length of the uint64 array that represents the bitset:
bitsetLen = 1 bitsetLen = 1
) )
@ -57,6 +57,10 @@ const (
// https://github.com/ircv3/ircv3-specifications/pull/398 // https://github.com/ircv3/ircv3-specifications/pull/398
Multiline Capability = iota Multiline Capability = iota
// Register is the proposed IRCv3 capability named "draft/register":
// https://gist.github.com/edk0/bf3b50fc219fd1bed1aa15d98bfb6495
Register Capability = iota
// Relaymsg is the proposed IRCv3 capability named "draft/relaymsg": // Relaymsg is the proposed IRCv3 capability named "draft/relaymsg":
// https://github.com/ircv3/ircv3-specifications/pull/417 // https://github.com/ircv3/ircv3-specifications/pull/417
Relaymsg Capability = iota Relaymsg Capability = iota
@ -136,6 +140,7 @@ var (
"draft/event-playback", "draft/event-playback",
"draft/languages", "draft/languages",
"draft/multiline", "draft/multiline",
"draft/register",
"draft/relaymsg", "draft/relaymsg",
"draft/resume-0.5", "draft/resume-0.5",
"echo-message", "echo-message",

View File

@ -106,6 +106,7 @@ type Client struct {
requireSASLMessage string requireSASLMessage string
requireSASL bool requireSASL bool
registered bool registered bool
registerCmdSent bool // already sent the draft/register command, can't send it again
registrationTimer *time.Timer registrationTimer *time.Timer
resumeID string resumeID string
server *Server server *Server
@ -441,7 +442,7 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
client.resizeHistory(config) client.resizeHistory(config)
_, err, _ := server.clients.SetNick(client, nil, account.Name) _, err, _ := server.clients.SetNick(client, nil, account.Name, false)
if err != nil { if err != nil {
server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error()) server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
return return

View File

@ -104,7 +104,9 @@ func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err e
} }
// SetNick sets a client's nickname, validating it against nicknames in use // SetNick sets a client's nickname, validating it against nicknames in use
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error, returnedFromAway bool) { // XXX: dryRun validates a client's ability to claim a nick, without
// actually claiming it
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string, dryRun bool) (setNick string, err error, returnedFromAway bool) {
config := client.server.Config() config := client.server.Config()
var newCfNick, newSkeleton string var newCfNick, newSkeleton string
@ -140,7 +142,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
return "", errNickMissing, false return "", errNickMissing, false
} }
if account == "" && config.Accounts.NickReservation.ForceGuestFormat { if account == "" && config.Accounts.NickReservation.ForceGuestFormat && !dryRun {
newCfNick, err = CasefoldName(newNick) newCfNick, err = CasefoldName(newNick)
if err != nil { if err != nil {
return "", errNicknameInvalid, false return "", errNicknameInvalid, false
@ -197,9 +199,10 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
currentClient := clients.byNick[newCfNick] currentClient := clients.byNick[newCfNick]
// the client may just be changing case // the client may just be changing case
if currentClient != nil && currentClient != client && session != nil { if currentClient != nil && currentClient != client {
// these conditions forbid reattaching to an existing session: // these conditions forbid reattaching to an existing session:
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() { if registered || !bouncerAllowed || account == "" || account != currentClient.Account() ||
dryRun || session == nil {
return "", errNicknameInUse, false return "", errNicknameInUse, false
} }
// check TLS modes // check TLS modes
@ -236,6 +239,10 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
return "", errNicknameInUse, false return "", errNicknameInUse, false
} }
if dryRun {
return "", nil, false
}
formercfnick, formerskeleton := client.uniqueIdentifiers() formercfnick, formerskeleton := client.uniqueIdentifiers()
if changeSuccess := client.SetNick(newNick, newCfNick, newSkeleton); !changeSuccess { if changeSuccess := client.SetNick(newNick, newCfNick, newSkeleton); !changeSuccess {
return "", errClientDestroyed, false return "", errClientDestroyed, false

View File

@ -256,6 +256,11 @@ func init() {
handler: relaymsgHandler, handler: relaymsgHandler,
minParams: 3, minParams: 3,
}, },
"REGISTER": {
handler: registerHandler,
minParams: 2,
usablePreReg: true,
},
"RENAME": { "RENAME": {
handler: renameHandler, handler: renameHandler,
minParams: 2, minParams: 2,
@ -336,6 +341,11 @@ func init() {
"USERS": { "USERS": {
handler: usersHandler, handler: usersHandler,
}, },
"VERIFY": {
handler: verifyHandler,
usablePreReg: true,
minParams: 2,
},
"VERSION": { "VERSION": {
handler: versionHandler, handler: versionHandler,
minParams: 0, minParams: 0,

View File

@ -16,7 +16,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -298,15 +297,18 @@ type AuthScriptConfig struct {
// AccountRegistrationConfig controls account registration. // AccountRegistrationConfig controls account registration.
type AccountRegistrationConfig struct { type AccountRegistrationConfig struct {
Enabled bool Enabled bool
Throttling ThrottleConfig AllowBeforeConnect bool `yaml:"allow-before-connect"`
EnabledCallbacks []string `yaml:"enabled-callbacks"` Throttling ThrottleConfig
EnabledCredentialTypes []string `yaml:"-"` // new-style (v2.4 email verification config):
VerifyTimeout custime.Duration `yaml:"verify-timeout"` EmailVerification email.MailtoConfig `yaml:"email-verification"`
Callbacks struct { // old-style email verification config, with "callbacks":
LegacyEnabledCallbacks []string `yaml:"enabled-callbacks"`
LegacyCallbacks struct {
Mailto email.MailtoConfig Mailto email.MailtoConfig
} } `yaml:"callbacks"`
BcryptCost uint `yaml:"bcrypt-cost"` VerifyTimeout custime.Duration `yaml:"verify-timeout"`
BcryptCost uint `yaml:"bcrypt-cost"`
} }
type VHostConfig struct { type VHostConfig struct {
@ -1049,24 +1051,29 @@ func LoadConfig(filename string) (config *Config, err error) {
} }
config.Logging = newLogConfigs config.Logging = newLogConfigs
// hardcode this for now if config.Accounts.Registration.EmailVerification.Enabled {
config.Accounts.Registration.EnabledCredentialTypes = []string{"passphrase", "certfp"} err := config.Accounts.Registration.EmailVerification.Postprocess(config.Server.Name)
mailtoEnabled := false
for i, name := range config.Accounts.Registration.EnabledCallbacks {
if name == "none" {
// we store "none" as "*" internally
config.Accounts.Registration.EnabledCallbacks[i] = "*"
} else if name == "mailto" {
mailtoEnabled = true
}
}
sort.Strings(config.Accounts.Registration.EnabledCallbacks)
if mailtoEnabled {
err := config.Accounts.Registration.Callbacks.Mailto.Postprocess(config.Server.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else {
// TODO: this processes the legacy "callback" config, clean this up in 2.5 or later
// TODO: also clean up the legacy "inline" MTA config format (from ee05a4324dfde)
mailtoEnabled := false
for _, name := range config.Accounts.Registration.LegacyEnabledCallbacks {
if name == "mailto" {
mailtoEnabled = true
break
}
}
if mailtoEnabled {
config.Accounts.Registration.EmailVerification = config.Accounts.Registration.LegacyCallbacks.Mailto
config.Accounts.Registration.EmailVerification.Enabled = true
err := config.Accounts.Registration.EmailVerification.Postprocess(config.Server.Name)
if err != nil {
return nil, err
}
}
} }
config.Accounts.defaultUserModes = ParseDefaultUserModes(config.Accounts.DefaultUserModes) config.Accounts.defaultUserModes = ParseDefaultUserModes(config.Accounts.DefaultUserModes)
@ -1104,6 +1111,24 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Server.supportedCaps.Disable(caps.SASL) config.Server.supportedCaps.Disable(caps.SASL)
} }
if !config.Accounts.Registration.Enabled {
config.Server.supportedCaps.Disable(caps.Register)
} else {
var registerValues []string
if config.Accounts.Registration.AllowBeforeConnect {
registerValues = append(registerValues, "before-connect")
}
if config.Accounts.Registration.EmailVerification.Enabled {
registerValues = append(registerValues, "email-required")
}
if config.Accounts.RequireSasl.Enabled {
registerValues = append(registerValues, "account-required")
}
if len(registerValues) != 0 {
config.Server.capValues[caps.Register] = strings.Join(registerValues, ",")
}
}
maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString) maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error()) return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())

View File

@ -31,6 +31,7 @@ type MailtoConfig struct {
// so server, port, etc. appear directly at top level // so server, port, etc. appear directly at top level
// XXX: see https://github.com/go-yaml/yaml/issues/63 // XXX: see https://github.com/go-yaml/yaml/issues/63
MTAConfig `yaml:",inline"` MTAConfig `yaml:",inline"`
Enabled bool
Sender string Sender string
HeloDomain string `yaml:"helo-domain"` HeloDomain string `yaml:"helo-domain"`
RequireTLS bool `yaml:"require-tls"` RequireTLS bool `yaml:"require-tls"`

View File

@ -71,6 +71,7 @@ var (
errWrongChannelKey = errors.New("Cannot join password-protected channel without the password") errWrongChannelKey = errors.New("Cannot join password-protected channel without the password")
errInviteOnly = errors.New("Cannot join invite-only channel without an invite") errInviteOnly = errors.New("Cannot join invite-only channel without an invite")
errRegisteredOnly = errors.New("Cannot join registered-only channel without an account") errRegisteredOnly = errors.New("Cannot join registered-only channel without an account")
errValidEmailRequired = errors.New("A valid email address is required for account registration")
) )
// String Errors // String Errors

View File

@ -33,28 +33,30 @@ import (
) )
// helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234 // helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234
func parseCallback(spec string, config AccountConfig) (callbackNamespace string, callbackValue string) { func parseCallback(spec string, config *Config) (callbackNamespace string, callbackValue string, err error) {
callback := strings.ToLower(spec) // XXX if we don't require verification, ignore any callback that was passed here
if callback == "*" { // (to avoid confusion in the case where the ircd has no mail server configured)
if !config.Accounts.Registration.EmailVerification.Enabled {
callbackNamespace = "*" callbackNamespace = "*"
} else if strings.Contains(callback, ":") { return
callbackValues := strings.SplitN(callback, ":", 2) }
callbackNamespace, callbackValue = callbackValues[0], callbackValues[1] callback := strings.ToLower(spec)
if colonIndex := strings.IndexByte(callback, ':'); colonIndex != -1 {
callbackNamespace, callbackValue = callback[:colonIndex], callback[colonIndex+1:]
} else { } else {
// "If a callback namespace is not ... provided, the IRC server MUST use mailto"" // "If a callback namespace is not ... provided, the IRC server MUST use mailto""
callbackNamespace = "mailto" callbackNamespace = "mailto"
callbackValue = callback callbackValue = callback
} }
// ensure the callback namespace is valid if config.Accounts.Registration.EmailVerification.Enabled {
// need to search callback list, maybe look at using a map later? if callbackNamespace != "mailto" {
for _, name := range config.Registration.EnabledCallbacks { err = errValidEmailRequired
if callbackNamespace == name { } else if strings.IndexByte(callbackValue, '@') < 1 {
return err = errValidEmailRequired
} }
} }
// error value
callbackNamespace = ""
return return
} }
@ -2398,6 +2400,116 @@ func quitHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
return true return true
} }
// REGISTER < email | * > <password>
func registerHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) (exiting bool) {
config := server.Config()
if !config.Accounts.Registration.Enabled {
rb.Add(nil, server.name, "FAIL", "REGISTER", "DISALLOWED", client.t("Account registration is disabled"))
return
}
if !client.registered && !config.Accounts.Registration.AllowBeforeConnect {
rb.Add(nil, server.name, "FAIL", "REGISTER", "DISALLOWED", client.t("You must complete the connection before registering your account"))
return
}
if client.registerCmdSent || client.Account() != "" {
rb.Add(nil, server.name, "FAIL", "REGISTER", "ALREADY_REGISTERED", client.t("You have already registered or attempted to register"))
return
}
accountName := client.Nick()
if accountName == "*" {
accountName = client.preregNick
}
if accountName == "" || accountName == "*" {
rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_USERNAME", client.t("Username invalid or not given"))
return
}
callbackNamespace, callbackValue, err := parseCallback(msg.Params[0], config)
if err != nil {
rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_EMAIL", client.t("A valid e-mail address is required"))
return
}
err = server.accounts.Register(client, accountName, callbackNamespace, callbackValue, msg.Params[1], rb.session.certfp)
switch err {
case nil:
if callbackNamespace == "*" {
err := server.accounts.Verify(client, accountName, "")
if err == nil {
if client.registered {
if !fixupNickEqualsAccount(client, rb, config) {
err = errNickAccountMismatch
}
}
if err == nil {
rb.Add(nil, server.name, "REGISTER", "SUCCESS", accountName, client.t("Account successfully registered"))
sendSuccessfulRegResponse(client, rb, true)
}
}
if err != nil {
server.logger.Error("internal", "accounts", "failed autoverification", accountName, err.Error())
rb.Add(nil, server.name, "FAIL", "REGISTER", "UNKNOWN_ERROR", client.t("An error occurred"))
}
} else {
rb.Add(nil, server.name, "REGISTER", "VERIFICATION_REQUIRED", accountName, fmt.Sprintf(client.t("Account created, pending verification; verification code has been sent to %s"), callbackValue))
client.registerCmdSent = true
}
case errAccountAlreadyRegistered, errAccountAlreadyUnregistered, errAccountMustHoldNick:
rb.Add(nil, server.name, "FAIL", "REGISTER", "USERNAME_EXISTS", client.t("Username is already registered or otherwise unavailable"))
case errAccountBadPassphrase:
rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_PASSWORD", client.t("Password was invalid"))
case errCallbackFailed:
// TODO detect this in NS REGISTER as well
rb.Add(nil, server.name, "FAIL", "REGISTER", "UNACCEPTABLE_EMAIL", client.t("Could not dispatch verification e-mail"))
default:
rb.Add(nil, server.name, "FAIL", "REGISTER", "UNKNOWN_ERROR", client.t("Could not register"))
}
return
}
// VERIFY <account> <code>
func verifyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) (exiting bool) {
config := server.Config()
if !config.Accounts.Registration.Enabled {
rb.Add(nil, server.name, "FAIL", "VERIFY", "DISALLOWED", client.t("Account registration is disabled"))
return
}
if !client.registered && !config.Accounts.Registration.AllowBeforeConnect {
rb.Add(nil, server.name, "FAIL", "VERIFY", "DISALLOWED", client.t("You must complete the connection before verifying your account"))
return
}
if client.Account() != "" {
rb.Add(nil, server.name, "FAIL", "VERIFY", "ALREADY_REGISTERED", client.t("You have already registered or attempted to register"))
return
}
accountName, verificationCode := msg.Params[0], msg.Params[1]
err := server.accounts.Verify(client, accountName, verificationCode)
if err == nil && client.registered {
if !fixupNickEqualsAccount(client, rb, config) {
err = errNickAccountMismatch
}
}
switch err {
case nil:
rb.Add(nil, server.name, "VERIFY", "SUCCESS", accountName, client.t("Account successfully registered"))
sendSuccessfulRegResponse(client, rb, true)
case errAccountVerificationInvalidCode:
rb.Add(nil, server.name, "FAIL", "VERIFY", "INVALID_CODE", client.t("Invalid verification code"))
default:
rb.Add(nil, server.name, "FAIL", "VERIFY", "UNKNOWN_ERROR", client.t("Failed to verify account"))
}
if err != nil && !client.registered {
// XXX pre-registration clients are exempt from fakelag;
// slow the client down to stop them spamming verify attempts
time.Sleep(time.Second)
}
return
}
// REHASH // REHASH
func rehashHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func rehashHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
nick := client.Nick() nick := client.Nick()

View File

@ -485,6 +485,11 @@ specs for more info: http://ircv3.net/specs/core/message-tags-3.3.html`,
text: `QUIT [reason] text: `QUIT [reason]
Indicates that you're leaving the server, and shows everyone the given reason.`, Indicates that you're leaving the server, and shows everyone the given reason.`,
},
"register": {
text: `REGISTER <email | *> <password>
Registers an account in accordance with the draft/register capability.`,
}, },
"rehash": { "rehash": {
oper: true, oper: true,
@ -544,6 +549,11 @@ The USERS command is not implemented.`,
text: `USERHOST <nickname>{ <nickname>} text: `USERHOST <nickname>{ <nickname>}
Shows information about the given users. Takes up to 10 nicknames.`, Shows information about the given users. Takes up to 10 nicknames.`,
},
"verify": {
text: `VERIFY <account> <password>
Verifies an account in accordance with the draft/register capability.`,
}, },
"version": { "version": {
text: `VERSION [server] text: `VERSION [server]

View File

@ -33,7 +33,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
origNickMask := details.nickMask origNickMask := details.nickMask
isSanick := client != target isSanick := client != target
assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname) assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname, false)
if err == errNicknameInUse { if err == errNicknameInUse {
if !isSanick { if !isSanick {
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, details.nick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use")) rb.Add(nil, server.name, ERR_NICKNAMEINUSE, details.nick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))

View File

@ -853,24 +853,10 @@ func nsRegisterHandler(server *Server, client *Client, command string, params []
account = matches[1] account = matches[1]
} }
var callbackNamespace, callbackValue string callbackNamespace, callbackValue, validationErr := parseCallback(email, config)
noneCallbackAllowed := false if validationErr != nil {
for _, callback := range config.Accounts.Registration.EnabledCallbacks { nsNotice(rb, client.t("Registration requires a valid e-mail address"))
if callback == "*" { return
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.Accounts)
if callbackNamespace == "" || callbackValue == "" {
nsNotice(rb, client.t("Registration requires a valid e-mail address"))
return
}
} }
err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, rb.session.certfp) err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, rb.session.certfp)