Minor features for parity with murmur.ini

MaxUsers: modifies existing sessionpool similar to Murmur
MaxUsersPerChannel: already implemented, inconsistent name
AllowPing: affects registration, too
DefaultChannel
RememberChannel
ServerPassword
SendOSInfo: already implemented, inconsistent name

Config keys are renamed to conform to murmur.ini
This commit is contained in:
rubenseyer 2018-02-14 20:50:09 +01:00
parent 693dd6f4e8
commit ae41a612ba
12 changed files with 241 additions and 109 deletions

View file

@ -510,7 +510,7 @@ func (client *Client) tlsRecvLoop() {
Release: proto.String("Grumble"), Release: proto.String("Grumble"),
CryptoModes: cryptstate.SupportedModes(), CryptoModes: cryptstate.SupportedModes(),
} }
if client.server.cfg.BoolValue("SendOSInfo") { if client.server.cfg.BoolValue("sendversion") {
version.Os = proto.String(runtime.GOOS) version.Os = proto.String(runtime.GOOS)
version.OsVersion = proto.String("(Unknown version)") version.OsVersion = proto.String("(Unknown version)")
} }

View file

@ -85,7 +85,7 @@ func main() {
if Args.LogPath != "" { if Args.LogPath != "" {
logFn = Args.LogPath logFn = Args.LogPath
} else { } else {
logFn = config.PathValue("LogPath", Args.DataDir) logFn = config.PathValue("logfile", Args.DataDir)
} }
logtarget.Default, err = logtarget.OpenFile(Args.LogPath, os.Stderr) logtarget.Default, err = logtarget.OpenFile(Args.LogPath, os.Stderr)
if err != nil { if err != nil {
@ -131,8 +131,8 @@ func main() {
// Check whether we should regenerate the default global keypair // Check whether we should regenerate the default global keypair
// and corresponding certificate. // and corresponding certificate.
// These are used as the default certificate of all virtual servers. // These are used as the default certificate of all virtual servers.
certFn := config.PathValue("CertPath", Args.DataDir) certFn := config.PathValue("sslCert", Args.DataDir)
keyFn := config.PathValue("KeyPath", Args.DataDir) keyFn := config.PathValue("sslKey", Args.DataDir)
shouldRegen := false shouldRegen := false
if Args.RegenKeys { if Args.RegenKeys {
shouldRegen = true shouldRegen = true

View file

@ -593,7 +593,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
return return
} }
maxChannelUsers := server.cfg.IntValue("MaxChannelUsers") maxChannelUsers := server.cfg.IntValue("usersperchannel")
if maxChannelUsers != 0 && len(dstChan.clients) >= maxChannelUsers { if maxChannelUsers != 0 && len(dstChan.clients) >= maxChannelUsers {
client.sendPermissionDeniedFallback(mumbleproto.PermissionDenied_ChannelFull, client.sendPermissionDeniedFallback(mumbleproto.PermissionDenied_ChannelFull,
0x010201, "Channel is full") 0x010201, "Channel is full")
@ -653,7 +653,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
// Texture change // Texture change
if userstate.Texture != nil { if userstate.Texture != nil {
maximg := server.cfg.IntValue("MaxImageMessageLength") maximg := server.cfg.IntValue("imagemessagelength")
if maximg > 0 && len(userstate.Texture) > maximg { if maximg > 0 && len(userstate.Texture) > maximg {
client.sendPermissionDeniedType(mumbleproto.PermissionDenied_TextTooLong) client.sendPermissionDeniedType(mumbleproto.PermissionDenied_TextTooLong)
return return

View file

@ -39,16 +39,19 @@ const registerUrl = "https://mumble.info/register.cgi"
// This function is used to determine whether or not to periodically // This function is used to determine whether or not to periodically
// contact the master server list and update this server's metadata. // contact the master server list and update this server's metadata.
func (server *Server) IsPublic() bool { func (server *Server) IsPublic() bool {
if len(server.cfg.StringValue("RegisterName")) == 0 { if len(server.cfg.StringValue("registerName")) == 0 {
return false return false
} }
if len(server.cfg.StringValue("RegisterHost")) == 0 { if len(server.cfg.StringValue("registerHostname")) == 0 {
return false return false
} }
if len(server.cfg.StringValue("RegisterPassword")) == 0 { if len(server.cfg.StringValue("registerPassword")) == 0 {
return false return false
} }
if len(server.cfg.StringValue("RegisterWebUrl")) == 0 { if len(server.cfg.StringValue("registerUrl")) == 0 {
return false
}
if !server.cfg.BoolValue("allowping") {
return false return false
} }
return true return true
@ -80,11 +83,11 @@ func (server *Server) RegisterPublicServer() {
// Render registration XML template // Render registration XML template
reg := Register{ reg := Register{
Name: server.cfg.StringValue("RegisterName"), Name: server.cfg.StringValue("registerName"),
Host: server.cfg.StringValue("RegisterHost"), Host: server.cfg.StringValue("registerHostname"),
Password: server.cfg.StringValue("RegisterPassword"), Password: server.cfg.StringValue("registerPassword"),
Url: server.cfg.StringValue("RegisterWebUrl"), Url: server.cfg.StringValue("registerUrl"),
Location: server.cfg.StringValue("RegisterLocation"), Location: server.cfg.StringValue("registerLocation"),
Port: server.CurrentPort(), Port: server.CurrentPort(),
Digest: digest, Digest: digest,
Users: len(server.clients), Users: len(server.clients),

View file

@ -178,6 +178,16 @@ func (server *Server) RootChannel() *Channel {
return root return root
} }
// Get a pointer to the default channel
func (server *Server) DefaultChannel() *Channel {
channel, exists := server.Channels[server.cfg.IntValue("defaultchannel")]
if !exists {
return server.RootChannel()
}
return channel
}
// Set password as the new SuperUser password
func (server *Server) setConfigPassword(key, password string) { func (server *Server) setConfigPassword(key, password string) {
saltBytes := make([]byte, 24) saltBytes := make([]byte, 24)
_, err := rand.Read(saltBytes) _, err := rand.Read(saltBytes)
@ -271,7 +281,14 @@ func (server *Server) handleIncomingClient(conn net.Conn) (err error) {
client.lf = &clientLogForwarder{client, server.Logger} client.lf = &clientLogForwarder{client, server.Logger}
client.Logger = log.New(client.lf, "", 0) client.Logger = log.New(client.lf, "", 0)
client.session = server.pool.Get() client.session, err = server.pool.Get()
if err != nil {
// Server is full. Murmur just closes the connection here anyway,
// so don't bother sending a Reject_ServerFull
client.Printf("Server is full, rejecting %v", conn.RemoteAddr())
conn.Close()
return nil
}
client.Printf("New connection: %v (%v)", conn.RemoteAddr(), client.Session()) client.Printf("New connection: %v (%v)", conn.RemoteAddr(), client.Session())
client.tcpaddr = addr.(*net.TCPAddr) client.tcpaddr = addr.(*net.TCPAddr)
@ -528,12 +545,13 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) {
client.user = user client.user = user
} }
} }
}
if client.user == nil && server.hasServerPassword() { // Otherwise, the user is unregistered. If there is a server-wide password, they now need it.
if auth.Password == nil || !server.CheckServerPassword(*auth.Password) { if client.user == nil && server.hasServerPassword() {
client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password") if auth.Password == nil || !server.CheckServerPassword(*auth.Password) {
return client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password")
return
}
} }
} }
@ -618,8 +636,8 @@ func (server *Server) finishAuthenticate(client *Client) {
server.hclients[host] = append(server.hclients[host], client) server.hclients[host] = append(server.hclients[host], client)
server.hmutex.Unlock() server.hmutex.Unlock()
channel := server.RootChannel() channel := server.DefaultChannel()
if client.IsRegistered() { if server.cfg.BoolValue("rememberchannel") && client.IsRegistered() {
lastChannel := server.Channels[client.user.LastChannelId] lastChannel := server.Channels[client.user.LastChannelId]
if lastChannel != nil { if lastChannel != nil {
channel = lastChannel channel = lastChannel
@ -675,8 +693,8 @@ func (server *Server) finishAuthenticate(client *Client) {
sync := &mumbleproto.ServerSync{} sync := &mumbleproto.ServerSync{}
sync.Session = proto.Uint32(client.Session()) sync.Session = proto.Uint32(client.Session())
sync.MaxBandwidth = proto.Uint32(server.cfg.Uint32Value("MaxBandwidth")) sync.MaxBandwidth = proto.Uint32(server.cfg.Uint32Value("bandwidth"))
sync.WelcomeText = proto.String(server.cfg.StringValue("WelcomeText")) sync.WelcomeText = proto.String(server.cfg.StringValue("welcometext"))
if client.IsSuperUser() { if client.IsSuperUser() {
sync.Permissions = proto.Uint64(uint64(acl.AllPermissions)) sync.Permissions = proto.Uint64(uint64(acl.AllPermissions))
} else { } else {
@ -693,9 +711,9 @@ func (server *Server) finishAuthenticate(client *Client) {
} }
err := client.sendMessage(&mumbleproto.ServerConfig{ err := client.sendMessage(&mumbleproto.ServerConfig{
AllowHtml: proto.Bool(server.cfg.BoolValue("AllowHTML")), AllowHtml: proto.Bool(server.cfg.BoolValue("allowhtml")),
MessageLength: proto.Uint32(server.cfg.Uint32Value("MaxTextMessageLength")), MessageLength: proto.Uint32(server.cfg.Uint32Value("textmessagelength")),
ImageMessageLength: proto.Uint32(server.cfg.Uint32Value("MaxImageMessageLength")), ImageMessageLength: proto.Uint32(server.cfg.Uint32Value("imagemessagelength")),
}) })
if err != nil { if err != nil {
client.Panicf("%v", err) client.Panicf("%v", err)
@ -992,6 +1010,9 @@ func (server *Server) udpListenLoop() {
// Length 12 is for ping datagrams from the ConnectDialog. // Length 12 is for ping datagrams from the ConnectDialog.
if nread == 12 { if nread == 12 {
if !server.cfg.BoolValue("allowping") {
return
}
readbuf := bytes.NewBuffer(buf) readbuf := bytes.NewBuffer(buf)
var ( var (
tmp32 uint32 tmp32 uint32
@ -1001,11 +1022,11 @@ func (server *Server) udpListenLoop() {
_ = binary.Read(readbuf, binary.BigEndian, &rand) _ = binary.Read(readbuf, binary.BigEndian, &rand)
buffer := bytes.NewBuffer(make([]byte, 0, 24)) buffer := bytes.NewBuffer(make([]byte, 0, 24))
_ = binary.Write(buffer, binary.BigEndian, uint32((1<<16)|(2<<8)|2)) _ = binary.Write(buffer, binary.BigEndian, uint32((1<<16)|(2<<8)|4))
_ = binary.Write(buffer, binary.BigEndian, rand) _ = binary.Write(buffer, binary.BigEndian, rand)
_ = binary.Write(buffer, binary.BigEndian, uint32(len(server.clients))) _ = binary.Write(buffer, binary.BigEndian, uint32(len(server.clients)))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxUsers")) _ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("users"))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxBandwidth")) _ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("bandwidth"))
err = server.SendUDP(buffer.Bytes(), udpaddr) err = server.SendUDP(buffer.Bytes(), udpaddr)
if err != nil { if err != nil {
@ -1283,9 +1304,9 @@ func (server *Server) IsCertHashBanned(hash string) bool {
// Filter incoming text according to the server's current rules. // Filter incoming text according to the server's current rules.
func (server *Server) FilterText(text string) (filtered string, err error) { func (server *Server) FilterText(text string) (filtered string, err error) {
options := &htmlfilter.Options{ options := &htmlfilter.Options{
StripHTML: !server.cfg.BoolValue("AllowHTML"), StripHTML: !server.cfg.BoolValue("allowhtml"),
MaxTextMessageLength: server.cfg.IntValue("MaxTextMessageLength"), MaxTextMessageLength: server.cfg.IntValue("textmessagelength"),
MaxImageMessageLength: server.cfg.IntValue("MaxImageMessageLength"), MaxImageMessageLength: server.cfg.IntValue("imagemessagelength"),
} }
return htmlfilter.Filter(text, options) return htmlfilter.Filter(text, options)
} }
@ -1339,7 +1360,7 @@ func isTimeout(err error) bool {
// Initialize the per-launch data // Initialize the per-launch data
func (server *Server) initPerLaunchData() { func (server *Server) initPerLaunchData() {
server.pool = sessionpool.New() server.pool = sessionpool.New(server.cfg.Uint32Value("users"))
server.clients = make(map[uint32]*Client) server.clients = make(map[uint32]*Client)
server.hclients = make(map[string][]*Client) server.hclients = make(map[string][]*Client)
server.hpclients = make(map[string]*Client) server.hpclients = make(map[string]*Client)
@ -1368,7 +1389,7 @@ func (server *Server) cleanPerLaunchData() {
// Port returns the port the native server will listen on when it is // Port returns the port the native server will listen on when it is
// started. // started.
func (server *Server) Port() int { func (server *Server) Port() int {
port := server.cfg.IntValue("Port") port := server.cfg.IntValue("port")
if port == 0 { if port == 0 {
return DefaultPort + int(server.Id) - 1 return DefaultPort + int(server.Id) - 1
} }
@ -1378,13 +1399,13 @@ func (server *Server) Port() int {
// ListenWebPort returns true if we should listen to the // ListenWebPort returns true if we should listen to the
// web port, otherwise false // web port, otherwise false
func (server *Server) ListenWebPort() bool { func (server *Server) ListenWebPort() bool {
return !server.cfg.BoolValue("NoWebServer") return !server.cfg.BoolValue("nowebserver")
} }
// WebPort returns the port the web server will listen on when it is // WebPort returns the port the web server will listen on when it is
// started. // started.
func (server *Server) WebPort() int { func (server *Server) WebPort() int {
port := server.cfg.IntValue("WebPort") port := server.cfg.IntValue("webport")
if port == 0 { if port == 0 {
return DefaultWebPort + int(server.Id) - 1 return DefaultWebPort + int(server.Id) - 1
} }
@ -1406,7 +1427,7 @@ func (server *Server) CurrentPort() int {
// it is started. This must be an IP address, either IPv4 // it is started. This must be an IP address, either IPv4
// or IPv6. // or IPv6.
func (server *Server) HostAddress() string { func (server *Server) HostAddress() string {
host := server.cfg.StringValue("Address") host := server.cfg.StringValue("host")
if host == "" { if host == "" {
return "0.0.0.0" return "0.0.0.0"
} }
@ -1449,8 +1470,8 @@ func (server *Server) Start() (err error) {
*/ */
// Wrap a TLS listener around the TCP connection // Wrap a TLS listener around the TCP connection
certFn := server.cfg.PathValue("CertPath", Args.DataDir) certFn := server.cfg.PathValue("sslCert", Args.DataDir)
keyFn := server.cfg.PathValue("KeyPath", Args.DataDir) keyFn := server.cfg.PathValue("sslKey", Args.DataDir)
cert, err := tls.LoadX509KeyPair(certFn, keyFn) cert, err := tls.LoadX509KeyPair(certFn, keyFn)
if err != nil { if err != nil {
return err return err
@ -1459,6 +1480,15 @@ func (server *Server) Start() (err error) {
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert, ClientAuth: tls.RequestClientCert,
} }
ciphersstr := server.cfg.StringValue("sslCiphers")
if ciphersstr != "" {
var invalid []string
server.tlscfg.CipherSuites, invalid = serverconf.ParseCipherlist(ciphersstr)
for _, cipher := range invalid {
log.Printf("Ignoring invalid or unsupported cipher \"%v\"", cipher)
}
server.tlscfg.PreferServerCipherSuites = true
}
server.tlsl = tls.NewListener(server.tcpl, server.tlscfg) server.tlsl = tls.NewListener(server.tcpl, server.tlscfg)
if shouldListenWeb { if shouldListenWeb {

View file

@ -0,0 +1,77 @@
package serverconf
import (
"crypto/tls"
"strings"
)
var cipherLookup = map[string]uint16{
// RFC
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
// These are the actual names per RFC 7905
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
// OpenSSL
"RC4-SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"DES-CBC3-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"AES128-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"AES256-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"AES128-SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"ECDHE-ECDSA-RC4-SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"ECDHE-ECDSA-AES128-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"ECDHE-ECDSA-AES256-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"ECDHE-RSA-RC4-SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"ECDHE-RSA-DES-CBC3-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"ECDHE-RSA-AES128-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"ECDHE-RSA-AES256-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"ECDHE-ECDSA-AES128-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"ECDHE-RSA-AES128-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
// ParseCipherlist parses a list of cipher suites separated by colons.
// It supports both RFC and OpenSSL names, but does not support OpenSSL
// cipher strings representing categories of cipher suites.
func ParseCipherlist(list string) (ciphers []uint16, invalid []string) {
strciphers := strings.Split(list, ":")
ciphers = make([]uint16, 0, len(strciphers))
invalid = make([]string, 0)
for _, v := range strciphers {
c, ok := cipherLookup[v]
if ok {
ciphers = append(ciphers, c)
} else {
invalid = append(invalid, v)
}
}
return
}

View file

@ -11,19 +11,20 @@ import (
) )
var defaultCfg = map[string]string{ var defaultCfg = map[string]string{
"MaxBandwidth": "72000", "bandwidth": "72000",
"MaxUsers": "1000", "users": "1000",
"MaxUsersPerChannel": "0", "usersperchannel": "0",
"MaxTextMessageLength": "5000", "textmessagelength": "5000",
"MaxImageMessageLength": "131072", "imagemessagelength": "131072",
"AllowHTML": "true", "allowhtml": "true",
"DefaultChannel": "0", "defaultchannel": "0",
"RememberChannel": "true", "rememberchannel": "true",
"WelcomeText": "Welcome to this server running <b>Grumble</b>.", "welcometext": "Welcome to this server running <b>Grumble</b>.",
"SendVersion": "true", "sendversion": "true",
"LogPath": "grumble.log", "allowping": "true",
"CertPath": "cert.pem", "logfile": "grumble.log",
"KeyPath": "key.pem", "sslCert": "cert.pem",
"sslKey": "key.pem",
} }
type Config struct { type Config struct {

View file

@ -26,7 +26,7 @@ func TestFloatAsInt(t *testing.T) {
func TestDefaultValue(t *testing.T) { func TestDefaultValue(t *testing.T) {
cfg := New(nil, nil) cfg := New(nil, nil)
if cfg.IntValue("MaxBandwidth") != 72000 { if cfg.IntValue("bandwidth") != 72000 {
t.Errorf("Expected 72000") t.Errorf("Expected 72000")
} }
} }

View file

@ -37,16 +37,16 @@ func (c *ConfigFile) ServerConfig(id int64, persistentMap map[string]string) *Co
// Some server specific values from the global config must be offset. // Some server specific values from the global config must be offset.
// These are read differently by the server as well. // These are read differently by the server as well.
if v, ok := m["Port"]; ok { if v, ok := m["port"]; ok {
i, err := strconv.ParseInt(v, 10, 64) i, err := strconv.ParseInt(v, 10, 64)
if err == nil { if err == nil {
m["Port"] = strconv.FormatInt(i+id-1, 10) m["port"] = strconv.FormatInt(i+id-1, 10)
} }
} }
if v, ok := m["WebPort"]; ok { if v, ok := m["webport"]; ok {
i, err := strconv.ParseInt(v, 10, 64) i, err := strconv.ParseInt(v, 10, 64)
if err == nil { if err == nil {
m["WebPort"] = strconv.FormatInt(i+id-1, 10) m["webport"] = strconv.FormatInt(i+id-1, 10)
} }
} }

View file

@ -21,7 +21,6 @@ func (f *inicfg) GlobalMap() map[string]string {
return f.file.Section("").KeysHash() return f.file.Section("").KeysHash()
} }
// todo(rubenseyer): not all of these even work.. make sure to implement them
var DefaultConfigFile = `# Grumble configuration file. var DefaultConfigFile = `# Grumble configuration file.
# #
# The commented out settings represent the defaults. # The commented out settings represent the defaults.
@ -29,55 +28,63 @@ var DefaultConfigFile = `# Grumble configuration file.
# Make sure to enclose values containing # or ; in double quotes or backticks. # Make sure to enclose values containing # or ; in double quotes or backticks.
# Address to bind the listeners to. # Address to bind the listeners to.
#Address = 0.0.0.0 #host = 0.0.0.0
# Port is the port to bind the native Mumble protocol to. # port is the port to bind the native Mumble protocol to.
# WebPort is the port to bind the WebSocket Mumble protocol to. # webport is the port to bind the WebSocket Mumble protocol to.
# They are incremented for each virtual server. # They are incremented for each virtual server (if set globally).
#Port = 64738 #port = 64738
#WebPort = 443 #webport = 443
# Whether to disable web server.
#nowebserver
# "Message of the day" HTML string sent to connecting clients. # "Message of the day" HTML string sent to connecting clients.
#WelcomeText = "Welcome to this server running <b>Grumble</b>." #welcometext = "Welcome to this server running <b>Grumble</b>."
# Password to join the server.
#serverpassword =
# Maximum bandwidth (in bits per second) per client for voice. # Maximum bandwidth (in bits per second) per client for voice.
# Grumble does not yet enforce this limit, but some clients nicely follow it. # Grumble does not yet enforce this limit, but some clients nicely follow it.
#MaxBandwidth = 72000 #bandwidth = 72000
# Maximum number of concurrent clients. # Maximum number of concurrent clients.
#MaxUsers = 1000 #users = 1000
#MaxUsersPerChannel = 0 #usersperchannel = 0
#MaxTextMessageLength = 5000 #textmessagelength = 5000
#MaxImageMessageLength = 131072 #imagemessagelength = 131072
#AllowHTML #allowhtml
# DefaultChannel is the channel (by ID) new users join. # The default channel is the channel (by ID) new users join.
# The root channel is the default. # The root channel (ID = 0) is the default.
#DefaultChannel = 0 #defaultchannel = 0
# Whether users will rejoin the last channel they were in. # Whether users will rejoin the last channel they were in.
#RememberChannel #rememberchannel
# Whether to include server version and server os in ping response. # Whether to include server OS info in ping response.
#SendVersion #sendversion
#SendOSInfo
# Whether to respond to pings from the Connect dialog.
#allowping
# Path to the log file (relative to the data directory). # Path to the log file (relative to the data directory).
#LogPath = grumble.log #logfile = grumble.log
# Path to TLS certificate and key (relative to the data directory). # Path to TLS certificate and key (relative to the data directory).
# The certificate needs to have the entire chain concatenated to be validate. # The certificate needs to have the entire chain concatenated to be valid.
# If these paths do not exist, Grumble will autogenerate a certificate. # If these paths do not exist, Grumble will autogenerate a certificate.
#CertPath = cert.pem #sslCert = cert.pem
#KeyPath = key.pem #sslKey = key.pem
# Options for public server registration. # Options for public server registration.
# All of these have to be set to make the server public. # All of these have to be set to make the server public.
# RegisterName additionally sets the name of the root channel. # registerName additionally sets the name of the root channel.
# RegisterPassword is a simple, arbitrary secret to guard your registration. Don't lose it. # registerPassword is a simple, arbitrary secret to guard your registration. Don't lose it.
#RegisterName = #registerName =
#RegisterHost = #registerHostname =
#RegisterPassword = #registerPassword =
#RegisterWebUrl = #registerUrl =
` `

View file

@ -6,6 +6,7 @@
package sessionpool package sessionpool
import ( import (
"errors"
"math" "math"
"sync" "sync"
) )
@ -17,11 +18,17 @@ type SessionPool struct {
used map[uint32]bool used map[uint32]bool
unused []uint32 unused []uint32
cur uint32 cur uint32
max uint32
} }
// Create a new SessionPool container. // Create a new SessionPool container.
func New() (pool *SessionPool) { func New(max uint32) (pool *SessionPool) {
pool = new(SessionPool) pool = new(SessionPool)
if max == 0 {
pool.max = math.MaxUint32
} else {
pool.max = max
}
return return
} }
@ -41,7 +48,7 @@ func (pool *SessionPool) EnableUseTracking() {
// Get a new session ID from the SessionPool. // Get a new session ID from the SessionPool.
// Must be reclaimed using Reclaim() when done using it. // Must be reclaimed using Reclaim() when done using it.
func (pool *SessionPool) Get() (id uint32) { func (pool *SessionPool) Get() (id uint32, err error) {
pool.mutex.Lock() pool.mutex.Lock()
defer pool.mutex.Unlock() defer pool.mutex.Unlock()
@ -60,11 +67,12 @@ func (pool *SessionPool) Get() (id uint32) {
return return
} }
// Check for depletion. If cur is MaxUint32, // Check for depletion. If cur is max,
// there aren't any session IDs left, since the // there aren't any session IDs left, since the
// increment below would overflow us back to 0. // increment below would return an out of range ID.
if pool.cur == math.MaxUint32 { if pool.cur == pool.max {
panic("SessionPool depleted") err = errors.New("depleted session pool")
return
} }
// Increment the next session id and return it. // Increment the next session id and return it.

View file

@ -6,35 +6,41 @@ import (
) )
func TestReclaim(t *testing.T) { func TestReclaim(t *testing.T) {
pool := New() pool := New(2)
id := pool.Get() id, err := pool.Get()
if err != nil {
t.Errorf("Expected no error: %v", err)
}
if id != 1 { if id != 1 {
t.Errorf("Got %v, expected 1 (first time)", id) t.Errorf("Got %v, expected 1 (first time)", id)
} }
pool.Reclaim(1) pool.Reclaim(1)
id = pool.Get() id, err = pool.Get()
if err != nil {
t.Errorf("Expected no error: %v", err)
}
if id != 1 { if id != 1 {
t.Errorf("Got %v, expected 1 (second time)", id) t.Errorf("Got %v, expected 1 (second time)", id)
} }
id = pool.Get() id, err = pool.Get()
if err != nil {
t.Errorf("Expected no error: %v", err)
}
if id != 2 { if id != 2 {
t.Errorf("Got %v, expected 2", id) t.Errorf("Got %v, expected 2", id)
} }
} }
func TestDepletion(t *testing.T) { func TestDepletion(t *testing.T) {
defer func() { pool := New(0)
r := recover()
if r != "SessionPool depleted" {
t.Errorf("Expected depletion panic")
}
}()
pool := New()
pool.cur = math.MaxUint32 pool.cur = math.MaxUint32
pool.Get() _, err := pool.Get()
if err == nil {
t.Errorf("Expected depletion error")
}
} }
func TestUseTracking(t *testing.T) { func TestUseTracking(t *testing.T) {
@ -45,7 +51,7 @@ func TestUseTracking(t *testing.T) {
} }
}() }()
pool := New() pool := New(0)
pool.EnableUseTracking() pool.EnableUseTracking()
pool.Reclaim(42) pool.Reclaim(42)
} }