retrofitting
This commit is contained in:
commit
e962e1f2aa
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
- name: "setup go"
|
||||
uses: "actions/setup-go@v2"
|
||||
with:
|
||||
go-version: "1.16.4"
|
||||
go-version: "1.17"
|
||||
- name: "install python3-pytest"
|
||||
run: "sudo apt install -y python3-pytest"
|
||||
- name: "make install"
|
||||
|
@ -1,5 +1,5 @@
|
||||
## build ergo binary
|
||||
FROM golang:1.16-alpine3.13 AS build-env
|
||||
FROM golang:1.17-alpine AS build-env
|
||||
|
||||
RUN apk add -U --force-refresh --no-cache --purge --clean-protected -l -u make
|
||||
|
||||
|
@ -414,6 +414,13 @@ accounts:
|
||||
blacklist-regexes:
|
||||
# - ".*@mailinator.com"
|
||||
timeout: 60s
|
||||
# email-based password reset:
|
||||
password-reset:
|
||||
enabled: false
|
||||
# time before we allow resending the email
|
||||
cooldown: 1h
|
||||
# time for which a password reset code is valid
|
||||
timeout: 1d
|
||||
|
||||
# throttle account login attempts (to prevent either password guessing, or DoS
|
||||
# attacks on the server aimed at forcing repeated expensive bcrypt computations)
|
||||
|
@ -183,7 +183,7 @@ The only major distribution that currently packages Ergo is Arch Linux; the afor
|
||||
|
||||
1. Create a dedicated, unprivileged role user who will own the ergo process and all its associated files: `adduser --system --group ergo`. This user now has a home directory at `/home/ergo`. To prevent other users from viewing Ergo's configuration file, database, and certificates, restrict the permissions on the home directory: `chmod 0700 /home/ergo`.
|
||||
1. Copy the executable binary `ergo`, the config file `ircd.yaml`, the database `ircd.db`, and the self-signed TLS certificate (`fullchain.pem` and `privkey.pem`) to `/home/ergo`. (If you don't have an `ircd.db`, it will be auto-created as `/home/ergo/ircd.db` on first launch.) Ensure that they are all owned by the new ergo role user: `sudo chown ergo:ergo /home/ergo/*`. Ensure that the configuration file logs to stderr.
|
||||
1. Install our example [ergo.service](https://github.com/ergochat/ergo/blob/master/distrib/systemd/ergo.service) file to `/etc/systemd/system/ergo.service`.
|
||||
1. Install our example [ergo.service](https://github.com/ergochat/ergo/blob/stable/distrib/systemd/ergo.service) file to `/etc/systemd/system/ergo.service`.
|
||||
1. Enable and start the new service with the following commands:
|
||||
1. `systemctl daemon-reload`
|
||||
1. `systemctl enable ergo.service`
|
||||
|
@ -177,6 +177,12 @@ CAPDEFS = [
|
||||
url="https://github.com/ircv3/ircv3-specifications/pull/435",
|
||||
standard="draft IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="ExtendedMonitor",
|
||||
name="draft/extended-monitor",
|
||||
url="https://github.com/ircv3/ircv3-specifications/pull/466",
|
||||
standard="draft IRCv3",
|
||||
),
|
||||
]
|
||||
|
||||
def validate_defs():
|
||||
|
15
go.mod
15
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/ergochat/ergo
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48
|
||||
@ -25,6 +25,19 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/tidwall/btree v0.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.8.0 // indirect
|
||||
github.com/tidwall/grect v0.1.2 // indirect
|
||||
github.com/tidwall/match v1.0.3 // indirect
|
||||
github.com/tidwall/pretty v1.1.0 // indirect
|
||||
github.com/tidwall/rtred v0.1.2 // indirect
|
||||
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1
|
||||
|
||||
replace github.com/xdg-go/scram => github.com/ergochat/scram v1.0.2-ergo1
|
||||
|
8
go.sum
8
go.sum
@ -39,20 +39,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tidwall/btree v0.4.2 h1:aLwwJlG+InuFzdAPuBf9YCAR1LvSQ9zhC5aorFPlIPs=
|
||||
github.com/tidwall/btree v0.4.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/btree v0.6.0 h1:JLYAFGV+1gjyFi3iQbO/fupBin+Ooh7dxqVV0twJ1Bo=
|
||||
github.com/tidwall/btree v0.6.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
|
||||
github.com/tidwall/buntdb v1.2.3 h1:AoGVe4yrhKmnEPHrPrW5EUOATHOCIk4VtFvd8xn/ZtU=
|
||||
github.com/tidwall/buntdb v1.2.3/go.mod h1:+i/gBwYOHWG19wLgwMXFLkl00twh9+VWkkaOhuNQ4PA=
|
||||
github.com/tidwall/buntdb v1.2.6 h1:eS0QSmzHfCKjxxYGh8eH6wnK5VLsJ7UjyyIr29JmnEg=
|
||||
github.com/tidwall/buntdb v1.2.6/go.mod h1:zpXqlA5D2772I4cTqV3ifr2AZihDgi8FV7xAQu6edfc=
|
||||
github.com/tidwall/gjson v1.7.4 h1:19cchw8FOxkG5mdLRkGf9jqIqEyqdZhPqW60XfyFxk8=
|
||||
github.com/tidwall/gjson v1.7.4/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/grect v0.1.1 h1:+kMEkxhoqB7rniVXzMEIA66XwU07STgINqxh+qVIndY=
|
||||
github.com/tidwall/grect v0.1.1/go.mod h1:CzvbGiFbWUwiJ1JohXLb28McpyBsI00TK9Y6pDWLGRQ=
|
||||
github.com/tidwall/grect v0.1.2 h1:wKVeQVZhjaFCKTTlpkDe3Ex4ko3cMGW3MRKawRe8uQ4=
|
||||
github.com/tidwall/grect v0.1.2/go.mod h1:v+n4ewstPGduVJebcp5Eh2WXBJBumNzyhK8GZt4gHNw=
|
||||
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
|
||||
|
279
irc/accounts.go
279
irc/accounts.go
@ -4,7 +4,6 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
@ -32,7 +31,6 @@ const (
|
||||
keyAccountExists = "account.exists %s"
|
||||
keyAccountVerified = "account.verified %s"
|
||||
keyAccountUnregistered = "account.unregistered %s"
|
||||
keyAccountCallback = "account.callback %s"
|
||||
keyAccountVerificationCode = "account.verificationcode %s"
|
||||
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
|
||||
keyAccountRegTime = "account.registered.time %s"
|
||||
@ -46,6 +44,8 @@ const (
|
||||
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
||||
keyAccountRealname = "account.realname %s" // client realname stored as string
|
||||
keyAccountSuspended = "account.suspended %s" // client realname stored as string
|
||||
keyAccountPwReset = "account.pwreset %s"
|
||||
keyAccountEmailChange = "account.emailchange %s"
|
||||
// for an always-on client, a map of channel names they're in to their current modes
|
||||
// (not to be confused with their amodes, which a non-always-on client can have):
|
||||
keyAccountChannelToModes = "account.channeltomodes %s"
|
||||
@ -391,10 +391,10 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
||||
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
||||
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
|
||||
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
||||
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||
certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
|
||||
|
||||
var creds AccountCredentials
|
||||
@ -409,8 +409,16 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
||||
return err
|
||||
}
|
||||
|
||||
var settingsStr string
|
||||
if callbackNamespace == "mailto" {
|
||||
settings := AccountSettings{Email: callbackValue}
|
||||
j, err := json.Marshal(settings)
|
||||
if err == nil {
|
||||
settingsStr = string(j)
|
||||
}
|
||||
}
|
||||
|
||||
registeredTimeStr := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
|
||||
|
||||
var setOptions *buntdb.SetOptions
|
||||
ttl := time.Duration(config.Accounts.Registration.VerifyTimeout)
|
||||
@ -449,7 +457,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
||||
tx.Set(accountNameKey, account, setOptions)
|
||||
tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
|
||||
tx.Set(credentialsKey, credStr, setOptions)
|
||||
tx.Set(callbackKey, callbackSpec, setOptions)
|
||||
tx.Set(settingsKey, settingsStr, setOptions)
|
||||
if certfp != "" {
|
||||
tx.Set(certFPKey, casefoldedAccount, setOptions)
|
||||
}
|
||||
@ -782,15 +790,7 @@ func (am *AccountManager) dispatchMailtoCallback(client *Client, account string,
|
||||
subject = fmt.Sprintf(client.t("Verify your account on %s"), am.server.name)
|
||||
}
|
||||
|
||||
var message bytes.Buffer
|
||||
fmt.Fprintf(&message, "From: %s\r\n", config.Sender)
|
||||
fmt.Fprintf(&message, "To: %s\r\n", callbackValue)
|
||||
if config.DKIM.Domain != "" {
|
||||
fmt.Fprintf(&message, "Message-ID: <%s@%s>\r\n", utils.GenerateSecretKey(), config.DKIM.Domain)
|
||||
}
|
||||
fmt.Fprintf(&message, "Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))
|
||||
fmt.Fprintf(&message, "Subject: %s\r\n", subject)
|
||||
message.WriteString("\r\n") // blank line: end headers, begin message body
|
||||
message := email.ComposeMail(config, callbackValue, subject)
|
||||
fmt.Fprintf(&message, client.t("Account: %s"), account)
|
||||
message.WriteString("\r\n")
|
||||
fmt.Fprintf(&message, client.t("Verification code: %s"), code)
|
||||
@ -823,8 +823,8 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
|
||||
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||
|
||||
var raw rawClientAccount
|
||||
|
||||
@ -892,8 +892,8 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
|
||||
tx.Set(accountKey, "1", nil)
|
||||
tx.Set(accountNameKey, raw.Name, nil)
|
||||
tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
|
||||
tx.Set(callbackKey, raw.Callback, nil)
|
||||
tx.Set(credentialsKey, raw.Credentials, nil)
|
||||
tx.Set(settingsKey, raw.Settings, nil)
|
||||
|
||||
var creds AccountCredentials
|
||||
// XXX we shouldn't do (de)serialization inside the txn,
|
||||
@ -955,6 +955,214 @@ func (am *AccountManager) SARegister(account, passphrase string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type EmailChangeRecord struct {
|
||||
TimeCreated time.Time
|
||||
Code string
|
||||
Email string
|
||||
}
|
||||
|
||||
func (am *AccountManager) NsSetEmail(client *Client, emailAddr string) (err error) {
|
||||
casefoldedAccount := client.Account()
|
||||
if casefoldedAccount == "" {
|
||||
return errAccountNotLoggedIn
|
||||
}
|
||||
|
||||
if am.touchRegisterThrottle() {
|
||||
am.server.logger.Warning("accounts", "global registration throttle exceeded by client changing email", client.Nick())
|
||||
return errLimitExceeded
|
||||
}
|
||||
|
||||
config := am.server.Config()
|
||||
if !config.Accounts.Registration.EmailVerification.Enabled {
|
||||
return errFeatureDisabled // redundant check, just in case
|
||||
}
|
||||
record := EmailChangeRecord{
|
||||
TimeCreated: time.Now().UTC(),
|
||||
Code: utils.GenerateSecretToken(),
|
||||
Email: emailAddr,
|
||||
}
|
||||
recordKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
|
||||
recordBytes, _ := json.Marshal(record)
|
||||
recordVal := string(recordBytes)
|
||||
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||
tx.Set(recordKey, recordVal, nil)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := email.ComposeMail(config.Accounts.Registration.EmailVerification,
|
||||
emailAddr,
|
||||
fmt.Sprintf(client.t("Verify your change of e-mail address on %s"), am.server.name))
|
||||
message.WriteString(fmt.Sprintf(client.t("To confirm your change of e-mail address on %s, issue the following command:"), am.server.name))
|
||||
message.WriteString("\r\n")
|
||||
fmt.Fprintf(&message, "/MSG NickServ VERIFYEMAIL %s\r\n", record.Code)
|
||||
|
||||
err = email.SendMail(config.Accounts.Registration.EmailVerification, emailAddr, message.Bytes())
|
||||
if err == nil {
|
||||
am.server.logger.Info("services",
|
||||
fmt.Sprintf("email change verification sent for account %s", casefoldedAccount))
|
||||
return
|
||||
} else {
|
||||
am.server.logger.Error("internal", "Failed to dispatch e-mail change verification to", emailAddr, err.Error())
|
||||
return ®istrationCallbackError{err}
|
||||
}
|
||||
}
|
||||
|
||||
func (am *AccountManager) NsVerifyEmail(client *Client, code string) (err error) {
|
||||
casefoldedAccount := client.Account()
|
||||
if casefoldedAccount == "" {
|
||||
return errAccountNotLoggedIn
|
||||
}
|
||||
|
||||
var record EmailChangeRecord
|
||||
success := false
|
||||
key := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
|
||||
ttl := time.Duration(am.server.Config().Accounts.Registration.VerifyTimeout)
|
||||
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||
rawStr, err := tx.Get(key)
|
||||
if err == nil && rawStr != "" {
|
||||
err := json.Unmarshal([]byte(rawStr), &record)
|
||||
if err == nil {
|
||||
if (ttl == 0 || time.Since(record.TimeCreated) < ttl) && utils.SecretTokensMatch(record.Code, code) {
|
||||
success = true
|
||||
tx.Delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if !success {
|
||||
return errAccountVerificationInvalidCode
|
||||
}
|
||||
|
||||
munger := func(in AccountSettings) (out AccountSettings, err error) {
|
||||
out = in
|
||||
out.Email = record.Email
|
||||
return
|
||||
}
|
||||
|
||||
_, err = am.ModifyAccountSettings(casefoldedAccount, munger)
|
||||
return
|
||||
}
|
||||
|
||||
func (am *AccountManager) NsSendpass(client *Client, accountName string) (err error) {
|
||||
config := am.server.Config()
|
||||
if !(config.Accounts.Registration.EmailVerification.Enabled && config.Accounts.Registration.EmailVerification.PasswordReset.Enabled) {
|
||||
return errFeatureDisabled
|
||||
}
|
||||
|
||||
account, err := am.LoadAccount(accountName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !account.Verified {
|
||||
return errAccountUnverified
|
||||
}
|
||||
if account.Suspended != nil {
|
||||
return errAccountSuspended
|
||||
}
|
||||
if account.Settings.Email == "" {
|
||||
return errValidEmailRequired
|
||||
}
|
||||
|
||||
record := PasswordResetRecord{
|
||||
TimeCreated: time.Now().UTC(),
|
||||
Code: utils.GenerateSecretToken(),
|
||||
}
|
||||
recordKey := fmt.Sprintf(keyAccountPwReset, account.NameCasefolded)
|
||||
recordBytes, _ := json.Marshal(record)
|
||||
recordVal := string(recordBytes)
|
||||
|
||||
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||
recStr, recErr := tx.Get(recordKey)
|
||||
if recErr == nil && recStr != "" {
|
||||
var existing PasswordResetRecord
|
||||
jErr := json.Unmarshal([]byte(recStr), &existing)
|
||||
cooldown := time.Duration(config.Accounts.Registration.EmailVerification.PasswordReset.Cooldown)
|
||||
if jErr == nil && time.Since(existing.TimeCreated) < cooldown {
|
||||
err = errLimitExceeded
|
||||
return nil
|
||||
}
|
||||
}
|
||||
tx.Set(recordKey, recordVal, &buntdb.SetOptions{
|
||||
Expires: true,
|
||||
TTL: time.Duration(config.Accounts.Registration.EmailVerification.PasswordReset.Timeout),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf(client.t("Reset your password on %s"), am.server.name)
|
||||
message := email.ComposeMail(config.Accounts.Registration.EmailVerification, account.Settings.Email, subject)
|
||||
fmt.Fprintf(&message, client.t("We received a request to reset your password on %s for account: %s"), am.server.name, account.Name)
|
||||
message.WriteString("\r\n")
|
||||
fmt.Fprintf(&message, client.t("If you did not initiate this request, you can safely ignore this message."))
|
||||
message.WriteString("\r\n")
|
||||
message.WriteString("\r\n")
|
||||
message.WriteString(client.t("Otherwise, to reset your password, issue the following command (replace `new_password` with your desired password):"))
|
||||
message.WriteString("\r\n")
|
||||
fmt.Fprintf(&message, "/MSG NickServ RESETPASS %s %s new_password\r\n", account.Name, record.Code)
|
||||
|
||||
err = email.SendMail(config.Accounts.Registration.EmailVerification, account.Settings.Email, message.Bytes())
|
||||
if err == nil {
|
||||
am.server.logger.Info("services",
|
||||
fmt.Sprintf("client %s sent a password reset email for account %s", client.Nick(), account.Name))
|
||||
} else {
|
||||
am.server.logger.Error("internal", "Failed to dispatch e-mail to", account.Settings.Email, err.Error())
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (am *AccountManager) NsResetpass(client *Client, accountName, code, password string) (err error) {
|
||||
if validatePassphrase(password) != nil {
|
||||
return errAccountBadPassphrase
|
||||
}
|
||||
account, err := am.LoadAccount(accountName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !account.Verified {
|
||||
return errAccountUnverified
|
||||
}
|
||||
if account.Suspended != nil {
|
||||
return errAccountSuspended
|
||||
}
|
||||
|
||||
success := false
|
||||
key := fmt.Sprintf(keyAccountPwReset, account.NameCasefolded)
|
||||
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||
rawStr, err := tx.Get(key)
|
||||
if err == nil && rawStr != "" {
|
||||
var record PasswordResetRecord
|
||||
err := json.Unmarshal([]byte(rawStr), &record)
|
||||
if err == nil && utils.SecretTokensMatch(record.Code, code) {
|
||||
success = true
|
||||
tx.Delete(key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if success {
|
||||
return am.setPassword(accountName, password, true)
|
||||
} else {
|
||||
return errAccountInvalidCredentials
|
||||
}
|
||||
}
|
||||
|
||||
type PasswordResetRecord struct {
|
||||
TimeCreated time.Time
|
||||
Code string
|
||||
}
|
||||
|
||||
func marshalReservedNicks(nicks []string) string {
|
||||
return strings.Join(nicks, ",")
|
||||
}
|
||||
@ -1294,9 +1502,6 @@ func (am *AccountManager) deserializeRawAccount(raw rawClientAccount, cfName str
|
||||
return
|
||||
}
|
||||
result.AdditionalNicks = unmarshalReservedNicks(raw.AdditionalNicks)
|
||||
if strings.HasPrefix(raw.Callback, "mailto:") {
|
||||
result.Email = strings.TrimPrefix(raw.Callback, "mailto:")
|
||||
}
|
||||
result.Verified = raw.Verified
|
||||
if raw.VHost != "" {
|
||||
e := json.Unmarshal([]byte(raw.VHost), &result.VHost)
|
||||
@ -1329,7 +1534,6 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
||||
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
||||
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||
@ -1344,7 +1548,6 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
||||
result.Name, _ = tx.Get(accountNameKey)
|
||||
result.RegisteredAt, _ = tx.Get(registeredTimeKey)
|
||||
result.Credentials, _ = tx.Get(credentialsKey)
|
||||
result.Callback, _ = tx.Get(callbackKey)
|
||||
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
||||
result.VHost, _ = tx.Get(vhostKey)
|
||||
result.Settings, _ = tx.Get(settingsKey)
|
||||
@ -1524,7 +1727,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
||||
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
||||
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||
@ -1537,6 +1739,8 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
||||
modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)
|
||||
realnameKey := fmt.Sprintf(keyAccountRealname, casefoldedAccount)
|
||||
suspendedKey := fmt.Sprintf(keyAccountSuspended, casefoldedAccount)
|
||||
pwResetKey := fmt.Sprintf(keyAccountPwReset, casefoldedAccount)
|
||||
emailChangeKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
|
||||
|
||||
var clients []*Client
|
||||
defer func() {
|
||||
@ -1582,7 +1786,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
||||
tx.Delete(accountNameKey)
|
||||
tx.Delete(verifiedKey)
|
||||
tx.Delete(registeredTimeKey)
|
||||
tx.Delete(callbackKey)
|
||||
tx.Delete(verificationCodeKey)
|
||||
tx.Delete(settingsKey)
|
||||
rawNicks, _ = tx.Get(nicksKey)
|
||||
@ -1597,6 +1800,8 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
||||
tx.Delete(modesKey)
|
||||
tx.Delete(realnameKey)
|
||||
tx.Delete(suspendedKey)
|
||||
tx.Delete(pwResetKey)
|
||||
tx.Delete(emailChangeKey)
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -1940,17 +2145,19 @@ const (
|
||||
CredentialsAnope = -2
|
||||
)
|
||||
|
||||
type SCRAMCreds struct {
|
||||
Salt []byte
|
||||
Iters int
|
||||
StoredKey []byte
|
||||
ServerKey []byte
|
||||
}
|
||||
|
||||
// AccountCredentials stores the various methods for verifying accounts.
|
||||
type AccountCredentials struct {
|
||||
Version CredentialsVersion
|
||||
PassphraseHash []byte
|
||||
Certfps []string
|
||||
SCRAMCreds struct {
|
||||
Salt []byte
|
||||
Iters int
|
||||
StoredKey []byte
|
||||
ServerKey []byte
|
||||
}
|
||||
SCRAMCreds
|
||||
}
|
||||
|
||||
func (ac *AccountCredentials) Empty() bool {
|
||||
@ -1970,6 +2177,7 @@ func (ac *AccountCredentials) Serialize() (result string, err error) {
|
||||
func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint) (err error) {
|
||||
if passphrase == "" {
|
||||
ac.PassphraseHash = nil
|
||||
ac.SCRAMCreds = SCRAMCreds{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1994,10 +2202,12 @@ func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint)
|
||||
// xdg-go/scram says: "Clients have a default minimum PBKDF2 iteration count of 4096."
|
||||
minIters := 4096
|
||||
scramCreds := scramClient.GetStoredCredentials(scram.KeyFactors{Salt: string(salt), Iters: minIters})
|
||||
ac.SCRAMCreds.Salt = salt
|
||||
ac.SCRAMCreds.Iters = minIters
|
||||
ac.SCRAMCreds.StoredKey = scramCreds.StoredKey
|
||||
ac.SCRAMCreds.ServerKey = scramCreds.ServerKey
|
||||
ac.SCRAMCreds = SCRAMCreds{
|
||||
Salt: salt,
|
||||
Iters: minIters,
|
||||
StoredKey: scramCreds.StoredKey,
|
||||
ServerKey: scramCreds.ServerKey,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -2112,6 +2322,7 @@ type AccountSettings struct {
|
||||
AutoreplayMissed bool
|
||||
DMHistory HistoryStatus
|
||||
AutoAway PersistentStatus
|
||||
Email string
|
||||
}
|
||||
|
||||
// ClientAccount represents a user account.
|
||||
@ -2120,7 +2331,6 @@ type ClientAccount struct {
|
||||
Name string
|
||||
NameCasefolded string
|
||||
RegisteredAt time.Time
|
||||
Email string
|
||||
Credentials AccountCredentials
|
||||
Verified bool
|
||||
Suspended *AccountSuspension
|
||||
@ -2134,7 +2344,6 @@ type rawClientAccount struct {
|
||||
Name string
|
||||
RegisteredAt string
|
||||
Credentials string
|
||||
Callback string
|
||||
Verified bool
|
||||
AdditionalNicks string
|
||||
VHost string
|
||||
|
@ -7,7 +7,7 @@ package caps
|
||||
|
||||
const (
|
||||
// number of recognized capabilities:
|
||||
numCapabs = 27
|
||||
numCapabs = 28
|
||||
// length of the uint64 array that represents the bitset:
|
||||
bitsetLen = 1
|
||||
)
|
||||
@ -53,6 +53,10 @@ const (
|
||||
// https://github.com/ircv3/ircv3-specifications/pull/362
|
||||
EventPlayback Capability = iota
|
||||
|
||||
// ExtendedMonitor is the draft IRCv3 capability named "draft/extended-monitor":
|
||||
// https://github.com/ircv3/ircv3-specifications/pull/466
|
||||
ExtendedMonitor Capability = iota
|
||||
|
||||
// Languages is the proposed IRCv3 capability named "draft/languages":
|
||||
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
|
||||
Languages Capability = iota
|
||||
@ -135,6 +139,7 @@ var (
|
||||
"draft/channel-rename",
|
||||
"draft/chathistory",
|
||||
"draft/event-playback",
|
||||
"draft/extended-monitor",
|
||||
"draft/languages",
|
||||
"draft/multiline",
|
||||
"draft/relaymsg",
|
||||
|
@ -613,11 +613,7 @@ func (client *Client) getIPNoMutex() net.IP {
|
||||
|
||||
// IPString returns the IP address of this client as a string.
|
||||
func (client *Client) IPString() string {
|
||||
ip := client.IP().String()
|
||||
if 0 < len(ip) && ip[0] == ':' {
|
||||
ip = "0" + ip
|
||||
}
|
||||
return ip
|
||||
return utils.IPStringToHostname(client.IP().String())
|
||||
}
|
||||
|
||||
// t returns the translated version of the given string, based on the languages configured by the client.
|
||||
@ -1029,6 +1025,13 @@ func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]em
|
||||
return
|
||||
}
|
||||
|
||||
// Friends refers to clients that share a channel or extended-monitor this client.
|
||||
func (client *Client) FriendsMonitors(capabs ...caps.Capability) (result map[*Session]empty) {
|
||||
result = client.Friends(capabs...)
|
||||
client.server.monitorManager.AddMonitors(result, client.nickCasefolded, capabs...)
|
||||
return
|
||||
}
|
||||
|
||||
// helper for Friends
|
||||
func addFriendsToSet(set map[*Session]empty, client *Client, capabs ...caps.Capability) {
|
||||
client.stateMutex.RLock()
|
||||
@ -1053,7 +1056,7 @@ func (client *Client) SetOper(oper *Oper) {
|
||||
func (client *Client) sendChghost(oldNickMask string, vhost string) {
|
||||
details := client.Details()
|
||||
isBot := client.HasMode(modes.Bot)
|
||||
for fClient := range client.Friends(caps.ChgHost) {
|
||||
for fClient := range client.FriendsMonitors(caps.ChgHost) {
|
||||
fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, details.accountName, isBot, nil, "CHGHOST", details.username, vhost)
|
||||
}
|
||||
}
|
||||
|
@ -303,6 +303,7 @@ func (t *ThrottleConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err e
|
||||
type AccountConfig struct {
|
||||
Registration AccountRegistrationConfig
|
||||
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
||||
AdvertiseSCRAM bool `yaml:"advertise-scram"` // undocumented, see #1782
|
||||
RequireSasl struct {
|
||||
Enabled bool
|
||||
Exempted []string
|
||||
@ -1382,7 +1383,12 @@ func LoadConfig(filename string) (config *Config, err error) {
|
||||
config.Accounts.VHosts.validRegexp = defaultValidVhostRegex
|
||||
}
|
||||
|
||||
config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL,SCRAM-SHA-256"
|
||||
saslCapValue := "PLAIN,EXTERNAL,SCRAM-SHA-256"
|
||||
// TODO(#1782) clean this up:
|
||||
if !config.Accounts.AdvertiseSCRAM {
|
||||
saslCapValue = "PLAIN,EXTERNAL"
|
||||
}
|
||||
config.Server.capValues[caps.SASL] = saslCapValue
|
||||
if !config.Accounts.AuthenticationEnabled {
|
||||
config.Server.supportedCaps.Disable(caps.SASL)
|
||||
}
|
||||
@ -1591,7 +1597,7 @@ func (config *Config) generateISupport() (err error) {
|
||||
isupport.Add("RPUSER", "E")
|
||||
}
|
||||
isupport.Add("STATUSMSG", "~&@%+")
|
||||
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries))
|
||||
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries))
|
||||
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))
|
||||
if config.Server.Casemapping == CasemappingPRECIS {
|
||||
isupport.Add("UTF8MAPPING", precisUTF8MappingToken)
|
||||
|
@ -24,7 +24,7 @@ const (
|
||||
// 'version' of the database schema
|
||||
keySchemaVersion = "db.version"
|
||||
// latest schema of the db
|
||||
latestDbSchema = 20
|
||||
latestDbSchema = 21
|
||||
|
||||
keyCloakSecret = "crypto.cloak_secret"
|
||||
)
|
||||
@ -1008,6 +1008,57 @@ func schemaChangeV19To20(config *Config, tx *buntdb.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// #734: move the email address into the settings object,
|
||||
// giving people a way to change it
|
||||
func schemaChangeV20To21(config *Config, tx *buntdb.Tx) error {
|
||||
type accountSettingsv21 struct {
|
||||
AutoreplayLines *int
|
||||
NickEnforcement NickEnforcementMethod
|
||||
AllowBouncer MulticlientAllowedSetting
|
||||
ReplayJoins ReplayJoinsSetting
|
||||
AlwaysOn PersistentStatus
|
||||
AutoreplayMissed bool
|
||||
DMHistory HistoryStatus
|
||||
AutoAway PersistentStatus
|
||||
Email string
|
||||
}
|
||||
var accounts []string
|
||||
var emails []string
|
||||
callbackPrefix := "account.callback "
|
||||
tx.AscendGreaterOrEqual("", callbackPrefix, func(key, value string) bool {
|
||||
if !strings.HasPrefix(key, callbackPrefix) {
|
||||
return false
|
||||
}
|
||||
account := strings.TrimPrefix(key, callbackPrefix)
|
||||
if _, err := tx.Get("account.verified " + account); err != nil {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(value, "mailto:") {
|
||||
accounts = append(accounts, account)
|
||||
emails = append(emails, strings.TrimPrefix(value, "mailto:"))
|
||||
}
|
||||
return true
|
||||
})
|
||||
for i, account := range accounts {
|
||||
var settings accountSettingsv21
|
||||
email := emails[i]
|
||||
settingsKey := "account.settings " + account
|
||||
settingsStr, err := tx.Get(settingsKey)
|
||||
if err == nil && settingsStr != "" {
|
||||
json.Unmarshal([]byte(settingsStr), &settings)
|
||||
}
|
||||
settings.Email = email
|
||||
settingsBytes, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
log.Printf("couldn't marshal settings for %s: %v\n", account, err)
|
||||
} else {
|
||||
tx.Set(settingsKey, string(settingsBytes), nil)
|
||||
}
|
||||
tx.Delete(callbackPrefix + account)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
|
||||
for _, change := range allChanges {
|
||||
if initialVersion == change.InitialVersion {
|
||||
@ -1113,4 +1164,9 @@ var allChanges = []SchemaChange{
|
||||
TargetVersion: 20,
|
||||
Changer: schemaChangeV19To20,
|
||||
},
|
||||
{
|
||||
InitialVersion: 20,
|
||||
TargetVersion: 21,
|
||||
Changer: schemaChangeV20To21,
|
||||
},
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -11,7 +12,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ergochat/ergo/irc/custime"
|
||||
"github.com/ergochat/ergo/irc/smtp"
|
||||
"github.com/ergochat/ergo/irc/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -42,6 +45,11 @@ type MailtoConfig struct {
|
||||
BlacklistRegexes []string `yaml:"blacklist-regexes"`
|
||||
blacklistRegexes []*regexp.Regexp
|
||||
Timeout time.Duration
|
||||
PasswordReset struct {
|
||||
Enabled bool
|
||||
Cooldown custime.Duration
|
||||
Timeout custime.Duration
|
||||
} `yaml:"password-reset"`
|
||||
}
|
||||
|
||||
func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
|
||||
@ -95,6 +103,19 @@ func lookupMX(domain string) (server string) {
|
||||
return
|
||||
}
|
||||
|
||||
func ComposeMail(config MailtoConfig, recipient, subject string) (message bytes.Buffer) {
|
||||
fmt.Fprintf(&message, "From: %s\r\n", config.Sender)
|
||||
fmt.Fprintf(&message, "To: %s\r\n", recipient)
|
||||
dkimDomain := config.DKIM.Domain
|
||||
if dkimDomain != "" {
|
||||
fmt.Fprintf(&message, "Message-ID: <%s@%s>\r\n", utils.GenerateSecretKey(), dkimDomain)
|
||||
}
|
||||
fmt.Fprintf(&message, "Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))
|
||||
fmt.Fprintf(&message, "Subject: %s\r\n", subject)
|
||||
message.WriteString("\r\n") // blank line: end headers, begin message body
|
||||
return message
|
||||
}
|
||||
|
||||
func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
|
||||
for _, reg := range config.blacklistRegexes {
|
||||
if reg.MatchString(recipient) {
|
||||
|
@ -145,6 +145,17 @@ func (client *Client) removeSession(session *Session) (success bool, length int)
|
||||
return
|
||||
}
|
||||
|
||||
// #1650: show an arbitrarily chosen session IP and hostname in RPL_WHOISACTUALLY
|
||||
func (client *Client) getWhoisActually() (ip net.IP, hostname string) {
|
||||
client.stateMutex.RLock()
|
||||
defer client.stateMutex.RUnlock()
|
||||
|
||||
for _, session := range client.sessions {
|
||||
return session.IP(), session.rawHostname
|
||||
}
|
||||
return utils.IPv4LoopbackAddress, client.server.name
|
||||
}
|
||||
|
||||
func (client *Client) Nick() string {
|
||||
client.stateMutex.RLock()
|
||||
defer client.stateMutex.RUnlock()
|
||||
|
@ -109,7 +109,7 @@ func sendSuccessfulAccountAuth(service *ircService, client *Client, rb *Response
|
||||
|
||||
if client.Registered() {
|
||||
// dispatch account-notify
|
||||
for friend := range client.Friends(caps.AccountNotify) {
|
||||
for friend := range client.FriendsMonitors(caps.AccountNotify) {
|
||||
if friend != rb.session {
|
||||
friend.Send(nil, details.nickMask, "ACCOUNT", details.accountName)
|
||||
}
|
||||
@ -421,7 +421,7 @@ func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
|
||||
// dispatch away-notify
|
||||
details := client.Details()
|
||||
isBot := client.HasMode(modes.Bot)
|
||||
for session := range client.Friends(caps.AwayNotify) {
|
||||
for session := range client.FriendsMonitors(caps.AwayNotify) {
|
||||
if isAway {
|
||||
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
|
||||
} else {
|
||||
@ -1315,6 +1315,9 @@ func kickHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
|
||||
if len(msg.Params) > 2 {
|
||||
comment = msg.Params[2]
|
||||
}
|
||||
if comment == "" {
|
||||
comment = client.Nick()
|
||||
}
|
||||
for _, kick := range kicks {
|
||||
channel := server.channels.Get(kick.channel)
|
||||
if channel == nil {
|
||||
@ -1327,10 +1330,6 @@ func kickHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
|
||||
rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, utils.SafeErrorParam(kick.nick), client.t("No such nick"))
|
||||
continue
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
comment = kick.nick
|
||||
}
|
||||
channel.Kick(client, target, comment, rb, hasPrivs)
|
||||
}
|
||||
return false
|
||||
@ -2869,7 +2868,7 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.Message, rb *Resp
|
||||
|
||||
// alert friends
|
||||
now := time.Now().UTC()
|
||||
friends := client.Friends(caps.SetName)
|
||||
friends := client.FriendsMonitors(caps.SetName)
|
||||
delete(friends, rb.session)
|
||||
isBot := client.HasMode(modes.Bot)
|
||||
for session := range friends {
|
||||
@ -2974,7 +2973,7 @@ func userHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
|
||||
|
||||
username, realname := msg.Params[0], msg.Params[3]
|
||||
if len(realname) == 0 {
|
||||
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), client.t("Not enough parameters"))
|
||||
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), "USER", client.t("Not enough parameters"))
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,9 @@ func doImportDBGeneric(config *Config, dbImport databaseImport, credsType Creden
|
||||
tx.Set(fmt.Sprintf(keyAccountExists, cfUsername), "1", nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountVerified, cfUsername), "1", nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountName, cfUsername), userInfo.Name, nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountCallback, cfUsername), "mailto:"+userInfo.Email, nil)
|
||||
settings := AccountSettings{Email: userInfo.Email}
|
||||
settingsBytes, _ := json.Marshal(settings)
|
||||
tx.Set(fmt.Sprintf(keyAccountSettings, cfUsername), string(settingsBytes), nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountCredentials, cfUsername), string(marshaledCredentials), nil)
|
||||
tx.Set(fmt.Sprintf(keyAccountRegTime, cfUsername), strconv.FormatInt(userInfo.RegisteredAt, 10), nil)
|
||||
if userInfo.Vhost != "" {
|
||||
|
@ -6,6 +6,8 @@ package irc
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ergochat/ergo/irc/caps"
|
||||
|
||||
"github.com/ergochat/irc-go/ircmsg"
|
||||
)
|
||||
|
||||
@ -23,6 +25,17 @@ func (mm *MonitorManager) Initialize() {
|
||||
mm.watchedby = make(map[string]map[*Session]empty)
|
||||
}
|
||||
|
||||
// AddMonitors adds clients using extended-monitor monitoring `client`'s nick to the passed user set.
|
||||
func (manager *MonitorManager) AddMonitors(users map[*Session]empty, cfnick string, capabs ...caps.Capability) {
|
||||
manager.RLock()
|
||||
defer manager.RUnlock()
|
||||
for session := range manager.watchedby[cfnick] {
|
||||
if session.capabilities.Has(caps.ExtendedMonitor) && session.capabilities.HasAll(capabs...) {
|
||||
users[session] = empty{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AlertAbout alerts everyone monitoring `client`'s nick that `client` is now {on,off}line.
|
||||
func (manager *MonitorManager) AlertAbout(nick, cfnick string, online bool) {
|
||||
var watchers []*Session
|
||||
|
198
irc/nickserv.go
198
irc/nickserv.go
@ -36,6 +36,11 @@ func servCmdRequiresBouncerEnabled(config *Config) bool {
|
||||
return config.Accounts.Multiclient.Enabled
|
||||
}
|
||||
|
||||
func servCmdRequiresEmailReset(config *Config) bool {
|
||||
return config.Accounts.Registration.EmailVerification.Enabled &&
|
||||
config.Accounts.Registration.EmailVerification.PasswordReset.Enabled
|
||||
}
|
||||
|
||||
const nickservHelp = `NickServ lets you register, log in to, and manage an account.`
|
||||
|
||||
var (
|
||||
@ -302,6 +307,12 @@ how the history of your direct messages is stored. Your options are:
|
||||
'auto-away' is only effective for always-on clients. If enabled, you will
|
||||
automatically be marked away when all your sessions are disconnected, and
|
||||
automatically return from away when you connect again.`,
|
||||
`$bEMAIL$b
|
||||
'email' controls the e-mail address associated with your account (if the
|
||||
server operator allows it, this address can be used for password resets).
|
||||
As an additional security measure, if you have a password set, you must
|
||||
provide it as an additional argument to $bSET$b, for example,
|
||||
SET EMAIL test@example.com hunter2`,
|
||||
},
|
||||
authRequired: true,
|
||||
enabled: servCmdRequiresAuthEnabled,
|
||||
@ -318,6 +329,27 @@ information on the settings and their possible values, see HELP SET.`,
|
||||
minParams: 3,
|
||||
capabs: []string{"accreg"},
|
||||
},
|
||||
"sendpass": {
|
||||
handler: nsSendpassHandler,
|
||||
help: `Syntax: $bSENDPASS <account>$b
|
||||
|
||||
SENDPASS sends a password reset email to the email address associated with
|
||||
the target account. The reset code in the email can then be used with the
|
||||
$bRESETPASS$b command.`,
|
||||
helpShort: `$bSENDPASS$b initiates an email-based password reset`,
|
||||
enabled: servCmdRequiresEmailReset,
|
||||
minParams: 1,
|
||||
},
|
||||
"resetpass": {
|
||||
handler: nsResetpassHandler,
|
||||
help: `Syntax: $bRESETPASS <account> <code> <password>$b
|
||||
|
||||
RESETPASS resets an account password, using a reset code that was emailed as
|
||||
the result of a previous $bSENDPASS$b command.`,
|
||||
helpShort: `$bRESETPASS$b completes an email-based password reset`,
|
||||
enabled: servCmdRequiresEmailReset,
|
||||
minParams: 3,
|
||||
},
|
||||
"cert": {
|
||||
handler: nsCertHandler,
|
||||
help: `Syntax: $bCERT <LIST | ADD | DEL> [account] [certfp]$b
|
||||
@ -357,6 +389,12 @@ Currently, you can only change the canonical casefolding of an account
|
||||
minParams: 2,
|
||||
capabs: []string{"accreg"},
|
||||
},
|
||||
"verifyemail": {
|
||||
handler: nsVerifyEmailHandler,
|
||||
authRequired: true,
|
||||
minParams: 1,
|
||||
hidden: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -459,7 +497,12 @@ func displaySetting(service *ircService, settingName string, settings AccountSet
|
||||
effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory)
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory)))
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, your direct message history setting is: %s"), historyStatusToString(effectiveValue)))
|
||||
|
||||
case "email":
|
||||
if settings.Email != "" {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Your stored e-mail address is: %s"), settings.Email))
|
||||
} else {
|
||||
service.Notice(rb, client.t("You have no stored e-mail address"))
|
||||
}
|
||||
default:
|
||||
service.Notice(rb, client.t("No such setting"))
|
||||
}
|
||||
@ -475,18 +518,27 @@ func userPersistentStatusToString(status PersistentStatus) string {
|
||||
}
|
||||
|
||||
func nsSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
var privileged bool
|
||||
var account string
|
||||
if command == "saset" {
|
||||
privileged = true
|
||||
account = params[0]
|
||||
params = params[1:]
|
||||
} else {
|
||||
account = client.Account()
|
||||
}
|
||||
|
||||
key := strings.ToLower(params[0])
|
||||
// unprivileged NS SET EMAIL is different because it requires a confirmation
|
||||
if !privileged && key == "email" {
|
||||
nsSetEmailHandler(service, client, params, rb)
|
||||
return
|
||||
}
|
||||
|
||||
var munger settingsMunger
|
||||
var finalSettings AccountSettings
|
||||
var err error
|
||||
switch strings.ToLower(params[0]) {
|
||||
switch key {
|
||||
case "pass", "password":
|
||||
service.Notice(rb, client.t("To change a password, use the PASSWD command. For details, /msg NickServ HELP PASSWD"))
|
||||
return
|
||||
@ -603,6 +655,13 @@ func nsSetHandler(service *ircService, server *Server, client *Client, command s
|
||||
return
|
||||
}
|
||||
}
|
||||
case "email":
|
||||
newValue := params[1]
|
||||
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
||||
out = in
|
||||
out.Email = newValue
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = errInvalidParams
|
||||
}
|
||||
@ -614,7 +673,7 @@ func nsSetHandler(service *ircService, server *Server, client *Client, command s
|
||||
switch err {
|
||||
case nil:
|
||||
service.Notice(rb, client.t("Successfully changed your account settings"))
|
||||
displaySetting(service, params[0], finalSettings, client, rb)
|
||||
displaySetting(service, key, finalSettings, client, rb)
|
||||
case errInvalidParams, errAccountDoesNotExist, errFeatureDisabled, errAccountUnverified, errAccountUpdateFailed:
|
||||
service.Notice(rb, client.t(err.Error()))
|
||||
case errNickAccountMismatch:
|
||||
@ -625,6 +684,55 @@ func nsSetHandler(service *ircService, server *Server, client *Client, command s
|
||||
}
|
||||
}
|
||||
|
||||
// handle unprivileged NS SET EMAIL, which sends a confirmation code
|
||||
func nsSetEmailHandler(service *ircService, client *Client, params []string, rb *ResponseBuffer) {
|
||||
config := client.server.Config()
|
||||
if !config.Accounts.Registration.EmailVerification.Enabled {
|
||||
rb.Notice(client.t("E-mail verification is disabled"))
|
||||
return
|
||||
}
|
||||
if !nsLoginThrottleCheck(service, client, rb) {
|
||||
return
|
||||
}
|
||||
var password string
|
||||
if len(params) > 2 {
|
||||
password = params[2]
|
||||
}
|
||||
account := client.Account()
|
||||
errorMessage := nsConfirmPassword(client.server, account, password)
|
||||
if errorMessage != "" {
|
||||
service.Notice(rb, client.t(errorMessage))
|
||||
return
|
||||
}
|
||||
err := client.server.accounts.NsSetEmail(client, params[1])
|
||||
switch err {
|
||||
case nil:
|
||||
service.Notice(rb, client.t("Check your e-mail for instructions on how to confirm your change of address"))
|
||||
case errLimitExceeded:
|
||||
service.Notice(rb, client.t("Try again later"))
|
||||
default:
|
||||
// if appropriate, show the client the error from the attempted email sending
|
||||
if rErr := registrationCallbackErrorText(config, client, err); rErr != "" {
|
||||
service.Notice(rb, rErr)
|
||||
} else {
|
||||
service.Notice(rb, client.t("An error occurred"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nsVerifyEmailHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
err := server.accounts.NsVerifyEmail(client, params[0])
|
||||
switch err {
|
||||
case nil:
|
||||
service.Notice(rb, client.t("Successfully changed your account settings"))
|
||||
displaySetting(service, "email", client.AccountSettings(), client, rb)
|
||||
case errAccountVerificationInvalidCode:
|
||||
service.Notice(rb, client.t(err.Error()))
|
||||
default:
|
||||
service.Notice(rb, client.t("An error occurred"))
|
||||
}
|
||||
}
|
||||
|
||||
func nsDropHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
sadrop := command == "sadrop"
|
||||
var nick string
|
||||
@ -815,8 +923,8 @@ func nsInfoHandler(service *ircService, server *Server, client *Client, command
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), registeredAt))
|
||||
|
||||
if account.Name == client.AccountName() || client.HasRoleCapabs("accreg") {
|
||||
if account.Email != "" {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Email address: %s"), account.Email))
|
||||
if account.Settings.Email != "" {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Email address: %s"), account.Settings.Email))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1018,6 +1126,19 @@ func nsVerifyHandler(service *ircService, server *Server, client *Client, comman
|
||||
}
|
||||
}
|
||||
|
||||
func nsConfirmPassword(server *Server, account, passphrase string) (errorMessage string) {
|
||||
accountData, err := server.accounts.LoadAccount(account)
|
||||
if err != nil {
|
||||
errorMessage = `You're not logged into an account`
|
||||
} else {
|
||||
hash := accountData.Credentials.PassphraseHash
|
||||
if hash != nil && passwd.CompareHashAndPassword(hash, []byte(passphrase)) != nil {
|
||||
errorMessage = `Password incorrect`
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func nsPasswdHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
var target string
|
||||
var newPassword string
|
||||
@ -1041,28 +1162,19 @@ func nsPasswdHandler(service *ircService, server *Server, client *Client, comman
|
||||
}
|
||||
case 3:
|
||||
target = client.Account()
|
||||
newPassword = params[1]
|
||||
if newPassword == "*" {
|
||||
newPassword = ""
|
||||
}
|
||||
if target == "" {
|
||||
errorMessage = `You're not logged into an account`
|
||||
} else if params[1] != params[2] {
|
||||
} else if newPassword != params[2] {
|
||||
errorMessage = `Passwords do not match`
|
||||
} else {
|
||||
if !nsLoginThrottleCheck(service, client, rb) {
|
||||
return
|
||||
}
|
||||
accountData, err := server.accounts.LoadAccount(target)
|
||||
if err != nil {
|
||||
errorMessage = `You're not logged into an account`
|
||||
} else {
|
||||
hash := accountData.Credentials.PassphraseHash
|
||||
if hash != nil && passwd.CompareHashAndPassword(hash, []byte(params[0])) != nil {
|
||||
errorMessage = `Password incorrect`
|
||||
} else {
|
||||
newPassword = params[1]
|
||||
if newPassword == "*" {
|
||||
newPassword = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
errorMessage = nsConfirmPassword(server, target, params[0])
|
||||
}
|
||||
default:
|
||||
errorMessage = `Invalid parameters`
|
||||
@ -1422,6 +1534,52 @@ func suspensionToString(client *Client, suspension AccountSuspension) (result st
|
||||
return fmt.Sprintf(client.t("Account %[1]s suspended at %[2]s. Duration: %[3]s. %[4]s"), suspension.AccountName, ts, duration, reason)
|
||||
}
|
||||
|
||||
func nsSendpassHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
if !nsLoginThrottleCheck(service, client, rb) {
|
||||
return
|
||||
}
|
||||
|
||||
account := params[0]
|
||||
var message string
|
||||
err := server.accounts.NsSendpass(client, account)
|
||||
switch err {
|
||||
case nil:
|
||||
server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf("Client %s sent a password reset for account %s", client.Nick(), account))
|
||||
message = `Successfully sent password reset email`
|
||||
case errAccountDoesNotExist, errAccountUnverified, errAccountSuspended:
|
||||
message = err.Error()
|
||||
case errValidEmailRequired:
|
||||
message = `That account is not associated with an email address`
|
||||
case errLimitExceeded:
|
||||
message = `Try again later`
|
||||
default:
|
||||
server.logger.Error("services", "error in NS SENDPASS", err.Error())
|
||||
message = `An error occurred`
|
||||
}
|
||||
rb.Notice(client.t(message))
|
||||
}
|
||||
|
||||
func nsResetpassHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
if !nsLoginThrottleCheck(service, client, rb) {
|
||||
return
|
||||
}
|
||||
|
||||
var message string
|
||||
err := server.accounts.NsResetpass(client, params[0], params[1], params[2])
|
||||
switch err {
|
||||
case nil:
|
||||
message = `Successfully reset account password`
|
||||
case errAccountDoesNotExist, errAccountUnverified, errAccountSuspended, errAccountBadPassphrase:
|
||||
message = err.Error()
|
||||
case errAccountInvalidCredentials:
|
||||
message = `Code did not match`
|
||||
default:
|
||||
server.logger.Error("services", "error in NS RESETPASS", err.Error())
|
||||
message = `An error occurred`
|
||||
}
|
||||
rb.Notice(client.t(message))
|
||||
}
|
||||
|
||||
func nsRenameHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
oldName, newName := params[0], params[1]
|
||||
err := server.accounts.Rename(oldName, newName)
|
||||
|
@ -478,7 +478,8 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff
|
||||
}
|
||||
}
|
||||
if client == target || oper.HasRoleCapab("ban") || !target.HasMode(modes.Cloaked) {
|
||||
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, target.RawHostname()), target.IPString(), client.t("Actual user@host, Actual IP"))
|
||||
ip, hostname := target.getWhoisActually()
|
||||
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, hostname), utils.IPStringToHostname(ip.String()), client.t("Actual user@host, Actual IP"))
|
||||
}
|
||||
if client == target || oper.HasRoleCapab("samode") {
|
||||
rb.Add(nil, client.server.name, RPL_WHOISMODES, cnick, tnick, fmt.Sprintf(client.t("is using modes +%s"), target.modes.String()))
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build !plan9
|
||||
// +build !plan9
|
||||
|
||||
// Copyright (c) 2020 Shivaram Lingamneni
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build plan9
|
||||
// +build plan9
|
||||
|
||||
// Copyright (c) 2020 Shivaram Lingamneni
|
||||
|
@ -354,7 +354,14 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string,
|
||||
return err
|
||||
}
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
config := &tls.Config{ServerName: c.serverName}
|
||||
var config *tls.Config
|
||||
if requireTLS {
|
||||
config = &tls.Config{ServerName: c.serverName}
|
||||
} else {
|
||||
// if TLS isn't a hard requirement, don't verify the certificate either,
|
||||
// since a MITM attacker could just remove the STARTTLS advertisement
|
||||
config = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
if testHookStartTLS != nil {
|
||||
testHookStartTLS(config)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package utils
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package utils
|
||||
|
@ -387,6 +387,13 @@ accounts:
|
||||
blacklist-regexes:
|
||||
# - ".*@mailinator.com"
|
||||
timeout: 60s
|
||||
# email-based password reset:
|
||||
password-reset:
|
||||
enabled: false
|
||||
# time before we allow resending the email
|
||||
cooldown: 1h
|
||||
# time for which a password reset code is valid
|
||||
timeout: 1d
|
||||
|
||||
# throttle account login attempts (to prevent either password guessing, or DoS
|
||||
# attacks on the server aimed at forcing repeated expensive bcrypt computations)
|
||||
|
3
vendor/github.com/go-sql-driver/mysql/go.mod
generated
vendored
3
vendor/github.com/go-sql-driver/mysql/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/go-sql-driver/mysql
|
||||
|
||||
go 1.10
|
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/gorilla/websocket
|
||||
|
||||
go 1.12
|
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
3
vendor/github.com/tidwall/btree/go.mod
generated
vendored
3
vendor/github.com/tidwall/btree/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/tidwall/btree
|
||||
|
||||
go 1.16
|
12
vendor/github.com/tidwall/buntdb/go.mod
generated
vendored
12
vendor/github.com/tidwall/buntdb/go.mod
generated
vendored
@ -1,12 +0,0 @@
|
||||
module github.com/tidwall/buntdb
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/tidwall/btree v0.6.0
|
||||
github.com/tidwall/gjson v1.8.0
|
||||
github.com/tidwall/grect v0.1.2
|
||||
github.com/tidwall/lotsa v1.0.2
|
||||
github.com/tidwall/match v1.0.3
|
||||
github.com/tidwall/rtred v0.1.2
|
||||
)
|
16
vendor/github.com/tidwall/buntdb/go.sum
generated
vendored
16
vendor/github.com/tidwall/buntdb/go.sum
generated
vendored
@ -1,16 +0,0 @@
|
||||
github.com/tidwall/btree v0.6.0 h1:JLYAFGV+1gjyFi3iQbO/fupBin+Ooh7dxqVV0twJ1Bo=
|
||||
github.com/tidwall/btree v0.6.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
|
||||
github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/grect v0.1.2 h1:wKVeQVZhjaFCKTTlpkDe3Ex4ko3cMGW3MRKawRe8uQ4=
|
||||
github.com/tidwall/grect v0.1.2/go.mod h1:v+n4ewstPGduVJebcp5Eh2WXBJBumNzyhK8GZt4gHNw=
|
||||
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
|
||||
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
|
||||
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
|
||||
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
||||
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
8
vendor/github.com/tidwall/gjson/go.mod
generated
vendored
8
vendor/github.com/tidwall/gjson/go.mod
generated
vendored
@ -1,8 +0,0 @@
|
||||
module github.com/tidwall/gjson
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/tidwall/match v1.0.3
|
||||
github.com/tidwall/pretty v1.1.0
|
||||
)
|
4
vendor/github.com/tidwall/gjson/go.sum
generated
vendored
4
vendor/github.com/tidwall/gjson/go.sum
generated
vendored
@ -1,4 +0,0 @@
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
5
vendor/github.com/tidwall/grect/go.mod
generated
vendored
5
vendor/github.com/tidwall/grect/go.mod
generated
vendored
@ -1,5 +0,0 @@
|
||||
module github.com/tidwall/grect
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/tidwall/gjson v1.8.0
|
6
vendor/github.com/tidwall/grect/go.sum
generated
vendored
6
vendor/github.com/tidwall/grect/go.sum
generated
vendored
@ -1,6 +0,0 @@
|
||||
github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
3
vendor/github.com/tidwall/match/go.mod
generated
vendored
3
vendor/github.com/tidwall/match/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/tidwall/match
|
||||
|
||||
go 1.15
|
5
vendor/github.com/tidwall/rtred/go.mod
generated
vendored
5
vendor/github.com/tidwall/rtred/go.mod
generated
vendored
@ -1,5 +0,0 @@
|
||||
module github.com/tidwall/rtred
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/tidwall/tinyqueue v0.1.1
|
2
vendor/github.com/tidwall/rtred/go.sum
generated
vendored
2
vendor/github.com/tidwall/rtred/go.sum
generated
vendored
@ -1,2 +0,0 @@
|
||||
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
||||
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
3
vendor/github.com/tidwall/tinyqueue/go.mod
generated
vendored
3
vendor/github.com/tidwall/tinyqueue/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/tidwall/tinyqueue
|
||||
|
||||
go 1.15
|
3
vendor/github.com/xdg-go/pbkdf2/go.mod
generated
vendored
3
vendor/github.com/xdg-go/pbkdf2/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/xdg-go/pbkdf2
|
||||
|
||||
go 1.9
|
8
vendor/github.com/xdg-go/scram/go.mod
generated
vendored
8
vendor/github.com/xdg-go/scram/go.mod
generated
vendored
@ -1,8 +0,0 @@
|
||||
module github.com/xdg-go/scram
|
||||
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
github.com/xdg-go/pbkdf2 v1.0.0
|
||||
github.com/xdg-go/stringprep v1.0.2
|
||||
)
|
7
vendor/github.com/xdg-go/scram/go.sum
generated
vendored
7
vendor/github.com/xdg-go/scram/go.sum
generated
vendored
@ -1,7 +0,0 @@
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
5
vendor/golang.org/x/term/go.mod
generated
vendored
5
vendor/golang.org/x/term/go.mod
generated
vendored
@ -1,5 +0,0 @@
|
||||
module golang.org/x/term
|
||||
|
||||
go 1.11
|
||||
|
||||
require golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
2
vendor/golang.org/x/term/go.sum
generated
vendored
2
vendor/golang.org/x/term/go.sum
generated
vendored
@ -1,2 +0,0 @@
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
5
vendor/gopkg.in/yaml.v2/go.mod
generated
vendored
5
vendor/gopkg.in/yaml.v2/go.mod
generated
vendored
@ -1,5 +0,0 @@
|
||||
module gopkg.in/yaml.v2
|
||||
|
||||
go 1.15
|
||||
|
||||
require gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
|
30
vendor/modules.txt
vendored
30
vendor/modules.txt
vendored
@ -17,74 +17,84 @@ github.com/ergochat/confusables
|
||||
## explicit
|
||||
github.com/ergochat/go-ident
|
||||
# github.com/ergochat/irc-go v0.0.0-20210617222258-256f1601d3ce
|
||||
## explicit
|
||||
## explicit; go 1.15
|
||||
github.com/ergochat/irc-go/ircfmt
|
||||
github.com/ergochat/irc-go/ircmsg
|
||||
github.com/ergochat/irc-go/ircreader
|
||||
github.com/ergochat/irc-go/ircutils
|
||||
# github.com/go-sql-driver/mysql v1.6.0
|
||||
## explicit
|
||||
## explicit; go 1.10
|
||||
github.com/go-sql-driver/mysql
|
||||
# github.com/go-test/deep v1.0.6
|
||||
## explicit
|
||||
## explicit; go 1.13
|
||||
# github.com/golang-jwt/jwt v3.2.1+incompatible
|
||||
## explicit
|
||||
github.com/golang-jwt/jwt
|
||||
# github.com/gorilla/websocket v1.4.2 => github.com/ergochat/websocket v1.4.2-oragono1
|
||||
## explicit
|
||||
## explicit; go 1.12
|
||||
github.com/gorilla/websocket
|
||||
# github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
|
||||
## explicit
|
||||
github.com/okzk/sdnotify
|
||||
# github.com/onsi/ginkgo v1.12.0
|
||||
## explicit
|
||||
## explicit; go 1.12
|
||||
# github.com/onsi/gomega v1.9.0
|
||||
## explicit
|
||||
# github.com/stretchr/testify v1.4.0
|
||||
## explicit
|
||||
# github.com/tidwall/btree v0.6.0
|
||||
## explicit; go 1.16
|
||||
github.com/tidwall/btree
|
||||
# github.com/tidwall/buntdb v1.2.6
|
||||
## explicit
|
||||
## explicit; go 1.16
|
||||
github.com/tidwall/buntdb
|
||||
# github.com/tidwall/gjson v1.8.0
|
||||
## explicit; go 1.12
|
||||
github.com/tidwall/gjson
|
||||
# github.com/tidwall/grect v0.1.2
|
||||
## explicit; go 1.15
|
||||
github.com/tidwall/grect
|
||||
# github.com/tidwall/match v1.0.3
|
||||
## explicit; go 1.15
|
||||
github.com/tidwall/match
|
||||
# github.com/tidwall/pretty v1.1.0
|
||||
## explicit
|
||||
github.com/tidwall/pretty
|
||||
# github.com/tidwall/rtred v0.1.2
|
||||
## explicit; go 1.15
|
||||
github.com/tidwall/rtred
|
||||
github.com/tidwall/rtred/base
|
||||
# github.com/tidwall/tinyqueue v0.1.1
|
||||
## explicit; go 1.15
|
||||
github.com/tidwall/tinyqueue
|
||||
# github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
|
||||
## explicit
|
||||
github.com/toorop/go-dkim
|
||||
# github.com/xdg-go/pbkdf2 v1.0.0
|
||||
## explicit; go 1.9
|
||||
github.com/xdg-go/pbkdf2
|
||||
# github.com/xdg-go/scram v1.0.2 => github.com/ergochat/scram v1.0.2-ergo1
|
||||
## explicit
|
||||
## explicit; go 1.11
|
||||
github.com/xdg-go/scram
|
||||
# golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
|
||||
## explicit
|
||||
## explicit; go 1.11
|
||||
golang.org/x/crypto/bcrypt
|
||||
golang.org/x/crypto/blowfish
|
||||
golang.org/x/crypto/pbkdf2
|
||||
golang.org/x/crypto/sha3
|
||||
golang.org/x/crypto/ssh/terminal
|
||||
# golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
||||
## explicit; go 1.12
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/internal/unsafeheader
|
||||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
## explicit; go 1.11
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.3.6
|
||||
## explicit
|
||||
## explicit; go 1.11
|
||||
golang.org/x/text/cases
|
||||
golang.org/x/text/internal
|
||||
golang.org/x/text/internal/language
|
||||
@ -99,7 +109,7 @@ golang.org/x/text/unicode/bidi
|
||||
golang.org/x/text/unicode/norm
|
||||
golang.org/x/text/width
|
||||
# gopkg.in/yaml.v2 v2.4.0
|
||||
## explicit
|
||||
## explicit; go 1.15
|
||||
gopkg.in/yaml.v2
|
||||
# github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1
|
||||
# github.com/xdg-go/scram => github.com/ergochat/scram v1.0.2-ergo1
|
||||
|
Loading…
Reference in New Issue
Block a user