3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-10 22:19:31 +01:00
ergo/irc/nickserv.go

355 lines
11 KiB
Go
Raw Normal View History

// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
"fmt"
"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.
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]
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
To unregister an account:
/NS UNREGISTER [username]
Leave out [username] if you're unregistering the user you're currently logged in as.
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.
To see account information:
/NS INFO [username]
Leave out [username] to see your own account information.
To associate your current nick with the account you're logged into:
/NS GROUP
To disassociate a nick with the account you're logged into:
/NS DROP [nickname]
Leave out [nickname] to drop your association with your current nickname.`
// 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) {
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)
}
} 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)
} else if command == "identify" {
username, passphrase := extractParam(params)
2018-02-05 15:21:08 +01:00
server.nickservIdentifyHandler(client, username, passphrase, rb)
} else if command == "unregister" {
username, _ := extractParam(params)
server.nickservUnregisterHandler(client, username, rb)
} else if command == "ghost" {
nick, _ := extractParam(params)
server.nickservGhostHandler(client, nick, rb)
} else if command == "info" {
nick, _ := extractParam(params)
server.nickservInfoHandler(client, nick, rb)
} else if command == "group" {
server.nickservGroupHandler(client, rb)
} else if command == "drop" {
nick, _ := extractParam(params)
server.nickservDropHandler(client, nick, rb)
} else {
2018-02-05 15:21:08 +01:00
rb.Notice(client.t("Command not recognised. To see the available commands, run /NS HELP"))
}
}
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)
}
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))
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) {
if !server.AccountConfig().Registration.Enabled {
2018-02-05 15:21:08 +01:00
rb.Notice(client.t("Account registration has been disabled"))
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
}
if client.LoggedIntoAccount() {
if server.AccountConfig().Registration.AllowMultiplePerConnection {
server.accounts.Logout(client)
} else {
2018-02-05 15:21:08 +01:00
rb.Notice(client.t("You're already logged into an account"))
return
}
}
2018-02-20 10:20:30 +01:00
config := server.AccountConfig()
var callbackNamespace, callbackValue string
noneCallbackAllowed := false
for _, callback := range config.Registration.EnabledCallbacks {
2018-02-20 10:20:30 +01:00
if callback == "*" {
noneCallbackAllowed = true
}
}
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-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)
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)
}
}
// 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"
} else if err == errAccountAlreadyRegistered {
errMsg = "Account already exists"
}
rb.Notice(client.t(errMsg))
return
}
}
2018-02-05 15:21:08 +01:00
func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string, rb *ResponseBuffer) {
// fail out if we need to
if !server.AccountConfig().AuthenticationEnabled {
2018-02-05 15:21:08 +01:00
rb.Notice(client.t("Login has been disabled"))
return
}
2018-02-20 10:20:30 +01:00
loginSuccessful := false
// try passphrase
if username != "" && passphrase != "" {
2018-02-20 10:20:30 +01:00
err := server.accounts.AuthenticateByPassphrase(client, username, passphrase)
loginSuccessful = (err == nil)
}
// try certfp
2018-02-20 10:20:30 +01:00
if !loginSuccessful && client.certfp != "" {
err := server.accounts.AuthenticateByCertFP(client)
2018-02-20 10:20:30 +01:00
loginSuccessful = (err == nil)
}
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"))
}
}
func (server *Server) nickservGhostHandler(client *Client, nick string, rb *ResponseBuffer) {
if !server.AccountConfig().NickReservation.Enabled {
rb.Notice(client.t("Nickname reservation is disabled"))
return
}
account := client.Account()
if account == "" || server.accounts.NickToAccount(nick) != account {
rb.Notice(client.t("You don't own that nick"))
return
}
ghost := server.clients.Get(nick)
if ghost == nil {
rb.Notice(client.t("No such nick"))
return
} else if ghost == client {
rb.Notice(client.t("You can't GHOST yourself (try /QUIT instead)"))
return
}
ghost.Quit(fmt.Sprintf(ghost.t("GHOSTed by %s"), client.Nick()))
ghost.destroy(false)
}
func (server *Server) nickservGroupHandler(client *Client, rb *ResponseBuffer) {
if !server.AccountConfig().NickReservation.Enabled {
rb.Notice(client.t("Nickname reservation is disabled"))
return
}
account := client.Account()
if account == "" {
rb.Notice(client.t("You're not logged into an account"))
return
}
nick := client.NickCasefolded()
err := server.accounts.SetNickReserved(client, nick, true)
if err == nil {
rb.Notice(fmt.Sprintf(client.t("Successfully grouped nick %s with your account"), nick))
} else if err == errAccountTooManyNicks {
rb.Notice(client.t("You have too many nicks reserved already (you can remove some with /NS DROP)"))
} else if err == errNicknameReserved {
rb.Notice(client.t("That nickname is already reserved"))
} else {
rb.Notice(client.t("Error reserving nickname"))
}
}
func (server *Server) nickservInfoHandler(client *Client, nick string, rb *ResponseBuffer) {
if nick == "" {
nick = client.Nick()
}
accountName := nick
if server.AccountConfig().NickReservation.Enabled {
accountName = server.accounts.NickToAccount(nick)
if accountName == "" {
rb.Notice(client.t("That nickname is not registered"))
return
}
}
account, err := server.accounts.LoadAccount(accountName)
if err != nil || !account.Verified {
rb.Notice(client.t("Account does not exist"))
}
rb.Notice(fmt.Sprintf(client.t("Account: %s"), account.Name))
registeredAt := account.RegisteredAt.Format("Jan 02, 2006 15:04:05Z")
rb.Notice(fmt.Sprintf(client.t("Registered at: %s"), registeredAt))
// TODO nicer formatting for this
for _, nick := range account.AdditionalNicks {
rb.Notice(fmt.Sprintf(client.t("Additional grouped nick: %s"), nick))
}
}
func (server *Server) nickservDropHandler(client *Client, nick string, rb *ResponseBuffer) {
account := client.Account()
if account == "" {
rb.Notice(client.t("You're not logged into an account"))
return
}
err := server.accounts.SetNickReserved(client, nick, false)
if err == nil {
rb.Notice(fmt.Sprintf(client.t("Successfully ungrouped nick %s with your account"), nick))
} else if err == errAccountCantDropPrimaryNick {
rb.Notice(fmt.Sprintf(client.t("You can't ungroup your primary nickname (try unregistering your account instead)")))
} else if err == errAccountNickReservationFailed {
rb.Notice(fmt.Sprintf(client.t("You don't own that nick")))
} else {
rb.Notice(client.t("Error ungrouping nick"))
}
}