diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 72b30dce..cc27ab9e 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -683,9 +683,9 @@ oper-classes: # capability names capabilities: - - "oper:local_kill" - - "oper:local_ban" - - "oper:local_unban" + - "local_kill" + - "local_ban" + - "local_unban" - "nofakelag" # ircd operators diff --git a/irc/channel.go b/irc/channel.go index 3958355f..cf61c390 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -1364,12 +1364,17 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuffer) { chname := channel.Name() if channel.flags.HasMode(modes.InviteOnly) && !channel.ClientIsAtLeast(inviter, modes.ChannelOperator) { - rb.Add(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, inviter.Nick(), channel.Name(), inviter.t("You're not a channel operator")) + rb.Add(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, inviter.Nick(), chname, inviter.t("You're not a channel operator")) return } if !channel.hasClient(inviter) { - rb.Add(nil, inviter.server.name, ERR_NOTONCHANNEL, inviter.Nick(), channel.Name(), inviter.t("You're not on that channel")) + rb.Add(nil, inviter.server.name, ERR_NOTONCHANNEL, inviter.Nick(), chname, inviter.t("You're not on that channel")) + return + } + + if channel.hasClient(invitee) { + rb.Add(nil, inviter.server.name, ERR_USERONCHANNEL, inviter.Nick(), invitee.Nick(), chname, inviter.t("User is already on that channel")) return } diff --git a/irc/chanserv.go b/irc/chanserv.go index adc1845c..e1d690d0 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -293,16 +293,17 @@ func csOpHandler(server *Server, client *Client, command string, params []string } func csRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { - channelName := params[0] - - channelKey, err := CasefoldChannel(channelName) - if err != nil { - csNotice(rb, client.t("Channel name is not valid")) + if server.Config().Channels.Registration.OperatorOnly && !client.HasRoleCapabs("chanreg") { + csNotice(rb, client.t("Channel registration is restricted to server operators")) return } - - channelInfo := server.channels.Get(channelKey) - if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) { + channelName := params[0] + channelInfo := server.channels.Get(channelName) + if channelInfo == nil { + csNotice(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")) return } @@ -313,7 +314,7 @@ func csRegisterHandler(server *Server, client *Client, command string, params [] } // this provides the synchronization that allows exactly one registration of the channel: - err = server.channels.SetRegistered(channelKey, account) + err := server.channels.SetRegistered(channelName, account) if err != nil { csNotice(rb, err.Error()) return diff --git a/irc/client.go b/irc/client.go index 1b3ed395..999343a2 100644 --- a/irc/client.go +++ b/irc/client.go @@ -973,7 +973,7 @@ func (client *Client) HasRoleCapabs(capabs ...string) bool { } for _, capab := range capabs { - if !oper.Class.Capabilities[capab] { + if !oper.Class.Capabilities.Has(capab) { return false } } diff --git a/irc/commands.go b/irc/commands.go index cdc7804f..f89b55eb 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -160,7 +160,7 @@ func init() { handler: killHandler, minParams: 1, oper: true, - capabs: []string{"oper:local_kill"}, //TODO(dan): when we have S2S, this will be checked in the command handler itself + capabs: []string{"local_kill"}, //TODO(dan): when we have S2S, this will be checked in the command handler itself }, "KLINE": { handler: klineHandler, @@ -289,7 +289,7 @@ func init() { handler: rehashHandler, minParams: 0, oper: true, - capabs: []string{"oper:rehash"}, + capabs: []string{"rehash"}, }, "TIME": { handler: timeHandler, diff --git a/irc/config.go b/irc/config.go index 9d5e9331..6912df7c 100644 --- a/irc/config.go +++ b/irc/config.go @@ -385,12 +385,6 @@ func (cm *Casemapping) UnmarshalYAML(unmarshal func(interface{}) error) (err err return nil } -// ChannelRegistrationConfig controls channel registration. -type ChannelRegistrationConfig struct { - Enabled bool - MaxChannelsPerAccount int `yaml:"max-channels-per-account"` -} - // OperClassConfig defines a specific operator class. type OperClassConfig struct { Title string @@ -534,7 +528,11 @@ type Config struct { defaultModes modes.Modes MaxChannelsPerClient int `yaml:"max-channels-per-client"` OpOnlyCreation bool `yaml:"operator-only-creation"` - Registration ChannelRegistrationConfig + Registration struct { + Enabled bool + OperatorOnly bool `yaml:"operator-only"` + MaxChannelsPerAccount int `yaml:"max-channels-per-account"` + } } OperClasses map[string]*OperClassConfig `yaml:"oper-classes"` @@ -584,12 +582,16 @@ type Config struct { // OperClass defines an assembled operator class. type OperClass struct { Title string - WhoisLine string `yaml:"whois-line"` - Capabilities map[string]bool // map to make lookups much easier + WhoisLine string `yaml:"whois-line"` + Capabilities StringSet // map to make lookups much easier } // OperatorClasses returns a map of assembled operator classes from the given config. func (conf *Config) OperatorClasses() (map[string]*OperClass, error) { + fixupCapability := func(capab string) string { + return strings.TrimPrefix(capab, "oper:") // #868 + } + ocs := make(map[string]*OperClass) // loop from no extends to most extended, breaking if we can't add any more @@ -619,21 +621,21 @@ func (conf *Config) OperatorClasses() (map[string]*OperClass, error) { // create new operclass var oc OperClass - oc.Capabilities = make(map[string]bool) + oc.Capabilities = make(StringSet) // get inhereted info from other operclasses if len(info.Extends) > 0 { einfo := ocs[info.Extends] for capab := range einfo.Capabilities { - oc.Capabilities[capab] = true + oc.Capabilities.Add(fixupCapability(capab)) } } // add our own info oc.Title = info.Title for _, capab := range info.Capabilities { - oc.Capabilities[capab] = true + oc.Capabilities.Add(fixupCapability(capab)) } if len(info.WhoisLine) > 0 { oc.WhoisLine = info.WhoisLine @@ -1114,7 +1116,7 @@ func (config *Config) generateISupport() (err error) { isupport.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen)) isupport.Add("CASEMAPPING", "ascii") isupport.Add("CHANLIMIT", fmt.Sprintf("%s:%d", chanTypes, config.Channels.MaxChannelsPerClient)) - isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), "", modes.Modes{modes.UserLimit, modes.Key}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret}.String()}, ",")) + isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), "", modes.Modes{modes.UserLimit, modes.Key}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret, modes.NoCTCP, modes.RegisteredOnly}.String()}, ",")) if config.History.Enabled && config.History.ChathistoryMax > 0 { isupport.Add("draft/CHATHISTORY", strconv.Itoa(config.History.ChathistoryMax)) } diff --git a/irc/handlers.go b/irc/handlers.go index 2c989d32..3a78261e 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -716,7 +716,7 @@ func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res rb.Notice(fmt.Sprintf("CPU profiling stopped")) case "CRASHSERVER": - if !client.HasRoleCapabs("oper:rehash") { + if !client.HasRoleCapabs("rehash") { rb.Notice(client.t("You must have rehash permissions in order to execute DEBUG CRASHSERVER")) return false } @@ -770,7 +770,7 @@ func formatBanForListing(client *Client, key string, info IPBanInfo) string { func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { // check oper permissions oper := client.Oper() - if oper == nil || !oper.Class.Capabilities["oper:local_ban"] { + if oper == nil || !oper.Class.Capabilities.Has("local_ban") { rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs")) return false } @@ -1042,7 +1042,7 @@ func inviteHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re channel := server.channels.Get(channelName) if channel == nil { - rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, utils.SafeErrorParam(channelName), client.t("No such channel")) + rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(channelName), client.t("No such channel")) return false } @@ -1229,7 +1229,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res details := client.Details() // check oper permissions oper := client.Oper() - if oper == nil || !oper.Class.Capabilities["oper:local_ban"] { + if oper == nil || !oper.Class.Capabilities.Has("local_ban") { rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs")) return false } @@ -2383,7 +2383,7 @@ func topicHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { // check oper permissions oper := client.Oper() - if oper == nil || !oper.Class.Capabilities["oper:local_unban"] { + if oper == nil || !oper.Class.Capabilities.Has("local_unban") { rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs")) return false } @@ -2417,7 +2417,7 @@ func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R details := client.Details() // check oper permissions oper := client.Oper() - if oper == nil || !oper.Class.Capabilities["oper:local_unban"] { + if oper == nil || !oper.Class.Capabilities.Has("local_unban") { rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs")) return false } @@ -2558,13 +2558,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re continue } - proxiedIP := msg.Params[3] - // see #211; websocket gateways will wrap ipv6 addresses in square brackets - // because IRC parameters can't start with : - if strings.HasPrefix(proxiedIP, "[") && strings.HasSuffix(proxiedIP, "]") { - proxiedIP = proxiedIP[1 : len(proxiedIP)-1] - } - err, quitMsg := client.ApplyProxiedIP(rb.session, proxiedIP, secure) + err, quitMsg := client.ApplyProxiedIP(rb.session, msg.Params[3], secure) if err != nil { client.Quit(quitMsg, rb.session) return true diff --git a/irc/hostserv.go b/irc/hostserv.go index 08ffc900..b27ada0b 100644 --- a/irc/hostserv.go +++ b/irc/hostserv.go @@ -12,8 +12,11 @@ import ( "github.com/oragono/oragono/irc/sno" ) -const hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed +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 ( errVHostBadCharacters = errors.New("Vhost contains prohibited characters") @@ -173,7 +176,7 @@ the offered vhosts, use /HOSTSERV OFFERLIST.`, // hsNotice sends the client a notice from HostServ func hsNotice(rb *ResponseBuffer, text string) { - rb.Add(nil, "HostServ!HostServ@localhost", "NOTICE", rb.target.Nick(), text) + rb.Add(nil, hsNickMask, "NOTICE", rb.target.Nick(), text) } // hsNotifyChannel notifies the designated channel of new vhost activity @@ -185,7 +188,7 @@ func hsNotifyChannel(server *Server, message string) { } chname = channel.Name() for _, client := range channel.Members() { - client.Send(nil, "HostServ", "PRIVMSG", chname, message) + client.Send(nil, hsNickMask, "PRIVMSG", chname, message) } } @@ -333,7 +336,7 @@ func hsApproveHandler(server *Server, client *Client, command string, params []s hsNotifyChannel(server, chanMsg) server.snomasks.Send(sno.LocalVhosts, chanMsg) for _, client := range server.accounts.AccountToClients(user) { - client.Notice(client.t("Your vhost request was approved by an administrator")) + client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was approved by an administrator")) } } } @@ -355,9 +358,9 @@ func hsRejectHandler(server *Server, client *Client, command string, params []st server.snomasks.Send(sno.LocalVhosts, chanMsg) for _, client := range server.accounts.AccountToClients(user) { if reason == "" { - client.Notice("Your vhost request was rejected by an administrator") + client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was rejected by an administrator")) } else { - client.Notice(fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason)) + client.Send(nil, hsNickMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason)) } } } diff --git a/irc/server.go b/irc/server.go index 0076cbc3..8c04cacf 100644 --- a/irc/server.go +++ b/irc/server.go @@ -443,7 +443,7 @@ func (server *Server) Lusers(client *Client, rb *ResponseBuffer) { rb.Add(nil, server.name, RPL_LUSEROP, nick, strconv.Itoa(stats.Operators), client.t("IRC Operators online")) rb.Add(nil, server.name, RPL_LUSERUNKNOWN, nick, strconv.Itoa(stats.Unknown), client.t("unregistered connections")) rb.Add(nil, server.name, RPL_LUSERCHANNELS, nick, strconv.Itoa(server.channels.Len()), client.t("channels formed")) - rb.Add(nil, server.name, RPL_LUSERME, nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), stats.Total, 1)) + rb.Add(nil, server.name, RPL_LUSERME, nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), stats.Total, 0)) total := strconv.Itoa(stats.Total) max := strconv.Itoa(stats.Max) rb.Add(nil, server.name, RPL_LOCALUSERS, nick, total, max, fmt.Sprintf(client.t("Current local users %[1]s, max %[2]s"), total, max)) diff --git a/oragono.yaml b/oragono.yaml index 4df55c55..f57151e3 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -472,6 +472,10 @@ channels: # can users register new channels? enabled: true + # restrict new channel registrations to operators only? + # (operators can then transfer channels to regular users using /CS TRANSFER) + operator-only: false + # how many channels can each account register? max-channels-per-account: 15 @@ -484,9 +488,9 @@ oper-classes: # capability names capabilities: - - "oper:local_kill" - - "oper:local_ban" - - "oper:local_unban" + - "local_kill" + - "local_ban" + - "local_unban" - "nofakelag" # network operator @@ -499,9 +503,9 @@ oper-classes: # capability names capabilities: - - "oper:remote_kill" - - "oper:remote_ban" - - "oper:remote_unban" + - "remote_kill" + - "remote_ban" + - "remote_unban" # server admin "server-admin": @@ -513,8 +517,8 @@ oper-classes: # capability names capabilities: - - "oper:rehash" - - "oper:die" + - "rehash" + - "die" - "accreg" - "sajoin" - "samode"