mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
refactor rehash to rely more on server.config
This commit is contained in:
parent
1a5db02236
commit
1383190249
@ -515,8 +515,9 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(topic) > client.server.limits.TopicLen {
|
topicLimit := client.server.Limits().TopicLen
|
||||||
topic = topic[:client.server.limits.TopicLen]
|
if len(topic) > topicLimit {
|
||||||
|
topic = topic[:topicLimit]
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.stateMutex.Lock()
|
channel.stateMutex.Lock()
|
||||||
|
@ -85,9 +85,9 @@ type Client struct {
|
|||||||
// NewClient returns a client with all the appropriate info setup.
|
// NewClient returns a client with all the appropriate info setup.
|
||||||
func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
limits := server.Limits()
|
config := server.Config()
|
||||||
fullLineLenLimit := limits.LineLen.Tags + limits.LineLen.Rest
|
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest
|
||||||
socket := NewSocket(conn, fullLineLenLimit*2, server.MaxSendQBytes())
|
socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
|
||||||
client := &Client{
|
client := &Client{
|
||||||
atime: now,
|
atime: now,
|
||||||
authorized: server.Password() == nil,
|
authorized: server.Password() == nil,
|
||||||
@ -112,7 +112,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
// error is not useful to us here anyways so we can ignore it
|
// error is not useful to us here anyways so we can ignore it
|
||||||
client.certfp, _ = client.socket.CertFP()
|
client.certfp, _ = client.socket.CertFP()
|
||||||
}
|
}
|
||||||
if server.checkIdent && !utils.AddrIsUnix(conn.RemoteAddr()) {
|
if config.Server.CheckIdent && !utils.AddrIsUnix(conn.RemoteAddr()) {
|
||||||
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
|
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||||
serverPort, _ := strconv.Atoi(serverPortString)
|
serverPort, _ := strconv.Atoi(serverPortString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,10 +28,10 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PassConfig holds the connection password.
|
// here's how this works: exported (capitalized) members of the config structs
|
||||||
type PassConfig struct {
|
// are defined in the YAML file and deserialized directly from there. They may
|
||||||
Password string
|
// be postprocessed and overwritten by LoadConfig. Unexported (lowercase) members
|
||||||
}
|
// are derived from the exported members in LoadConfig.
|
||||||
|
|
||||||
// TLSListenConfig defines configuration options for listening on TLS.
|
// TLSListenConfig defines configuration options for listening on TLS.
|
||||||
type TLSListenConfig struct {
|
type TLSListenConfig struct {
|
||||||
@ -51,15 +51,6 @@ func (conf *TLSListenConfig) Config() (*tls.Config, error) {
|
|||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordBytes returns the bytes represented by the password hash.
|
|
||||||
func (conf *PassConfig) PasswordBytes() []byte {
|
|
||||||
bytes, err := passwd.DecodePasswordHash(conf.Password)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("decode password error: ", err)
|
|
||||||
}
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountConfig struct {
|
type AccountConfig struct {
|
||||||
Registration AccountRegistrationConfig
|
Registration AccountRegistrationConfig
|
||||||
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
||||||
@ -171,11 +162,24 @@ func (conf *OperConfig) PasswordBytes() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LineLenConfig controls line lengths.
|
// LineLenConfig controls line lengths.
|
||||||
type LineLenConfig struct {
|
type LineLenLimits struct {
|
||||||
Tags int
|
Tags int
|
||||||
Rest int
|
Rest int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Various server-enforced limits on data size.
|
||||||
|
type Limits struct {
|
||||||
|
AwayLen int `yaml:"awaylen"`
|
||||||
|
ChanListModes int `yaml:"chan-list-modes"`
|
||||||
|
ChannelLen int `yaml:"channellen"`
|
||||||
|
KickLen int `yaml:"kicklen"`
|
||||||
|
MonitorEntries int `yaml:"monitor-entries"`
|
||||||
|
NickLen int `yaml:"nicklen"`
|
||||||
|
TopicLen int `yaml:"topiclen"`
|
||||||
|
WhowasEntries int `yaml:"whowas-entries"`
|
||||||
|
LineLen LineLenLimits `yaml:"linelen"`
|
||||||
|
}
|
||||||
|
|
||||||
// STSConfig controls the STS configuration/
|
// STSConfig controls the STS configuration/
|
||||||
type STSConfig struct {
|
type STSConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
@ -219,9 +223,10 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Server struct {
|
Server struct {
|
||||||
PassConfig
|
|
||||||
Password string
|
Password string
|
||||||
|
passwordBytes []byte
|
||||||
Name string
|
Name string
|
||||||
|
nameCasefolded string
|
||||||
Listen []string
|
Listen []string
|
||||||
TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"`
|
TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"`
|
||||||
STS STSConfig
|
STS STSConfig
|
||||||
@ -251,7 +256,8 @@ type Config struct {
|
|||||||
Accounts AccountConfig
|
Accounts AccountConfig
|
||||||
|
|
||||||
Channels struct {
|
Channels struct {
|
||||||
DefaultModes *string `yaml:"default-modes"`
|
RawDefaultModes *string `yaml:"default-modes"`
|
||||||
|
defaultModes modes.Modes
|
||||||
Registration ChannelRegistrationConfig
|
Registration ChannelRegistrationConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,6 +265,10 @@ type Config struct {
|
|||||||
|
|
||||||
Opers map[string]*OperConfig
|
Opers map[string]*OperConfig
|
||||||
|
|
||||||
|
// parsed operator definitions, unexported so they can't be defined
|
||||||
|
// directly in YAML:
|
||||||
|
operators map[string]*Oper
|
||||||
|
|
||||||
Logging []logger.LoggingConfig
|
Logging []logger.LoggingConfig
|
||||||
|
|
||||||
Debug struct {
|
Debug struct {
|
||||||
@ -267,17 +277,7 @@ type Config struct {
|
|||||||
StackImpact StackImpactConfig
|
StackImpact StackImpactConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
Limits struct {
|
Limits Limits
|
||||||
AwayLen uint `yaml:"awaylen"`
|
|
||||||
ChanListModes uint `yaml:"chan-list-modes"`
|
|
||||||
ChannelLen uint `yaml:"channellen"`
|
|
||||||
KickLen uint `yaml:"kicklen"`
|
|
||||||
MonitorEntries uint `yaml:"monitor-entries"`
|
|
||||||
NickLen uint `yaml:"nicklen"`
|
|
||||||
TopicLen uint `yaml:"topiclen"`
|
|
||||||
WhowasEntries uint `yaml:"whowas-entries"`
|
|
||||||
LineLen LineLenConfig `yaml:"linelen"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Fakelag FakelagConfig
|
Fakelag FakelagConfig
|
||||||
|
|
||||||
@ -437,11 +437,6 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
|
|
||||||
config.Filename = filename
|
config.Filename = filename
|
||||||
|
|
||||||
// we need this so PasswordBytes returns the correct info
|
|
||||||
if config.Server.Password != "" {
|
|
||||||
config.Server.PassConfig.Password = config.Server.Password
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Network.Name == "" {
|
if config.Network.Name == "" {
|
||||||
return nil, ErrNetworkNameMissing
|
return nil, ErrNetworkNameMissing
|
||||||
}
|
}
|
||||||
@ -691,5 +686,33 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// casefold/validate server name
|
||||||
|
config.Server.nameCasefolded, err = Casefold(config.Server.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Server name isn't valid [%s]: %s", config.Server.Name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// process operator definitions, store them to config.operators
|
||||||
|
operclasses, err := config.OperatorClasses()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opers, err := config.Operators(operclasses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.operators = opers
|
||||||
|
|
||||||
|
// parse default channel modes
|
||||||
|
config.Channels.defaultModes = ParseDefaultChannelModes(config.Channels.RawDefaultModes)
|
||||||
|
|
||||||
|
if config.Server.Password != "" {
|
||||||
|
bytes, err := passwd.DecodePasswordHash(config.Server.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Server.passwordBytes = bytes
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// explicitly store the channel modes
|
// explicitly store the channel modes
|
||||||
defaultModes := ParseDefaultChannelModes(config)
|
defaultModes := ParseDefaultChannelModes(config.Channels.RawDefaultModes)
|
||||||
modeStrings := make([]string, len(defaultModes))
|
modeStrings := make([]string, len(defaultModes))
|
||||||
for i, mode := range defaultModes {
|
for i, mode := range defaultModes {
|
||||||
modeStrings[i] = string(mode)
|
modeStrings[i] = string(mode)
|
||||||
|
@ -6,15 +6,12 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"github.com/oragono/oragono/irc/isupport"
|
"github.com/oragono/oragono/irc/isupport"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (server *Server) MaxSendQBytes() int {
|
func (server *Server) Config() *Config {
|
||||||
return int(atomic.LoadUint32(&server.maxSendQBytes))
|
server.configurableStateMutex.RLock()
|
||||||
}
|
defer server.configurableStateMutex.RUnlock()
|
||||||
|
return server.config
|
||||||
func (server *Server) SetMaxSendQBytes(m int) {
|
|
||||||
atomic.StoreUint32(&server.maxSendQBytes, uint32(m))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) ISupport() *isupport.List {
|
func (server *Server) ISupport() *isupport.List {
|
||||||
@ -24,63 +21,41 @@ func (server *Server) ISupport() *isupport.List {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Limits() Limits {
|
func (server *Server) Limits() Limits {
|
||||||
server.configurableStateMutex.RLock()
|
return server.Config().Limits
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
return server.limits
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Password() []byte {
|
func (server *Server) Password() []byte {
|
||||||
server.configurableStateMutex.RLock()
|
return server.Config().Server.passwordBytes
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
return server.password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) RecoverFromErrors() bool {
|
func (server *Server) RecoverFromErrors() bool {
|
||||||
server.configurableStateMutex.RLock()
|
// default to true if unset
|
||||||
defer server.configurableStateMutex.RUnlock()
|
rfe := server.Config().Debug.RecoverFromErrors
|
||||||
return server.recoverFromErrors
|
return rfe == nil || *rfe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) ProxyAllowedFrom() []string {
|
func (server *Server) ProxyAllowedFrom() []string {
|
||||||
server.configurableStateMutex.RLock()
|
return server.Config().Server.ProxyAllowedFrom
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
return server.proxyAllowedFrom
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) WebIRCConfig() []webircConfig {
|
func (server *Server) WebIRCConfig() []webircConfig {
|
||||||
server.configurableStateMutex.RLock()
|
return server.Config().Server.WebIRC
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
return server.webirc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) DefaultChannelModes() modes.Modes {
|
func (server *Server) DefaultChannelModes() modes.Modes {
|
||||||
server.configurableStateMutex.RLock()
|
return server.Config().Channels.defaultModes
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
return server.defaultChannelModes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) ChannelRegistrationEnabled() bool {
|
func (server *Server) ChannelRegistrationEnabled() bool {
|
||||||
server.configurableStateMutex.RLock()
|
return server.Config().Channels.Registration.Enabled
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
return server.config.Channels.Registration.Enabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) AccountConfig() *AccountConfig {
|
func (server *Server) AccountConfig() *AccountConfig {
|
||||||
server.configurableStateMutex.RLock()
|
return &server.Config().Accounts
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
if server.config == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &server.config.Accounts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) FakelagConfig() *FakelagConfig {
|
func (server *Server) FakelagConfig() *FakelagConfig {
|
||||||
server.configurableStateMutex.RLock()
|
return &server.Config().Fakelag
|
||||||
defer server.configurableStateMutex.RUnlock()
|
|
||||||
if server.config == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &server.config.Fakelag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) GetOperator(name string) (oper *Oper) {
|
func (server *Server) GetOperator(name string) (oper *Oper) {
|
||||||
@ -90,7 +65,7 @@ func (server *Server) GetOperator(name string) (oper *Oper) {
|
|||||||
}
|
}
|
||||||
server.configurableStateMutex.RLock()
|
server.configurableStateMutex.RLock()
|
||||||
defer server.configurableStateMutex.RUnlock()
|
defer server.configurableStateMutex.RUnlock()
|
||||||
return server.operators[name]
|
return server.config.operators[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) Nick() string {
|
func (client *Client) Nick() string {
|
||||||
|
@ -1501,12 +1501,12 @@ func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb
|
|||||||
var online []string
|
var online []string
|
||||||
var offline []string
|
var offline []string
|
||||||
|
|
||||||
limit := server.Limits().MonitorEntries
|
limits := server.Limits()
|
||||||
|
|
||||||
targets := strings.Split(msg.Params[1], ",")
|
targets := strings.Split(msg.Params[1], ",")
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
// check name length
|
// check name length
|
||||||
if len(target) < 1 || len(targets) > server.limits.NickLen {
|
if len(target) < 1 || len(targets) > limits.NickLen {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1516,9 +1516,9 @@ func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.monitorManager.Add(client, casefoldedTarget, limit)
|
err = server.monitorManager.Add(client, casefoldedTarget, limits.MonitorEntries)
|
||||||
if err == errMonitorLimitExceeded {
|
if err == errMonitorLimitExceeded {
|
||||||
rb.Add(nil, server.name, ERR_MONLISTFULL, client.Nick(), strconv.Itoa(server.limits.MonitorEntries), strings.Join(targets, ","))
|
rb.Add(nil, server.name, ERR_MONLISTFULL, client.Nick(), strconv.Itoa(limits.MonitorEntries), strings.Join(targets, ","))
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
continue
|
continue
|
||||||
@ -1814,14 +1814,15 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if no password exists, skip checking
|
// if no password exists, skip checking
|
||||||
if len(server.password) == 0 {
|
serverPassword := server.Password()
|
||||||
|
if serverPassword == nil {
|
||||||
client.SetAuthorized(true)
|
client.SetAuthorized(true)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the provided password
|
// check the provided password
|
||||||
password := []byte(msg.Params[0])
|
password := []byte(msg.Params[0])
|
||||||
if passwd.ComparePassword(server.password, password) != nil {
|
if passwd.ComparePassword(serverPassword, password) != nil {
|
||||||
rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
|
rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
|
||||||
rb.Add(nil, server.name, "ERROR", client.t("Password incorrect"))
|
rb.Add(nil, server.name, "ERROR", client.t("Password incorrect"))
|
||||||
return true
|
return true
|
||||||
|
@ -87,12 +87,12 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseDefaultChannelModes parses the `default-modes` line of the config
|
// ParseDefaultChannelModes parses the `default-modes` line of the config
|
||||||
func ParseDefaultChannelModes(config *Config) modes.Modes {
|
func ParseDefaultChannelModes(rawModes *string) modes.Modes {
|
||||||
if config.Channels.DefaultModes == nil {
|
if rawModes == nil {
|
||||||
// not present in config, fall back to compile-time default
|
// not present in config, fall back to compile-time default
|
||||||
return DefaultChannelModes
|
return DefaultChannelModes
|
||||||
}
|
}
|
||||||
modeChangeStrings := strings.Split(strings.TrimSpace(*config.Channels.DefaultModes), " ")
|
modeChangeStrings := strings.Fields(*rawModes)
|
||||||
modeChanges, _ := modes.ParseChannelModeChanges(modeChangeStrings...)
|
modeChanges, _ := modes.ParseChannelModeChanges(modeChangeStrings...)
|
||||||
defaultChannelModes := make(modes.Modes, 0)
|
defaultChannelModes := make(modes.Modes, 0)
|
||||||
for _, modeChange := range modeChanges {
|
for _, modeChange := range modeChanges {
|
||||||
|
@ -27,10 +27,8 @@ func TestParseDefaultChannelModes(t *testing.T) {
|
|||||||
{nil, modes.Modes{modes.NoOutside, modes.OpOnlyTopic}},
|
{nil, modes.Modes{modes.NoOutside, modes.OpOnlyTopic}},
|
||||||
}
|
}
|
||||||
|
|
||||||
var config Config
|
|
||||||
for _, testcase := range parseTests {
|
for _, testcase := range parseTests {
|
||||||
config.Channels.DefaultModes = testcase.raw
|
result := ParseDefaultChannelModes(testcase.raw)
|
||||||
result := ParseDefaultChannelModes(&config)
|
|
||||||
if !reflect.DeepEqual(result, testcase.expected) {
|
if !reflect.DeepEqual(result, testcase.expected) {
|
||||||
t.Errorf("expected modes %s, got %s", testcase.expected, result)
|
t.Errorf("expected modes %s, got %s", testcase.expected, result)
|
||||||
}
|
}
|
||||||
|
174
irc/server.go
174
irc/server.go
@ -58,24 +58,6 @@ var (
|
|||||||
CapValues = caps.NewValues()
|
CapValues = caps.NewValues()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Limits holds the maximum limits for various things such as topic lengths.
|
|
||||||
type Limits struct {
|
|
||||||
AwayLen int
|
|
||||||
ChannelLen int
|
|
||||||
KickLen int
|
|
||||||
MonitorEntries int
|
|
||||||
NickLen int
|
|
||||||
TopicLen int
|
|
||||||
ChanListModes int
|
|
||||||
LineLen LineLenLimits
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineLenLimits holds the maximum limits for IRC lines.
|
|
||||||
type LineLenLimits struct {
|
|
||||||
Tags int
|
|
||||||
Rest int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
|
// ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
|
||||||
type ListenerWrapper struct {
|
type ListenerWrapper struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
@ -91,7 +73,6 @@ type Server struct {
|
|||||||
batches *BatchManager
|
batches *BatchManager
|
||||||
channels *ChannelManager
|
channels *ChannelManager
|
||||||
channelRegistry *ChannelRegistry
|
channelRegistry *ChannelRegistry
|
||||||
checkIdent bool
|
|
||||||
clients *ClientManager
|
clients *ClientManager
|
||||||
config *Config
|
config *Config
|
||||||
configFilename string
|
configFilename string
|
||||||
@ -99,35 +80,23 @@ type Server struct {
|
|||||||
connectionLimiter *connection_limits.Limiter
|
connectionLimiter *connection_limits.Limiter
|
||||||
connectionThrottler *connection_limits.Throttler
|
connectionThrottler *connection_limits.Throttler
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
defaultChannelModes modes.Modes
|
|
||||||
dlines *DLineManager
|
dlines *DLineManager
|
||||||
loggingRawIO bool
|
|
||||||
isupport *isupport.List
|
isupport *isupport.List
|
||||||
klines *KLineManager
|
klines *KLineManager
|
||||||
languages *languages.Manager
|
languages *languages.Manager
|
||||||
limits Limits
|
|
||||||
listeners map[string]*ListenerWrapper
|
listeners map[string]*ListenerWrapper
|
||||||
logger *logger.Manager
|
logger *logger.Manager
|
||||||
maxSendQBytes uint32
|
|
||||||
monitorManager *MonitorManager
|
monitorManager *MonitorManager
|
||||||
motdLines []string
|
motdLines []string
|
||||||
name string
|
name string
|
||||||
nameCasefolded string
|
nameCasefolded string
|
||||||
networkName string
|
|
||||||
operators map[string]*Oper
|
|
||||||
operclasses map[string]*OperClass
|
|
||||||
password []byte
|
|
||||||
passwords *passwd.SaltedManager
|
passwords *passwd.SaltedManager
|
||||||
recoverFromErrors bool
|
|
||||||
rehashMutex sync.Mutex // tier 4
|
rehashMutex sync.Mutex // tier 4
|
||||||
rehashSignal chan os.Signal
|
rehashSignal chan os.Signal
|
||||||
pprofServer *http.Server
|
pprofServer *http.Server
|
||||||
proxyAllowedFrom []string
|
|
||||||
signals chan os.Signal
|
signals chan os.Signal
|
||||||
snomasks *SnoManager
|
snomasks *SnoManager
|
||||||
store *buntdb.DB
|
store *buntdb.DB
|
||||||
stsEnabled bool
|
|
||||||
webirc []webircConfig
|
|
||||||
whoWas *WhoWasList
|
whoWas *WhoWasList
|
||||||
stats *Stats
|
stats *Stats
|
||||||
semaphores *ServerSemaphores
|
semaphores *ServerSemaphores
|
||||||
@ -204,35 +173,35 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
|
|||||||
func (server *Server) setISupport() {
|
func (server *Server) setISupport() {
|
||||||
maxTargetsString := strconv.Itoa(maxTargets)
|
maxTargetsString := strconv.Itoa(maxTargets)
|
||||||
|
|
||||||
server.configurableStateMutex.RLock()
|
config := server.Config()
|
||||||
|
|
||||||
// add RPL_ISUPPORT tokens
|
// add RPL_ISUPPORT tokens
|
||||||
isupport := isupport.NewList()
|
isupport := isupport.NewList()
|
||||||
isupport.Add("AWAYLEN", strconv.Itoa(server.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()}, ","))
|
||||||
isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
|
isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
|
||||||
isupport.Add("CHANTYPES", "#")
|
isupport.Add("CHANTYPES", "#")
|
||||||
isupport.Add("ELIST", "U")
|
isupport.Add("ELIST", "U")
|
||||||
isupport.Add("EXCEPTS", "")
|
isupport.Add("EXCEPTS", "")
|
||||||
isupport.Add("INVEX", "")
|
isupport.Add("INVEX", "")
|
||||||
isupport.Add("KICKLEN", strconv.Itoa(server.limits.KickLen))
|
isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
|
||||||
isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(server.limits.ChanListModes)))
|
isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes)))
|
||||||
isupport.Add("MAXTARGETS", maxTargetsString)
|
isupport.Add("MAXTARGETS", maxTargetsString)
|
||||||
isupport.Add("MODES", "")
|
isupport.Add("MODES", "")
|
||||||
isupport.Add("MONITOR", strconv.Itoa(server.limits.MonitorEntries))
|
isupport.Add("MONITOR", strconv.Itoa(config.Limits.MonitorEntries))
|
||||||
isupport.Add("NETWORK", server.networkName)
|
isupport.Add("NETWORK", config.Network.Name)
|
||||||
isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
|
isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
|
||||||
isupport.Add("PREFIX", "(qaohv)~&@%+")
|
isupport.Add("PREFIX", "(qaohv)~&@%+")
|
||||||
isupport.Add("RPCHAN", "E")
|
isupport.Add("RPCHAN", "E")
|
||||||
isupport.Add("RPUSER", "E")
|
isupport.Add("RPUSER", "E")
|
||||||
isupport.Add("STATUSMSG", "~&@%+")
|
isupport.Add("STATUSMSG", "~&@%+")
|
||||||
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString, maxTargetsString))
|
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString, maxTargetsString))
|
||||||
isupport.Add("TOPICLEN", strconv.Itoa(server.limits.TopicLen))
|
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))
|
||||||
isupport.Add("UTF8MAPPING", casemappingName)
|
isupport.Add("UTF8MAPPING", casemappingName)
|
||||||
|
|
||||||
// account registration
|
// account registration
|
||||||
if server.config.Accounts.Registration.Enabled {
|
if config.Accounts.Registration.Enabled {
|
||||||
// 'none' isn't shown in the REGCALLBACKS vars
|
// 'none' isn't shown in the REGCALLBACKS vars
|
||||||
var enabledCallbacks []string
|
var enabledCallbacks []string
|
||||||
for _, name := range server.config.Accounts.Registration.EnabledCallbacks {
|
for _, name := range server.config.Accounts.Registration.EnabledCallbacks {
|
||||||
@ -246,8 +215,6 @@ func (server *Server) setISupport() {
|
|||||||
isupport.Add("REGCREDTYPES", "passphrase,certfp")
|
isupport.Add("REGCREDTYPES", "passphrase,certfp")
|
||||||
}
|
}
|
||||||
|
|
||||||
server.configurableStateMutex.RUnlock()
|
|
||||||
|
|
||||||
isupport.RegenerateCachedReply()
|
isupport.RegenerateCachedReply()
|
||||||
|
|
||||||
server.configurableStateMutex.Lock()
|
server.configurableStateMutex.Lock()
|
||||||
@ -672,7 +639,7 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
|
|||||||
rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, target.AccountName(), client.t("is logged in as"))
|
rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, target.AccountName(), client.t("is logged in as"))
|
||||||
}
|
}
|
||||||
if target.HasMode(modes.Bot) {
|
if target.HasMode(modes.Bot) {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
|
rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.Config().Network.Name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if 0 < len(target.languages) {
|
if 0 < len(target.languages) {
|
||||||
@ -744,13 +711,16 @@ func (server *Server) rehash() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) applyConfig(config *Config, initial bool) error {
|
func (server *Server) applyConfig(config *Config, initial bool) (err error) {
|
||||||
if initial {
|
if initial {
|
||||||
server.ctime = time.Now()
|
server.ctime = time.Now()
|
||||||
server.configFilename = config.Filename
|
server.configFilename = config.Filename
|
||||||
|
server.name = config.Server.Name
|
||||||
|
server.nameCasefolded = config.Server.nameCasefolded
|
||||||
} else {
|
} else {
|
||||||
// enforce configs that can't be changed after launch:
|
// enforce configs that can't be changed after launch:
|
||||||
if server.limits.LineLen.Tags != config.Limits.LineLen.Tags || server.limits.LineLen.Rest != config.Limits.LineLen.Rest {
|
currentLimits := server.Limits()
|
||||||
|
if currentLimits.LineLen.Tags != config.Limits.LineLen.Tags || currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
|
||||||
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")
|
||||||
@ -759,48 +729,11 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.logger.Info("rehash", "Using config file", server.configFilename)
|
|
||||||
|
|
||||||
casefoldedName, err := Casefold(config.Server.Name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Server name isn't valid [%s]: %s", config.Server.Name, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// confirm operator stuff all exists and is fine
|
|
||||||
operclasses, err := config.OperatorClasses()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error rehashing config file operclasses: %s", err.Error())
|
|
||||||
}
|
|
||||||
opers, err := config.Operators(operclasses)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error rehashing config file opers: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support rehash of existing operator perms?
|
|
||||||
|
|
||||||
// sanity checks complete, start modifying server state
|
// sanity checks complete, start modifying server state
|
||||||
|
server.logger.Info("rehash", "Using config file", server.configFilename)
|
||||||
|
oldConfig := server.Config()
|
||||||
|
|
||||||
if initial {
|
// first, reload config sections for functionality implemented in subpackages:
|
||||||
server.name = config.Server.Name
|
|
||||||
server.nameCasefolded = casefoldedName
|
|
||||||
}
|
|
||||||
|
|
||||||
server.configurableStateMutex.Lock()
|
|
||||||
server.networkName = config.Network.Name
|
|
||||||
if config.Server.Password != "" {
|
|
||||||
server.password = config.Server.PasswordBytes()
|
|
||||||
} else {
|
|
||||||
server.password = nil
|
|
||||||
}
|
|
||||||
// apply new WebIRC command restrictions
|
|
||||||
server.webirc = config.Server.WebIRC
|
|
||||||
// apply new PROXY command restrictions
|
|
||||||
server.proxyAllowedFrom = config.Server.ProxyAllowedFrom
|
|
||||||
server.recoverFromErrors = true
|
|
||||||
if config.Debug.RecoverFromErrors != nil {
|
|
||||||
server.recoverFromErrors = *config.Debug.RecoverFromErrors
|
|
||||||
}
|
|
||||||
server.configurableStateMutex.Unlock()
|
|
||||||
|
|
||||||
err = server.connectionLimiter.ApplyConfig(config.Server.ConnectionLimiter)
|
err = server.connectionLimiter.ApplyConfig(config.Server.ConnectionLimiter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -812,6 +745,16 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reload logging config
|
||||||
|
wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO()
|
||||||
|
err = server.logger.ApplyConfig(config.Logging)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nowLoggingRawIO := server.logger.IsLoggingRawIO()
|
||||||
|
// notify existing clients if raw i/o logging was enabled by a rehash
|
||||||
|
sendRawOutputNotice := !wasLoggingRawIO && nowLoggingRawIO
|
||||||
|
|
||||||
// setup new and removed caps
|
// setup new and removed caps
|
||||||
addedCaps := caps.NewSet()
|
addedCaps := caps.NewSet()
|
||||||
removedCaps := caps.NewSet()
|
removedCaps := caps.NewSet()
|
||||||
@ -844,8 +787,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
server.languages = lm
|
server.languages = lm
|
||||||
|
|
||||||
// SASL
|
// SASL
|
||||||
oldAccountConfig := server.AccountConfig()
|
authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled
|
||||||
authPreviouslyEnabled := oldAccountConfig != nil && oldAccountConfig.AuthenticationEnabled
|
|
||||||
if config.Accounts.AuthenticationEnabled && !authPreviouslyEnabled {
|
if config.Accounts.AuthenticationEnabled && !authPreviouslyEnabled {
|
||||||
// enabling SASL
|
// enabling SASL
|
||||||
SupportedCapabilities.Enable(caps.SASL)
|
SupportedCapabilities.Enable(caps.SASL)
|
||||||
@ -857,39 +799,39 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
removedCaps.Add(caps.SASL)
|
removedCaps.Add(caps.SASL)
|
||||||
}
|
}
|
||||||
|
|
||||||
nickReservationPreviouslyDisabled := oldAccountConfig != nil && !oldAccountConfig.NickReservation.Enabled
|
nickReservationPreviouslyDisabled := oldConfig != nil && !oldConfig.Accounts.NickReservation.Enabled
|
||||||
nickReservationNowEnabled := config.Accounts.NickReservation.Enabled
|
nickReservationNowEnabled := config.Accounts.NickReservation.Enabled
|
||||||
if nickReservationPreviouslyDisabled && nickReservationNowEnabled {
|
if nickReservationPreviouslyDisabled && nickReservationNowEnabled {
|
||||||
server.accounts.buildNickToAccountIndex()
|
server.accounts.buildNickToAccountIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
hsPreviouslyDisabled := oldAccountConfig != nil && !oldAccountConfig.VHosts.Enabled
|
hsPreviouslyDisabled := oldConfig != nil && !oldConfig.Accounts.VHosts.Enabled
|
||||||
hsNowEnabled := config.Accounts.VHosts.Enabled
|
hsNowEnabled := config.Accounts.VHosts.Enabled
|
||||||
if hsPreviouslyDisabled && hsNowEnabled {
|
if hsPreviouslyDisabled && hsNowEnabled {
|
||||||
server.accounts.initVHostRequestQueue()
|
server.accounts.initVHostRequestQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// STS
|
// STS
|
||||||
|
stsPreviouslyEnabled := oldConfig != nil && oldConfig.Server.STS.Enabled
|
||||||
stsValue := config.Server.STS.Value()
|
stsValue := config.Server.STS.Value()
|
||||||
var stsDisabled bool
|
stsDisabledByRehash := false
|
||||||
stsCurrentCapValue, _ := CapValues.Get(caps.STS)
|
stsCurrentCapValue, _ := CapValues.Get(caps.STS)
|
||||||
server.logger.Debug("rehash", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", server.stsEnabled, config.Server.STS.Enabled))
|
server.logger.Debug("rehash", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled))
|
||||||
if config.Server.STS.Enabled && !server.stsEnabled {
|
if config.Server.STS.Enabled && !stsPreviouslyEnabled {
|
||||||
// enabling STS
|
// enabling STS
|
||||||
SupportedCapabilities.Enable(caps.STS)
|
SupportedCapabilities.Enable(caps.STS)
|
||||||
addedCaps.Add(caps.STS)
|
addedCaps.Add(caps.STS)
|
||||||
CapValues.Set(caps.STS, stsValue)
|
CapValues.Set(caps.STS, stsValue)
|
||||||
} else if !config.Server.STS.Enabled && server.stsEnabled {
|
} else if !config.Server.STS.Enabled && stsPreviouslyEnabled {
|
||||||
// disabling STS
|
// disabling STS
|
||||||
SupportedCapabilities.Disable(caps.STS)
|
SupportedCapabilities.Disable(caps.STS)
|
||||||
removedCaps.Add(caps.STS)
|
removedCaps.Add(caps.STS)
|
||||||
stsDisabled = true
|
stsDisabledByRehash = true
|
||||||
} else if config.Server.STS.Enabled && server.stsEnabled && stsValue != stsCurrentCapValue {
|
} else if config.Server.STS.Enabled && stsPreviouslyEnabled && stsValue != stsCurrentCapValue {
|
||||||
// STS policy updated
|
// STS policy updated
|
||||||
CapValues.Set(caps.STS, stsValue)
|
CapValues.Set(caps.STS, stsValue)
|
||||||
updatedCaps.Add(caps.STS)
|
updatedCaps.Add(caps.STS)
|
||||||
}
|
}
|
||||||
server.stsEnabled = config.Server.STS.Enabled
|
|
||||||
|
|
||||||
// burst new and removed caps
|
// burst new and removed caps
|
||||||
var capBurstClients ClientSet
|
var capBurstClients ClientSet
|
||||||
@ -912,7 +854,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for sClient := range capBurstClients {
|
for sClient := range capBurstClients {
|
||||||
if stsDisabled {
|
if stsDisabledByRehash {
|
||||||
// remove STS policy
|
// remove STS policy
|
||||||
//TODO(dan): this is an ugly hack. we can write this better.
|
//TODO(dan): this is an ugly hack. we can write this better.
|
||||||
stsPolicy := "sts=duration=0"
|
stsPolicy := "sts=duration=0"
|
||||||
@ -932,44 +874,8 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set server options
|
|
||||||
server.configurableStateMutex.Lock()
|
|
||||||
lineLenConfig := LineLenLimits{
|
|
||||||
Tags: config.Limits.LineLen.Tags,
|
|
||||||
Rest: config.Limits.LineLen.Rest,
|
|
||||||
}
|
|
||||||
server.limits = Limits{
|
|
||||||
AwayLen: int(config.Limits.AwayLen),
|
|
||||||
ChannelLen: int(config.Limits.ChannelLen),
|
|
||||||
KickLen: int(config.Limits.KickLen),
|
|
||||||
MonitorEntries: int(config.Limits.MonitorEntries),
|
|
||||||
NickLen: int(config.Limits.NickLen),
|
|
||||||
TopicLen: int(config.Limits.TopicLen),
|
|
||||||
ChanListModes: int(config.Limits.ChanListModes),
|
|
||||||
LineLen: lineLenConfig,
|
|
||||||
}
|
|
||||||
server.operclasses = operclasses
|
|
||||||
server.operators = opers
|
|
||||||
server.checkIdent = config.Server.CheckIdent
|
|
||||||
|
|
||||||
server.defaultChannelModes = ParseDefaultChannelModes(config)
|
|
||||||
server.configurableStateMutex.Unlock()
|
|
||||||
|
|
||||||
// set new sendqueue size
|
|
||||||
server.SetMaxSendQBytes(config.Server.MaxSendQBytes)
|
|
||||||
|
|
||||||
server.loadMOTD(config.Server.MOTD, config.Server.MOTDFormatting)
|
server.loadMOTD(config.Server.MOTD, config.Server.MOTDFormatting)
|
||||||
|
|
||||||
// reload logging config
|
|
||||||
err = server.logger.ApplyConfig(config.Logging)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nowLoggingRawIO := server.logger.IsLoggingRawIO()
|
|
||||||
// notify clients if raw i/o logging was enabled by a rehash
|
|
||||||
sendRawOutputNotice := !initial && !server.loggingRawIO && nowLoggingRawIO
|
|
||||||
server.loggingRawIO = nowLoggingRawIO
|
|
||||||
|
|
||||||
// save a pointer to the new config
|
// save a pointer to the new config
|
||||||
server.configurableStateMutex.Lock()
|
server.configurableStateMutex.Lock()
|
||||||
server.config = config
|
server.config = config
|
||||||
|
@ -52,11 +52,9 @@ func BitsetSet(set []uint64, position uint, on bool) (changed bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BitsetEmpty returns whether the bitset is empty.
|
// BitsetEmpty returns whether the bitset is empty.
|
||||||
// Right now, this is technically free of race conditions because we don't
|
// This has false positives under concurrent modification (i.e., it can return true
|
||||||
// have a method that can simultaneously modify two bits separated by a word boundary
|
// even though w.r.t. the sequence of atomic modifications, there was no point at
|
||||||
// such that one of those modifications is an unset. If we did, there would be a race
|
// which the bitset was completely empty), but that's not how we're using this method.
|
||||||
// that could produce false positives. It's probably better to assume that they are
|
|
||||||
// already possible under concurrent modification (which is not how we're using this).
|
|
||||||
func BitsetEmpty(set []uint64) (empty bool) {
|
func BitsetEmpty(set []uint64) (empty bool) {
|
||||||
for i := 0; i < len(set); i++ {
|
for i := 0; i < len(set); i++ {
|
||||||
if atomic.LoadUint64(&set[i]) != 0 {
|
if atomic.LoadUint64(&set[i]) != 0 {
|
||||||
|
@ -32,7 +32,7 @@ type WhoWas struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWhoWasList returns a new WhoWasList
|
// NewWhoWasList returns a new WhoWasList
|
||||||
func NewWhoWasList(size uint) *WhoWasList {
|
func NewWhoWasList(size int) *WhoWasList {
|
||||||
return &WhoWasList{
|
return &WhoWasList{
|
||||||
buffer: make([]WhoWas, size),
|
buffer: make([]WhoWas, size),
|
||||||
start: -1,
|
start: -1,
|
||||||
|
Loading…
Reference in New Issue
Block a user