diff --git a/default.yaml b/default.yaml index 063e736b..6415f072 100644 --- a/default.yaml +++ b/default.yaml @@ -335,7 +335,11 @@ server: # oragono will write files to disk under certain circumstances, e.g., # CPU profiling or data export. by default, these files will be written # to the working directory. set this to customize: - # output-path: "/home/oragono/out" + #output-path: "/home/oragono/out" + + # the hostname used by "services", e.g., NickServ, defaults to "localhost", + # e.g., `NickServ!NickServ@localhost`. uncomment this to override: + #override-services-hostname: "example.network" # account options accounts: diff --git a/irc/channel.go b/irc/channel.go index b2416d73..753f1220 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -1037,7 +1037,7 @@ func (channel *Channel) replayHistoryForResume(session *Session, after time.Time } if !complete && !session.resumeDetails.HistoryIncomplete { // warn here if we didn't warn already - rb.Add(nil, histServMask, "NOTICE", channel.Name(), session.client.t("Some additional message history may have been lost")) + rb.Add(nil, histservService.prefix, "NOTICE", channel.Name(), session.client.t("Some additional message history may have been lost")) } rb.Send(true) } @@ -1099,7 +1099,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I } else { message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName) } - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } case history.Part: if eventPlayback { @@ -1109,14 +1109,14 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I continue // #474 } message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message) - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } case history.Kick: if eventPlayback { rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "KICK", chname, item.Params[0], item.Message.Message) } else { message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message) - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } case history.Quit: if eventPlayback { @@ -1126,21 +1126,21 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I continue // #474 } message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message) - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } case history.Nick: if eventPlayback { rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "NICK", item.Params[0]) } else { message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0]) - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } case history.Topic: if eventPlayback { rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "TOPIC", chname, item.Message.Message) } else { message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message) - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } case history.Mode: params := make([]string, len(item.Message.Split)+1) @@ -1152,7 +1152,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "MODE", params...) } else { message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " ")) - rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message) } } } diff --git a/irc/chanserv.go b/irc/chanserv.go index ebfdc8b1..71c8d366 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -17,7 +17,6 @@ import ( ) const chanservHelp = `ChanServ lets you register and manage channels.` -const chanservMask = "ChanServ!ChanServ@localhost" func chanregEnabled(config *Config) bool { return config.Channels.Registration.Enabled @@ -188,27 +187,22 @@ SET modifies a channel's settings. The following settings are available:`, } ) -// csNotice sends the client a notice from ChanServ -func csNotice(rb *ResponseBuffer, text string) { - rb.Add(nil, chanservMask, "NOTICE", rb.target.Nick(), text) -} - -func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csAmodeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { channelName := params[0] channel := server.channels.Get(channelName) if channel == nil { - csNotice(rb, client.t("Channel does not exist")) + service.Notice(rb, client.t("Channel does not exist")) return } else if channel.Founder() == "" { - csNotice(rb, client.t("Channel is not registered")) + service.Notice(rb, client.t("Channel is not registered")) return } modeChanges, unknown := modes.ParseChannelModeChanges(params[1:]...) var change modes.ModeChange if len(modeChanges) > 1 || len(unknown) > 0 { - csNotice(rb, client.t("Invalid mode change")) + service.Notice(rb, client.t("Invalid mode change")) return } else if len(modeChanges) == 1 { change = modeChanges[0] @@ -233,17 +227,17 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str accountIsValid = (change.Arg != "") } if !accountIsValid { - csNotice(rb, client.t("Account does not exist")) + service.Notice(rb, client.t("Account does not exist")) return } affectedModes, err := channel.ProcessAccountToUmodeChange(client, change) if err == errInsufficientPrivs { - csNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return } else if err != nil { - csNotice(rb, client.t("Internal error")) + service.Notice(rb, client.t("Internal error")) return } @@ -253,13 +247,13 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str sort.Slice(affectedModes, func(i, j int) bool { return umodeGreaterThan(affectedModes[i].Mode, affectedModes[j].Mode) }) - csNotice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes))) + service.Notice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes))) for _, modeChange := range affectedModes { - csNotice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode))) + service.Notice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode))) } case modes.Add, modes.Remove: if len(affectedModes) > 0 { - csNotice(rb, fmt.Sprintf(client.t("Successfully set persistent mode %[1]s on %[2]s"), strings.Join([]string{string(change.Op), string(change.Mode)}, ""), change.Arg)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully set persistent mode %[1]s on %[2]s"), strings.Join([]string{string(change.Op), string(change.Mode)}, ""), change.Arg)) // #729: apply change to current membership for _, member := range channel.Members() { if member.Account() == change.Arg { @@ -270,22 +264,22 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str } } } else { - csNotice(rb, client.t("No changes were made")) + service.Notice(rb, client.t("No changes were made")) } } } -func csOpHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csOpHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { channelInfo := server.channels.Get(params[0]) if channelInfo == nil { - csNotice(rb, client.t("Channel does not exist")) + service.Notice(rb, client.t("Channel does not exist")) return } channelName := channelInfo.Name() clientAccount := client.Account() if clientAccount == "" || clientAccount != channelInfo.Founder() { - csNotice(rb, client.t("Only the channel founder can do this")) + service.Notice(rb, client.t("Only the channel founder can do this")) return } @@ -293,7 +287,7 @@ func csOpHandler(server *Server, client *Client, command string, params []string if len(params) > 1 { target = server.clients.Get(params[1]) if target == nil { - csNotice(rb, client.t("Could not find given client")) + service.Notice(rb, client.t("Could not find given client")) return } } else { @@ -315,21 +309,21 @@ func csOpHandler(server *Server, client *Client, command string, params []string announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", rb) } - csNotice(rb, client.t("Successfully granted operator privileges")) + service.Notice(rb, client.t("Successfully granted operator privileges")) tnick := target.Nick() server.logger.Info("services", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.Nick(), tnick, channelName)) server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.NickMaskString(), tnick, channelName)) } -func csDeopHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csDeopHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { channel := server.channels.Get(params[0]) if channel == nil { - csNotice(rb, client.t("Channel does not exist")) + service.Notice(rb, client.t("Channel does not exist")) return } if !channel.hasClient(client) { - csNotice(rb, client.t("You're not on that channel")) + service.Notice(rb, client.t("You're not on that channel")) return } @@ -337,7 +331,7 @@ func csDeopHandler(server *Server, client *Client, command string, params []stri if len(params) > 1 { target = server.clients.Get(params[1]) if target == nil { - csNotice(rb, client.t("Could not find given client")) + service.Notice(rb, client.t("Could not find given client")) return } } else { @@ -346,7 +340,7 @@ func csDeopHandler(server *Server, client *Client, command string, params []stri present, cumodes := channel.ClientStatus(target) if !present || len(cumodes) == 0 { - csNotice(rb, client.t("Target has no privileges to remove")) + service.Notice(rb, client.t("Target has no privileges to remove")) return } @@ -370,38 +364,38 @@ func csDeopHandler(server *Server, client *Client, command string, params []stri return } - csNotice(rb, client.t("Successfully removed operator privileges")) + service.Notice(rb, client.t("Successfully removed operator privileges")) } -func csRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csRegisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if server.Config().Channels.Registration.OperatorOnly && !client.HasRoleCapabs("chanreg") { - csNotice(rb, client.t("Channel registration is restricted to server operators")) + service.Notice(rb, client.t("Channel registration is restricted to server operators")) return } channelName := params[0] channelInfo := server.channels.Get(channelName) if channelInfo == nil { - csNotice(rb, client.t("No such channel")) + service.Notice(rb, client.t("No such channel")) return } if !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) { - csNotice(rb, client.t("You must be an oper on the channel to register it")) + service.Notice(rb, client.t("You must be an oper on the channel to register it")) return } account := client.Account() - if !checkChanLimit(client, rb) { + if !checkChanLimit(service, client, rb) { return } // this provides the synchronization that allows exactly one registration of the channel: err := server.channels.SetRegistered(channelName, account) if err != nil { - csNotice(rb, err.Error()) + service.Notice(rb, err.Error()) return } - csNotice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName)) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName)) server.logger.Info("services", fmt.Sprintf("Client %s registered channel %s", client.Nick(), channelName)) server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString)) @@ -415,38 +409,38 @@ func csRegisterHandler(server *Server, client *Client, command string, params [] }, rb) if applied { - announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", "", rb) + announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", rb) } } // check whether a client has already registered too many channels -func checkChanLimit(client *Client, rb *ResponseBuffer) (ok bool) { +func checkChanLimit(service *ircService, client *Client, rb *ResponseBuffer) (ok bool) { account := client.Account() channelsAlreadyRegistered := client.server.accounts.ChannelsForAccount(account) ok = len(channelsAlreadyRegistered) < client.server.Config().Channels.Registration.MaxChannelsPerAccount || client.HasRoleCapabs("chanreg") if !ok { - csNotice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER")) + service.Notice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER")) } return } -func csPrivsCheck(channel RegisteredChannel, client *Client, rb *ResponseBuffer) (success bool) { +func csPrivsCheck(service *ircService, channel RegisteredChannel, client *Client, rb *ResponseBuffer) (success bool) { founder := channel.Founder if founder == "" { - csNotice(rb, client.t("That channel is not registered")) + service.Notice(rb, client.t("That channel is not registered")) return false } if client.HasRoleCapabs("chanreg") { return true } if founder != client.Account() { - csNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return false } return true } -func csUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csUnregisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { channelName := params[0] var verificationCode string if len(params) > 1 { @@ -455,41 +449,41 @@ func csUnregisterHandler(server *Server, client *Client, command string, params channel := server.channels.Get(channelName) if channel == nil { - csNotice(rb, client.t("No such channel")) + service.Notice(rb, client.t("No such channel")) return } info := channel.ExportRegistration(0) channelKey := info.NameCasefolded - if !csPrivsCheck(info, client, rb) { + if !csPrivsCheck(service, info, client, rb) { return } expectedCode := utils.ConfirmationCode(info.Name, info.RegisteredAt) if expectedCode != verificationCode { - csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b"))) - csNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS UNREGISTER %s %s", channelKey, expectedCode))) + service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b"))) + service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS UNREGISTER %s %s", channelKey, expectedCode))) return } server.channels.SetUnregistered(channelKey, info.Founder) - csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey)) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey)) } -func csClearHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csClearHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { channel := server.channels.Get(params[0]) if channel == nil { - csNotice(rb, client.t("Channel does not exist")) + service.Notice(rb, client.t("Channel does not exist")) return } - if !csPrivsCheck(channel.ExportRegistration(0), client, rb) { + if !csPrivsCheck(service, channel.ExportRegistration(0), client, rb) { return } switch strings.ToLower(params[1]) { case "access": channel.resetAccess() - csNotice(rb, client.t("Successfully reset channel access")) + service.Notice(rb, client.t("Successfully reset channel access")) case "users": for _, target := range channel.Members() { if target != client { @@ -497,20 +491,20 @@ func csClearHandler(server *Server, client *Client, command string, params []str } } default: - csNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) } } -func csTransferHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csTransferHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if strings.ToLower(params[0]) == "accept" { - processTransferAccept(client, params[1], rb) + processTransferAccept(service, client, params[1], rb) return } chname := params[0] channel := server.channels.Get(chname) if channel == nil { - csNotice(rb, client.t("Channel does not exist")) + service.Notice(rb, client.t("Channel does not exist")) return } regInfo := channel.ExportRegistration(0) @@ -519,21 +513,21 @@ func csTransferHandler(server *Server, client *Client, command string, params [] isFounder := account != "" && account == regInfo.Founder hasPrivs := client.HasRoleCapabs("chanreg") if !(isFounder || hasPrivs) { - csNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return } target := params[1] targetAccount, err := server.accounts.LoadAccount(params[1]) if err != nil { - csNotice(rb, client.t("Account does not exist")) + service.Notice(rb, client.t("Account does not exist")) return } if targetAccount.NameCasefolded != account { expectedCode := utils.ConfirmationCode(regInfo.Name, regInfo.RegisteredAt) codeValidated := 2 < len(params) && params[2] == expectedCode if !codeValidated { - csNotice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to transfer control of your channel to another user.$b"))) - csNotice(rb, fmt.Sprintf(client.t("To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s"), chname, target, expectedCode)) + service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to transfer control of your channel to another user.$b"))) + service.Notice(rb, fmt.Sprintf(client.t("To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s"), chname, target, expectedCode)) return } } @@ -541,19 +535,19 @@ func csTransferHandler(server *Server, client *Client, command string, params [] if err == nil { switch status { case channelTransferComplete: - csNotice(rb, fmt.Sprintf(client.t("Successfully transferred channel %[1]s to account %[2]s"), chname, target)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully transferred channel %[1]s to account %[2]s"), chname, target)) case channelTransferPending: - sendTransferPendingNotice(server, target, chname) - csNotice(rb, fmt.Sprintf(client.t("Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance"), chname, target)) + sendTransferPendingNotice(service, server, target, chname) + service.Notice(rb, fmt.Sprintf(client.t("Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance"), chname, target)) case channelTransferCancelled: - csNotice(rb, fmt.Sprintf(client.t("Cancelled pending transfer of channel %s"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Cancelled pending transfer of channel %s"), chname)) } } else { - csNotice(rb, client.t("Could not transfer channel")) + service.Notice(rb, client.t("Could not transfer channel")) } } -func sendTransferPendingNotice(server *Server, account, chname string) { +func sendTransferPendingNotice(service *ircService, server *Server, account, chname string) { clients := server.accounts.AccountToClients(account) if len(clients) == 0 { return @@ -565,29 +559,29 @@ func sendTransferPendingNotice(server *Server, account, chname string) { break // prefer the login where the nick is the account } } - client.Send(nil, chanservMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s"), chname)) + client.Send(nil, service.prefix, "NOTICE", client.Nick(), fmt.Sprintf(client.t("You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s"), chname)) } -func processTransferAccept(client *Client, chname string, rb *ResponseBuffer) { +func processTransferAccept(service *ircService, client *Client, chname string, rb *ResponseBuffer) { channel := client.server.channels.Get(chname) if channel == nil { - csNotice(rb, client.t("Channel does not exist")) + service.Notice(rb, client.t("Channel does not exist")) return } - if !checkChanLimit(client, rb) { + if !checkChanLimit(service, client, rb) { return } switch channel.AcceptTransfer(client) { case nil: - csNotice(rb, fmt.Sprintf(client.t("Successfully accepted ownership of channel %s"), channel.Name())) + service.Notice(rb, fmt.Sprintf(client.t("Successfully accepted ownership of channel %s"), channel.Name())) case errChannelTransferNotOffered: - csNotice(rb, fmt.Sprintf(client.t("You weren't offered ownership of channel %s"), channel.Name())) + service.Notice(rb, fmt.Sprintf(client.t("You weren't offered ownership of channel %s"), channel.Name())) default: - csNotice(rb, fmt.Sprintf(client.t("Could not accept ownership of channel %s"), channel.Name())) + service.Notice(rb, fmt.Sprintf(client.t("Could not accept ownership of channel %s"), channel.Name())) } } -func csPurgeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csPurgeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { oper := client.Oper() if oper == nil { return // should be impossible because you need oper capabs for this @@ -611,29 +605,29 @@ func csPurgeHandler(server *Server, client *Client, command string, params []str channel.Kick(client, target, "Cleared by ChanServ", rb, true) } } - csNotice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname)) case errInvalidChannelName: - csNotice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname)) default: - csNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } -func csUnpurgeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csUnpurgeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { chname := params[0] switch server.channels.Unpurge(chname) { case nil: - csNotice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname)) case errNoSuchChannel: - csNotice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname)) default: - csNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } -func csListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csListHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if !client.HasRoleCapabs("chanreg") { - csNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return } @@ -642,27 +636,27 @@ func csListHandler(server *Server, client *Client, command string, params []stri var err error searchRegex, err = regexp.Compile(params[0]) if err != nil { - csNotice(rb, client.t("Invalid regex")) + service.Notice(rb, client.t("Invalid regex")) return } } - csNotice(rb, ircfmt.Unescape(client.t("*** $bChanServ LIST$b ***"))) + service.Notice(rb, ircfmt.Unescape(client.t("*** $bChanServ LIST$b ***"))) channels := server.channelRegistry.AllChannels() for _, channel := range channels { if searchRegex == nil || searchRegex.MatchString(channel) { - csNotice(rb, fmt.Sprintf(" %s", channel)) + service.Notice(rb, fmt.Sprintf(" %s", channel)) } } - csNotice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ LIST$b ***"))) + service.Notice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ LIST$b ***"))) } -func csInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { chname, err := CasefoldChannel(params[0]) if err != nil { - csNotice(rb, client.t("Invalid channel name")) + service.Notice(rb, client.t("Invalid channel name")) return } @@ -670,16 +664,16 @@ func csInfoHandler(server *Server, client *Client, command string, params []stri if client.HasRoleCapabs("chanreg") { purgeRecord, err := server.channelRegistry.LoadPurgeRecord(chname) if err == nil { - csNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname)) - csNotice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper)) - csNotice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123))) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper)) + service.Notice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123))) if purgeRecord.Reason != "" { - csNotice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason)) + service.Notice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason)) } } } else { if server.channels.IsPurged(chname) { - csNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname)) } } @@ -690,59 +684,59 @@ func csInfoHandler(server *Server, client *Client, command string, params []stri } else { chinfo, err = server.channelRegistry.LoadChannel(chname) if err != nil && !(err == errNoSuchChannel || err == errFeatureDisabled) { - csNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) return } } // channel exists but is unregistered, or doesn't exist: if chinfo.Founder == "" { - csNotice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname)) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname)) return } - csNotice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name)) - csNotice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder)) - csNotice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123))) + service.Notice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name)) + service.Notice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder)) + service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123))) } -func displayChannelSetting(settingName string, settings ChannelSettings, client *Client, rb *ResponseBuffer) { +func displayChannelSetting(service *ircService, settingName string, settings ChannelSettings, client *Client, rb *ResponseBuffer) { config := client.server.Config() switch strings.ToLower(settingName) { case "history": effectiveValue := historyEnabled(config.History.Persistent.RegisteredChannels, settings.History) - csNotice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History))) - csNotice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue))) + service.Notice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History))) + service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue))) default: - csNotice(rb, client.t("Invalid params")) + service.Notice(rb, client.t("Invalid params")) } } -func csGetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csGetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { chname, setting := params[0], params[1] channel := server.channels.Get(chname) if channel == nil { - csNotice(rb, client.t("No such channel")) + service.Notice(rb, client.t("No such channel")) return } info := channel.ExportRegistration(IncludeSettings) - if !csPrivsCheck(info, client, rb) { + if !csPrivsCheck(service, info, client, rb) { return } - displayChannelSetting(setting, info.Settings, client, rb) + displayChannelSetting(service, setting, info.Settings, client, rb) } -func csSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func csSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { chname, setting, value := params[0], params[1], params[2] channel := server.channels.Get(chname) if channel == nil { - csNotice(rb, client.t("No such channel")) + service.Notice(rb, client.t("No such channel")) return } info := channel.ExportRegistration(IncludeSettings) settings := info.Settings - if !csPrivsCheck(info, client, rb) { + if !csPrivsCheck(service, info, client, rb) { return } @@ -760,12 +754,12 @@ func csSetHandler(server *Server, client *Client, command string, params []strin switch err { case nil: - csNotice(rb, client.t("Successfully changed the channel settings")) - displayChannelSetting(setting, settings, client, rb) + service.Notice(rb, client.t("Successfully changed the channel settings")) + displayChannelSetting(service, setting, settings, client, rb) case errInvalidParams: - csNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) default: server.logger.Error("internal", "CS SET error:", err.Error()) - csNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } diff --git a/irc/config.go b/irc/config.go index 94bc5277..72c414fa 100644 --- a/irc/config.go +++ b/irc/config.go @@ -526,17 +526,18 @@ type Config struct { forceTrailing bool SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"` } - isupport isupport.List - IPLimits connection_limits.LimiterConfig `yaml:"ip-limits"` - Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` - SecureNetDefs []string `yaml:"secure-nets"` - secureNets []net.IPNet - supportedCaps *caps.Set - capValues caps.Values - Casemapping Casemapping - EnforceUtf8 bool `yaml:"enforce-utf8"` - OutputPath string `yaml:"output-path"` - IPCheckScript ScriptConfig `yaml:"ip-check-script"` + isupport isupport.List + IPLimits connection_limits.LimiterConfig `yaml:"ip-limits"` + Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` + SecureNetDefs []string `yaml:"secure-nets"` + secureNets []net.IPNet + supportedCaps *caps.Set + capValues caps.Values + Casemapping Casemapping + EnforceUtf8 bool `yaml:"enforce-utf8"` + OutputPath string `yaml:"output-path"` + IPCheckScript ScriptConfig `yaml:"ip-check-script"` + OverrideServicesHostname string `yaml:"override-services-hostname"` } Roleplay struct { diff --git a/irc/handlers.go b/irc/handlers.go index a10cf650..67ce5c89 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -77,23 +77,23 @@ func registrationErrorToMessage(err error) (message string) { } // helper function to dispatch messages when a client successfully registers -func sendSuccessfulRegResponse(client *Client, rb *ResponseBuffer, forNS bool) { +func sendSuccessfulRegResponse(service *ircService, client *Client, rb *ResponseBuffer) { details := client.Details() - if forNS { - nsNotice(rb, client.t("Account created")) + if service != nil { + service.Notice(rb, client.t("Account created")) } else { rb.Add(nil, client.server.name, RPL_REG_SUCCESS, details.nick, details.accountName, client.t("Account created")) } client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] registered account $c[grey][$r%s$c[grey]] from IP %s"), details.nickMask, details.accountName, rb.session.IP().String())) - sendSuccessfulAccountAuth(client, rb, forNS, false) + sendSuccessfulAccountAuth(service, client, rb, false) } // sendSuccessfulAccountAuth means that an account auth attempt completed successfully, and is used to dispatch messages. -func sendSuccessfulAccountAuth(client *Client, rb *ResponseBuffer, forNS, forSASL bool) { +func sendSuccessfulAccountAuth(service *ircService, client *Client, rb *ResponseBuffer, forSASL bool) { details := client.Details() - if forNS { - nsNotice(rb, fmt.Sprintf(client.t("You're now logged in as %s"), details.accountName)) + if service != nil { + service.Notice(rb, 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)) @@ -253,11 +253,11 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value [] msg := authErrorToMessage(server, err) rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg))) return false - } else if !fixupNickEqualsAccount(client, rb, server.Config()) { + } else if !fixupNickEqualsAccount(client, rb, server.Config(), "") { return false } - sendSuccessfulAccountAuth(client, rb, false, true) + sendSuccessfulAccountAuth(nil, client, rb, true) return false } @@ -311,11 +311,11 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value msg := authErrorToMessage(server, err) rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg))) return false - } else if !fixupNickEqualsAccount(client, rb, server.Config()) { + } else if !fixupNickEqualsAccount(client, rb, server.Config(), "") { return false } - sendSuccessfulAccountAuth(client, rb, false, true) + sendSuccessfulAccountAuth(nil, client, rb, true) return false } @@ -1525,7 +1525,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp config := server.Config() if time.Since(client.ctime) < config.Channels.ListDelay && client.Account() == "" && !client.HasMode(modes.Operator) { remaining := time.Until(client.ctime.Add(config.Channels.ListDelay)) - csNotice(rb, fmt.Sprintf(client.t("This server requires that you wait %v after connecting before you can use /LIST. You have %v left."), config.Channels.ListDelay, remaining)) + rb.Notice(fmt.Sprintf(client.t("This server requires that you wait %v after connecting before you can use /LIST. You have %v left."), config.Channels.ListDelay, remaining)) rb.Add(nil, server.name, RPL_LISTEND, client.Nick(), client.t("End of LIST")) return false } @@ -2355,7 +2355,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp } err := server.accounts.AuthenticateByPassphrase(client, account, accountPass) if err == nil { - sendSuccessfulAccountAuth(client, rb, false, true) + sendSuccessfulAccountAuth(nil, client, rb, true) // login-via-pass-command entails that we do not need to check // an actual server password (either no password or skip-server-password) rb.session.passStatus = serverPassSuccessful @@ -2446,13 +2446,13 @@ func registerHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb * err := server.accounts.Verify(client, accountName, "") if err == nil { if client.registered { - if !fixupNickEqualsAccount(client, rb, config) { + if !fixupNickEqualsAccount(client, rb, config, "") { err = errNickAccountMismatch } } if err == nil { rb.Add(nil, server.name, "REGISTER", "SUCCESS", accountName, client.t("Account successfully registered")) - sendSuccessfulRegResponse(client, rb, true) + sendSuccessfulRegResponse(nil, client, rb) } } if err != nil { @@ -2494,14 +2494,14 @@ func verifyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re accountName, verificationCode := msg.Params[0], msg.Params[1] err := server.accounts.Verify(client, accountName, verificationCode) if err == nil && client.registered { - if !fixupNickEqualsAccount(client, rb, config) { + if !fixupNickEqualsAccount(client, rb, config, "") { err = errNickAccountMismatch } } switch err { case nil: rb.Add(nil, server.name, "VERIFY", "SUCCESS", accountName, client.t("Account successfully registered")) - sendSuccessfulRegResponse(client, rb, true) + sendSuccessfulRegResponse(nil, client, rb) case errAccountVerificationInvalidCode: rb.Add(nil, server.name, "FAIL", "VERIFY", "INVALID_CODE", client.t("Invalid verification code")) default: @@ -2873,7 +2873,7 @@ func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp username, password = username[:colonIndex], username[colonIndex+1:] err := server.accounts.AuthenticateByPassphrase(client, username, password) if err == nil { - sendSuccessfulAccountAuth(client, rb, false, true) + sendSuccessfulAccountAuth(nil, client, rb, true) } else { // this is wrong, but send something for debugging that will show up in a raw transcript rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), client.t("SASL authentication failed")) diff --git a/irc/histserv.go b/irc/histserv.go index 6f61552a..b5aadc5a 100644 --- a/irc/histserv.go +++ b/irc/histserv.go @@ -18,7 +18,6 @@ import ( const ( histservHelp = `HistServ provides commands related to history.` - histServMask = "HistServ!HistServ@localhost" ) func histservEnabled(config *Config) bool { @@ -83,24 +82,19 @@ CHATHISTORY.`, } ) -// histNotice sends the client a notice from HistServ -func histNotice(rb *ResponseBuffer, text string) { - rb.Add(nil, histServMask, "NOTICE", rb.target.Nick(), text) -} - -func histservForgetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func histservForgetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { accountName := server.accounts.AccountToAccountName(params[0]) if accountName == "" { - histNotice(rb, client.t("Could not look up account name, proceeding anyway")) + service.Notice(rb, client.t("Could not look up account name, proceeding anyway")) accountName = params[0] } server.ForgetHistory(accountName) - histNotice(rb, fmt.Sprintf(client.t("Enqueued account %s for message deletion"), accountName)) + service.Notice(rb, fmt.Sprintf(client.t("Enqueued account %s for message deletion"), accountName)) } -func histservDeleteHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func histservDeleteHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var target, msgid string if len(params) == 1 { msgid = params[0] @@ -113,27 +107,27 @@ func histservDeleteHandler(server *Server, client *Client, command string, param if !hasPrivs { accountName = client.AccountName() if !(server.Config().History.Retention.AllowIndividualDelete && accountName != "*") { - histNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return } } err := server.DeleteMessage(target, msgid, accountName) if err == nil { - histNotice(rb, client.t("Successfully deleted message")) + service.Notice(rb, client.t("Successfully deleted message")) } else { if hasPrivs { - histNotice(rb, fmt.Sprintf(client.t("Error deleting message: %v"), err)) + service.Notice(rb, fmt.Sprintf(client.t("Error deleting message: %v"), err)) } else { - histNotice(rb, client.t("Could not delete message")) + service.Notice(rb, client.t("Could not delete message")) } } } -func histservExportHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func histservExportHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { cfAccount, err := CasefoldName(params[0]) if err != nil { - histNotice(rb, client.t("Invalid account name")) + service.Notice(rb, client.t("Invalid account name")) return } @@ -143,15 +137,15 @@ func histservExportHandler(server *Server, client *Client, command string, param pathname := config.getOutputPath(filename) outfile, err := os.Create(pathname) if err != nil { - histNotice(rb, fmt.Sprintf(client.t("Error opening export file: %v"), err)) + service.Notice(rb, fmt.Sprintf(client.t("Error opening export file: %v"), err)) } else { - histNotice(rb, fmt.Sprintf(client.t("Started exporting data for account %[1]s to file %[2]s"), cfAccount, filename)) + service.Notice(rb, fmt.Sprintf(client.t("Started exporting data for account %[1]s to file %[2]s"), cfAccount, filename)) } - go histservExportAndNotify(server, cfAccount, outfile, filename, client.Nick()) + go histservExportAndNotify(service, server, cfAccount, outfile, filename, client.Nick()) } -func histservExportAndNotify(server *Server, cfAccount string, outfile *os.File, filename, alertNick string) { +func histservExportAndNotify(service *ircService, server *Server, cfAccount string, outfile *os.File, filename, alertNick string) { defer func() { if r := recover(); r != nil { server.logger.Error("history", @@ -167,19 +161,19 @@ func histservExportAndNotify(server *Server, cfAccount string, outfile *os.File, client := server.clients.Get(alertNick) if client != nil && client.HasRoleCapabs("history") { - client.Send(nil, histServMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Data export for %[1]s completed and written to %[2]s"), cfAccount, filename)) + client.Send(nil, service.prefix, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Data export for %[1]s completed and written to %[2]s"), cfAccount, filename)) } } -func histservPlayHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func histservPlayHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { items, _, err := easySelectHistory(server, client, params) if err != nil { - histNotice(rb, client.t("Could not retrieve history")) + service.Notice(rb, client.t("Could not retrieve history")) return } playMessage := func(timestamp time.Time, nick, message string) { - histNotice(rb, fmt.Sprintf("%s <%s> %s", timestamp.Format("15:04:05"), stripMaskFromNick(nick), message)) + service.Notice(rb, fmt.Sprintf("%s <%s> %s", timestamp.Format("15:04:05"), stripMaskFromNick(nick), message)) } for _, item := range items { @@ -196,7 +190,7 @@ func histservPlayHandler(server *Server, client *Client, command string, params } } - histNotice(rb, client.t("End of history playback")) + service.Notice(rb, client.t("End of history playback")) } // handles parameter parsing and history queries for /HISTORY and /HISTSERV PLAY diff --git a/irc/hostserv.go b/irc/hostserv.go index add7330d..6a8ed9cf 100644 --- a/irc/hostserv.go +++ b/irc/hostserv.go @@ -16,7 +16,6 @@ import ( const ( hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed in place of your client's hostname/IP).` - hsNickMask = "HostServ!HostServ@localhost" ) var ( @@ -95,12 +94,7 @@ display the necessary code.`, } ) -// hsNotice sends the client a notice from HostServ -func hsNotice(rb *ResponseBuffer, text string) { - rb.Add(nil, hsNickMask, "NOTICE", rb.target.Nick(), text) -} - -func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func hsOnOffHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { enable := false if command == "on" { enable = true @@ -108,28 +102,28 @@ func hsOnOffHandler(server *Server, client *Client, command string, params []str _, err := server.accounts.VHostSetEnabled(client, enable) if err == errNoVhost { - hsNotice(rb, client.t(err.Error())) + service.Notice(rb, client.t(err.Error())) } else if err != nil { - hsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } else if enable { - hsNotice(rb, client.t("Successfully enabled your vhost")) + service.Notice(rb, client.t("Successfully enabled your vhost")) } else { - hsNotice(rb, client.t("Successfully disabled your vhost")) + service.Notice(rb, client.t("Successfully disabled your vhost")) } } -func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func hsStatusHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var accountName string if len(params) > 0 { if !client.HasRoleCapabs("vhosts") { - hsNotice(rb, client.t("Command restricted")) + service.Notice(rb, client.t("Command restricted")) return } accountName = params[0] } else { accountName = client.Account() if accountName == "" { - hsNotice(rb, client.t("You're not logged into an account")) + service.Notice(rb, client.t("You're not logged into an account")) return } } @@ -139,17 +133,17 @@ func hsStatusHandler(server *Server, client *Client, command string, params []st if err != errAccountDoesNotExist { server.logger.Warning("internal", "error loading account info", accountName, err.Error()) } - hsNotice(rb, client.t("No such account")) + service.Notice(rb, client.t("No such account")) return } if account.VHost.ApprovedVHost != "" { - hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost)) + service.Notice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost)) if !account.VHost.Enabled { - hsNotice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON")) + service.Notice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON")) } } else { - hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName)) + service.Notice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName)) } } @@ -164,14 +158,14 @@ func validateVhost(server *Server, vhost string, oper bool) error { return nil } -func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func hsSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { user := params[0] var vhost string if command == "set" { vhost = params[1] if validateVhost(server, vhost, true) != nil { - hsNotice(rb, client.t("Invalid vhost")) + service.Notice(rb, client.t("Invalid vhost")) return } } @@ -179,22 +173,22 @@ func hsSetHandler(server *Server, client *Client, command string, params []strin _, err := server.accounts.VHostSet(user, vhost) if err != nil { - hsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } else if vhost != "" { - hsNotice(rb, client.t("Successfully set vhost")) + service.Notice(rb, client.t("Successfully set vhost")) } else { - hsNotice(rb, client.t("Successfully cleared vhost")) + service.Notice(rb, client.t("Successfully cleared vhost")) } } -func hsSetCloakSecretHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func hsSetCloakSecretHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { secret := params[0] expectedCode := utils.ConfirmationCode(secret, server.ctime) if len(params) == 1 || params[1] != expectedCode { - hsNotice(rb, ircfmt.Unescape(client.t("$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b"))) - hsNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/HS SETCLOAKSECRET %s %s", secret, expectedCode))) + service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b"))) + service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/HS SETCLOAKSECRET %s %s", secret, expectedCode))) return } StoreCloakSecret(server.store, secret) - hsNotice(rb, client.t("Rotated the cloak secret; you must rehash or restart the server for it to take effect")) + service.Notice(rb, client.t("Rotated the cloak secret; you must rehash or restart the server for it to take effect")) } diff --git a/irc/nickname.go b/irc/nickname.go index 801354b3..180f9953 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -151,7 +151,7 @@ func (server *Server) RandomlyRename(client *Client) { // so we need to re-NICK automatically on every login event (IDENTIFY, // VERIFY, and a REGISTER that auto-verifies). if we can't get the nick // then we log them out (they will be able to reattach with SASL) -func fixupNickEqualsAccount(client *Client, rb *ResponseBuffer, config *Config) (success bool) { +func fixupNickEqualsAccount(client *Client, rb *ResponseBuffer, config *Config, source string) (success bool) { if !config.Accounts.NickReservation.ForceNickEqualsAccount { return true } @@ -161,7 +161,10 @@ func fixupNickEqualsAccount(client *Client, rb *ResponseBuffer, config *Config) err := performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) if err != nil && err != errNoop { client.server.accounts.Logout(client) - nsNotice(rb, client.t("A client is already using that account; try logging out and logging back in with SASL")) + if source == "" { + source = client.server.name + } + rb.Add(nil, source, "NOTICE", client.t("A client is already using that account; try logging out and logging back in with SASL")) return false } return true diff --git a/irc/nickserv.go b/irc/nickserv.go index b0544264..1bdba9b2 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -36,10 +36,6 @@ func servCmdRequiresBouncerEnabled(config *Config) bool { return config.Accounts.Multiclient.Enabled } -const ( - nsPrefix = "NickServ!NickServ@localhost" -) - const nickservHelp = `NickServ lets you register, log in to, and manage an account.` var ( @@ -361,14 +357,7 @@ Currently, you can only change the canonical casefolding of an account } ) -// nsNotice sends the client a notice from NickServ -func nsNotice(rb *ResponseBuffer, text string) { - // XXX i can't figure out how to use OragonoServices[servicename].prefix here - // without creating a compile-time initialization loop - rb.Add(nil, nsPrefix, "NOTICE", rb.target.Nick(), text) -} - -func nsGetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsGetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var account string if command == "saget" { account = params[0] @@ -379,101 +368,101 @@ func nsGetHandler(server *Server, client *Client, command string, params []strin accountData, err := server.accounts.LoadAccount(account) if err == errAccountDoesNotExist { - nsNotice(rb, client.t("No such account")) + service.Notice(rb, client.t("No such account")) return } else if err != nil { - nsNotice(rb, client.t("Error loading account data")) + service.Notice(rb, client.t("Error loading account data")) return } - displaySetting(params[0], accountData.Settings, client, rb) + displaySetting(service, params[0], accountData.Settings, client, rb) } -func displaySetting(settingName string, settings AccountSettings, client *Client, rb *ResponseBuffer) { +func displaySetting(service *ircService, settingName string, settings AccountSettings, client *Client, rb *ResponseBuffer) { config := client.server.Config() switch strings.ToLower(settingName) { case "enforce": storedValue := settings.NickEnforcement serializedStoredValue := nickReservationToString(storedValue) - nsNotice(rb, fmt.Sprintf(client.t("Your stored nickname enforcement setting is: %s"), serializedStoredValue)) + service.Notice(rb, fmt.Sprintf(client.t("Your stored nickname enforcement setting is: %s"), serializedStoredValue)) serializedActualValue := nickReservationToString(configuredEnforcementMethod(config, storedValue)) - nsNotice(rb, fmt.Sprintf(client.t("Given current server settings, your nickname is enforced with: %s"), serializedActualValue)) + service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, your nickname is enforced with: %s"), serializedActualValue)) case "autoreplay-lines": if settings.AutoreplayLines == nil { - nsNotice(rb, fmt.Sprintf(client.t("You will receive the server default of %d lines of autoreplayed history"), config.History.AutoreplayOnJoin)) + service.Notice(rb, fmt.Sprintf(client.t("You will receive the server default of %d lines of autoreplayed history"), config.History.AutoreplayOnJoin)) } else { - nsNotice(rb, fmt.Sprintf(client.t("You will receive %d lines of autoreplayed history"), *settings.AutoreplayLines)) + service.Notice(rb, fmt.Sprintf(client.t("You will receive %d lines of autoreplayed history"), *settings.AutoreplayLines)) } case "replay-joins": switch settings.ReplayJoins { case ReplayJoinsCommandsOnly: - nsNotice(rb, client.t("You will see JOINs and PARTs in /HISTORY output, but not in autoreplay")) + service.Notice(rb, client.t("You will see JOINs and PARTs in /HISTORY output, but not in autoreplay")) case ReplayJoinsAlways: - nsNotice(rb, client.t("You will see JOINs and PARTs in /HISTORY output and in autoreplay")) + service.Notice(rb, client.t("You will see JOINs and PARTs in /HISTORY output and in autoreplay")) case ReplayJoinsNever: - nsNotice(rb, client.t("You will not see JOINs and PARTs in /HISTORY output or in autoreplay")) + service.Notice(rb, client.t("You will not see JOINs and PARTs in /HISTORY output or in autoreplay")) } case "multiclient": if !config.Accounts.Multiclient.Enabled { - nsNotice(rb, client.t("This feature has been disabled by the server administrators")) + service.Notice(rb, client.t("This feature has been disabled by the server administrators")) } else { switch settings.AllowBouncer { case MulticlientAllowedServerDefault: if config.Accounts.Multiclient.AllowedByDefault { - nsNotice(rb, client.t("Multiclient functionality is currently enabled for your account, but you can opt out")) + service.Notice(rb, client.t("Multiclient functionality is currently enabled for your account, but you can opt out")) } else { - nsNotice(rb, client.t("Multiclient functionality is currently disabled for your account, but you can opt in")) + service.Notice(rb, client.t("Multiclient functionality is currently disabled for your account, but you can opt in")) } case MulticlientDisallowedByUser: - nsNotice(rb, client.t("Multiclient functionality is currently disabled for your account")) + service.Notice(rb, client.t("Multiclient functionality is currently disabled for your account")) case MulticlientAllowedByUser: - nsNotice(rb, client.t("Multiclient functionality is currently enabled for your account")) + service.Notice(rb, client.t("Multiclient functionality is currently enabled for your account")) } } case "always-on": stored := settings.AlwaysOn actual := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, stored) - nsNotice(rb, fmt.Sprintf(client.t("Your stored always-on setting is: %s"), persistentStatusToString(stored))) + service.Notice(rb, fmt.Sprintf(client.t("Your stored always-on setting is: %s"), persistentStatusToString(stored))) if actual { - nsNotice(rb, client.t("Given current server settings, your client is always-on")) + service.Notice(rb, client.t("Given current server settings, your client is always-on")) } else { - nsNotice(rb, client.t("Given current server settings, your client is not always-on")) + service.Notice(rb, client.t("Given current server settings, your client is not always-on")) } case "autoreplay-missed": stored := settings.AutoreplayMissed if stored { alwaysOn := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, settings.AlwaysOn) if alwaysOn { - nsNotice(rb, client.t("Autoreplay of missed messages is enabled")) + service.Notice(rb, client.t("Autoreplay of missed messages is enabled")) } else { - nsNotice(rb, client.t("You have enabled autoreplay of missed messages, but you can't receive them because your client isn't set to always-on")) + service.Notice(rb, client.t("You have enabled autoreplay of missed messages, but you can't receive them because your client isn't set to always-on")) } } else { - nsNotice(rb, client.t("Your account is not configured to receive autoreplayed missed messages")) + service.Notice(rb, client.t("Your account is not configured to receive autoreplayed missed messages")) } case "auto-away": stored := settings.AutoAway alwaysOn := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, settings.AlwaysOn) actual := persistenceEnabled(config.Accounts.Multiclient.AutoAway, settings.AutoAway) - nsNotice(rb, fmt.Sprintf(client.t("Your stored auto-away setting is: %s"), persistentStatusToString(stored))) + service.Notice(rb, fmt.Sprintf(client.t("Your stored auto-away setting is: %s"), persistentStatusToString(stored))) if actual && alwaysOn { - nsNotice(rb, client.t("Given current server settings, auto-away is enabled for your client")) + service.Notice(rb, client.t("Given current server settings, auto-away is enabled for your client")) } else if actual && !alwaysOn { - nsNotice(rb, client.t("Because your client is not always-on, auto-away is disabled")) + service.Notice(rb, client.t("Because your client is not always-on, auto-away is disabled")) } else if !actual { - nsNotice(rb, client.t("Given current server settings, auto-away is disabled for your client")) + service.Notice(rb, client.t("Given current server settings, auto-away is disabled for your client")) } case "dm-history": effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory) - nsNotice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory))) - nsNotice(rb, fmt.Sprintf(client.t("Given current server settings, your direct message history setting is: %s"), historyStatusToString(effectiveValue))) + 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))) default: - nsNotice(rb, client.t("No such setting")) + service.Notice(rb, client.t("No such setting")) } } -func nsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var account string if command == "saset" { account = params[0] @@ -487,7 +476,7 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin var err error switch strings.ToLower(params[0]) { case "pass", "password": - nsNotice(rb, client.t("To change a password, use the PASSWD command. For details, /msg NickServ HELP PASSWD")) + service.Notice(rb, client.t("To change a password, use the PASSWD command. For details, /msg NickServ HELP PASSWD")) return case "enforce": var method NickEnforcementMethod @@ -612,19 +601,19 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin switch err { case nil: - nsNotice(rb, client.t("Successfully changed your account settings")) - displaySetting(params[0], finalSettings, client, rb) + service.Notice(rb, client.t("Successfully changed your account settings")) + displaySetting(service, params[0], finalSettings, client, rb) case errInvalidParams, errAccountDoesNotExist, errFeatureDisabled, errAccountUnverified, errAccountUpdateFailed: - nsNotice(rb, client.t(err.Error())) + service.Notice(rb, client.t(err.Error())) case errNickAccountMismatch: - nsNotice(rb, fmt.Sprintf(client.t("Your nickname must match your account name %s exactly to modify this setting. Try changing it with /NICK, or logging out and back in with the correct nickname."), client.AccountName())) + service.Notice(rb, fmt.Sprintf(client.t("Your nickname must match your account name %s exactly to modify this setting. Try changing it with /NICK, or logging out and back in with the correct nickname."), client.AccountName())) default: // unknown error - nsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } -func nsDropHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsDropHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { sadrop := command == "sadrop" var nick string if len(params) > 0 { @@ -635,28 +624,28 @@ func nsDropHandler(server *Server, client *Client, command string, params []stri err := server.accounts.SetNickReserved(client, nick, sadrop, false) if err == nil { - nsNotice(rb, fmt.Sprintf(client.t("Successfully ungrouped nick %s with your account"), nick)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully ungrouped nick %s with your account"), nick)) } else if err == errAccountNotLoggedIn { - nsNotice(rb, client.t("You're not logged into an account")) + service.Notice(rb, client.t("You're not logged into an account")) } else if err == errAccountCantDropPrimaryNick { - nsNotice(rb, client.t("You can't ungroup your primary nickname (try unregistering your account instead)")) + service.Notice(rb, client.t("You can't ungroup your primary nickname (try unregistering your account instead)")) } else { - nsNotice(rb, client.t("Could not ungroup nick")) + service.Notice(rb, client.t("Could not ungroup nick")) } } -func nsGhostHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsGhostHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { nick := params[0] ghost := server.clients.Get(nick) if ghost == nil { - nsNotice(rb, client.t("No such nick")) + service.Notice(rb, client.t("No such nick")) return } else if ghost == client { - nsNotice(rb, client.t("You can't GHOST yourself (try /QUIT instead)")) + service.Notice(rb, client.t("You can't GHOST yourself (try /QUIT instead)")) return } else if ghost.AlwaysOn() { - nsNotice(rb, client.t("You can't GHOST an always-on client")) + service.Notice(rb, client.t("You can't GHOST an always-on client")) return } @@ -667,7 +656,7 @@ func nsGhostHandler(server *Server, client *Client, command string, params []str authorized = (server.accounts.NickToAccount(nick) == account) || (ghost.Account() == account) } if !authorized { - nsNotice(rb, client.t("You don't own that nick")) + service.Notice(rb, client.t("You don't own that nick")) return } @@ -675,31 +664,31 @@ func nsGhostHandler(server *Server, client *Client, command string, params []str ghost.destroy(nil) } -func nsGroupHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsGroupHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { nick := client.Nick() err := server.accounts.SetNickReserved(client, nick, false, true) if err == nil { - nsNotice(rb, fmt.Sprintf(client.t("Successfully grouped nick %s with your account"), nick)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully grouped nick %s with your account"), nick)) } else if err == errAccountTooManyNicks { - nsNotice(rb, client.t("You have too many nicks reserved already (you can remove some with /NS DROP)")) + service.Notice(rb, client.t("You have too many nicks reserved already (you can remove some with /NS DROP)")) } else if err == errNicknameReserved { - nsNotice(rb, client.t("That nickname is already reserved by someone else")) + service.Notice(rb, client.t("That nickname is already reserved by someone else")) } else { - nsNotice(rb, client.t("Error reserving nickname")) + service.Notice(rb, client.t("Error reserving nickname")) } } -func nsLoginThrottleCheck(client *Client, rb *ResponseBuffer) (success bool) { +func nsLoginThrottleCheck(service *ircService, client *Client, rb *ResponseBuffer) (success bool) { throttled, remainingTime := client.checkLoginThrottle() if throttled { - nsNotice(rb, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime)) + service.Notice(rb, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime)) } return !throttled } -func nsIdentifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsIdentifyHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if client.LoggedIntoAccount() { - nsNotice(rb, client.t("You're already logged into an account")) + service.Notice(rb, client.t("You're already logged into an account")) return } @@ -735,7 +724,7 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params [] nickFixupFailed := false if loginSuccessful { - if !fixupNickEqualsAccount(client, rb, server.Config()) { + if !fixupNickEqualsAccount(client, rb, server.Config(), service.prefix) { loginSuccessful = false // fixupNickEqualsAccount sends its own error message, don't send another nickFixupFailed = true @@ -743,15 +732,15 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params [] } if loginSuccessful { - sendSuccessfulAccountAuth(client, rb, true, true) + sendSuccessfulAccountAuth(service, client, rb, true) } else if !nickFixupFailed { - nsNotice(rb, fmt.Sprintf(client.t("Authentication failed: %s"), authErrorToMessage(server, err))) + service.Notice(rb, fmt.Sprintf(client.t("Authentication failed: %s"), authErrorToMessage(server, err))) } } -func nsListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsListHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if !client.HasRoleCapabs("accreg") { - nsNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return } @@ -760,26 +749,26 @@ func nsListHandler(server *Server, client *Client, command string, params []stri var err error searchRegex, err = regexp.Compile(params[0]) if err != nil { - nsNotice(rb, client.t("Invalid regex")) + service.Notice(rb, client.t("Invalid regex")) return } } - nsNotice(rb, ircfmt.Unescape(client.t("*** $bNickServ LIST$b ***"))) + service.Notice(rb, ircfmt.Unescape(client.t("*** $bNickServ LIST$b ***"))) nicks := server.accounts.AllNicks() for _, nick := range nicks { if searchRegex == nil || searchRegex.MatchString(nick) { - nsNotice(rb, fmt.Sprintf(" %s", nick)) + service.Notice(rb, fmt.Sprintf(" %s", nick)) } } - nsNotice(rb, ircfmt.Unescape(client.t("*** $bEnd of NickServ LIST$b ***"))) + service.Notice(rb, ircfmt.Unescape(client.t("*** $bEnd of NickServ LIST$b ***"))) } -func nsInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if !server.Config().Accounts.AuthenticationEnabled && !client.HasRoleCapabs("accreg") { - nsNotice(rb, client.t("This command has been disabled by the server administrators")) + service.Notice(rb, client.t("This command has been disabled by the server administrators")) return } @@ -789,7 +778,7 @@ func nsInfoHandler(server *Server, client *Client, command string, params []stri if server.Config().Accounts.NickReservation.Enabled { accountName = server.accounts.NickToAccount(nick) if accountName == "" { - nsNotice(rb, client.t("That nickname is not registered")) + service.Notice(rb, client.t("That nickname is not registered")) return } } else { @@ -798,33 +787,33 @@ func nsInfoHandler(server *Server, client *Client, command string, params []stri } else { accountName = client.Account() if accountName == "" { - nsNotice(rb, client.t("You're not logged into an account")) + service.Notice(rb, client.t("You're not logged into an account")) return } } account, err := server.accounts.LoadAccount(accountName) if err != nil || !account.Verified { - nsNotice(rb, client.t("Account does not exist")) + service.Notice(rb, client.t("Account does not exist")) return } - nsNotice(rb, fmt.Sprintf(client.t("Account: %s"), account.Name)) + service.Notice(rb, fmt.Sprintf(client.t("Account: %s"), account.Name)) registeredAt := account.RegisteredAt.Format(time.RFC1123) - nsNotice(rb, fmt.Sprintf(client.t("Registered at: %s"), registeredAt)) + service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), registeredAt)) // TODO nicer formatting for this for _, nick := range account.AdditionalNicks { - nsNotice(rb, fmt.Sprintf(client.t("Additional grouped nick: %s"), nick)) + service.Notice(rb, fmt.Sprintf(client.t("Additional grouped nick: %s"), nick)) } for _, channel := range server.accounts.ChannelsForAccount(accountName) { - nsNotice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel)) + service.Notice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel)) } if account.Suspended != nil { - nsNotice(rb, suspensionToString(client, *account.Suspended)) + service.Notice(rb, suspensionToString(client, *account.Suspended)) } } -func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsRegisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { details := client.Details() passphrase := params[0] var email string @@ -835,7 +824,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] certfp := rb.session.certfp if passphrase == "*" { if certfp == "" { - nsNotice(rb, client.t("You must be connected with TLS and a client certificate to do this")) + service.Notice(rb, client.t("You must be connected with TLS and a client certificate to do this")) return } else { passphrase = "" @@ -845,12 +834,12 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] if passphrase != "" { cfPassphrase, err := Casefold(passphrase) if err == nil && cfPassphrase == details.nickCasefolded { - nsNotice(rb, client.t("Usage: REGISTER [email]")) // #1179 + service.Notice(rb, client.t("Usage: REGISTER [email]")) // #1179 return } } - if !nsLoginThrottleCheck(client, rb) { + if !nsLoginThrottleCheck(service, client, rb) { return } @@ -859,7 +848,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] if config.Accounts.NickReservation.ForceGuestFormat { matches := config.Accounts.NickReservation.guestRegexp.FindStringSubmatch(account) if matches == nil || len(matches) < 2 { - nsNotice(rb, client.t("Erroneous nickname")) + service.Notice(rb, client.t("Erroneous nickname")) return } account = matches[1] @@ -867,7 +856,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] callbackNamespace, callbackValue, validationErr := parseCallback(email, config) if validationErr != nil { - nsNotice(rb, client.t("Registration requires a valid e-mail address")) + service.Notice(rb, client.t("Registration requires a valid e-mail address")) return } @@ -875,22 +864,22 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] if err == nil { if callbackNamespace == "*" { err = server.accounts.Verify(client, account, "") - if err == nil && fixupNickEqualsAccount(client, rb, config) { - sendSuccessfulRegResponse(client, rb, true) + if err == nil && fixupNickEqualsAccount(client, rb, config, service.prefix) { + sendSuccessfulRegResponse(service, client, rb) } } else { messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s") message := fmt.Sprintf(messageTemplate, callbackValue) - nsNotice(rb, message) + service.Notice(rb, message) } } else { // details could not be stored and relevant numerics have been dispatched, abort message := registrationErrorToMessage(err) - nsNotice(rb, client.t(message)) + service.Notice(rb, client.t(message)) } } -func nsSaregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsSaregisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var account, passphrase string account = params[0] if 1 < len(params) && params[1] != "*" { @@ -908,14 +897,14 @@ func nsSaregisterHandler(server *Server, client *Client, command string, params server.logger.Error("services", "unknown error from saregister", err.Error()) errMsg = client.t("Could not register") } - nsNotice(rb, errMsg) + service.Notice(rb, errMsg) } else { - nsNotice(rb, fmt.Sprintf(client.t("Successfully registered account %s"), account)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully registered account %s"), account)) server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Operator $c[grey][$r%s$c[grey]] registered account $c[grey][$r%s$c[grey]] with SAREGISTER"), client.Oper().Name, account)) } } -func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsUnregisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { erase := command == "erase" username := params[0] @@ -925,7 +914,7 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params } if username == "" { - nsNotice(rb, client.t("You must specify an account")) + service.Notice(rb, client.t("You must specify an account")) return } @@ -939,10 +928,10 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params } else { account, err := server.accounts.LoadAccount(username) if err == errAccountDoesNotExist { - nsNotice(rb, client.t("Invalid account name")) + service.Notice(rb, client.t("Invalid account name")) return } else if err != nil { - nsNotice(rb, client.t("Internal error")) + service.Notice(rb, client.t("Internal error")) return } accountName = account.Name @@ -950,34 +939,34 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params } if !(accountName == client.AccountName() || client.HasRoleCapabs("accreg")) { - nsNotice(rb, client.t("Insufficient oper privs")) + service.Notice(rb, client.t("Insufficient oper privs")) return } expectedCode := utils.ConfirmationCode(accountName, registeredAt) if expectedCode != verificationCode { if erase { - nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b"))) + service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b"))) } else { - nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b"))) + service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b"))) } - nsNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/NS %s %s %s", strings.ToUpper(command), accountName, expectedCode))) + service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/NS %s %s %s", strings.ToUpper(command), accountName, expectedCode))) return } err := server.accounts.Unregister(accountName, erase) if err == errAccountDoesNotExist { - nsNotice(rb, client.t(err.Error())) + service.Notice(rb, client.t(err.Error())) } else if err != nil { - nsNotice(rb, client.t("Error while unregistering account")) + service.Notice(rb, client.t("Error while unregistering account")) } else { - nsNotice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), accountName)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), accountName)) server.logger.Info("accounts", "client", client.Nick(), "unregistered account", accountName) client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]"), client.NickMaskString(), accountName)) } } -func nsVerifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsVerifyHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { username, code := params[0], params[1] err := server.accounts.Verify(client, username, code) @@ -992,16 +981,16 @@ func nsVerifyHandler(server *Server, client *Client, command string, params []st } if errorMessage != "" { - nsNotice(rb, client.t(errorMessage)) + service.Notice(rb, client.t(errorMessage)) return } - if fixupNickEqualsAccount(client, rb, server.Config()) { - sendSuccessfulRegResponse(client, rb, true) + if fixupNickEqualsAccount(client, rb, server.Config(), service.prefix) { + sendSuccessfulRegResponse(service, client, rb) } } -func nsPasswdHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsPasswdHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var target string var newPassword string var errorMessage string @@ -1025,7 +1014,7 @@ func nsPasswdHandler(server *Server, client *Client, command string, params []st } else if params[1] != params[2] { errorMessage = `Passwords do not match` } else { - if !nsLoginThrottleCheck(client, rb) { + if !nsLoginThrottleCheck(service, client, rb) { return } accountData, err := server.accounts.LoadAccount(target) @@ -1048,37 +1037,37 @@ func nsPasswdHandler(server *Server, client *Client, command string, params []st } if errorMessage != "" { - nsNotice(rb, client.t(errorMessage)) + service.Notice(rb, client.t(errorMessage)) return } err := server.accounts.setPassword(target, newPassword, hasPrivs) switch err { case nil: - nsNotice(rb, client.t("Password changed")) + service.Notice(rb, client.t("Password changed")) case errEmptyCredentials: - nsNotice(rb, client.t("You can't delete your password unless you add a certificate fingerprint")) + service.Notice(rb, client.t("You can't delete your password unless you add a certificate fingerprint")) case errCredsExternallyManaged: - nsNotice(rb, client.t("Your account credentials are managed externally and cannot be changed here")) + service.Notice(rb, client.t("Your account credentials are managed externally and cannot be changed here")) case errCASFailed: - nsNotice(rb, client.t("Try again later")) + service.Notice(rb, client.t("Try again later")) default: server.logger.Error("internal", "could not upgrade user password:", err.Error()) - nsNotice(rb, client.t("Password could not be changed due to server error")) + service.Notice(rb, client.t("Password could not be changed due to server error")) } } -func nsEnforceHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsEnforceHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { newParams := []string{"enforce"} if len(params) == 0 { - nsGetHandler(server, client, "get", newParams, rb) + nsGetHandler(service, server, client, "get", newParams, rb) } else { newParams = append(newParams, params[0]) - nsSetHandler(server, client, "set", newParams, rb) + nsSetHandler(service, server, client, "set", newParams, rb) } } -func nsClientsHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsClientsHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { var verb string if command == "sessions" { @@ -1091,56 +1080,56 @@ func nsClientsHandler(server *Server, client *Client, command string, params []s switch verb { case "list": - nsClientsListHandler(server, client, params, rb) + nsClientsListHandler(service, server, client, params, rb) case "logout": - nsClientsLogoutHandler(server, client, params, rb) + nsClientsLogoutHandler(service, server, client, params, rb) default: - nsNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) } } -func nsClientsListHandler(server *Server, client *Client, params []string, rb *ResponseBuffer) { +func nsClientsListHandler(service *ircService, server *Server, client *Client, params []string, rb *ResponseBuffer) { target := client hasPrivs := client.HasRoleCapabs("local_ban") if 0 < len(params) { target = server.clients.Get(params[0]) if target == nil { - nsNotice(rb, client.t("No such nick")) + service.Notice(rb, client.t("No such nick")) return } if target != client && !hasPrivs { - nsNotice(rb, client.t("Command restricted")) + service.Notice(rb, client.t("Command restricted")) return } } sessionData, currentIndex := target.AllSessionData(rb.session, hasPrivs) - nsNotice(rb, fmt.Sprintf(client.t("Nickname %[1]s has %[2]d attached clients(s)"), target.Nick(), len(sessionData))) + service.Notice(rb, fmt.Sprintf(client.t("Nickname %[1]s has %[2]d attached clients(s)"), target.Nick(), len(sessionData))) for i, session := range sessionData { if currentIndex == i { - nsNotice(rb, fmt.Sprintf(client.t("Client %d (currently attached client):"), session.sessionID)) + service.Notice(rb, fmt.Sprintf(client.t("Client %d (currently attached client):"), session.sessionID)) } else { - nsNotice(rb, fmt.Sprintf(client.t("Client %d:"), session.sessionID)) + service.Notice(rb, fmt.Sprintf(client.t("Client %d:"), session.sessionID)) } if session.deviceID != "" { - nsNotice(rb, fmt.Sprintf(client.t("Device ID: %s"), session.deviceID)) + service.Notice(rb, fmt.Sprintf(client.t("Device ID: %s"), session.deviceID)) } - nsNotice(rb, fmt.Sprintf(client.t("IP address: %s"), session.ip.String())) - nsNotice(rb, fmt.Sprintf(client.t("Hostname: %s"), session.hostname)) + service.Notice(rb, fmt.Sprintf(client.t("IP address: %s"), session.ip.String())) + service.Notice(rb, fmt.Sprintf(client.t("Hostname: %s"), session.hostname)) if hasPrivs { - nsNotice(rb, fmt.Sprintf(client.t("Connection: %s"), session.connInfo)) + service.Notice(rb, fmt.Sprintf(client.t("Connection: %s"), session.connInfo)) } - nsNotice(rb, fmt.Sprintf(client.t("Created at: %s"), session.ctime.Format(time.RFC1123))) - nsNotice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(time.RFC1123))) + service.Notice(rb, fmt.Sprintf(client.t("Created at: %s"), session.ctime.Format(time.RFC1123))) + service.Notice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(time.RFC1123))) if session.certfp != "" { - nsNotice(rb, fmt.Sprintf(client.t("Certfp: %s"), session.certfp)) + service.Notice(rb, fmt.Sprintf(client.t("Certfp: %s"), session.certfp)) } } } -func nsClientsLogoutHandler(server *Server, client *Client, params []string, rb *ResponseBuffer) { +func nsClientsLogoutHandler(service *ircService, server *Server, client *Client, params []string, rb *ResponseBuffer) { if len(params) < 1 { - nsNotice(rb, client.t("Missing client ID to logout (or \"all\")")) + service.Notice(rb, client.t("Missing client ID to logout (or \"all\")")) return } @@ -1149,14 +1138,14 @@ func nsClientsLogoutHandler(server *Server, client *Client, params []string, rb // CLIENTS LOGOUT [nickname] [client ID] target = server.clients.Get(params[0]) if target == nil { - nsNotice(rb, client.t("No such nick")) + service.Notice(rb, client.t("No such nick")) return } // User must have "local_kill" privileges to logout other user sessions. if target != client { oper := client.Oper() if oper == nil || !oper.Class.Capabilities.Has("local_kill") { - nsNotice(rb, client.t("Insufficient oper privs")) + service.Notice(rb, client.t("Insufficient oper privs")) return } } @@ -1167,7 +1156,7 @@ func nsClientsLogoutHandler(server *Server, client *Client, params []string, rb if strings.ToLower(params[0]) != "all" { sessionID, err := strconv.ParseInt(params[0], 10, 64) if err != nil { - nsNotice(rb, client.t("Client ID to logout should be an integer (or \"all\")")) + service.Notice(rb, client.t("Client ID to logout should be an integer (or \"all\")")) return } // Find the client ID that the user requested to logout. @@ -1178,7 +1167,7 @@ func nsClientsLogoutHandler(server *Server, client *Client, params []string, rb } } if sessionToDestroy == nil { - nsNotice(rb, client.t("Specified client ID does not exist")) + service.Notice(rb, client.t("Specified client ID does not exist")) return } } @@ -1186,14 +1175,14 @@ func nsClientsLogoutHandler(server *Server, client *Client, params []string, rb target.destroy(sessionToDestroy) if (sessionToDestroy != nil && rb.session != sessionToDestroy) || client != target { if sessionToDestroy != nil { - nsNotice(rb, client.t("Successfully logged out session")) + service.Notice(rb, client.t("Successfully logged out session")) } else { - nsNotice(rb, client.t("Successfully logged out all sessions")) + service.Notice(rb, client.t("Successfully logged out all sessions")) } } } -func nsCertHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsCertHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { verb := strings.ToLower(params[0]) params = params[1:] var target, certfp string @@ -1211,22 +1200,22 @@ func nsCertHandler(server *Server, client *Client, command string, params []stri } else if len(params) == 0 && verb == "add" && rb.session.certfp != "" { certfp = rb.session.certfp // #1059 } else { - nsNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) return } default: - nsNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) return } hasPrivs := client.HasRoleCapabs("accreg") if target != "" && !hasPrivs { - nsNotice(rb, client.t("Insufficient privileges")) + service.Notice(rb, client.t("Insufficient privileges")) return } else if target == "" { target = client.Account() if target == "" { - nsNotice(rb, client.t("You're not logged into an account")) + service.Notice(rb, client.t("You're not logged into an account")) return } } @@ -1236,16 +1225,16 @@ func nsCertHandler(server *Server, client *Client, command string, params []stri case "list": accountData, err := server.accounts.LoadAccount(target) if err == errAccountDoesNotExist { - nsNotice(rb, client.t("Account does not exist")) + service.Notice(rb, client.t("Account does not exist")) return } else if err != nil { - nsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) return } certfps := accountData.Credentials.Certfps - nsNotice(rb, fmt.Sprintf(client.t("There are %[1]d certificate fingerprint(s) authorized for account %[2]s."), len(certfps), accountData.Name)) + service.Notice(rb, fmt.Sprintf(client.t("There are %[1]d certificate fingerprint(s) authorized for account %[2]s."), len(certfps), accountData.Name)) for i, certfp := range certfps { - nsNotice(rb, fmt.Sprintf("%d: %s", i+1, certfp)) + service.Notice(rb, fmt.Sprintf("%d: %s", i+1, certfp)) } return case "add": @@ -1257,54 +1246,54 @@ func nsCertHandler(server *Server, client *Client, command string, params []stri switch err { case nil: if verb == "add" { - nsNotice(rb, client.t("Certificate fingerprint successfully added")) + service.Notice(rb, client.t("Certificate fingerprint successfully added")) } else { - nsNotice(rb, client.t("Certificate fingerprint successfully removed")) + service.Notice(rb, client.t("Certificate fingerprint successfully removed")) } case errNoop: if verb == "add" { - nsNotice(rb, client.t("That certificate fingerprint was already authorized")) + service.Notice(rb, client.t("That certificate fingerprint was already authorized")) } else { - nsNotice(rb, client.t("Certificate fingerprint not found")) + service.Notice(rb, client.t("Certificate fingerprint not found")) } case errAccountDoesNotExist: - nsNotice(rb, client.t("Account does not exist")) + service.Notice(rb, client.t("Account does not exist")) case errLimitExceeded: - nsNotice(rb, client.t("You already have too many certificate fingerprints")) + service.Notice(rb, client.t("You already have too many certificate fingerprints")) case utils.ErrInvalidCertfp: - nsNotice(rb, client.t("Invalid certificate fingerprint")) + service.Notice(rb, client.t("Invalid certificate fingerprint")) case errCertfpAlreadyExists: - nsNotice(rb, client.t("That certificate fingerprint is already associated with another account")) + service.Notice(rb, client.t("That certificate fingerprint is already associated with another account")) case errEmptyCredentials: - nsNotice(rb, client.t("You can't remove all your certificate fingerprints unless you add a password")) + service.Notice(rb, client.t("You can't remove all your certificate fingerprints unless you add a password")) case errCredsExternallyManaged: - nsNotice(rb, client.t("Your account credentials are managed externally and cannot be changed here")) + service.Notice(rb, client.t("Your account credentials are managed externally and cannot be changed here")) case errCASFailed: - nsNotice(rb, client.t("Try again later")) + service.Notice(rb, client.t("Try again later")) default: server.logger.Error("internal", "could not modify certificates:", err.Error()) - nsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } -func nsSuspendHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsSuspendHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { subCmd := strings.ToLower(params[0]) params = params[1:] switch subCmd { case "add": - nsSuspendAddHandler(server, client, command, params, rb) + nsSuspendAddHandler(service, server, client, command, params, rb) case "del", "delete", "remove": - nsSuspendRemoveHandler(server, client, command, params, rb) + nsSuspendRemoveHandler(service, server, client, command, params, rb) case "list": - nsSuspendListHandler(server, client, command, params, rb) + nsSuspendListHandler(service, server, client, command, params, rb) default: - nsNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) } } -func nsSuspendAddHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsSuspendAddHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if len(params) == 0 { - nsNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) return } @@ -1316,7 +1305,7 @@ func nsSuspendAddHandler(server *Server, client *Client, command string, params var err error cDuration, err := custime.ParseDuration(params[1]) if err != nil { - nsNotice(rb, client.t("Invalid time duration for NS SUSPEND")) + service.Notice(rb, client.t("Invalid time duration for NS SUSPEND")) return } duration = time.Duration(cDuration) @@ -1333,30 +1322,30 @@ func nsSuspendAddHandler(server *Server, client *Client, command string, params err := server.accounts.Suspend(account, duration, name, reason) switch err { case nil: - nsNotice(rb, fmt.Sprintf(client.t("Successfully suspended account %s"), account)) + service.Notice(rb, fmt.Sprintf(client.t("Successfully suspended account %s"), account)) case errAccountDoesNotExist: - nsNotice(rb, client.t("No such account")) + service.Notice(rb, client.t("No such account")) default: - nsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } -func nsSuspendRemoveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsSuspendRemoveHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if len(params) == 0 { - nsNotice(rb, client.t("Invalid parameters")) + service.Notice(rb, client.t("Invalid parameters")) return } err := server.accounts.Unsuspend(params[0]) switch err { case nil: - nsNotice(rb, fmt.Sprintf(client.t("Successfully un-suspended account %s"), params[0])) + service.Notice(rb, fmt.Sprintf(client.t("Successfully un-suspended account %s"), params[0])) case errAccountDoesNotExist: - nsNotice(rb, client.t("No such account")) + service.Notice(rb, client.t("No such account")) case errNoop: - nsNotice(rb, client.t("Account was not suspended")) + service.Notice(rb, client.t("Account was not suspended")) default: - nsNotice(rb, client.t("An error occurred")) + service.Notice(rb, client.t("An error occurred")) } } @@ -1367,12 +1356,12 @@ func (a ByCreationTime) Len() int { return len(a) } func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByCreationTime) Less(i, j int) bool { return a[i].TimeCreated.After(a[j].TimeCreated) } -func nsSuspendListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +func nsSuspendListHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { suspensions := server.accounts.ListSuspended() sort.Sort(ByCreationTime(suspensions)) - nsNotice(rb, fmt.Sprintf(client.t("There are %d active suspensions."), len(suspensions))) + service.Notice(rb, fmt.Sprintf(client.t("There are %d active suspensions."), len(suspensions))) for _, suspension := range suspensions { - nsNotice(rb, suspensionToString(client, suspension)) + service.Notice(rb, suspensionToString(client, suspension)) } } @@ -1389,21 +1378,21 @@ 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 nsRenameHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { +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) if err != nil { - nsNotice(rb, fmt.Sprintf(client.t("Couldn't rename account: %s"), client.t(err.Error()))) + service.Notice(rb, fmt.Sprintf(client.t("Couldn't rename account: %s"), client.t(err.Error()))) return } - nsNotice(rb, client.t("Successfully renamed account")) + service.Notice(rb, client.t("Successfully renamed account")) if server.Config().Accounts.NickReservation.ForceNickEqualsAccount { if curClient := server.clients.Get(oldName); curClient != nil { renameErr := performNickChange(client.server, client, curClient, nil, newName, rb) if renameErr != nil && renameErr != errNoop { - nsNotice(rb, fmt.Sprintf(client.t("Warning: could not rename affected client: %v"), err)) + service.Notice(rb, fmt.Sprintf(client.t("Warning: could not rename affected client: %v"), err)) } } } diff --git a/irc/server.go b/irc/server.go index c983fa00..13d6764f 100644 --- a/irc/server.go +++ b/irc/server.go @@ -530,6 +530,8 @@ func (server *Server) applyConfig(config *Config) (err error) { } else if oldConfig.Server.IPCheckScript.MaxConcurrency != config.Server.IPCheckScript.MaxConcurrency || oldConfig.Accounts.AuthScript.MaxConcurrency != config.Accounts.AuthScript.MaxConcurrency { return fmt.Errorf("Cannot change max-concurrency for scripts after launching the server, rehash aborted") + } else if oldConfig.Server.OverrideServicesHostname != config.Server.OverrideServicesHostname { + return fmt.Errorf("Cannot change override-services-hostname after launching the server, rehash aborted") } } @@ -563,6 +565,10 @@ func (server *Server) applyConfig(config *Config) (err error) { if maxAuthConc != 0 { server.semaphores.AuthScript.Initialize(maxAuthConc) } + + if err := overrideServicePrefixes(config.Server.OverrideServicesHostname); err != nil { + return err + } } if oldConfig != nil { diff --git a/irc/services.go b/irc/services.go index 9fe7dd19..c4451c34 100644 --- a/irc/services.go +++ b/irc/services.go @@ -20,7 +20,7 @@ import ( type ircService struct { Name string ShortName string - prefix string + prefix string // NUH source of messages from this service CommandAliases []string Commands map[string]*serviceCommand HelpBanner string @@ -30,7 +30,7 @@ type ircService struct { type serviceCommand struct { aliasOf string // marks this command as an alias of another capabs []string // oper capabs the given user has to have to access this command - handler func(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) + handler func(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) help string helpStrings []string helpShort string @@ -60,36 +60,47 @@ func lookupServiceCommand(commands map[string]*serviceCommand, command string) * return nil } -// all services, by lowercase name -var OragonoServices = map[string]*ircService{ - "nickserv": { +var ( + nickservService = &ircService{ Name: "NickServ", ShortName: "NS", CommandAliases: []string{"NICKSERV", "NS"}, Commands: nickservCommands, HelpBanner: nickservHelp, - }, - "chanserv": { + } + chanservService = &ircService{ Name: "ChanServ", ShortName: "CS", CommandAliases: []string{"CHANSERV", "CS"}, Commands: chanservCommands, HelpBanner: chanservHelp, - }, - "hostserv": { + } + hostservService = &ircService{ Name: "HostServ", ShortName: "HS", CommandAliases: []string{"HOSTSERV", "HS"}, Commands: hostservCommands, HelpBanner: hostservHelp, - }, - "histserv": { + } + histservService = &ircService{ Name: "HistServ", ShortName: "HISTSERV", CommandAliases: []string{"HISTSERV"}, Commands: histservCommands, HelpBanner: histservHelp, - }, + } +) + +// all services, by lowercase name +var OragonoServices = map[string]*ircService{ + "nickserv": nickservService, + "chanserv": chanservService, + "hostserv": hostservService, + "histserv": histservService, +} + +func (service *ircService) Notice(rb *ResponseBuffer, text string) { + rb.Add(nil, service.prefix, "NOTICE", rb.target.Nick(), text) } // all service commands at the protocol level, by uppercase command name @@ -212,7 +223,7 @@ func serviceRunCommand(service *ircService, server *Server, client *Client, cmd if commandName == "help" { serviceHelpHandler(service, server, client, params, rb) } else { - cmd.handler(server, client, commandName, params, rb) + cmd.handler(service, server, client, commandName, params, rb) } } @@ -305,6 +316,19 @@ func makeServiceHelpTextGenerator(cmd string, banner string) func(*Client) strin } } +func overrideServicePrefixes(hostname string) error { + if hostname == "" { + return nil + } + if !utils.IsHostname(hostname) { + return fmt.Errorf("`%s` is an invalid services hostname", hostname) + } + for _, serv := range OragonoServices { + serv.prefix = fmt.Sprintf("%s!%s@%s", serv.Name, serv.Name, hostname) + } + return nil +} + func initializeServices() { // this modifies the global Commands map, // so it must be called from irc/commands.go's init() diff --git a/traditional.yaml b/traditional.yaml index b836de48..7e163919 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -307,7 +307,11 @@ server: # oragono will write files to disk under certain circumstances, e.g., # CPU profiling or data export. by default, these files will be written # to the working directory. set this to customize: - # output-path: "/home/oragono/out" + #output-path: "/home/oragono/out" + + # the hostname used by "services", e.g., NickServ, defaults to "localhost", + # e.g., `NickServ!NickServ@localhost`. uncomment this to override: + #override-services-hostname: "example.network" # account options accounts: