From 4df71df45420d25a862727292c4ce0fb0ea4cb0c Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Mon, 8 Apr 2019 10:40:19 +1000 Subject: [PATCH 1/4] Initial ACC update to match the new spec --- gencapdefs.py | 6 + irc/caps/defs.go | 7 +- irc/commands.go | 5 +- irc/handlers.go | 128 +++++++++++------ irc/nickserv.go | 5 +- irc/numerics.go | 364 +++++++++++++++++++++++------------------------ irc/server.go | 15 -- 7 files changed, 283 insertions(+), 247 deletions(-) diff --git a/gencapdefs.py b/gencapdefs.py index 90ac244f..a15b6052 100644 --- a/gencapdefs.py +++ b/gencapdefs.py @@ -15,6 +15,12 @@ from collections import namedtuple CapDef = namedtuple("CapDef", ['identifier', 'name', 'url', 'standard']) CAPDEFS = [ + CapDef( + identifier="Acc", + name="draft/acc", + url="https://github.com/ircv3/ircv3-specifications/pull/276", + standard="proposed IRCv3", + ), CapDef( identifier="AccountNotify", name="account-notify", diff --git a/irc/caps/defs.go b/irc/caps/defs.go index 2d8b783b..ee336635 100644 --- a/irc/caps/defs.go +++ b/irc/caps/defs.go @@ -7,12 +7,16 @@ package caps const ( // number of recognized capabilities: - numCapabs = 21 + numCapabs = 22 // length of the uint64 array that represents the bitset: bitsetLen = 1 ) const ( + // Acc is the proposed IRCv3 capability named "draft/acc": + // https://github.com/ircv3/ircv3-specifications/pull/276 + Acc Capability = iota + // AccountNotify is the IRCv3 capability named "account-notify": // https://ircv3.net/specs/extensions/account-notify-3.1.html AccountNotify Capability = iota @@ -101,6 +105,7 @@ const ( // `capabilityNames[capab]` is the string name of the capability `capab` var ( capabilityNames = [numCapabs]string{ + "draft/acc", "account-notify", "account-tag", "away-notify", diff --git a/irc/commands.go b/irc/commands.go index 0aa6c575..af1aea57 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -71,8 +71,9 @@ var Commands map[string]Command func init() { Commands = map[string]Command{ "ACC": { - handler: accHandler, - minParams: 3, + handler: accHandler, + usablePreReg: true, + minParams: 1, }, "AMBIANCE": { handler: sceneHandler, diff --git a/irc/handlers.go b/irc/handlers.go index bddefc07..d93313fe 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -31,15 +31,41 @@ import ( "golang.org/x/crypto/bcrypt" ) -// ACC [REGISTER|VERIFY] ... +// ACC [LS|REGISTER|VERIFY] ... func accHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - // make sure reg is enabled - if !server.AccountConfig().Registration.Enabled { - rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("Account registration is disabled")) + subcommand := strings.ToLower(msg.Params[0]) + + if subcommand == "ls" { + config := server.Config().Accounts + + rb.Add(nil, server.name, "ACC", "LS", "SUBCOMMANDS", "LS REGISTER VERIFY") + + var enabledCallbacks []string + for _, name := range config.Registration.EnabledCallbacks { + enabledCallbacks = append(enabledCallbacks, name) + } + sort.Strings(enabledCallbacks) + rb.Add(nil, server.name, "ACC", "LS", "CALLBACKS", strings.Join(enabledCallbacks, " ")) + + rb.Add(nil, server.name, "ACC", "LS", "CREDTYPES", "passphrase certfp") + + if config.NickReservation.Enabled { + rb.Add(nil, server.name, "ACC", "LS", "FLAGS", "regnick") + } return false } - subcommand := strings.ToLower(msg.Params[0]) + // disallow account stuff before connection registration has completed, for now + if !client.Registered() { + client.Send(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command")) + return false + } + + // make sure reg is enabled + if !server.AccountConfig().Registration.Enabled { + rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNAVAILABLE", client.t("Account registration is disabled")) + return false + } if subcommand == "register" { return accRegisterHandler(server, client, msg, rb) @@ -61,7 +87,7 @@ func parseCallback(spec string, config *AccountConfig) (callbackNamespace string callbackValues := strings.SplitN(callback, ":", 2) callbackNamespace, callbackValue = callbackValues[0], callbackValues[1] } else { - // "the IRC server MAY choose to use mailto as a default" + // "If a callback namespace is not ... provided, the IRC server MUST use mailto"" callbackNamespace = "mailto" callbackValue = callback } @@ -81,31 +107,43 @@ func parseCallback(spec string, config *AccountConfig) (callbackNamespace string // ACC REGISTER [callback_namespace:] [cred_type] : func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { nick := client.Nick() - // clients can't reg new accounts if they're already logged in - if client.LoggedIntoAccount() { - rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, nick, "*", client.t("You're already logged into an account")) - return false - } - - // get and sanitise account name - account := strings.TrimSpace(msg.Params[1]) - casefoldedAccount, err := CasefoldName(account) - // probably don't need explicit check for "*" here... but let's do it anyway just to make sure - if err != nil || msg.Params[1] == "*" { - rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, nick, account, client.t("Account name is not valid")) - return false - } if len(msg.Params) < 4 { rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, nick, msg.Command, client.t("Not enough parameters")) return false } + account := strings.TrimSpace(msg.Params[1]) + + // check for account name of * + if account == "*" { + account = nick + } else { + if server.Config().Accounts.NickReservation.Enabled { + rb.Add(nil, server.name, "FAIL", "ACC", "REG_MUST_USE_REGNICK", account, client.t("Must register with current nickname instead of separate account name")) + return false + } + } + + // clients can't reg new accounts if they're already logged in + if client.LoggedIntoAccount() { + rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNSPECIFIED_ERROR", account, client.t("You're already logged into an account")) + return false + } + + // sanitise account name + casefoldedAccount, err := CasefoldName(account) + // probably don't need explicit check for "*" here... but let's do it anyway just to make sure + if err != nil || msg.Params[1] == "*" { + rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_ACCOUNT_NAME", account, client.t("Account name is not valid")) + return false + } + callbackSpec := msg.Params[2] callbackNamespace, callbackValue := parseCallback(callbackSpec, server.AccountConfig()) if callbackNamespace == "" { - rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, nick, account, callbackSpec, client.t("Callback namespace is not supported")) + rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CALLBACK", account, callbackSpec, client.t("Cannot send verification code there")) return false } @@ -129,12 +167,12 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r } } if credentialType == "certfp" && client.certfp == "" { - rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, nick, credentialType, callbackNamespace, client.t("You are not using a TLS certificate")) + rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CRED_TYPE", account, credentialType, client.t("You are not using a TLS certificate")) return false } if !credentialValid { - rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, nick, credentialType, callbackNamespace, client.t("Credential type is not supported")) + rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CRED_TYPE", account, credentialType, client.t("Credential type is not supported")) return false } @@ -147,14 +185,14 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r throttled, remainingTime := client.loginThrottle.Touch() if throttled { - rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, nick, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime)) + rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNSPECIFIED_ERROR", account, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime)) return false } err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp) if err != nil { - msg, code := registrationErrorToMessageAndCode(err) - rb.Add(nil, server.name, code, nick, "ACC", "REGISTER", client.t(msg)) + msg := registrationErrorToMessageAndCode(err) + rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNSPECIFIED_ERROR", account, client.t(msg)) return false } @@ -174,15 +212,13 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r return false } -func registrationErrorToMessageAndCode(err error) (message, numeric string) { +func registrationErrorToMessageAndCode(err error) (message string) { // default responses: let's be risk-averse about displaying internal errors // to the clients, especially for something as sensitive as accounts message = `Could not register` - numeric = ERR_UNKNOWNERROR switch err { case errAccountAlreadyRegistered, errAccountAlreadyVerified: message = err.Error() - numeric = ERR_ACCOUNT_ALREADY_EXISTS case errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled: message = err.Error() } @@ -194,20 +230,23 @@ func sendSuccessfulRegResponse(client *Client, rb *ResponseBuffer, forNS bool) { if forNS { rb.Notice(client.t("Account created")) } else { - rb.Add(nil, client.server.name, RPL_REGISTRATION_SUCCESS, client.nick, client.AccountName(), client.t("Account created")) + rb.Add(nil, client.server.name, RPL_REG_SUCCESS, client.nick, client.AccountName(), client.t("Account created")) } - sendSuccessfulSaslAuth(client, rb, forNS) + sendSuccessfulAccountAuth(client, rb, forNS, false) } -// sendSuccessfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages. -func sendSuccessfulSaslAuth(client *Client, rb *ResponseBuffer, forNS bool) { +// sendSuccessfulAccountAuth means that an account auth attempt completed successfully, and is used to dispatch messages. +func sendSuccessfulAccountAuth(client *Client, rb *ResponseBuffer, forNS, forSASL bool) { details := client.Details() if forNS { rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), details.accountName)) } else { + //TODO(dan): some servers send this numeric even for NickServ logins iirc? to confirm and maybe do too rb.Add(nil, client.server.name, RPL_LOGGEDIN, details.nick, details.nickMask, details.accountName, fmt.Sprintf(client.t("You are now logged in as %s"), details.accountName)) - rb.Add(nil, client.server.name, RPL_SASLSUCCESS, details.nick, client.t("Authentication successful")) + if forSASL { + rb.Add(nil, client.server.name, RPL_SASLSUCCESS, details.nick, client.t("Authentication successful")) + } } // dispatch account-notify @@ -223,26 +262,33 @@ func sendSuccessfulSaslAuth(client *Client, rb *ResponseBuffer, forNS bool) { // ACC VERIFY func accVerifyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { account := strings.TrimSpace(msg.Params[1]) + + if len(msg.Params) < 3 { + rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters")) + return false + } + err := server.accounts.Verify(client, account, msg.Params[2]) var code string var message string if err == errAccountVerificationInvalidCode { - code = ERR_ACCOUNT_INVALID_VERIFY_CODE + code = "ACCOUNT_INVALID_VERIFY_CODE" message = err.Error() } else if err == errAccountAlreadyVerified { - code = ERR_ACCOUNT_ALREADY_VERIFIED + code = "ACCOUNT_ALREADY_VERIFIED" message = err.Error() } else if err != nil { - code = ERR_UNKNOWNERROR + code = "VERIFY_UNSPECIFIED_ERROR" message = errAccountVerificationFailed.Error() } if err == nil { - sendSuccessfulRegResponse(client, rb, false) + rb.Add(nil, server.name, RPL_VERIFY_SUCCESS, client.Nick(), account, client.t("Account verification successful")) + sendSuccessfulAccountAuth(client, rb, false, false) } else { - rb.Add(nil, server.name, code, client.Nick(), account, client.t(message)) + rb.Add(nil, server.name, "FAIL", "ACC", code, account, client.t(message)) } return false @@ -373,7 +419,7 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value [] return false } - sendSuccessfulSaslAuth(client, rb, false) + sendSuccessfulAccountAuth(client, rb, false, true) return false } @@ -401,7 +447,7 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value return false } - sendSuccessfulSaslAuth(client, rb, false) + sendSuccessfulAccountAuth(client, rb, false, true) return false } diff --git a/irc/nickserv.go b/irc/nickserv.go index e873e8c9..2a991836 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -295,7 +295,7 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params [] } if loginSuccessful { - sendSuccessfulSaslAuth(client, rb, true) + sendSuccessfulAccountAuth(client, rb, true, true) } else { nsNotice(rb, client.t("Could not login with your TLS certificate or supplied username/password")) } @@ -407,8 +407,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] // details could not be stored and relevant numerics have been dispatched, abort if err != nil { - errMsg, _ := registrationErrorToMessageAndCode(err) - nsNotice(rb, errMsg) + nsNotice(rb, client.t(registrationErrorToMessageAndCode(err))) return } } diff --git a/irc/numerics.go b/irc/numerics.go index dc5d436e..2a87256e 100644 --- a/irc/numerics.go +++ b/irc/numerics.go @@ -12,191 +12,185 @@ package irc // server ecosystem out there. Custom numerics will be marked as such. const ( - RPL_WELCOME = "001" - RPL_YOURHOST = "002" - RPL_CREATED = "003" - RPL_MYINFO = "004" - RPL_ISUPPORT = "005" - RPL_SNOMASKIS = "008" - RPL_BOUNCE = "010" - RPL_TRACELINK = "200" - RPL_TRACECONNECTING = "201" - RPL_TRACEHANDSHAKE = "202" - RPL_TRACEUNKNOWN = "203" - RPL_TRACEOPERATOR = "204" - RPL_TRACEUSER = "205" - RPL_TRACESERVER = "206" - RPL_TRACESERVICE = "207" - RPL_TRACENEWTYPE = "208" - RPL_TRACECLASS = "209" - RPL_TRACERECONNECT = "210" - RPL_STATSLINKINFO = "211" - RPL_STATSCOMMANDS = "212" - RPL_ENDOFSTATS = "219" - RPL_UMODEIS = "221" - RPL_SERVLIST = "234" - RPL_SERVLISTEND = "235" - RPL_STATSUPTIME = "242" - RPL_STATSOLINE = "243" - RPL_LUSERCLIENT = "251" - RPL_LUSEROP = "252" - RPL_LUSERUNKNOWN = "253" - RPL_LUSERCHANNELS = "254" - RPL_LUSERME = "255" - RPL_ADMINME = "256" - RPL_ADMINLOC1 = "257" - RPL_ADMINLOC2 = "258" - RPL_ADMINEMAIL = "259" - RPL_TRACELOG = "261" - RPL_TRACEEND = "262" - RPL_TRYAGAIN = "263" - RPL_WHOISCERTFP = "276" - RPL_AWAY = "301" - RPL_USERHOST = "302" - RPL_ISON = "303" - RPL_UNAWAY = "305" - RPL_NOWAWAY = "306" - RPL_WHOISUSER = "311" - RPL_WHOISSERVER = "312" - RPL_WHOISOPERATOR = "313" - RPL_WHOWASUSER = "314" - RPL_ENDOFWHO = "315" - RPL_WHOISIDLE = "317" - RPL_ENDOFWHOIS = "318" - RPL_WHOISCHANNELS = "319" - RPL_LIST = "322" - RPL_LISTEND = "323" - RPL_CHANNELMODEIS = "324" - RPL_UNIQOPIS = "325" - RPL_CHANNELCREATED = "329" - RPL_WHOISACCOUNT = "330" - RPL_NOTOPIC = "331" - RPL_TOPIC = "332" - RPL_TOPICTIME = "333" - RPL_WHOISBOT = "335" - RPL_WHOISACTUALLY = "338" - RPL_INVITING = "341" - RPL_SUMMONING = "342" - RPL_INVITELIST = "346" - RPL_ENDOFINVITELIST = "347" - RPL_EXCEPTLIST = "348" - RPL_ENDOFEXCEPTLIST = "349" - RPL_VERSION = "351" - RPL_WHOREPLY = "352" - RPL_NAMREPLY = "353" - RPL_LINKS = "364" - RPL_ENDOFLINKS = "365" - RPL_ENDOFNAMES = "366" - RPL_BANLIST = "367" - RPL_ENDOFBANLIST = "368" - RPL_ENDOFWHOWAS = "369" - RPL_INFO = "371" - RPL_MOTD = "372" - RPL_ENDOFINFO = "374" - RPL_MOTDSTART = "375" - RPL_ENDOFMOTD = "376" - RPL_YOUREOPER = "381" - RPL_REHASHING = "382" - RPL_YOURESERVICE = "383" - RPL_TIME = "391" - RPL_USERSSTART = "392" - RPL_USERS = "393" - RPL_ENDOFUSERS = "394" - RPL_NOUSERS = "395" - ERR_UNKNOWNERROR = "400" - ERR_NOSUCHNICK = "401" - ERR_NOSUCHSERVER = "402" - ERR_NOSUCHCHANNEL = "403" - ERR_CANNOTSENDTOCHAN = "404" - ERR_TOOMANYCHANNELS = "405" - ERR_WASNOSUCHNICK = "406" - ERR_TOOMANYTARGETS = "407" - ERR_NOSUCHSERVICE = "408" - ERR_NOORIGIN = "409" - ERR_INVALIDCAPCMD = "410" - ERR_NORECIPIENT = "411" - ERR_NOTEXTTOSEND = "412" - ERR_NOTOPLEVEL = "413" - ERR_WILDTOPLEVEL = "414" - ERR_BADMASK = "415" - ERR_INPUTTOOLONG = "417" - ERR_UNKNOWNCOMMAND = "421" - ERR_NOMOTD = "422" - ERR_NOADMININFO = "423" - ERR_FILEERROR = "424" - ERR_NONICKNAMEGIVEN = "431" - ERR_ERRONEUSNICKNAME = "432" - ERR_NICKNAMEINUSE = "433" - ERR_NICKCOLLISION = "436" - ERR_UNAVAILRESOURCE = "437" - ERR_REG_UNAVAILABLE = "440" - ERR_USERNOTINCHANNEL = "441" - ERR_NOTONCHANNEL = "442" - ERR_USERONCHANNEL = "443" - ERR_NOLOGIN = "444" - ERR_SUMMONDISABLED = "445" - ERR_USERSDISABLED = "446" - ERR_NOTREGISTERED = "451" - ERR_NEEDMOREPARAMS = "461" - ERR_ALREADYREGISTRED = "462" - ERR_NOPERMFORHOST = "463" - ERR_PASSWDMISMATCH = "464" - ERR_YOUREBANNEDCREEP = "465" - ERR_YOUWILLBEBANNED = "466" - ERR_KEYSET = "467" - ERR_INVALIDUSERNAME = "468" - ERR_CHANNELISFULL = "471" - ERR_UNKNOWNMODE = "472" - ERR_INVITEONLYCHAN = "473" - ERR_BANNEDFROMCHAN = "474" - ERR_BADCHANNELKEY = "475" - ERR_BADCHANMASK = "476" - ERR_NOCHANMODES = "477" - ERR_BANLISTFULL = "478" - ERR_NOPRIVILEGES = "481" - ERR_CHANOPRIVSNEEDED = "482" - ERR_CANTKILLSERVER = "483" - ERR_RESTRICTED = "484" - ERR_UNIQOPPRIVSNEEDED = "485" - ERR_NOOPERHOST = "491" - ERR_UMODEUNKNOWNFLAG = "501" - ERR_USERSDONTMATCH = "502" - ERR_HELPNOTFOUND = "524" - ERR_CANNOTSENDRP = "573" - RPL_WHOISSECURE = "671" - RPL_YOURLANGUAGESARE = "687" - RPL_WHOISLANGUAGE = "690" - ERR_CHANNAMEINUSE = "692" - ERR_CANNOTRENAME = "693" - RPL_HELPSTART = "704" - RPL_HELPTXT = "705" - RPL_ENDOFHELP = "706" - ERR_NOPRIVS = "723" - RPL_MONONLINE = "730" - RPL_MONOFFLINE = "731" - RPL_MONLIST = "732" - RPL_ENDOFMONLIST = "733" - ERR_MONLISTFULL = "734" - RPL_LOGGEDIN = "900" - RPL_LOGGEDOUT = "901" - ERR_NICKLOCKED = "902" - RPL_SASLSUCCESS = "903" - ERR_SASLFAIL = "904" - ERR_SASLTOOLONG = "905" - ERR_SASLABORTED = "906" - ERR_SASLALREADY = "907" - RPL_SASLMECHS = "908" - RPL_REGISTRATION_SUCCESS = "920" - ERR_ACCOUNT_ALREADY_EXISTS = "921" - ERR_REG_UNSPECIFIED_ERROR = "922" - RPL_VERIFYSUCCESS = "923" - ERR_ACCOUNT_ALREADY_VERIFIED = "924" - ERR_ACCOUNT_INVALID_VERIFY_CODE = "925" - RPL_REG_VERIFICATION_REQUIRED = "927" - ERR_REG_INVALID_CRED_TYPE = "928" - ERR_REG_INVALID_CALLBACK = "929" - ERR_TOOMANYLANGUAGES = "981" - ERR_NOLANGUAGE = "982" + RPL_WELCOME = "001" + RPL_YOURHOST = "002" + RPL_CREATED = "003" + RPL_MYINFO = "004" + RPL_ISUPPORT = "005" + RPL_SNOMASKIS = "008" + RPL_BOUNCE = "010" + RPL_TRACELINK = "200" + RPL_TRACECONNECTING = "201" + RPL_TRACEHANDSHAKE = "202" + RPL_TRACEUNKNOWN = "203" + RPL_TRACEOPERATOR = "204" + RPL_TRACEUSER = "205" + RPL_TRACESERVER = "206" + RPL_TRACESERVICE = "207" + RPL_TRACENEWTYPE = "208" + RPL_TRACECLASS = "209" + RPL_TRACERECONNECT = "210" + RPL_STATSLINKINFO = "211" + RPL_STATSCOMMANDS = "212" + RPL_ENDOFSTATS = "219" + RPL_UMODEIS = "221" + RPL_SERVLIST = "234" + RPL_SERVLISTEND = "235" + RPL_STATSUPTIME = "242" + RPL_STATSOLINE = "243" + RPL_LUSERCLIENT = "251" + RPL_LUSEROP = "252" + RPL_LUSERUNKNOWN = "253" + RPL_LUSERCHANNELS = "254" + RPL_LUSERME = "255" + RPL_ADMINME = "256" + RPL_ADMINLOC1 = "257" + RPL_ADMINLOC2 = "258" + RPL_ADMINEMAIL = "259" + RPL_TRACELOG = "261" + RPL_TRACEEND = "262" + RPL_TRYAGAIN = "263" + RPL_WHOISCERTFP = "276" + RPL_AWAY = "301" + RPL_USERHOST = "302" + RPL_ISON = "303" + RPL_UNAWAY = "305" + RPL_NOWAWAY = "306" + RPL_WHOISUSER = "311" + RPL_WHOISSERVER = "312" + RPL_WHOISOPERATOR = "313" + RPL_WHOWASUSER = "314" + RPL_ENDOFWHO = "315" + RPL_WHOISIDLE = "317" + RPL_ENDOFWHOIS = "318" + RPL_WHOISCHANNELS = "319" + RPL_LIST = "322" + RPL_LISTEND = "323" + RPL_CHANNELMODEIS = "324" + RPL_UNIQOPIS = "325" + RPL_CHANNELCREATED = "329" + RPL_WHOISACCOUNT = "330" + RPL_NOTOPIC = "331" + RPL_TOPIC = "332" + RPL_TOPICTIME = "333" + RPL_WHOISBOT = "335" + RPL_WHOISACTUALLY = "338" + RPL_INVITING = "341" + RPL_SUMMONING = "342" + RPL_INVITELIST = "346" + RPL_ENDOFINVITELIST = "347" + RPL_EXCEPTLIST = "348" + RPL_ENDOFEXCEPTLIST = "349" + RPL_VERSION = "351" + RPL_WHOREPLY = "352" + RPL_NAMREPLY = "353" + RPL_LINKS = "364" + RPL_ENDOFLINKS = "365" + RPL_ENDOFNAMES = "366" + RPL_BANLIST = "367" + RPL_ENDOFBANLIST = "368" + RPL_ENDOFWHOWAS = "369" + RPL_INFO = "371" + RPL_MOTD = "372" + RPL_ENDOFINFO = "374" + RPL_MOTDSTART = "375" + RPL_ENDOFMOTD = "376" + RPL_YOUREOPER = "381" + RPL_REHASHING = "382" + RPL_YOURESERVICE = "383" + RPL_TIME = "391" + RPL_USERSSTART = "392" + RPL_USERS = "393" + RPL_ENDOFUSERS = "394" + RPL_NOUSERS = "395" + ERR_UNKNOWNERROR = "400" + ERR_NOSUCHNICK = "401" + ERR_NOSUCHSERVER = "402" + ERR_NOSUCHCHANNEL = "403" + ERR_CANNOTSENDTOCHAN = "404" + ERR_TOOMANYCHANNELS = "405" + ERR_WASNOSUCHNICK = "406" + ERR_TOOMANYTARGETS = "407" + ERR_NOSUCHSERVICE = "408" + ERR_NOORIGIN = "409" + ERR_INVALIDCAPCMD = "410" + ERR_NORECIPIENT = "411" + ERR_NOTEXTTOSEND = "412" + ERR_NOTOPLEVEL = "413" + ERR_WILDTOPLEVEL = "414" + ERR_BADMASK = "415" + ERR_INPUTTOOLONG = "417" + ERR_UNKNOWNCOMMAND = "421" + ERR_NOMOTD = "422" + ERR_NOADMININFO = "423" + ERR_FILEERROR = "424" + ERR_NONICKNAMEGIVEN = "431" + ERR_ERRONEUSNICKNAME = "432" + ERR_NICKNAMEINUSE = "433" + ERR_NICKCOLLISION = "436" + ERR_UNAVAILRESOURCE = "437" + ERR_REG_UNAVAILABLE = "440" + ERR_USERNOTINCHANNEL = "441" + ERR_NOTONCHANNEL = "442" + ERR_USERONCHANNEL = "443" + ERR_NOLOGIN = "444" + ERR_SUMMONDISABLED = "445" + ERR_USERSDISABLED = "446" + ERR_NOTREGISTERED = "451" + ERR_NEEDMOREPARAMS = "461" + ERR_ALREADYREGISTRED = "462" + ERR_NOPERMFORHOST = "463" + ERR_PASSWDMISMATCH = "464" + ERR_YOUREBANNEDCREEP = "465" + ERR_YOUWILLBEBANNED = "466" + ERR_KEYSET = "467" + ERR_INVALIDUSERNAME = "468" + ERR_CHANNELISFULL = "471" + ERR_UNKNOWNMODE = "472" + ERR_INVITEONLYCHAN = "473" + ERR_BANNEDFROMCHAN = "474" + ERR_BADCHANNELKEY = "475" + ERR_BADCHANMASK = "476" + ERR_NOCHANMODES = "477" + ERR_BANLISTFULL = "478" + ERR_NOPRIVILEGES = "481" + ERR_CHANOPRIVSNEEDED = "482" + ERR_CANTKILLSERVER = "483" + ERR_RESTRICTED = "484" + ERR_UNIQOPPRIVSNEEDED = "485" + ERR_NOOPERHOST = "491" + ERR_UMODEUNKNOWNFLAG = "501" + ERR_USERSDONTMATCH = "502" + ERR_HELPNOTFOUND = "524" + ERR_CANNOTSENDRP = "573" + RPL_WHOISSECURE = "671" + RPL_YOURLANGUAGESARE = "687" + RPL_WHOISLANGUAGE = "690" + ERR_CHANNAMEINUSE = "692" + ERR_CANNOTRENAME = "693" + RPL_HELPSTART = "704" + RPL_HELPTXT = "705" + RPL_ENDOFHELP = "706" + ERR_NOPRIVS = "723" + RPL_MONONLINE = "730" + RPL_MONOFFLINE = "731" + RPL_MONLIST = "732" + RPL_ENDOFMONLIST = "733" + ERR_MONLISTFULL = "734" + RPL_LOGGEDIN = "900" + RPL_LOGGEDOUT = "901" + ERR_NICKLOCKED = "902" + RPL_SASLSUCCESS = "903" + ERR_SASLFAIL = "904" + ERR_SASLTOOLONG = "905" + ERR_SASLABORTED = "906" + ERR_SASLALREADY = "907" + RPL_SASLMECHS = "908" + RPL_REG_SUCCESS = "920" + RPL_VERIFY_SUCCESS = "923" + RPL_REG_VERIFICATION_REQUIRED = "927" + ERR_TOOMANYLANGUAGES = "981" + ERR_NOLANGUAGE = "982" // draft numerics // these haven't been assigned actual codes, so we use RPL_NONE's code (300), diff --git a/irc/server.go b/irc/server.go index d76ec136..a359907a 100644 --- a/irc/server.go +++ b/irc/server.go @@ -173,21 +173,6 @@ func (server *Server) setISupport() (err error) { isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen)) isupport.Add("UTF8MAPPING", casemappingName) - // account registration - if config.Accounts.Registration.Enabled { - // 'none' isn't shown in the REGCALLBACKS vars - var enabledCallbacks []string - for _, name := range server.config.Accounts.Registration.EnabledCallbacks { - if name != "*" { - enabledCallbacks = append(enabledCallbacks, name) - } - } - - isupport.Add("ACCCOMMANDS", "CREATE,VERIFY") - isupport.Add("REGCALLBACKS", strings.Join(enabledCallbacks, ",")) - isupport.Add("REGCREDTYPES", "passphrase,certfp") - } - err = isupport.RegenerateCachedReply() if err != nil { return From 8f47b3a6cf33846d5fe58b09ef09ab5a7072463a Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Mon, 8 Apr 2019 10:42:17 +1000 Subject: [PATCH 2/4] Actually advertise the draft/acc capability lol --- irc/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irc/server.go b/irc/server.go index a359907a..98c63a06 100644 --- a/irc/server.go +++ b/irc/server.go @@ -42,7 +42,7 @@ var ( // SupportedCapabilities are the caps we advertise. // MaxLine, SASL and STS are set during server startup. - SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.Languages, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.Resume, caps.ServerTime, caps.SetName, caps.UserhostInNames) + SupportedCapabilities = caps.NewSet(caps.Acc, caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.Languages, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.Resume, caps.ServerTime, caps.SetName, caps.UserhostInNames) // CapValues are the actual values we advertise to v3.2 clients. // actual values are set during server startup. From 7bcba602eca350718fc97a6b0ba5702dd2469710 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Mon, 8 Apr 2019 10:54:52 +1000 Subject: [PATCH 3/4] Don't check for * again, we handle that properly now --- irc/handlers.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/irc/handlers.go b/irc/handlers.go index d93313fe..9bfcb1fb 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -133,8 +133,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r // sanitise account name casefoldedAccount, err := CasefoldName(account) - // probably don't need explicit check for "*" here... but let's do it anyway just to make sure - if err != nil || msg.Params[1] == "*" { + if err != nil { rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_ACCOUNT_NAME", account, client.t("Account name is not valid")) return false } From 0b644065b79c63e63776179a955d6f7a84c85b42 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Mon, 8 Apr 2019 11:36:48 +1000 Subject: [PATCH 4/4] Review and spec updates --- irc/config.go | 2 ++ irc/handlers.go | 27 +++++++++++++++------------ irc/help.go | 3 ++- irc/nickserv.go | 3 ++- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/irc/config.go b/irc/config.go index 524c73c4..7a655dde 100644 --- a/irc/config.go +++ b/irc/config.go @@ -13,6 +13,7 @@ import ( "net" "os" "regexp" + "sort" "strings" "time" @@ -609,6 +610,7 @@ func LoadConfig(filename string) (config *Config, err error) { config.Accounts.Registration.EnabledCallbacks[i] = "*" } } + sort.Strings(config.Accounts.Registration.EnabledCallbacks) config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted) if err != nil { diff --git a/irc/handlers.go b/irc/handlers.go index 9bfcb1fb..399f54fa 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -40,18 +40,17 @@ func accHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo rb.Add(nil, server.name, "ACC", "LS", "SUBCOMMANDS", "LS REGISTER VERIFY") - var enabledCallbacks []string - for _, name := range config.Registration.EnabledCallbacks { - enabledCallbacks = append(enabledCallbacks, name) - } - sort.Strings(enabledCallbacks) - rb.Add(nil, server.name, "ACC", "LS", "CALLBACKS", strings.Join(enabledCallbacks, " ")) + // this list is sorted by the config loader, yay + rb.Add(nil, server.name, "ACC", "LS", "CALLBACKS", strings.Join(config.Registration.EnabledCallbacks, " ")) rb.Add(nil, server.name, "ACC", "LS", "CREDTYPES", "passphrase certfp") + flags := []string{"nospaces"} if config.NickReservation.Enabled { - rb.Add(nil, server.name, "ACC", "LS", "FLAGS", "regnick") + flags = append(flags, "regnick") } + sort.Strings(flags) + rb.Add(nil, server.name, "ACC", "LS", "FLAGS", strings.Join(flags, " ")) return false } @@ -113,7 +112,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r return false } - account := strings.TrimSpace(msg.Params[1]) + account := msg.Params[1] // check for account name of * if account == "*" { @@ -166,7 +165,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r } } if credentialType == "certfp" && client.certfp == "" { - rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CRED_TYPE", account, credentialType, client.t("You are not using a TLS certificate")) + rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CREDENTIAL", account, client.t("You must connect with a TLS client certificate to use certfp")) return false } @@ -190,8 +189,8 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp) if err != nil { - msg := registrationErrorToMessageAndCode(err) - rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNSPECIFIED_ERROR", account, client.t(msg)) + msg, code := registrationErrorToMessageAndCode(err) + rb.Add(nil, server.name, "FAIL", "ACC", code, account, client.t(msg)) return false } @@ -211,11 +210,15 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r return false } -func registrationErrorToMessageAndCode(err error) (message string) { +func registrationErrorToMessageAndCode(err error) (message, code string) { // default responses: let's be risk-averse about displaying internal errors // to the clients, especially for something as sensitive as accounts + code = "REG_UNSPECIFIED_ERROR" message = `Could not register` switch err { + case errAccountBadPassphrase: + code = "REG_INVALID_CREDENTIAL" + message = err.Error() case errAccountAlreadyRegistered, errAccountAlreadyVerified: message = err.Error() case errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled: diff --git a/irc/help.go b/irc/help.go index 5bbffa48..38bb62d5 100644 --- a/irc/help.go +++ b/irc/help.go @@ -97,7 +97,8 @@ For instance, this would set the kill, oper, account and xline snomasks on dan: var Help = map[string]HelpEntry{ // Commands "acc": { - text: `ACC REGISTER [callback_namespace:] [cred_type] : + text: `ACC LS +ACC REGISTER [callback_namespace:] [cred_type] : ACC VERIFY Used in account registration. See the relevant specs for more info: diff --git a/irc/nickserv.go b/irc/nickserv.go index 2a991836..bc5eaf3c 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -406,8 +406,9 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] } // details could not be stored and relevant numerics have been dispatched, abort + message, _ := registrationErrorToMessageAndCode(err) if err != nil { - nsNotice(rb, client.t(registrationErrorToMessageAndCode(err))) + nsNotice(rb, client.t(message)) return } }