Skip to content

Commit

Permalink
Minor features for parity with murmur.ini
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rubenseyer committed Apr 16, 2020
1 parent 693dd6f commit ae41a61
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 109 deletions.
2 changes: 1 addition & 1 deletion cmd/grumble/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ func (client *Client) tlsRecvLoop() {
Release: proto.String("Grumble"),
CryptoModes: cryptstate.SupportedModes(),
}
if client.server.cfg.BoolValue("SendOSInfo") {
if client.server.cfg.BoolValue("sendversion") {
version.Os = proto.String(runtime.GOOS)
version.OsVersion = proto.String("(Unknown version)")
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/grumble/grumble.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func main() {
if Args.LogPath != "" {
logFn = Args.LogPath
} else {
logFn = config.PathValue("LogPath", Args.DataDir)
logFn = config.PathValue("logfile", Args.DataDir)
}
logtarget.Default, err = logtarget.OpenFile(Args.LogPath, os.Stderr)
if err != nil {
Expand Down Expand Up @@ -131,8 +131,8 @@ func main() {
// Check whether we should regenerate the default global keypair
// and corresponding certificate.
// These are used as the default certificate of all virtual servers.
certFn := config.PathValue("CertPath", Args.DataDir)
keyFn := config.PathValue("KeyPath", Args.DataDir)
certFn := config.PathValue("sslCert", Args.DataDir)
keyFn := config.PathValue("sslKey", Args.DataDir)
shouldRegen := false
if Args.RegenKeys {
shouldRegen = true
Expand Down
4 changes: 2 additions & 2 deletions cmd/grumble/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
return
}

maxChannelUsers := server.cfg.IntValue("MaxChannelUsers")
maxChannelUsers := server.cfg.IntValue("usersperchannel")
if maxChannelUsers != 0 && len(dstChan.clients) >= maxChannelUsers {
client.sendPermissionDeniedFallback(mumbleproto.PermissionDenied_ChannelFull,
0x010201, "Channel is full")
Expand Down Expand Up @@ -653,7 +653,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {

// Texture change
if userstate.Texture != nil {
maximg := server.cfg.IntValue("MaxImageMessageLength")
maximg := server.cfg.IntValue("imagemessagelength")
if maximg > 0 && len(userstate.Texture) > maximg {
client.sendPermissionDeniedType(mumbleproto.PermissionDenied_TextTooLong)
return
Expand Down
21 changes: 12 additions & 9 deletions cmd/grumble/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,19 @@ const registerUrl = "https://mumble.info/register.cgi"
// This function is used to determine whether or not to periodically
// contact the master server list and update this server's metadata.
func (server *Server) IsPublic() bool {
if len(server.cfg.StringValue("RegisterName")) == 0 {
if len(server.cfg.StringValue("registerName")) == 0 {
return false
}
if len(server.cfg.StringValue("RegisterHost")) == 0 {
if len(server.cfg.StringValue("registerHostname")) == 0 {
return false
}
if len(server.cfg.StringValue("RegisterPassword")) == 0 {
if len(server.cfg.StringValue("registerPassword")) == 0 {
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 true
Expand Down Expand Up @@ -80,11 +83,11 @@ func (server *Server) RegisterPublicServer() {

// Render registration XML template
reg := Register{
Name: server.cfg.StringValue("RegisterName"),
Host: server.cfg.StringValue("RegisterHost"),
Password: server.cfg.StringValue("RegisterPassword"),
Url: server.cfg.StringValue("RegisterWebUrl"),
Location: server.cfg.StringValue("RegisterLocation"),
Name: server.cfg.StringValue("registerName"),
Host: server.cfg.StringValue("registerHostname"),
Password: server.cfg.StringValue("registerPassword"),
Url: server.cfg.StringValue("registerUrl"),
Location: server.cfg.StringValue("registerLocation"),
Port: server.CurrentPort(),
Digest: digest,
Users: len(server.clients),
Expand Down
82 changes: 56 additions & 26 deletions cmd/grumble/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ func (server *Server) RootChannel() *Channel {
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) {
saltBytes := make([]byte, 24)
_, err := rand.Read(saltBytes)
Expand Down Expand Up @@ -271,7 +281,14 @@ func (server *Server) handleIncomingClient(conn net.Conn) (err error) {
client.lf = &clientLogForwarder{client, server.Logger}
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.tcpaddr = addr.(*net.TCPAddr)
Expand Down Expand Up @@ -528,12 +545,13 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) {
client.user = user
}
}
}

if client.user == nil && server.hasServerPassword() {
if auth.Password == nil || !server.CheckServerPassword(*auth.Password) {
client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password")
return
// Otherwise, the user is unregistered. If there is a server-wide password, they now need it.
if client.user == nil && server.hasServerPassword() {
if auth.Password == nil || !server.CheckServerPassword(*auth.Password) {
client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password")
return
}
}
}

Expand Down Expand Up @@ -618,8 +636,8 @@ func (server *Server) finishAuthenticate(client *Client) {
server.hclients[host] = append(server.hclients[host], client)
server.hmutex.Unlock()

channel := server.RootChannel()
if client.IsRegistered() {
channel := server.DefaultChannel()
if server.cfg.BoolValue("rememberchannel") && client.IsRegistered() {
lastChannel := server.Channels[client.user.LastChannelId]
if lastChannel != nil {
channel = lastChannel
Expand Down Expand Up @@ -675,8 +693,8 @@ func (server *Server) finishAuthenticate(client *Client) {

sync := &mumbleproto.ServerSync{}
sync.Session = proto.Uint32(client.Session())
sync.MaxBandwidth = proto.Uint32(server.cfg.Uint32Value("MaxBandwidth"))
sync.WelcomeText = proto.String(server.cfg.StringValue("WelcomeText"))
sync.MaxBandwidth = proto.Uint32(server.cfg.Uint32Value("bandwidth"))
sync.WelcomeText = proto.String(server.cfg.StringValue("welcometext"))
if client.IsSuperUser() {
sync.Permissions = proto.Uint64(uint64(acl.AllPermissions))
} else {
Expand All @@ -693,9 +711,9 @@ func (server *Server) finishAuthenticate(client *Client) {
}

err := client.sendMessage(&mumbleproto.ServerConfig{
AllowHtml: proto.Bool(server.cfg.BoolValue("AllowHTML")),
MessageLength: proto.Uint32(server.cfg.Uint32Value("MaxTextMessageLength")),
ImageMessageLength: proto.Uint32(server.cfg.Uint32Value("MaxImageMessageLength")),
AllowHtml: proto.Bool(server.cfg.BoolValue("allowhtml")),
MessageLength: proto.Uint32(server.cfg.Uint32Value("textmessagelength")),
ImageMessageLength: proto.Uint32(server.cfg.Uint32Value("imagemessagelength")),
})
if err != nil {
client.Panicf("%v", err)
Expand Down Expand Up @@ -992,6 +1010,9 @@ func (server *Server) udpListenLoop() {

// Length 12 is for ping datagrams from the ConnectDialog.
if nread == 12 {
if !server.cfg.BoolValue("allowping") {
return
}
readbuf := bytes.NewBuffer(buf)
var (
tmp32 uint32
Expand All @@ -1001,11 +1022,11 @@ func (server *Server) udpListenLoop() {
_ = binary.Read(readbuf, binary.BigEndian, &rand)

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, uint32(len(server.clients)))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxUsers"))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxBandwidth"))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("users"))
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("bandwidth"))

err = server.SendUDP(buffer.Bytes(), udpaddr)
if err != nil {
Expand Down Expand Up @@ -1283,9 +1304,9 @@ func (server *Server) IsCertHashBanned(hash string) bool {
// Filter incoming text according to the server's current rules.
func (server *Server) FilterText(text string) (filtered string, err error) {
options := &htmlfilter.Options{
StripHTML: !server.cfg.BoolValue("AllowHTML"),
MaxTextMessageLength: server.cfg.IntValue("MaxTextMessageLength"),
MaxImageMessageLength: server.cfg.IntValue("MaxImageMessageLength"),
StripHTML: !server.cfg.BoolValue("allowhtml"),
MaxTextMessageLength: server.cfg.IntValue("textmessagelength"),
MaxImageMessageLength: server.cfg.IntValue("imagemessagelength"),
}
return htmlfilter.Filter(text, options)
}
Expand Down Expand Up @@ -1339,7 +1360,7 @@ func isTimeout(err error) bool {

// Initialize the per-launch data
func (server *Server) initPerLaunchData() {
server.pool = sessionpool.New()
server.pool = sessionpool.New(server.cfg.Uint32Value("users"))
server.clients = make(map[uint32]*Client)
server.hclients = make(map[string][]*Client)
server.hpclients = make(map[string]*Client)
Expand Down Expand Up @@ -1368,7 +1389,7 @@ func (server *Server) cleanPerLaunchData() {
// Port returns the port the native server will listen on when it is
// started.
func (server *Server) Port() int {
port := server.cfg.IntValue("Port")
port := server.cfg.IntValue("port")
if port == 0 {
return DefaultPort + int(server.Id) - 1
}
Expand All @@ -1378,13 +1399,13 @@ func (server *Server) Port() int {
// ListenWebPort returns true if we should listen to the
// web port, otherwise false
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
// started.
func (server *Server) WebPort() int {
port := server.cfg.IntValue("WebPort")
port := server.cfg.IntValue("webport")
if port == 0 {
return DefaultWebPort + int(server.Id) - 1
}
Expand All @@ -1406,7 +1427,7 @@ func (server *Server) CurrentPort() int {
// it is started. This must be an IP address, either IPv4
// or IPv6.
func (server *Server) HostAddress() string {
host := server.cfg.StringValue("Address")
host := server.cfg.StringValue("host")
if host == "" {
return "0.0.0.0"
}
Expand Down Expand Up @@ -1449,8 +1470,8 @@ func (server *Server) Start() (err error) {
*/

// Wrap a TLS listener around the TCP connection
certFn := server.cfg.PathValue("CertPath", Args.DataDir)
keyFn := server.cfg.PathValue("KeyPath", Args.DataDir)
certFn := server.cfg.PathValue("sslCert", Args.DataDir)
keyFn := server.cfg.PathValue("sslKey", Args.DataDir)
cert, err := tls.LoadX509KeyPair(certFn, keyFn)
if err != nil {
return err
Expand All @@ -1459,6 +1480,15 @@ func (server *Server) Start() (err error) {
Certificates: []tls.Certificate{cert},
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)

if shouldListenWeb {
Expand Down
77 changes: 77 additions & 0 deletions pkg/serverconf/cipherlist.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 14 additions & 13 deletions pkg/serverconf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@ import (
)

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

type Config struct {
Expand Down
Loading

0 comments on commit ae41a61

Please sign in to comment.