mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-23 04:19:25 +01:00
commit
6bbedadfd5
@ -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
|
|
||||||
# helo-domain: "my.network" # defaults to server name if unset
|
|
||||||
# dkim:
|
# dkim:
|
||||||
# domain: "my.network"
|
# domain: "my.network"
|
||||||
# selector: "20200229"
|
# selector: "20200229"
|
||||||
# key-file: "dkim.pem"
|
# key-file: "dkim.pem"
|
||||||
# # to use an MTA/smarthost instead of sending email directly:
|
# to use an MTA/smarthost instead of sending email directly:
|
||||||
# # mta:
|
# mta:
|
||||||
# # server: localhost
|
# server: localhost
|
||||||
# # port: 25
|
# port: 25
|
||||||
# # username: "admin"
|
# username: "admin"
|
||||||
# # password: "hunter2"
|
# password: "hunter2"
|
||||||
# blacklist-regexes:
|
blacklist-regexes:
|
||||||
# # - ".*@mailinator.com"
|
# - ".*@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)
|
||||||
|
37
default.yaml
37
default.yaml
@ -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
|
|
||||||
# helo-domain: "my.network" # defaults to server name if unset
|
|
||||||
# dkim:
|
# dkim:
|
||||||
# domain: "my.network"
|
# domain: "my.network"
|
||||||
# selector: "20200229"
|
# selector: "20200229"
|
||||||
# key-file: "dkim.pem"
|
# key-file: "dkim.pem"
|
||||||
# # to use an MTA/smarthost instead of sending email directly:
|
# to use an MTA/smarthost instead of sending email directly:
|
||||||
# # mta:
|
# mta:
|
||||||
# # server: localhost
|
# server: localhost
|
||||||
# # port: 25
|
# port: 25
|
||||||
# # username: "admin"
|
# username: "admin"
|
||||||
# # password: "hunter2"
|
# password: "hunter2"
|
||||||
# blacklist-regexes:
|
blacklist-regexes:
|
||||||
# # - ".*@mailinator.com"
|
# - ".*@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)
|
||||||
|
@ -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():
|
||||||
|
@ -403,18 +403,19 @@ 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
|
||||||
if config.Accounts.NickReservation.guestRegexpFolded.MatchString(casefoldedAccount) {
|
if config.Accounts.NickReservation.guestRegexpFolded.MatchString(casefoldedAccount) {
|
||||||
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -299,13 +298,16 @@ type AuthScriptConfig struct {
|
|||||||
// AccountRegistrationConfig controls account registration.
|
// AccountRegistrationConfig controls account registration.
|
||||||
type AccountRegistrationConfig struct {
|
type AccountRegistrationConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
AllowBeforeConnect bool `yaml:"allow-before-connect"`
|
||||||
Throttling ThrottleConfig
|
Throttling ThrottleConfig
|
||||||
EnabledCallbacks []string `yaml:"enabled-callbacks"`
|
// new-style (v2.4 email verification config):
|
||||||
EnabledCredentialTypes []string `yaml:"-"`
|
EmailVerification email.MailtoConfig `yaml:"email-verification"`
|
||||||
VerifyTimeout custime.Duration `yaml:"verify-timeout"`
|
// old-style email verification config, with "callbacks":
|
||||||
Callbacks struct {
|
LegacyEnabledCallbacks []string `yaml:"enabled-callbacks"`
|
||||||
|
LegacyCallbacks struct {
|
||||||
Mailto email.MailtoConfig
|
Mailto email.MailtoConfig
|
||||||
}
|
} `yaml:"callbacks"`
|
||||||
|
VerifyTimeout custime.Duration `yaml:"verify-timeout"`
|
||||||
BcryptCost uint `yaml:"bcrypt-cost"`
|
BcryptCost uint `yaml:"bcrypt-cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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())
|
||||||
|
@ -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"`
|
||||||
|
@ -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
|
||||||
|
138
irc/handlers.go
138
irc/handlers.go
@ -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()
|
||||||
|
10
irc/help.go
10
irc/help.go
@ -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]
|
||||||
|
@ -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"))
|
||||||
|
@ -853,25 +853,11 @@ 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 {
|
|
||||||
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.Accounts)
|
|
||||||
if callbackNamespace == "" || callbackValue == "" {
|
|
||||||
nsNotice(rb, client.t("Registration requires a valid e-mail address"))
|
nsNotice(rb, client.t("Registration requires a valid e-mail address"))
|
||||||
return
|
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)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user