2017-03-11 13:01:40 +01:00
|
|
|
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
|
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package irc
|
|
|
|
|
|
|
|
import (
|
2018-02-02 14:44:52 +01:00
|
|
|
"fmt"
|
2017-03-11 13:01:40 +01:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
// TODO: "email" is an oversimplification here; it's actually any callback, e.g.,
|
|
|
|
// person@example.com, mailto:person@example.com, tel:16505551234.
|
2018-02-02 14:44:52 +01:00
|
|
|
const nickservHelp = `NickServ lets you register and log into a user account.
|
|
|
|
|
|
|
|
To register an account:
|
2018-02-20 10:20:30 +01:00
|
|
|
/NS REGISTER username email [password]
|
2018-02-02 14:44:52 +01:00
|
|
|
Leave out [password] if you're registering using your client certificate fingerprint.
|
2018-02-20 10:20:30 +01:00
|
|
|
The server may or may not allow you to register anonymously (by sending * as your
|
|
|
|
email address).
|
|
|
|
|
|
|
|
To verify an account (if you were sent a verification code):
|
|
|
|
/NS VERIFY username code
|
2018-02-02 14:44:52 +01:00
|
|
|
|
2018-02-12 07:09:30 +01:00
|
|
|
To unregister an account:
|
|
|
|
/NS UNREGISTER [username]
|
|
|
|
Leave out [username] if you're unregistering the user you're currently logged in as.
|
|
|
|
|
2018-02-02 14:44:52 +01:00
|
|
|
To login to an account:
|
|
|
|
/NS IDENTIFY [username password]
|
|
|
|
Leave out [username password] to use your client certificate fingerprint. Otherwise,
|
|
|
|
the given username and password will be used.`
|
|
|
|
|
|
|
|
// extractParam extracts a parameter from the given string, returning the param and the rest of the string.
|
|
|
|
func extractParam(line string) (string, string) {
|
|
|
|
rawParams := strings.SplitN(strings.TrimSpace(line), " ", 2)
|
|
|
|
param0 := rawParams[0]
|
|
|
|
var param1 string
|
|
|
|
if 1 < len(rawParams) {
|
|
|
|
param1 = strings.TrimSpace(rawParams[1])
|
|
|
|
}
|
|
|
|
return param0, param1
|
|
|
|
}
|
|
|
|
|
2018-02-03 12:15:07 +01:00
|
|
|
// nickservNoticeHandler handles NOTICEs that NickServ receives.
|
2018-02-05 15:21:08 +01:00
|
|
|
func (server *Server) nickservNoticeHandler(client *Client, message string, rb *ResponseBuffer) {
|
2018-02-03 12:15:07 +01:00
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
// nickservPrivmsgHandler handles PRIVMSGs that NickServ receives.
|
2018-02-05 15:21:08 +01:00
|
|
|
func (server *Server) nickservPrivmsgHandler(client *Client, message string, rb *ResponseBuffer) {
|
2018-02-02 14:44:52 +01:00
|
|
|
command, params := extractParam(message)
|
|
|
|
command = strings.ToLower(command)
|
|
|
|
|
|
|
|
if command == "help" {
|
|
|
|
for _, line := range strings.Split(nickservHelp, "\n") {
|
2018-02-05 15:21:08 +01:00
|
|
|
rb.Notice(line)
|
2018-02-02 14:44:52 +01:00
|
|
|
}
|
|
|
|
} else if command == "register" {
|
|
|
|
// get params
|
2018-02-20 10:20:30 +01:00
|
|
|
username, afterUsername := extractParam(params)
|
|
|
|
email, passphrase := extractParam(afterUsername)
|
|
|
|
server.nickservRegisterHandler(client, username, email, passphrase, rb)
|
|
|
|
} else if command == "verify" {
|
|
|
|
username, code := extractParam(params)
|
|
|
|
server.nickservVerifyHandler(client, username, code, rb)
|
2018-02-03 12:38:28 +01:00
|
|
|
} else if command == "identify" {
|
|
|
|
username, passphrase := extractParam(params)
|
2018-02-05 15:21:08 +01:00
|
|
|
server.nickservIdentifyHandler(client, username, passphrase, rb)
|
2018-02-12 07:09:30 +01:00
|
|
|
} else if command == "unregister" {
|
|
|
|
username, _ := extractParam(params)
|
|
|
|
server.nickservUnregisterHandler(client, username, rb)
|
2018-02-03 12:38:28 +01:00
|
|
|
} else {
|
2018-02-05 15:21:08 +01:00
|
|
|
rb.Notice(client.t("Command not recognised. To see the available commands, run /NS HELP"))
|
2018-02-03 12:38:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 07:09:30 +01:00
|
|
|
func (server *Server) nickservUnregisterHandler(client *Client, username string, rb *ResponseBuffer) {
|
|
|
|
if !server.AccountConfig().Registration.Enabled {
|
|
|
|
rb.Notice(client.t("Account registration has been disabled"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if username == "" {
|
|
|
|
username = client.Account()
|
|
|
|
}
|
|
|
|
if username == "" {
|
|
|
|
rb.Notice(client.t("You're not logged into an account"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cfname, err := CasefoldName(username)
|
|
|
|
if err != nil {
|
|
|
|
rb.Notice(client.t("Invalid username"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !(cfname == client.Account() || client.HasRoleCapabs("unregister")) {
|
|
|
|
rb.Notice(client.t("Insufficient oper privs"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
if cfname == client.Account() {
|
|
|
|
client.server.accounts.Logout(client)
|
|
|
|
}
|
|
|
|
|
2018-02-12 07:09:30 +01:00
|
|
|
err = server.accounts.Unregister(cfname)
|
|
|
|
if err == errAccountDoesNotExist {
|
|
|
|
rb.Notice(client.t(err.Error()))
|
|
|
|
} else if err != nil {
|
|
|
|
rb.Notice(client.t("Error while unregistering account"))
|
|
|
|
} else {
|
|
|
|
rb.Notice(fmt.Sprintf(client.t("Successfully unregistered account %s"), cfname))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
func (server *Server) nickservVerifyHandler(client *Client, username string, code string, rb *ResponseBuffer) {
|
|
|
|
err := server.accounts.Verify(client, username, code)
|
|
|
|
|
|
|
|
var errorMessage string
|
|
|
|
if err == errAccountVerificationInvalidCode || err == errAccountAlreadyVerified {
|
|
|
|
errorMessage = err.Error()
|
|
|
|
} else if err != nil {
|
|
|
|
errorMessage = errAccountVerificationFailed.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
if errorMessage != "" {
|
|
|
|
rb.Notice(client.t(errorMessage))
|
2018-02-03 12:38:28 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
sendSuccessfulRegResponse(client, rb, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (server *Server) nickservRegisterHandler(client *Client, username, email, passphrase string, rb *ResponseBuffer) {
|
2018-02-11 11:30:40 +01:00
|
|
|
if !server.AccountConfig().Registration.Enabled {
|
2018-02-05 15:21:08 +01:00
|
|
|
rb.Notice(client.t("Account registration has been disabled"))
|
2018-02-03 12:38:28 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
if username == "" {
|
|
|
|
rb.Notice(client.t("No username supplied"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
certfp := client.certfp
|
|
|
|
if passphrase == "" && certfp == "" {
|
|
|
|
rb.Notice(client.t("You need to either supply a passphrase or be connected via TLS with a client cert"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-03 12:38:28 +01:00
|
|
|
if client.LoggedIntoAccount() {
|
2018-02-11 11:30:40 +01:00
|
|
|
if server.AccountConfig().Registration.AllowMultiplePerConnection {
|
|
|
|
server.accounts.Logout(client)
|
2018-02-03 12:38:28 +01:00
|
|
|
} else {
|
2018-02-05 15:21:08 +01:00
|
|
|
rb.Notice(client.t("You're already logged into an account"))
|
2018-02-02 14:44:52 +01:00
|
|
|
return
|
|
|
|
}
|
2018-02-03 12:38:28 +01:00
|
|
|
}
|
2018-02-02 14:44:52 +01:00
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
config := server.AccountConfig()
|
|
|
|
var callbackNamespace, callbackValue string
|
|
|
|
noneCallbackAllowed := false
|
|
|
|
for _, callback := range(config.Registration.EnabledCallbacks) {
|
|
|
|
if callback == "*" {
|
|
|
|
noneCallbackAllowed = true
|
|
|
|
}
|
2018-02-03 12:38:28 +01:00
|
|
|
}
|
2018-02-20 10:20:30 +01:00
|
|
|
// XXX if ACC REGISTER allows registration with the `none` callback, then ignore
|
|
|
|
// any callback that was passed here (to avoid confusion in the case where the ircd
|
|
|
|
// has no mail server configured). otherwise, register using the provided callback:
|
|
|
|
if noneCallbackAllowed {
|
|
|
|
callbackNamespace = "*"
|
|
|
|
} else {
|
|
|
|
callbackNamespace, callbackValue = parseCallback(email, config)
|
|
|
|
if callbackNamespace == "" {
|
|
|
|
rb.Notice(client.t("Registration requires a valid e-mail address"))
|
|
|
|
return
|
2018-02-02 14:44:52 +01:00
|
|
|
}
|
2018-02-03 12:38:28 +01:00
|
|
|
}
|
2018-02-02 14:44:52 +01:00
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
// get and sanitise account name
|
|
|
|
account := strings.TrimSpace(username)
|
|
|
|
|
|
|
|
err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, client.certfp)
|
2018-02-11 11:30:40 +01:00
|
|
|
if err == nil {
|
2018-02-20 10:20:30 +01:00
|
|
|
if callbackNamespace == "*" {
|
|
|
|
err = server.accounts.Verify(client, account, "")
|
|
|
|
if err == nil {
|
|
|
|
sendSuccessfulRegResponse(client, rb, true)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s:%s")
|
|
|
|
message := fmt.Sprintf(messageTemplate, callbackNamespace, callbackValue)
|
|
|
|
rb.Notice(message)
|
|
|
|
}
|
2018-02-11 11:30:40 +01:00
|
|
|
}
|
2018-02-02 14:44:52 +01:00
|
|
|
|
2018-02-03 12:38:28 +01:00
|
|
|
// details could not be stored and relevant numerics have been dispatched, abort
|
|
|
|
if err != nil {
|
|
|
|
errMsg := "Could not register"
|
|
|
|
if err == errCertfpAlreadyExists {
|
|
|
|
errMsg = "An account already exists for your certificate fingerprint"
|
2018-02-11 11:30:40 +01:00
|
|
|
} else if err == errAccountAlreadyRegistered {
|
|
|
|
errMsg = "Account already exists"
|
2018-02-03 12:38:28 +01:00
|
|
|
}
|
2018-02-11 11:30:40 +01:00
|
|
|
rb.Notice(client.t(errMsg))
|
2018-02-03 12:38:28 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-02-02 14:44:52 +01:00
|
|
|
|
2018-02-05 15:21:08 +01:00
|
|
|
func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string, rb *ResponseBuffer) {
|
2018-02-03 12:38:28 +01:00
|
|
|
// fail out if we need to
|
2018-02-11 11:30:40 +01:00
|
|
|
if !server.AccountConfig().AuthenticationEnabled {
|
2018-02-05 15:21:08 +01:00
|
|
|
rb.Notice(client.t("Login has been disabled"))
|
2018-02-03 12:38:28 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
loginSuccessful := false
|
|
|
|
|
2018-02-03 12:38:28 +01:00
|
|
|
// try passphrase
|
|
|
|
if username != "" && passphrase != "" {
|
2018-02-20 10:20:30 +01:00
|
|
|
err := server.accounts.AuthenticateByPassphrase(client, username, passphrase)
|
|
|
|
loginSuccessful = (err == nil)
|
2018-02-03 12:38:28 +01:00
|
|
|
}
|
2018-02-02 14:44:52 +01:00
|
|
|
|
2018-02-03 12:38:28 +01:00
|
|
|
// try certfp
|
2018-02-20 10:20:30 +01:00
|
|
|
if !loginSuccessful && client.certfp != "" {
|
2018-02-11 11:30:40 +01:00
|
|
|
err := server.accounts.AuthenticateByCertFP(client)
|
2018-02-20 10:20:30 +01:00
|
|
|
loginSuccessful = (err == nil)
|
2018-02-02 14:44:52 +01:00
|
|
|
}
|
2018-02-03 12:38:28 +01:00
|
|
|
|
2018-02-20 10:20:30 +01:00
|
|
|
if loginSuccessful {
|
|
|
|
sendSuccessfulSaslAuth(client, rb, true)
|
|
|
|
} else {
|
|
|
|
rb.Notice(client.t("Could not login with your TLS certificate or supplied username/password"))
|
|
|
|
}
|
2017-03-11 13:01:40 +01:00
|
|
|
}
|