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

Merge pull request #479 from slingamn/compat.4

add client compatibility switches
This commit is contained in:
Daniel Oaks 2019-05-12 15:38:35 +10:00 committed by GitHub
commit 585a6557a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 120 additions and 152 deletions

View File

@ -5,7 +5,6 @@ package irc
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/smtp" "net/smtp"
"strconv" "strconv"
@ -483,7 +482,7 @@ func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount str
} else if callbackNamespace == "mailto" { } else if callbackNamespace == "mailto" {
return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue) return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue)
} else { } else {
return "", errors.New(fmt.Sprintf("Callback not implemented: %s", callbackNamespace)) return "", fmt.Errorf("Callback not implemented: %s", callbackNamespace)
} }
} }
@ -1265,7 +1264,6 @@ func (am *AccountManager) Logout(client *Client) {
} }
} }
am.accountToClients[casefoldedAccount] = remainingClients am.accountToClients[casefoldedAccount] = remainingClients
return
} }
var ( var (

View File

@ -1094,34 +1094,6 @@ func (channel *Channel) ShowMaskList(client *Client, mode modes.Mode, rb *Respon
rb.Add(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list")) rb.Add(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list"))
} }
func (channel *Channel) applyModeMask(client *Client, mode modes.Mode, op modes.ModeOp, mask string, rb *ResponseBuffer) bool {
list := channel.lists[mode]
if list == nil {
// This should never happen, but better safe than panicky.
return false
}
if (op == modes.List) || (mask == "") {
channel.ShowMaskList(client, mode, rb)
return false
}
if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You're not a channel operator"))
return false
}
if op == modes.Add {
return list.Add(mask)
}
if op == modes.Remove {
return list.Remove(mask)
}
return false
}
// Quit removes the given client from the channel // Quit removes the given client from the channel
func (channel *Channel) Quit(client *Client) { func (channel *Channel) Quit(client *Client) {
channelEmpty := func() bool { channelEmpty := func() bool {

View File

@ -557,14 +557,12 @@ func (client *Client) tryResume() (success bool) {
func (client *Client) tryResumeChannels() { func (client *Client) tryResumeChannels() {
details := client.resumeDetails details := client.resumeDetails
channels := make([]*Channel, len(details.Channels))
for _, name := range details.Channels { for _, name := range details.Channels {
channel := client.server.channels.Get(name) channel := client.server.channels.Get(name)
if channel == nil { if channel == nil {
continue continue
} }
channel.Resume(client, details.OldClient, details.Timestamp) channel.Resume(client, details.OldClient, details.Timestamp)
channels = append(channels, channel)
} }
// replay direct PRIVSMG history // replay direct PRIVSMG history
@ -868,7 +866,8 @@ func (client *Client) LoggedIntoAccount() bool {
func (client *Client) RplISupport(rb *ResponseBuffer) { func (client *Client) RplISupport(rb *ResponseBuffer) {
translatedISupport := client.t("are supported by this server") translatedISupport := client.t("are supported by this server")
nick := client.Nick() nick := client.Nick()
for _, cachedTokenLine := range client.server.ISupport().CachedReply { config := client.server.Config()
for _, cachedTokenLine := range config.Server.isupport.CachedReply {
length := len(cachedTokenLine) + 2 length := len(cachedTokenLine) + 2
tokenline := make([]string, length) tokenline := make([]string, length)
tokenline[0] = nick tokenline[0] = nick
@ -1122,7 +1121,8 @@ var (
func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error { func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
// use dumb hack to force the last param to be a trailing param if required // use dumb hack to force the last param to be a trailing param if required
var usedTrailingHack bool var usedTrailingHack bool
if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 { config := session.client.server.Config()
if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
lastParam := message.Params[len(message.Params)-1] lastParam := message.Params[len(message.Params)-1]
// to force trailing, we ensure the final param contains a space // to force trailing, we ensure the final param contains a space
if strings.IndexByte(lastParam, ' ') == -1 { if strings.IndexByte(lastParam, ' ') == -1 {

View File

@ -20,6 +20,7 @@ import (
"code.cloudfoundry.org/bytefmt" "code.cloudfoundry.org/bytefmt"
"github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/connection_limits"
"github.com/oragono/oragono/irc/custime" "github.com/oragono/oragono/irc/custime"
"github.com/oragono/oragono/irc/isupport"
"github.com/oragono/oragono/irc/languages" "github.com/oragono/oragono/irc/languages"
"github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/logger"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
@ -280,6 +281,7 @@ type Config struct {
STS STSConfig STS STSConfig
CheckIdent bool `yaml:"check-ident"` CheckIdent bool `yaml:"check-ident"`
MOTD string MOTD string
motdLines []string
MOTDFormatting bool `yaml:"motd-formatting"` MOTDFormatting bool `yaml:"motd-formatting"`
ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` ProxyAllowedFrom []string `yaml:"proxy-allowed-from"`
proxyAllowedFromNets []net.IPNet proxyAllowedFromNets []net.IPNet
@ -287,6 +289,12 @@ type Config struct {
MaxSendQString string `yaml:"max-sendq"` MaxSendQString string `yaml:"max-sendq"`
MaxSendQBytes int MaxSendQBytes int
AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` AllowPlaintextResume bool `yaml:"allow-plaintext-resume"`
Compatibility struct {
ForceTrailing *bool `yaml:"force-trailing"`
forceTrailing bool
SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"`
}
isupport isupport.List
ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"`
ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
} }
@ -385,7 +393,7 @@ func (conf *Config) OperatorClasses() (map[string]*OperClass, error) {
// get inhereted info from other operclasses // get inhereted info from other operclasses
if len(info.Extends) > 0 { if len(info.Extends) > 0 {
einfo, _ := ocs[info.Extends] einfo := ocs[info.Extends]
for capab := range einfo.Capabilities { for capab := range einfo.Capabilities {
oc.Capabilities[capab] = true oc.Capabilities[capab] = true
@ -698,6 +706,20 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Channels.Registration.MaxChannelsPerAccount = 15 config.Channels.Registration.MaxChannelsPerAccount = 15
} }
forceTrailingPtr := config.Server.Compatibility.ForceTrailing
if forceTrailingPtr != nil {
config.Server.Compatibility.forceTrailing = *forceTrailingPtr
} else {
config.Server.Compatibility.forceTrailing = true
}
config.loadMOTD()
err = config.generateISupport()
if err != nil {
return nil, err
}
// in the current implementation, we disable history by creating a history buffer // in the current implementation, we disable history by creating a history buffer
// with zero capacity. but the `enabled` config option MUST be respected regardless // with zero capacity. but the `enabled` config option MUST be respected regardless
// of this detail // of this detail

View File

@ -5,24 +5,20 @@ package irc
import ( import (
"net" "net"
"sync/atomic"
"time" "time"
"unsafe"
"github.com/oragono/oragono/irc/isupport"
"github.com/oragono/oragono/irc/languages" "github.com/oragono/oragono/irc/languages"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
) )
func (server *Server) Config() (config *Config) { func (server *Server) Config() (config *Config) {
server.configurableStateMutex.RLock() return (*Config)(atomic.LoadPointer(&server.config))
config = server.config
server.configurableStateMutex.RUnlock()
return
} }
func (server *Server) ISupport() *isupport.List { func (server *Server) SetConfig(config *Config) {
server.configurableStateMutex.RLock() atomic.StorePointer(&server.config, unsafe.Pointer(config))
defer server.configurableStateMutex.RUnlock()
return server.isupport
} }
func (server *Server) Limits() Limits { func (server *Server) Limits() Limits {
@ -54,9 +50,7 @@ func (server *Server) GetOperator(name string) (oper *Oper) {
if err != nil { if err != nil {
return return
} }
server.configurableStateMutex.RLock() return server.Config().operators[name]
defer server.configurableStateMutex.RUnlock()
return server.config.operators[name]
} }
func (server *Server) Languages() (lm *languages.Manager) { func (server *Server) Languages() (lm *languages.Manager) {

View File

@ -298,7 +298,9 @@ func accVerifyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb
// AUTHENTICATE [<mechanism>|<data>|*] // AUTHENTICATE [<mechanism>|<data>|*]
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
config := server.Config()
details := client.Details() details := client.Details()
if details.account != "" { if details.account != "" {
rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account")) rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
return false return false
@ -321,7 +323,14 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
if mechanismIsEnabled { if mechanismIsEnabled {
client.saslInProgress = true client.saslInProgress = true
client.saslMechanism = mechanism client.saslMechanism = mechanism
if !config.Server.Compatibility.SendUnprefixedSasl {
// normal behavior
rb.Add(nil, server.name, "AUTHENTICATE", "+") rb.Add(nil, server.name, "AUTHENTICATE", "+")
} else {
// gross hack: send a raw message to ensure no tags or prefix
rb.Flush(true)
rb.session.SendRawMessage(ircmsg.MakeMessage(nil, "", "AUTHENTICATE", "+"), true)
}
} else { } else {
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed")) rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
} }
@ -1175,20 +1184,14 @@ func inviteHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
func isonHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func isonHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
var nicks = msg.Params var nicks = msg.Params
var err error ison := make([]string, 0, len(msg.Params))
var casefoldedNick string
ison := make([]string, 0)
for _, nick := range nicks { for _, nick := range nicks {
casefoldedNick, err = CasefoldName(nick) if iclient := server.clients.Get(nick); iclient != nil {
if err != nil { ison = append(ison, iclient.Nick())
continue
}
if iclient := server.clients.Get(casefoldedNick); iclient != nil {
ison = append(ison, iclient.nick)
} }
} }
rb.Add(nil, server.name, RPL_ISON, client.nick, strings.Join(nicks, " ")) rb.Add(nil, server.name, RPL_ISON, client.nick, strings.Join(ison, " "))
return false return false
} }
@ -2089,7 +2092,7 @@ func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
// OPER <name> <password> // OPER <name> <password>
func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
if client.HasMode(modes.Operator) == true { if client.HasMode(modes.Operator) {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "OPER", client.t("You're already opered-up!")) rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), "OPER", client.t("You're already opered-up!"))
return false return false
} }

View File

@ -22,9 +22,13 @@ type List struct {
// NewList returns a new List // NewList returns a new List
func NewList() *List { func NewList() *List {
var il List var il List
il.Initialize()
return &il
}
func (il *List) Initialize() {
il.Tokens = make(map[string]*string) il.Tokens = make(map[string]*string)
il.CachedReply = make([][]string, 0) il.CachedReply = make([][]string, 0)
return &il
} }
// Add adds an RPL_ISUPPORT token to our internal list // Add adds an RPL_ISUPPORT token to our internal list

View File

@ -170,7 +170,7 @@ func SplitChannelMembershipPrefixes(target string) (prefixes string, name string
prefixes = target[:i+1] prefixes = target[:i+1]
name = target[i+1:] name = target[i+1:]
default: default:
break return
} }
} }

View File

@ -36,5 +36,4 @@ func (serversem *ServerSemaphores) Initialize() {
capacity = MaxServerSemaphoreCapacity capacity = MaxServerSemaphoreCapacity
} }
serversem.ClientDestroy.Initialize(capacity) serversem.ClientDestroy.Initialize(capacity)
return
} }

View File

@ -19,11 +19,11 @@ import (
"sync" "sync"
"syscall" "syscall"
"time" "time"
"unsafe"
"github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/connection_limits"
"github.com/oragono/oragono/irc/isupport"
"github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/logger"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/sno" "github.com/oragono/oragono/irc/sno"
@ -65,20 +65,17 @@ type Server struct {
channels ChannelManager channels ChannelManager
channelRegistry ChannelRegistry channelRegistry ChannelRegistry
clients ClientManager clients ClientManager
config *Config config unsafe.Pointer
configFilename string configFilename string
configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash()
connectionLimiter *connection_limits.Limiter connectionLimiter *connection_limits.Limiter
connectionThrottler *connection_limits.Throttler connectionThrottler *connection_limits.Throttler
ctime time.Time ctime time.Time
dlines *DLineManager dlines *DLineManager
helpIndexManager HelpIndexManager helpIndexManager HelpIndexManager
isupport *isupport.List
klines *KLineManager klines *KLineManager
listeners map[string]*ListenerWrapper listeners map[string]*ListenerWrapper
logger *logger.Manager logger *logger.Manager
monitorManager *MonitorManager monitorManager *MonitorManager
motdLines []string
name string name string
nameCasefolded string nameCasefolded string
rehashMutex sync.Mutex // tier 4 rehashMutex sync.Mutex // tier 4
@ -140,13 +137,12 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
} }
// setISupport sets up our RPL_ISUPPORT reply. // setISupport sets up our RPL_ISUPPORT reply.
func (server *Server) setISupport() (err error) { func (config *Config) generateISupport() (err error) {
maxTargetsString := strconv.Itoa(maxTargets) maxTargetsString := strconv.Itoa(maxTargets)
config := server.Config()
// add RPL_ISUPPORT tokens // add RPL_ISUPPORT tokens
isupport := isupport.NewList() isupport := &config.Server.isupport
isupport.Initialize()
isupport.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen)) isupport.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen))
isupport.Add("CASEMAPPING", "ascii") isupport.Add("CASEMAPPING", "ascii")
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}.String()}, ","))
@ -174,21 +170,7 @@ func (server *Server) setISupport() (err error) {
isupport.Add("UTF8MAPPING", casemappingName) isupport.Add("UTF8MAPPING", casemappingName)
err = isupport.RegenerateCachedReply() err = isupport.RegenerateCachedReply()
if err != nil {
return return
}
server.configurableStateMutex.Lock()
server.isupport = isupport
server.configurableStateMutex.Unlock()
return
}
func loadChannelList(channel *Channel, list string, maskMode modes.Mode) {
if list == "" {
return
}
channel.lists[maskMode].AddAll(strings.Split(list, " "))
} }
// Shutdown shuts down the server. // Shutdown shuts down the server.
@ -474,9 +456,7 @@ func (client *Client) t(originalString string) string {
// MOTD serves the Message of the Day. // MOTD serves the Message of the Day.
func (server *Server) MOTD(client *Client, rb *ResponseBuffer) { func (server *Server) MOTD(client *Client, rb *ResponseBuffer) {
server.configurableStateMutex.RLock() motdLines := server.Config().Server.motdLines
motdLines := server.motdLines
server.configurableStateMutex.RUnlock()
if len(motdLines) < 1 { if len(motdLines) < 1 {
rb.Add(nil, server.name, ERR_NOMOTD, client.nick, client.t("MOTD File is missing")) rb.Add(nil, server.name, ERR_NOMOTD, client.nick, client.t("MOTD File is missing"))
@ -535,9 +515,7 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
tLanguages := target.Languages() tLanguages := target.Languages()
if 0 < len(tLanguages) { if 0 < len(tLanguages) {
params := []string{cnick, tnick} params := []string{cnick, tnick}
for _, str := range client.server.Languages().Codes(tLanguages) { params = append(params, client.server.Languages().Codes(tLanguages)...)
params = append(params, str)
}
params = append(params, client.t("can speak these languages")) params = append(params, client.t("can speak these languages"))
rb.Add(nil, client.server.name, RPL_WHOISLANGUAGE, params...) rb.Add(nil, client.server.name, RPL_WHOISLANGUAGE, params...)
} }
@ -609,7 +587,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted") return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
} else if server.name != config.Server.Name { } else if server.name != config.Server.Name {
return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted") return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
} else if server.config.Datastore.Path != config.Datastore.Path { } else if server.Config().Datastore.Path != config.Datastore.Path {
return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted") return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted")
} }
} }
@ -780,12 +758,8 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
} }
} }
server.loadMOTD(config.Server.MOTD, config.Server.MOTDFormatting)
// save a pointer to the new config // save a pointer to the new config
server.configurableStateMutex.Lock() server.SetConfig(config)
server.config = config
server.configurableStateMutex.Unlock()
server.logger.Info("server", "Using datastore", config.Datastore.Path) server.logger.Info("server", "Using datastore", config.Datastore.Path)
if initial { if initial {
@ -798,13 +772,8 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
// set RPL_ISUPPORT // set RPL_ISUPPORT
var newISupportReplies [][]string var newISupportReplies [][]string
oldISupportList := server.ISupport() if oldConfig != nil {
err = server.setISupport() newISupportReplies = oldConfig.Server.isupport.GetDifference(&config.Server.isupport)
if err != nil {
return err
}
if oldISupportList != nil {
newISupportReplies = oldISupportList.GetDifference(server.ISupport())
} }
// we are now open for business // we are now open for business
@ -859,11 +828,9 @@ func (server *Server) setupPprofListener(config *Config) {
} }
} }
func (server *Server) loadMOTD(motdPath string, useFormatting bool) error { func (config *Config) loadMOTD() (err error) {
server.logger.Info("server", "Using MOTD", motdPath) if config.Server.MOTD != "" {
motdLines := make([]string, 0) file, err := os.Open(config.Server.MOTD)
if motdPath != "" {
file, err := os.Open(motdPath)
if err == nil { if err == nil {
defer file.Close() defer file.Close()
@ -875,7 +842,7 @@ func (server *Server) loadMOTD(motdPath string, useFormatting bool) error {
} }
line = strings.TrimRight(line, "\r\n") line = strings.TrimRight(line, "\r\n")
if useFormatting { if config.Server.MOTDFormatting {
line = ircfmt.Unescape(line) line = ircfmt.Unescape(line)
} }
@ -883,17 +850,11 @@ func (server *Server) loadMOTD(motdPath string, useFormatting bool) error {
// bursting it out to clients easier // bursting it out to clients easier
line = fmt.Sprintf("- %s", line) line = fmt.Sprintf("- %s", line)
motdLines = append(motdLines, line) config.Server.motdLines = append(config.Server.motdLines, line)
}
} else {
return err
} }
} }
}
server.configurableStateMutex.Lock() return
server.motdLines = motdLines
server.configurableStateMutex.Unlock()
return nil
} }
func (server *Server) loadDatastore(config *Config) error { func (server *Server) loadDatastore(config *Config) error {

View File

@ -52,7 +52,7 @@ func (m *SnoManager) RemoveMasks(client *Client, masks ...sno.Mask) {
for _, mask := range masks { for _, mask := range masks {
currentClientList := m.sendLists[mask] currentClientList := m.sendLists[mask]
if currentClientList == nil || len(currentClientList) == 0 { if len(currentClientList) == 0 {
continue continue
} }
@ -70,7 +70,7 @@ func (m *SnoManager) RemoveClient(client *Client) {
for mask := range m.sendLists { for mask := range m.sendLists {
currentClientList := m.sendLists[mask] currentClientList := m.sendLists[mask]
if currentClientList == nil || len(currentClientList) == 0 { if len(currentClientList) == 0 {
continue continue
} }
@ -87,7 +87,7 @@ func (m *SnoManager) Send(mask sno.Mask, content string) {
currentClientList := m.sendLists[mask] currentClientList := m.sendLists[mask]
if currentClientList == nil || len(currentClientList) == 0 { if len(currentClientList) == 0 {
return return
} }

View File

@ -125,6 +125,21 @@ server:
# this should be big enough to hold bursts of channel/direct messages # this should be big enough to hold bursts of channel/direct messages
max-sendq: 16k max-sendq: 16k
# compatibility with legacy clients
compatibility:
# many clients require that the final parameter of certain messages be an
# RFC1459 trailing parameter, i.e., prefixed with :, whether or not this is
# actually required. this forces Oragono to send those parameters
# as trailings. this is recommended unless you're testing clients for conformance;
# defaults to true when unset for that reason.
force-trailing: true
# some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower, Adium) do not
# respond correctly to SASL messages with the server name as a prefix:
# https://github.com/znc/znc/issues/1212
# this works around that bug, allowing them to use SASL.
send-unprefixed-sasl: true
# maximum number of connections per subnet # maximum number of connections per subnet
connection-limits: connection-limits:
# whether to enforce connection limits or not # whether to enforce connection limits or not