grumble: move grumble cmd to 'cmd/grumble' instead of the source tree root.

This commit is contained in:
Mikkel Krautz 2014-04-15 14:31:42 +02:00
parent 8302bb2b08
commit ef533d8f2b
19 changed files with 2 additions and 2 deletions

97
cmd/grumble/args.go Normal file
View file

@ -0,0 +1,97 @@
package main
import (
"flag"
"os"
"path/filepath"
"runtime"
"text/template"
)
type UsageArgs struct {
Version string
BuildDate string
OS string
Arch string
}
var usageTmpl = `usage: grumble [options]
grumble {{.Version}} ({{.BuildDate}})
target: {{.OS}}, {{.Arch}}
--help
Shows this help listing.
--datadir <data-dir> (default: $HOME/.grumble)
Directory to use for server storage.
--log <log-path> (default: $DATADIR/grumble.log)
Log file path.
--regen-keys
Force grumble to regenerate its global RSA
keypair (and certificate).
The global keypair lives in the root of the
grumble data directory.
--import-murmurdb <murmur-sqlite-path>
Import a Murmur SQLite database into grumble.
Use the --cleanup argument to force grumble to
clean up its data directory when doing the
import. This is *DESTRUCTIVE*! Use with care.
`
type args struct {
ShowHelp bool
DataDir string
LogPath string
RegenKeys bool
SQLiteDB string
CleanUp bool
}
func defaultDataDir() string {
dirname := ".grumble"
if runtime.GOOS == "windows" {
dirname = "grumble"
}
return filepath.Join(os.Getenv("HOME"), dirname)
}
func defaultLogPath() string {
return filepath.Join(defaultDataDir(), "grumble.log")
}
func Usage() {
t, err := template.New("usage").Parse(usageTmpl)
if err != nil {
panic("unable to parse usage template")
}
err = t.Execute(os.Stdout, UsageArgs{
Version: version,
BuildDate: buildDate,
OS: runtime.GOOS,
Arch: runtime.GOARCH,
})
if err != nil {
panic("unable to execute usage template")
}
}
var Args args
func init() {
flag.Usage = Usage
flag.BoolVar(&Args.ShowHelp, "help", false, "")
flag.StringVar(&Args.DataDir, "datadir", defaultDataDir(), "")
flag.StringVar(&Args.LogPath, "log", defaultLogPath(), "")
flag.BoolVar(&Args.RegenKeys, "regen-keys", false, "")
flag.StringVar(&Args.SQLiteDB, "import-murmurdb", "", "")
flag.BoolVar(&Args.CleanUp, "cleanup", false, "")
}

131
cmd/grumble/channel.go Normal file
View file

@ -0,0 +1,131 @@
// Copyright (c) 2010 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"encoding/hex"
"mumble.info/grumble/pkg/acl"
)
// A Mumble channel
type Channel struct {
Id int
Name string
Position int
temporary bool
clients map[uint32]*Client
parent *Channel
children map[int]*Channel
// ACL
ACL acl.Context
// Links
Links map[int]*Channel
// Blobs
DescriptionBlob string
}
func NewChannel(id int, name string) (channel *Channel) {
channel = new(Channel)
channel.Id = id
channel.Name = name
channel.clients = make(map[uint32]*Client)
channel.children = make(map[int]*Channel)
channel.ACL.Groups = make(map[string]acl.Group)
channel.Links = make(map[int]*Channel)
return
}
// Add a child channel to a channel
func (channel *Channel) AddChild(child *Channel) {
child.parent = channel
child.ACL.Parent = &channel.ACL
channel.children[child.Id] = child
}
// Remove a child channel from a parent
func (channel *Channel) RemoveChild(child *Channel) {
child.parent = nil
child.ACL.Parent = nil
delete(channel.children, child.Id)
}
// Add client
func (channel *Channel) AddClient(client *Client) {
channel.clients[client.Session()] = client
client.Channel = channel
}
// Remove client
func (channel *Channel) RemoveClient(client *Client) {
delete(channel.clients, client.Session())
client.Channel = nil
}
// Does the channel have a description?
func (channel *Channel) HasDescription() bool {
return len(channel.DescriptionBlob) > 0
}
// Get the channel's blob hash as a byte slice for sending via a protobuf message.
// Returns nil if there is no blob.
func (channel *Channel) DescriptionBlobHashBytes() (buf []byte) {
buf, err := hex.DecodeString(channel.DescriptionBlob)
if err != nil {
return nil
}
return buf
}
// Returns a slice of all channels in this channel's
// link chain.
func (channel *Channel) AllLinks() (seen map[int]*Channel) {
seen = make(map[int]*Channel)
walk := []*Channel{channel}
for len(walk) > 0 {
current := walk[len(walk)-1]
walk = walk[0 : len(walk)-1]
for _, linked := range current.Links {
if _, alreadySeen := seen[linked.Id]; !alreadySeen {
seen[linked.Id] = linked
walk = append(walk, linked)
}
}
}
return
}
// Returns a slice of all of this channel's subchannels.
func (channel *Channel) AllSubChannels() (seen map[int]*Channel) {
seen = make(map[int]*Channel)
walk := []*Channel{}
if len(channel.children) > 0 {
walk = append(walk, channel)
for len(walk) > 0 {
current := walk[len(walk)-1]
walk = walk[0 : len(walk)-1]
for _, child := range current.children {
if _, alreadySeen := seen[child.Id]; !alreadySeen {
seen[child.Id] = child
walk = append(walk, child)
}
}
}
}
return
}
// Checks whether the channel is temporary
func (channel *Channel) IsTemporary() bool {
return channel.temporary
}
// Checks whether the channel is temporary
func (channel *Channel) IsEmpty() bool {
return len(channel.clients) == 0
}

648
cmd/grumble/client.go Normal file
View file

@ -0,0 +1,648 @@
// Copyright (c) 2010-2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"bufio"
"bytes"
"code.google.com/p/goprotobuf/proto"
"crypto/tls"
"encoding/binary"
"errors"
"io"
"log"
"mumble.info/grumble/pkg/acl"
"mumble.info/grumble/pkg/cryptstate"
"mumble.info/grumble/pkg/mumbleproto"
"mumble.info/grumble/pkg/packetdata"
"net"
"runtime"
"time"
)
// A client connection
type Client struct {
// Logging
*log.Logger
lf *clientLogForwarder
// Connection-related
tcpaddr *net.TCPAddr
udpaddr *net.UDPAddr
conn net.Conn
reader *bufio.Reader
state int
server *Server
udprecv chan []byte
disconnected bool
lastResync int64
crypt cryptstate.CryptState
codecs []int32
opus bool
udp bool
voiceTargets map[uint32]*VoiceTarget
// Ping stats
UdpPingAvg float32
UdpPingVar float32
UdpPackets uint32
TcpPingAvg float32
TcpPingVar float32
TcpPackets uint32
// If the client is a registered user on the server,
// the user field will point to the registration record.
user *User
// The clientReady channel signals the client's reciever routine that
// the client has been successfully authenticated and that it has been
// sent the necessary information to be a participant on the server.
// When this signal is received, the client has transitioned into the
// 'ready' state.
clientReady chan bool
// Version
Version uint32
ClientName string
OSName string
OSVersion string
CryptoMode string
// Personal
Username string
session uint32
certHash string
Email string
tokens []string
Channel *Channel
SelfMute bool
SelfDeaf bool
Mute bool
Deaf bool
Suppress bool
PrioritySpeaker bool
Recording bool
PluginContext []byte
PluginIdentity string
}
// Debugf implements debug-level printing for Clients.
func (client *Client) Debugf(format string, v ...interface{}) {
client.Printf(format, v...)
}
// Is the client a registered user?
func (client *Client) IsRegistered() bool {
return client.user != nil
}
// Does the client have a certificate?
func (client *Client) HasCertificate() bool {
return len(client.certHash) > 0
}
// Is the client the SuperUser?
func (client *Client) IsSuperUser() bool {
if client.user == nil {
return false
}
return client.user.Id == 0
}
func (client *Client) ACLContext() *acl.Context {
return &client.Channel.ACL
}
func (client *Client) CertHash() string {
return client.certHash
}
func (client *Client) Session() uint32 {
return client.session
}
func (client *Client) Tokens() []string {
return client.tokens
}
// Get the User ID of this client.
// Returns -1 if the client is not a registered user.
func (client *Client) UserId() int {
if client.user == nil {
return -1
}
return int(client.user.Id)
}
// Get the client's shown name.
func (client *Client) ShownName() string {
if client.IsSuperUser() {
return "SuperUser"
}
if client.IsRegistered() {
return client.user.Name
}
return client.Username
}
// Check whether the client's certificate is
// verified.
func (client *Client) IsVerified() bool {
tlsconn := client.conn.(*tls.Conn)
state := tlsconn.ConnectionState()
return len(state.VerifiedChains) > 0
}
// Log a panic and disconnect the client.
func (client *Client) Panic(v ...interface{}) {
client.Print(v)
client.Disconnect()
}
// Log a formatted panic and disconnect the client.
func (client *Client) Panicf(format string, v ...interface{}) {
client.Printf(format, v...)
client.Disconnect()
}
// Internal disconnect function
func (client *Client) disconnect(kicked bool) {
if !client.disconnected {
client.disconnected = true
client.server.RemoveClient(client, kicked)
// Close the client's UDP reciever goroutine.
close(client.udprecv)
// If the client paniced during authentication, before reaching
// the ready state, the receiver goroutine will be waiting for
// a signal telling it that the client is ready to receive 'real'
// messages from the server.
//
// In case of a premature disconnect, close the channel so the
// receiver routine can exit correctly.
if client.state == StateClientSentVersion || client.state == StateClientAuthenticated {
close(client.clientReady)
}
client.Printf("Disconnected")
client.conn.Close()
client.server.updateCodecVersions(nil)
}
}
// Disconnect a client (client requested or server shutdown)
func (client *Client) Disconnect() {
client.disconnect(false)
}
// Disconnect a client (kick/ban)
func (client *Client) ForceDisconnect() {
client.disconnect(true)
}
// Clear the client's caches
func (client *Client) ClearCaches() {
for _, vt := range client.voiceTargets {
vt.ClearCache()
}
}
// Reject an authentication attempt
func (client *Client) RejectAuth(rejectType mumbleproto.Reject_RejectType, reason string) {
var reasonString *string = nil
if len(reason) > 0 {
reasonString = proto.String(reason)
}
client.sendMessage(&mumbleproto.Reject{
Type: rejectType.Enum(),
Reason: reasonString,
})
client.ForceDisconnect()
}
// Read a protobuf message from a client
func (client *Client) readProtoMessage() (msg *Message, err error) {
var (
length uint32
kind uint16
)
// Read the message type (16-bit big-endian unsigned integer)
err = binary.Read(client.reader, binary.BigEndian, &kind)
if err != nil {
return
}
// Read the message length (32-bit big-endian unsigned integer)
err = binary.Read(client.reader, binary.BigEndian, &length)
if err != nil {
return
}
buf := make([]byte, length)
_, err = io.ReadFull(client.reader, buf)
if err != nil {
return
}
msg = &Message{
buf: buf,
kind: kind,
client: client,
}
return
}
// Send permission denied by type
func (c *Client) sendPermissionDeniedType(denyType mumbleproto.PermissionDenied_DenyType) {
c.sendPermissionDeniedTypeUser(denyType, nil)
}
// Send permission denied by type (and user)
func (c *Client) sendPermissionDeniedTypeUser(denyType mumbleproto.PermissionDenied_DenyType, user *Client) {
pd := &mumbleproto.PermissionDenied{
Type: denyType.Enum(),
}
if user != nil {
pd.Session = proto.Uint32(uint32(user.Session()))
}
err := c.sendMessage(pd)
if err != nil {
c.Panicf("%v", err.Error())
return
}
}
// Send permission denied by who, what, where
func (c *Client) sendPermissionDenied(who *Client, where *Channel, what acl.Permission) {
pd := &mumbleproto.PermissionDenied{
Permission: proto.Uint32(uint32(what)),
ChannelId: proto.Uint32(uint32(where.Id)),
Session: proto.Uint32(who.Session()),
Type: mumbleproto.PermissionDenied_Permission.Enum(),
}
err := c.sendMessage(pd)
if err != nil {
c.Panicf("%v", err.Error())
return
}
}
// Send permission denied fallback
func (client *Client) sendPermissionDeniedFallback(denyType mumbleproto.PermissionDenied_DenyType, version uint32, text string) {
pd := &mumbleproto.PermissionDenied{
Type: denyType.Enum(),
}
if client.Version < version {
pd.Reason = proto.String(text)
}
err := client.sendMessage(pd)
if err != nil {
client.Panicf("%v", err.Error())
return
}
}
// UDP receive loop
func (client *Client) udpRecvLoop() {
for buf := range client.udprecv {
// Received a zero-valued buffer. This means that the udprecv
// channel was closed, so exit cleanly.
if len(buf) == 0 {
return
}
kind := (buf[0] >> 5) & 0x07
switch kind {
case mumbleproto.UDPMessageVoiceSpeex:
fallthrough
case mumbleproto.UDPMessageVoiceCELTAlpha:
fallthrough
case mumbleproto.UDPMessageVoiceCELTBeta:
if (client.server.Opus) {
return
}
fallthrough
case mumbleproto.UDPMessageVoiceOpus:
target := buf[0] & 0x1f
var counter uint8
outbuf := make([]byte, 1024)
incoming := packetdata.New(buf[1 : 1+(len(buf)-1)])
outgoing := packetdata.New(outbuf[1 : 1+(len(outbuf)-1)])
_ = incoming.GetUint32()
if kind != mumbleproto.UDPMessageVoiceOpus {
for {
counter = incoming.Next8()
incoming.Skip(int(counter & 0x7f))
if !((counter&0x80) != 0 && incoming.IsValid()) {
break
}
}
} else {
size := int(incoming.GetUint16())
incoming.Skip(size & 0x1fff)
}
outgoing.PutUint32(client.Session())
outgoing.PutBytes(buf[1 : 1+(len(buf)-1)])
outbuf[0] = buf[0] & 0xe0 // strip target
if target != 0x1f { // VoiceTarget
client.server.voicebroadcast <- &VoiceBroadcast{
client: client,
buf: outbuf[0 : 1+outgoing.Size()],
target: target,
}
} else { // Server loopback
buf := outbuf[0 : 1+outgoing.Size()]
err := client.SendUDP(buf)
if err != nil {
client.Panicf("Unable to send UDP message: %v", err.Error())
}
}
case mumbleproto.UDPMessagePing:
err := client.SendUDP(buf)
if err != nil {
client.Panicf("Unable to send UDP message: %v", err.Error())
}
}
}
}
// Send buf as a UDP message. If the client does not have
// an established UDP connection, the datagram will be tunelled
// through the client's control channel (TCP).
func (client *Client) SendUDP(buf []byte) error {
if client.udp {
crypted := make([]byte, len(buf)+client.crypt.Overhead())
client.crypt.Encrypt(crypted, buf)
return client.server.SendUDP(crypted, client.udpaddr)
} else {
return client.sendMessage(buf)
}
panic("unreachable")
}
// Send a Message to the client. The Message in msg to the client's
// buffered writer and flushes it when done.
//
// This method should only be called from within the client's own
// sender goroutine, since it serializes access to the underlying
// buffered writer.
func (client *Client) sendMessage(msg interface{}) error {
buf := new(bytes.Buffer)
var (
kind uint16
msgData []byte
err error
)
kind = mumbleproto.MessageType(msg)
if kind == mumbleproto.MessageUDPTunnel {
msgData = msg.([]byte)
} else {
protoMsg, ok := (msg).(proto.Message)
if !ok {
return errors.New("client: exepcted a proto.Message")
}
msgData, err = proto.Marshal(protoMsg)
if err != nil {
return err
}
}
err = binary.Write(buf, binary.BigEndian, kind)
if err != nil {
return err
}
err = binary.Write(buf, binary.BigEndian, uint32(len(msgData)))
if err != nil {
return err
}
_, err = buf.Write(msgData)
if err != nil {
return err
}
_, err = client.conn.Write(buf.Bytes())
if err != nil {
return err
}
return nil
}
// TLS receive loop
func (client *Client) tlsRecvLoop() {
for {
// The version handshake is done, the client has been authenticated and it has received
// all necessary information regarding the server. Now we're ready to roll!
if client.state == StateClientReady {
// Try to read the next message in the pool
msg, err := client.readProtoMessage()
if err != nil {
if err == io.EOF {
client.Disconnect()
} else {
client.Panicf("%v", err)
}
return
}
// Special case UDPTunnel messages. They're high priority and shouldn't
// go through our synchronous path.
if msg.kind == mumbleproto.MessageUDPTunnel {
client.udp = false
client.udprecv <- msg.buf
} else {
client.server.incoming <- msg
}
}
// The client has responded to our version query. It will try to authenticate.
if client.state == StateClientSentVersion {
// Try to read the next message in the pool
msg, err := client.readProtoMessage()
if err != nil {
if err == io.EOF {
client.Disconnect()
} else {
client.Panicf("%v", err)
}
return
}
client.clientReady = make(chan bool)
go client.server.handleAuthenticate(client, msg)
<-client.clientReady
// It's possible that the client has disconnected in the meantime.
// In that case, step out of the receiver, since there's nothing left
// to receive.
if client.disconnected {
return
}
close(client.clientReady)
client.clientReady = nil
}
// The client has just connected. Before it sends its authentication
// information we must send it our version information so it knows
// what version of the protocol it should speak.
if client.state == StateClientConnected {
version := &mumbleproto.Version{
Version: proto.Uint32(0x10205),
Release: proto.String("Grumble"),
CryptoModes: cryptstate.SupportedModes(),
}
if client.server.cfg.BoolValue("SendOSInfo") {
version.Os = proto.String(runtime.GOOS)
version.OsVersion = proto.String("(Unknown version)")
}
client.sendMessage(version)
client.state = StateServerSentVersion
continue
} else if client.state == StateServerSentVersion {
msg, err := client.readProtoMessage()
if err != nil {
if err == io.EOF {
client.Disconnect()
} else {
client.Panicf("%v", err)
}
return
}
version := &mumbleproto.Version{}
err = proto.Unmarshal(msg.buf, version)
if err != nil {
client.Panicf("%v", err)
return
}
if version.Version != nil {
client.Version = *version.Version
} else {
client.Version = 0x10200
}
if version.Release != nil {
client.ClientName = *version.Release
}
if version.Os != nil {
client.OSName = *version.Os
}
if version.OsVersion != nil {
client.OSVersion = *version.OsVersion
}
// Extract the client's supported crypto mode.
// If the client does not pick a crypto mode
// itself, use an invalid mode (the empty string)
// as its requested mode. This is effectively
// a flag asking for the default crypto mode.
requestedMode := ""
if len(version.CryptoModes) > 0 {
requestedMode = version.CryptoModes[0]
}
// Check if the requested crypto mode is supported
// by us. If not, fall back to the default crypto
// mode.
supportedModes := cryptstate.SupportedModes()
ok := false
for _, mode := range supportedModes {
if requestedMode == mode {
ok = true
break
}
}
if !ok {
requestedMode = "OCB2-AES128"
}
client.CryptoMode = requestedMode
client.state = StateClientSentVersion
}
}
}
func (client *Client) sendChannelList() {
client.sendChannelTree(client.server.RootChannel())
}
func (client *Client) sendChannelTree(channel *Channel) {
chanstate := &mumbleproto.ChannelState{
ChannelId: proto.Uint32(uint32(channel.Id)),
Name: proto.String(channel.Name),
}
if channel.parent != nil {
chanstate.Parent = proto.Uint32(uint32(channel.parent.Id))
}
if channel.HasDescription() {
if client.Version >= 0x10202 {
chanstate.DescriptionHash = channel.DescriptionBlobHashBytes()
} else {
buf, err := blobStore.Get(channel.DescriptionBlob)
if err != nil {
panic("Blobstore error.")
}
chanstate.Description = proto.String(string(buf))
}
}
if channel.IsTemporary() {
chanstate.Temporary = proto.Bool(true)
}
chanstate.Position = proto.Int32(int32(channel.Position))
links := []uint32{}
for cid, _ := range channel.Links {
links = append(links, uint32(cid))
}
chanstate.Links = links
err := client.sendMessage(chanstate)
if err != nil {
client.Panicf("%v", err)
}
for _, subchannel := range channel.children {
client.sendChannelTree(subchannel)
}
}
// Try to do a crypto resync
func (client *Client) cryptResync() {
client.Debugf("requesting crypt resync")
goodElapsed := time.Now().Unix() - client.crypt.LastGoodTime
if goodElapsed > 5 {
requestElapsed := time.Now().Unix() - client.lastResync
if requestElapsed > 5 {
client.lastResync = time.Now().Unix()
cryptsetup := &mumbleproto.CryptSetup{}
err := client.sendMessage(cryptsetup)
if err != nil {
client.Panicf("%v", err)
}
}
}
}

861
cmd/grumble/freeze.go Normal file
View file

@ -0,0 +1,861 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"code.google.com/p/goprotobuf/proto"
"errors"
"io"
"io/ioutil"
"log"
"mumble.info/grumble/pkg/acl"
"mumble.info/grumble/pkg/ban"
"mumble.info/grumble/pkg/freezer"
"mumble.info/grumble/pkg/mumbleproto"
"mumble.info/grumble/pkg/serverconf"
"os"
"path/filepath"
"strconv"
"time"
)
// Freeze a server to disk and closes the log file.
// This must be called from within the Server's synchronous handler.
func (server *Server) FreezeToFile() error {
// See freeeze_{windows,unix}.go for real implementations.
err := server.freezeToFile()
if err != nil {
return err
}
if server.running {
// Re-open the freeze log.
err = server.openFreezeLog()
if err != nil {
return err
}
}
return nil
}
// Open a new freeze log.
func (server *Server) openFreezeLog() error {
if server.freezelog != nil {
err := server.freezelog.Close()
if err != nil {
return err
}
}
logfn := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "log.fz")
err := os.Remove(logfn)
if os.IsNotExist(err) {
// fallthrough
} else if err != nil {
return err
}
server.freezelog, err = freezer.NewLogFile(logfn)
if err != nil {
return err
}
return nil
}
// Freeze a server to a flattened protobuf-based structure ready to
// persist to disk.
func (server *Server) Freeze() (fs *freezer.Server, err error) {
fs = new(freezer.Server)
// Freeze all config kv-pairs
allCfg := server.cfg.GetAll()
for k, v := range allCfg {
fs.Config = append(fs.Config, &freezer.ConfigKeyValuePair{
Key: proto.String(k),
Value: proto.String(v),
})
}
// Freeze all bans
server.banlock.RLock()
fs.BanList = &freezer.BanList{}
fs.BanList.Bans = make([]*freezer.Ban, len(server.Bans))
for i := 0; i < len(server.Bans); i++ {
fs.BanList.Bans[i] = FreezeBan(server.Bans[i])
}
server.banlock.RUnlock()
// Freeze all channels
channels := []*freezer.Channel{}
for _, c := range server.Channels {
fc, err := c.Freeze()
if err != nil {
return nil, err
}
channels = append(channels, fc)
}
fs.Channels = channels
// Freeze all registered users
users := []*freezer.User{}
for _, u := range server.Users {
fu, err := u.Freeze()
if err != nil {
return nil, err
}
users = append(users, fu)
}
fs.Users = users
return fs, nil
}
// Merge the contents of a freezer.BanList into the server's
// ban list.
func (s *Server) UnfreezeBanList(fblist *freezer.BanList) {
s.Bans = nil
for _, fb := range fblist.Bans {
ban := ban.Ban{}
ban.IP = fb.Ip
if fb.Mask != nil {
ban.Mask = int(*fb.Mask)
}
if fb.Username != nil {
ban.Username = *fb.Username
}
if fb.CertHash != nil {
ban.CertHash = *fb.CertHash
}
if fb.Reason != nil {
ban.Reason = *fb.Reason
}
if fb.Start != nil {
ban.Start = *fb.Start
}
if fb.Duration != nil {
ban.Duration = *fb.Duration
}
s.Bans = append(s.Bans, ban)
}
}
// Freeze a ban into a flattened protobuf-based struct
// ready to be persisted to disk.
func FreezeBan(ban ban.Ban) (fb *freezer.Ban) {
fb = new(freezer.Ban)
fb.Ip = ban.IP
fb.Mask = proto.Uint32(uint32(ban.Mask))
fb.Username = proto.String(ban.Username)
fb.CertHash = proto.String(ban.CertHash)
fb.Reason = proto.String(ban.Reason)
fb.Start = proto.Int64(ban.Start)
fb.Duration = proto.Uint32(ban.Duration)
return
}
// Freeze a channel into a flattened protobuf-based struct
// ready to be persisted to disk.
func (channel *Channel) Freeze() (fc *freezer.Channel, err error) {
fc = new(freezer.Channel)
fc.Id = proto.Uint32(uint32(channel.Id))
fc.Name = proto.String(channel.Name)
if channel.parent != nil {
fc.ParentId = proto.Uint32(uint32(channel.parent.Id))
}
fc.Position = proto.Int64(int64(channel.Position))
fc.InheritAcl = proto.Bool(channel.ACL.InheritACL)
// Freeze the channel's ACLs
acls := []*freezer.ACL{}
for _, acl := range channel.ACL.ACLs {
facl, err := FreezeACL(acl)
if err != nil {
return nil, err
}
acls = append(acls, facl)
}
fc.Acl = acls
// Freeze the channel's groups
groups := []*freezer.Group{}
for _, grp := range channel.ACL.Groups {
fgrp, err := FreezeGroup(grp)
if err != nil {
return nil, err
}
groups = append(groups, fgrp)
}
fc.Groups = groups
// Add linked channels
links := []uint32{}
for cid, _ := range channel.Links {
links = append(links, uint32(cid))
}
fc.Links = links
// Blobstore reference to the channel's description.
fc.DescriptionBlob = proto.String(channel.DescriptionBlob)
return
}
// Unfreeze unfreezes the contents of a freezer.Channel
// into a channel.
func (c *Channel) Unfreeze(fc *freezer.Channel) {
if fc.Name != nil {
c.Name = *fc.Name
}
if fc.Position != nil {
c.Position = int(*fc.Position)
}
if fc.InheritAcl != nil {
c.ACL.InheritACL = *fc.InheritAcl
}
if fc.DescriptionBlob != nil {
c.DescriptionBlob = *fc.DescriptionBlob
}
// Update ACLs
if fc.Acl != nil {
c.ACL.ACLs = nil
for _, facl := range fc.Acl {
aclEntry := acl.ACL{}
if facl.ApplyHere != nil {
aclEntry.ApplyHere = *facl.ApplyHere
}
if facl.ApplySubs != nil {
aclEntry.ApplySubs = *facl.ApplySubs
}
if facl.UserId != nil {
aclEntry.UserId = int(*facl.UserId)
} else {
aclEntry.UserId = -1
}
if facl.Group != nil {
aclEntry.Group = *facl.Group
}
if facl.Deny != nil {
aclEntry.Deny = acl.Permission(*facl.Deny)
}
if facl.Allow != nil {
aclEntry.Allow = acl.Permission(*facl.Allow)
}
c.ACL.ACLs = append(c.ACL.ACLs, aclEntry)
}
}
// Update groups
if fc.Groups != nil {
c.ACL.Groups = make(map[string]acl.Group)
for _, fgrp := range fc.Groups {
if fgrp.Name == nil {
continue
}
g := acl.Group{}
if fgrp.Inherit != nil {
g.Inherit = *fgrp.Inherit
}
if fgrp.Inheritable != nil {
g.Inheritable = *fgrp.Inheritable
}
for _, uid := range fgrp.Add {
g.Add[int(uid)] = true
}
for _, uid := range fgrp.Remove {
g.Remove[int(uid)] = true
}
c.ACL.Groups[g.Name] = g
}
}
// Hook up links, but make them point to the channel itself.
// We can't be sure that the channels the links point to exist
// yet, so we delay hooking up the map 'correctly' to later.
if fc.Links != nil {
c.Links = make(map[int]*Channel)
for _, link := range fc.Links {
c.Links[int(link)] = c
}
}
}
// Freeze a User into a flattened protobuf-based structure
// ready to be persisted to disk.
func (user *User) Freeze() (fu *freezer.User, err error) {
fu = new(freezer.User)
fu.Id = proto.Uint32(user.Id)
fu.Name = proto.String(user.Name)
fu.CertHash = proto.String(user.CertHash)
fu.Email = proto.String(user.Email)
fu.TextureBlob = proto.String(user.TextureBlob)
fu.CommentBlob = proto.String(user.CommentBlob)
fu.LastChannelId = proto.Uint32(uint32(user.LastChannelId))
fu.LastActive = proto.Uint64(user.LastActive)
return
}
// Merge the contents of a frozen User into an existing user struct.
func (u *User) Unfreeze(fu *freezer.User) {
if fu.Name != nil {
u.Name = *fu.Name
}
if fu.CertHash != nil {
u.CertHash = *fu.CertHash
}
if fu.Email != nil {
u.Email = *fu.Email
}
if fu.TextureBlob != nil {
u.TextureBlob = *fu.TextureBlob
}
if fu.CommentBlob != nil {
u.CommentBlob = *fu.CommentBlob
}
if fu.LastChannelId != nil {
u.LastChannelId = int(*fu.LastChannelId)
}
if fu.LastActive != nil {
u.LastActive = *fu.LastActive
}
}
// Freeze a ChannelACL into it a flattened protobuf-based structure
// ready to be persisted to disk.
func FreezeACL(aclEntry acl.ACL) (*freezer.ACL, error) {
frozenAcl := &freezer.ACL{}
if aclEntry.UserId != -1 {
frozenAcl.UserId = proto.Uint32(uint32(aclEntry.UserId))
} else {
frozenAcl.Group = proto.String(aclEntry.Group)
}
frozenAcl.ApplyHere = proto.Bool(aclEntry.ApplyHere)
frozenAcl.ApplySubs = proto.Bool(aclEntry.ApplySubs)
frozenAcl.Allow = proto.Uint32(uint32(aclEntry.Allow))
frozenAcl.Deny = proto.Uint32(uint32(aclEntry.Deny))
return frozenAcl, nil
}
// Freeze a Group into a flattened protobuf-based structure
// ready to be persisted to disk.
func FreezeGroup(group acl.Group) (*freezer.Group, error) {
frozenGroup := &freezer.Group{}
frozenGroup.Name = proto.String(group.Name)
frozenGroup.Inherit = proto.Bool(group.Inherit)
frozenGroup.Inheritable = proto.Bool(group.Inheritable)
for _, id := range group.AddUsers() {
frozenGroup.Add = append(frozenGroup.Add, uint32(id))
}
for _, id := range group.RemoveUsers() {
frozenGroup.Remove = append(frozenGroup.Remove, uint32(id))
}
return frozenGroup, nil
}
// Create a new server from its on-disk representation.
//
// This will read a full serialized server (typically stored in
// a file called 'main.fz') from disk. It will also check for
// a log file ('log.fz') and iterate through the entries of the log
// file and apply the updates incrementally to the server.
//
// Once both the full server and the log file has been merged together
// in memory, a new full seralized server will be written and synced to
// disk, and the existing log file will be removed.
func NewServerFromFrozen(name string) (s *Server, err error) {
id, err := strconv.ParseInt(name, 10, 64)
if err != nil {
return nil, err
}
path := filepath.Join(Args.DataDir, "servers", name)
mainFile := filepath.Join(path, "main.fz")
backupFile := filepath.Join(path, "backup.fz")
logFn := filepath.Join(path, "log.fz")
r, err := os.Open(mainFile)
if os.IsNotExist(err) {
err = os.Rename(backupFile, mainFile)
if err != nil {
return nil, err
}
r, err = os.Open(mainFile)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
defer r.Close()
buf, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
// Unmarshal the server from it's frozen state
fs := freezer.Server{}
err = proto.Unmarshal(buf, &fs)
if err != nil {
return nil, err
}
// Create a config map from the frozen server.
cfgMap := map[string]string{}
for _, cfgEntry := range fs.Config {
if cfgEntry.Key != nil && cfgEntry.Value != nil {
cfgMap[*cfgEntry.Key] = *cfgEntry.Value
}
}
s, err = NewServer(id)
if err != nil {
return nil, err
}
s.cfg = serverconf.New(cfgMap)
// Unfreeze the server's frozen bans.
s.UnfreezeBanList(fs.BanList)
// Add all channels, but don't hook up parent/child relationships
// until after we've walked the log file. No need to make it harder
// than it really is.
parents := make(map[uint32]uint32)
for _, fc := range fs.Channels {
// The frozen channel must contain an Id and a Name,
// since the server's frozen channels are guaranteed to
// not be deltas.
if fc.Id == nil || fc.Name == nil {
continue
}
// Create the channel on the server.
// Update the server's nextChanId field if it needs to be,
// to make sure the server doesn't re-use channel id's.
c := NewChannel(int(*fc.Id), *fc.Name)
if c.Id >= s.nextChanId {
s.nextChanId = c.Id + 1
}
// Update the channel with the contents of the freezer.Channel.
c.Unfreeze(fc)
// Add the channel's id to the server's channel-id-map.
s.Channels[c.Id] = c
// Mark the channel's parent
if fc.ParentId != nil {
parents[*fc.Id] = *fc.ParentId
} else {
delete(parents, *fc.Id)
}
}
// Add all users
for _, fu := range fs.Users {
if fu.Id == nil && fu.Name == nil {
continue
}
u, err := NewUser(*fu.Id, *fu.Name)
if err != nil {
return nil, err
}
if u.Id >= s.nextUserId {
s.nextUserId = u.Id + 1
}
// Merge the contents of the freezer.User into
// the user struct.
u.Unfreeze(fu)
// Update the server's user maps to point correctly
// to the new user.
s.Users[u.Id] = u
s.UserNameMap[u.Name] = u
if len(u.CertHash) > 0 {
s.UserCertMap[u.CertHash] = u
}
}
// Attempt to walk the stored log file
logFile, err := os.Open(logFn)
walker, err := freezer.NewReaderWalker(logFile)
if err != nil {
return nil, err
}
for {
values, err := walker.Next()
if err == io.EOF {
err = logFile.Close()
if err != nil {
return nil, err
}
break
} else if err != nil {
return nil, err
}
for _, val := range values {
switch val.(type) {
case *freezer.User:
fu := val.(*freezer.User)
// Check if it's a valid freezer.User message. It must at least
// have the Id field filled out for us to be able to do anything
// with it. Warn the admin if an illegal entry is encountered.
if fu.Id == nil {
log.Printf("Skipped User log entry: No id given.")
continue
}
userId := *fu.Id
// Determine whether the user already exists on the server or not.
// If the user already exists, this log entry simply updates the
// data for that user.
// If the user doesn't exist, we create it with the data given in
// this log entry.
user, ok := s.Users[userId]
if !ok {
// If no name is given in the log entry, skip this entry.
// Also, warn the admin.
if fu.Name == nil {
log.Printf("Skipped User creation log entry: No name given.")
continue
}
// Create the new user and increment the UserId
// counter for the server if needed.
user, err = NewUser(userId, *fu.Name)
if err != nil {
return nil, err
}
if user.Id >= s.nextUserId {
s.nextUserId = user.Id + 1
}
}
// Merge the contents of the frozen.User into the
// user struct.
user.Unfreeze(fu)
// Update the various user maps in the server to
// be able to correctly look up the user.
s.Users[user.Id] = user
s.UserNameMap[user.Name] = user
if len(user.CertHash) > 0 {
s.UserCertMap[user.CertHash] = user
}
case *freezer.UserRemove:
fu := val.(*freezer.UserRemove)
// Check for an invalid message and warn if appropriate.
if fu.Id == nil {
log.Printf("Skipped UserRemove log entry: No id given.")
continue
}
userId := *fu.Id
// Does this user even exist?
// Warn if we encounter an illegal delete op.
user, ok := s.Users[userId]
if ok {
// Clear the server maps. That should do it.
delete(s.Users, userId)
delete(s.UserNameMap, user.Name)
if len(user.CertHash) > 0 {
delete(s.UserCertMap, user.CertHash)
}
} else {
log.Printf("Skipped UserRemove log entry: No user for given id.")
continue
}
case *freezer.Channel:
fc := val.(*freezer.Channel)
// Check whether the log entry is legal.
if fc.Id == nil {
log.Printf("Skipped Channel log entry: No id given.")
continue
}
channelId := int(*fc.Id)
channel, alreadyExists := s.Channels[channelId]
if !alreadyExists {
if fc.Name == nil {
log.Printf("Skipped Channel creation log entry: No name given.")
continue
}
// Add the channel and increment the server's
// nextChanId field to a consistent state.
channel = NewChannel(channelId, *fc.Name)
if channel.Id >= s.nextChanId {
s.nextChanId = channel.Id + 1
}
}
// Unfreeze the contents of the frozen channel
// into the existing or newly-created channel.
channel.Unfreeze(fc)
// Re-add it to the server's channel map (in case
// the channel was newly-created)
s.Channels[channelId] = channel
// Mark the channel's parent
if !alreadyExists {
if fc.ParentId != nil {
parents[*fc.Id] = *fc.ParentId
} else {
delete(parents, *fc.Id)
}
}
case *freezer.ChannelRemove:
fc := val.(*freezer.ChannelRemove)
if fc.Id == nil {
log.Printf("Skipped ChannelRemove log entry: No id given.")
continue
}
s.Channels[int(*fc.Id)] = nil
delete(parents, *fc.Id)
case *freezer.BanList:
fbl := val.(*freezer.BanList)
s.UnfreezeBanList(fbl)
case *freezer.ConfigKeyValuePair:
fcfg := val.(*freezer.ConfigKeyValuePair)
if fcfg.Key != nil {
// It's an update operation
if fcfg.Value != nil {
s.cfg.Set(*fcfg.Key, *fcfg.Value)
// It's a delete/reset operation.
} else {
s.cfg.Reset(*fcfg.Key)
}
}
}
}
}
// Hook up children with their parents
for chanId, parentId := range parents {
childChan, exists := s.Channels[int(chanId)]
if !exists {
return nil, errors.New("Non-existant child channel")
}
parentChan, exists := s.Channels[int(parentId)]
if !exists {
return nil, errors.New("Non-existant parent channel")
}
parentChan.AddChild(childChan)
}
// Hook up all channel links
for _, channel := range s.Channels {
if len(channel.Links) > 0 {
links := channel.Links
channel.Links = make(map[int]*Channel)
for chanId, _ := range links {
targetChannel := s.Channels[chanId]
if targetChannel != nil {
s.LinkChannels(channel, targetChannel)
}
}
}
}
return s, nil
}
// Update the datastore with the user's current state.
func (server *Server) UpdateFrozenUser(client *Client, state *mumbleproto.UserState) {
// Full sync If there's no userstate messgae provided, or if there is one, and
// it includes a registration operation.
user := client.user
nanos := time.Now().Unix()
if state == nil || state.UserId != nil {
fu, err := user.Freeze()
if err != nil {
server.Fatal(err)
}
fu.LastActive = proto.Uint64(uint64(nanos))
err = server.freezelog.Put(fu)
if err != nil {
server.Fatal(err)
}
} else {
fu := &freezer.User{}
fu.Id = proto.Uint32(user.Id)
if state.ChannelId != nil {
fu.LastChannelId = proto.Uint32(uint32(client.Channel.Id))
}
if state.TextureHash != nil {
fu.TextureBlob = proto.String(user.TextureBlob)
}
if state.CommentHash != nil {
fu.CommentBlob = proto.String(user.CommentBlob)
}
fu.LastActive = proto.Uint64(uint64(nanos))
err := server.freezelog.Put(fu)
if err != nil {
server.Fatal(err)
}
}
server.numLogOps += 1
}
// Update a user's last active channel
func (server *Server) UpdateFrozenUserLastChannel(client *Client) {
if client.IsRegistered() {
user := client.user
fu := &freezer.User{}
fu.Id = proto.Uint32(user.Id)
fu.LastChannelId = proto.Uint32(uint32(client.Channel.Id))
fu.LastActive = proto.Uint64(uint64(time.Now().Unix()))
err := server.freezelog.Put(fu)
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
}
// Mark a user as deleted in the datstore.
func (server *Server) DeleteFrozenUser(user *User) {
err := server.freezelog.Put(&freezer.UserRemove{Id: proto.Uint32(user.Id)})
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
// Given a target channel and a ChannelState protocol message, create a freezer.Channel that
// only includes the values changed by the given ChannelState message. When done, write that
// frozen.Channel to the datastore.
func (server *Server) UpdateFrozenChannel(channel *Channel, state *mumbleproto.ChannelState) {
fc := &freezer.Channel{}
fc.Id = proto.Uint32(uint32(channel.Id))
if state.Name != nil {
fc.Name = state.Name
}
if state.Parent != nil {
fc.ParentId = state.Parent
}
if len(state.LinksAdd) > 0 || len(state.LinksRemove) > 0 {
links := []uint32{}
for cid, _ := range channel.Links {
links = append(links, uint32(cid))
}
fc.Links = links
}
if state.Position != nil {
fc.Position = proto.Int64(int64(*state.Position))
}
if len(state.DescriptionHash) > 0 {
fc.DescriptionBlob = proto.String(channel.DescriptionBlob)
}
err := server.freezelog.Put(fc)
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
// Write a channel's ACL and Group data to disk. Mumble doesn't support
// incremental ACL updates and as such we must write all ACLs and groups
// to the datastore on each change.
func (server *Server) UpdateFrozenChannelACLs(channel *Channel) {
fc := &freezer.Channel{}
fc.Id = proto.Uint32(uint32(channel.Id))
fc.InheritAcl = proto.Bool(channel.ACL.InheritACL)
acls := []*freezer.ACL{}
for _, aclEntry := range channel.ACL.ACLs {
facl, err := FreezeACL(aclEntry)
if err != nil {
return
}
acls = append(acls, facl)
}
fc.Acl = acls
groups := []*freezer.Group{}
for _, grp := range channel.ACL.Groups {
fgrp, err := FreezeGroup(grp)
if err != nil {
return
}
groups = append(groups, fgrp)
}
fc.Groups = groups
err := server.freezelog.Put(fc)
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
// Mark a channel as deleted in the datastore.
func (server *Server) DeleteFrozenChannel(channel *Channel) {
err := server.freezelog.Put(&freezer.ChannelRemove{Id: proto.Uint32(uint32(channel.Id))})
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
// Write the server's banlist to the datastore.
func (server *Server) UpdateFrozenBans(bans []ban.Ban) {
fbl := &freezer.BanList{}
for _, ban := range server.Bans {
fbl.Bans = append(fbl.Bans, FreezeBan(ban))
}
err := server.freezelog.Put(fbl)
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
// Write an updated config value to the datastore.
func (server *Server) UpdateConfig(key, value string) {
fcfg := &freezer.ConfigKeyValuePair{
Key: proto.String(key),
Value: proto.String(value),
}
err := server.freezelog.Put(fcfg)
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}
// Write to the freezelog that the config with key
// has been reset to its default value.
func (server *Server) ResetConfig(key string) {
fcfg := &freezer.ConfigKeyValuePair{
Key: proto.String(key),
}
err := server.freezelog.Put(fcfg)
if err != nil {
server.Fatal(err)
}
server.numLogOps += 1
}

View file

@ -0,0 +1,57 @@
// Copyright (c) 2012 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
// +build !windows
package main
import (
"code.google.com/p/goprotobuf/proto"
"io/ioutil"
"os"
"path/filepath"
"strconv"
)
func (server *Server) freezeToFile() (err error) {
// Close the log file, if it's open
if server.freezelog != nil {
err = server.freezelog.Close()
if err != nil {
return err
}
}
// Make sure the whole server is synced to disk
fs, err := server.Freeze()
if err != nil {
return err
}
f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_")
if err != nil {
return err
}
buf, err := proto.Marshal(fs)
if err != nil {
return err
}
_, err = f.Write(buf)
if err != nil {
return err
}
err = f.Sync()
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
err = os.Rename(f.Name(), filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz"))
if err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,59 @@
// Copyright (c) 2012 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"code.google.com/p/goprotobuf/proto"
"mumble.info/grumble/pkg/replacefile"
"io/ioutil"
"path/filepath"
"strconv"
)
func (server *Server) freezeToFile() (err error) {
// Close the log file, if it's open
if server.freezelog != nil {
err = server.freezelog.Close()
if err != nil {
return err
}
}
// Make sure the whole server is synced to disk
fs, err := server.Freeze()
if err != nil {
return err
}
f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_")
if err != nil {
return err
}
buf, err := proto.Marshal(fs)
if err != nil {
return err
}
_, err = f.Write(buf)
if err != nil {
return err
}
err = f.Sync()
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
src := f.Name()
dst := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz")
backup := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "backup.fz")
err = replacefile.ReplaceFile(dst, src, backup, replacefile.Flag(0))
if err != nil {
return err
}
return nil
}

82
cmd/grumble/gencert.go Normal file
View file

@ -0,0 +1,82 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"os"
"path/filepath"
"time"
)
// Generate a 4096-bit RSA keypair and a Grumble auto-generated X509
// certificate. Output PEM-encoded DER representations of the resulting
// certificate and private key to certpath and keypath.
func GenerateSelfSignedCert(certpath, keypath string) (err error) {
now := time.Now()
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: "Grumble Autogenerated Certificate",
},
NotBefore: now.Add(-300 * time.Second),
// Valid for 1 year.
NotAfter: now.Add(24 * time.Hour * 365),
SubjectKeyId: []byte{1, 2, 3, 4},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
}
priv, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return err
}
certbuf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
if err != nil {
log.Printf("Error: %v", err)
return err
}
certblk := pem.Block{
Type: "CERTIFICATE",
Bytes: certbuf,
}
keybuf := x509.MarshalPKCS1PrivateKey(priv)
keyblk := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: keybuf,
}
certfn := filepath.Join(Args.DataDir, "cert.pem")
file, err := os.OpenFile(certfn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700)
if err != nil {
return err
}
defer file.Close()
err = pem.Encode(file, &certblk)
if err != nil {
return err
}
keyfn := filepath.Join(Args.DataDir, "key.pem")
file, err = os.OpenFile(keyfn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0700)
if err != nil {
return err
}
defer file.Close()
err = pem.Encode(file, &keyblk)
if err != nil {
return err
}
return nil
}

220
cmd/grumble/grumble.go Normal file
View file

@ -0,0 +1,220 @@
// Copyright (c) 2010 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"flag"
"fmt"
"log"
"mumble.info/grumble/pkg/blobstore"
"mumble.info/grumble/pkg/logtarget"
"os"
"path/filepath"
"regexp"
)
var servers map[int64]*Server
var blobStore blobstore.BlobStore
func main() {
var err error
flag.Parse()
if Args.ShowHelp == true {
Usage()
return
}
// Open the data dir to check whether it exists.
dataDir, err := os.Open(Args.DataDir)
if err != nil {
log.Fatalf("Unable to open data directory: %v", err)
return
}
dataDir.Close()
// Set up logging
err = logtarget.Target.OpenFile(Args.LogPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open log file: %v", err)
return
}
log.SetPrefix("[G] ")
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.SetOutput(&logtarget.Target)
log.Printf("Grumble")
log.Printf("Using data directory: %s", Args.DataDir)
// Open the blobstore. If the directory doesn't
// already exist, create the directory and open
// the blobstore.
// The Open method of the blobstore performs simple
// sanity checking of content of the blob directory,
// and will return an error if something's amiss.
blobDir := filepath.Join(Args.DataDir, "blob")
err = os.Mkdir(blobDir, 0700)
if err != nil && !os.IsExist(err) {
log.Fatalf("Unable to create blob directory: %v", err)
}
blobStore = blobstore.Open(blobDir)
// Check whether we should regenerate the default global keypair
// and corresponding certificate.
// These are used as the default certificate of all virtual servers
// and the SSH admin console, but can be overridden using the "key"
// and "cert" arguments to Grumble.
certFn := filepath.Join(Args.DataDir, "cert.pem")
keyFn := filepath.Join(Args.DataDir, "key.pem")
shouldRegen := false
if Args.RegenKeys {
shouldRegen = true
} else {
// OK. Here's the idea: We check for the existence of the cert.pem
// and key.pem files in the data directory on launch. Although these
// might be deleted later (and this check could be deemed useless),
// it's simply here to be convenient for admins.
hasKey := true
hasCert := true
_, err = os.Stat(certFn)
if err != nil && os.IsNotExist(err) {
hasCert = false
}
_, err = os.Stat(keyFn)
if err != nil && os.IsNotExist(err) {
hasKey = false
}
if !hasCert && !hasKey {
shouldRegen = true
} else if !hasCert || !hasKey {
if !hasCert {
log.Fatal("Grumble could not find its default certificate (cert.pem)")
}
if !hasKey {
log.Fatal("Grumble could not find its default private key (key.pem)")
}
}
}
if shouldRegen {
log.Printf("Generating 4096-bit RSA keypair for self-signed certificate...")
err := GenerateSelfSignedCert(certFn, keyFn)
if err != nil {
log.Printf("Error: %v", err)
return
}
log.Printf("Certificate output to %v", certFn)
log.Printf("Private key output to %v", keyFn)
}
// Should we import data from a Murmur SQLite file?
if SQLiteSupport && len(Args.SQLiteDB) > 0 {
f, err := os.Open(Args.DataDir)
if err != nil {
log.Fatalf("Murmur import failed: %s", err.Error())
}
defer f.Close()
names, err := f.Readdirnames(-1)
if err != nil {
log.Fatalf("Murmur import failed: %s", err.Error())
}
if !Args.CleanUp && len(names) > 0 {
log.Fatalf("Non-empty datadir. Refusing to import Murmur data.")
}
if Args.CleanUp {
log.Print("Cleaning up existing data directory")
for _, name := range names {
if err := os.RemoveAll(filepath.Join(Args.DataDir, name)); err != nil {
log.Fatalf("Unable to cleanup file: %s", name)
}
}
}
log.Printf("Importing Murmur data from '%s'", Args.SQLiteDB)
if err = MurmurImport(Args.SQLiteDB); err != nil {
log.Fatalf("Murmur import failed: %s", err.Error())
}
log.Printf("Import from Murmur SQLite database succeeded.")
log.Printf("Please restart Grumble to make use of the imported data.")
return
}
// Create the servers directory if it doesn't already
// exist.
serversDirPath := filepath.Join(Args.DataDir, "servers")
err = os.Mkdir(serversDirPath, 0700)
if err != nil && !os.IsExist(err) {
log.Fatalf("Unable to create servers directory: %v", err)
}
// Read all entries of the servers directory.
// We need these to load our virtual servers.
serversDir, err := os.Open(serversDirPath)
if err != nil {
log.Fatalf("Unable to open the servers directory: %v", err.Error())
}
names, err := serversDir.Readdirnames(-1)
if err != nil {
log.Fatalf("Unable to read file from data directory: %v", err.Error())
}
// The data dir file descriptor.
err = serversDir.Close()
if err != nil {
log.Fatalf("Unable to close data directory: %v", err.Error())
return
}
// Look through the list of files in the data directory, and
// load all virtual servers from disk.
servers = make(map[int64]*Server)
for _, name := range names {
if matched, _ := regexp.MatchString("^[0-9]+$", name); matched {
log.Printf("Loading server %v", name)
s, err := NewServerFromFrozen(name)
if err != nil {
log.Fatalf("Unable to load server: %v", err.Error())
}
err = s.FreezeToFile()
if err != nil {
log.Fatalf("Unable to freeze server to disk: %v", err.Error())
}
servers[s.Id] = s
}
}
// If no servers were found, create the default virtual server.
if len(servers) == 0 {
s, err := NewServer(1)
if err != nil {
log.Fatalf("Couldn't start server: %s", err.Error())
}
servers[s.Id] = s
os.Mkdir(filepath.Join(serversDirPath, fmt.Sprintf("%v", 1)), 0750)
err = s.FreezeToFile()
if err != nil {
log.Fatalf("Unable to freeze newly created server to disk: %v", err.Error())
}
}
// Launch the servers we found during launch...
for _, server := range servers {
err = server.Start()
if err != nil {
log.Printf("Unable to start server %v: %v", server.Id, err.Error())
}
}
// If any servers were loaded, launch the signal
// handler goroutine and sleep...
if len(servers) > 0 {
go SignalHandler()
select {}
}
}

1609
cmd/grumble/message.go Normal file

File diff suppressed because it is too large Load diff

529
cmd/grumble/murmurdb.go Normal file
View file

@ -0,0 +1,529 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
// This file implements a Server that can be created from a Murmur SQLite file.
// This is read-only, so it's not generally useful. It's meant as a convenient
// way to import a Murmur server into Grumble, to be able to dump the structure of the
// SQLite datbase into a format that Grumble can understand.
import (
"database/sql"
"errors"
"log"
"mumble.info/grumble/pkg/acl"
"mumble.info/grumble/pkg/ban"
"net"
"os"
"path/filepath"
"strconv"
)
const (
ChannelInfoDescription int = iota
ChannelInfoPosition
)
const (
UserInfoName int = iota
UserInfoEmail
UserInfoComment
UserInfoHash
UserInfoPassword
UserInfoLastActive
)
const SQLiteSupport = true
// Import the structure of an existing Murmur SQLite database.
func MurmurImport(filename string) (err error) {
db, err := sql.Open("sqlite", filename)
if err != nil {
panic(err.Error())
}
rows, err := db.Query("SELECT server_id FROM servers")
if err != nil {
panic(err.Error())
}
var serverids []int64
var sid int64
for rows.Next() {
err = rows.Scan(&sid)
if err != nil {
return err
}
serverids = append(serverids, sid)
}
log.Printf("Found servers: %v (%v servers)", serverids, len(serverids))
for _, sid := range serverids {
m, err := NewServerFromSQLite(sid, db)
if err != nil {
return err
}
err = os.Mkdir(filepath.Join(Args.DataDir, strconv.FormatInt(sid, 10)), 0750)
if err != nil {
return err
}
err = m.FreezeToFile()
if err != nil {
return err
}
log.Printf("Successfully imported server %v", sid)
}
return
}
// Create a new Server from a Murmur SQLite database
func NewServerFromSQLite(id int64, db *sql.DB) (s *Server, err error) {
s, err = NewServer(id)
if err != nil {
return nil, err
}
err = populateChannelInfoFromDatabase(s, s.RootChannel(), db)
if err != nil {
return nil, err
}
err = populateChannelACLFromDatabase(s, s.RootChannel(), db)
if err != nil {
return nil, err
}
err = populateChannelGroupsFromDatabase(s, s.RootChannel(), db)
if err != nil {
return nil, err
}
err = populateChannelsFromDatabase(s, db, 0)
if err != nil {
return nil, err
}
err = populateChannelLinkInfo(s, db)
if err != nil {
return nil, err
}
err = populateUsers(s, db)
if err != nil {
return nil, err
}
err = populateBans(s, db)
if err != nil {
return nil, err
}
return
}
// Add channel metadata (channel_info table from SQLite) by reading the SQLite database.
func populateChannelInfoFromDatabase(server *Server, c *Channel, db *sql.DB) error {
stmt, err := db.Prepare("SELECT value FROM channel_info WHERE server_id=? AND channel_id=? AND key=?")
if err != nil {
return err
}
// Fetch description
rows, err := stmt.Query(server.Id, c.Id, ChannelInfoDescription)
if err != nil {
return err
}
for rows.Next() {
var description string
err = rows.Scan(&description)
if err != nil {
return err
}
if len(description) > 0 {
key, err := blobStore.Put([]byte(description))
if err != nil {
return err
}
c.DescriptionBlob = key
}
}
// Fetch position
rows, err = stmt.Query(server.Id, c.Id, ChannelInfoPosition)
if err != nil {
return err
}
for rows.Next() {
var pos int
if err := rows.Scan(&pos); err != nil {
return err
}
c.Position = pos
}
return nil
}
// Populate channel with its ACLs by reading the SQLite databse.
func populateChannelACLFromDatabase(server *Server, c *Channel, db *sql.DB) error {
stmt, err := db.Prepare("SELECT user_id, group_name, apply_here, apply_sub, grantpriv, revokepriv FROM acl WHERE server_id=? AND channel_id=? ORDER BY priority")
if err != nil {
return err
}
rows, err := stmt.Query(server.Id, c.Id)
if err != nil {
return err
}
for rows.Next() {
var (
UserId string
Group string
ApplyHere bool
ApplySub bool
Allow int64
Deny int64
)
if err := rows.Scan(&UserId, &Group, &ApplyHere, &ApplySub, &Allow, &Deny); err != nil {
return err
}
aclEntry := acl.ACL{}
aclEntry.ApplyHere = ApplyHere
aclEntry.ApplySubs = ApplySub
if len(UserId) > 0 {
aclEntry.UserId, err = strconv.Atoi(UserId)
if err != nil {
return err
}
} else if len(Group) > 0 {
aclEntry.Group = Group
} else {
return errors.New("Invalid ACL: Neither Group or UserId specified")
}
aclEntry.Deny = acl.Permission(Deny)
aclEntry.Allow = acl.Permission(Allow)
c.ACL.ACLs = append(c.ACL.ACLs, aclEntry)
}
return nil
}
// Populate channel with groups by reading the SQLite database.
func populateChannelGroupsFromDatabase(server *Server, c *Channel, db *sql.DB) error {
stmt, err := db.Prepare("SELECT group_id, name, inherit, inheritable FROM groups WHERE server_id=? AND channel_id=?")
if err != nil {
return err
}
rows, err := stmt.Query(server.Id, c.Id)
if err != nil {
return err
}
groups := make(map[int64]acl.Group)
for rows.Next() {
var (
GroupId int64
Name string
Inherit bool
Inheritable bool
)
if err := rows.Scan(&GroupId, &Name, &Inherit, &Inheritable); err != nil {
return err
}
g := acl.EmptyGroupWithName(Name)
g.Inherit = Inherit
g.Inheritable = Inheritable
c.ACL.Groups[g.Name] = g
groups[GroupId] = g
}
stmt, err = db.Prepare("SELECT user_id, addit FROM group_members WHERE server_id=? AND group_id=?")
if err != nil {
return err
}
for gid, grp := range groups {
rows, err = stmt.Query(server.Id, gid)
if err != nil {
return err
}
for rows.Next() {
var (
UserId int64
Add bool
)
if err := rows.Scan(&UserId, &Add); err != nil {
return err
}
if Add {
grp.Add[int(UserId)] = true
} else {
grp.Remove[int(UserId)] = true
}
}
}
return nil
}
// Populate the Server with Channels from the database.
func populateChannelsFromDatabase(server *Server, db *sql.DB, parentId int) error {
parent, exists := server.Channels[parentId]
if !exists {
return errors.New("Non-existant parent")
}
stmt, err := db.Prepare("SELECT channel_id, name, inheritacl FROM channels WHERE server_id=? AND parent_id=?")
if err != nil {
return err
}
rows, err := stmt.Query(server.Id, parentId)
if err != nil {
return err
}
for rows.Next() {
var (
name string
chanid int
inherit bool
)
err = rows.Scan(&chanid, &name, &inherit)
if err != nil {
return err
}
c := NewChannel(chanid, name)
server.Channels[c.Id] = c
c.ACL.InheritACL = inherit
parent.AddChild(c)
}
// Add channel_info
for _, c := range parent.children {
err = populateChannelInfoFromDatabase(server, c, db)
if err != nil {
return err
}
}
// Add ACLs
for _, c := range parent.children {
err = populateChannelACLFromDatabase(server, c, db)
if err != nil {
return err
}
}
// Add groups
for _, c := range parent.children {
err = populateChannelGroupsFromDatabase(server, c, db)
if err != nil {
return err
}
}
// Add subchannels
for id, _ := range parent.children {
err = populateChannelsFromDatabase(server, db, id)
if err != nil {
return err
}
}
return nil
}
// Link a Server's channels together
func populateChannelLinkInfo(server *Server, db *sql.DB) (err error) {
stmt, err := db.Prepare("SELECT channel_id, link_id FROM channel_links WHERE server_id=?")
if err != nil {
return err
}
rows, err := stmt.Query(server.Id)
if err != nil {
return err
}
for rows.Next() {
var (
ChannelId int
LinkId int
)
if err := rows.Scan(&ChannelId, &LinkId); err != nil {
return err
}
channel, exists := server.Channels[ChannelId]
if !exists {
return errors.New("Attempt to perform link operation on non-existant channel.")
}
other, exists := server.Channels[LinkId]
if !exists {
return errors.New("Attempt to perform link operation on non-existant channel.")
}
server.LinkChannels(channel, other)
}
return nil
}
func populateUsers(server *Server, db *sql.DB) (err error) {
// Populate the server with regular user data
stmt, err := db.Prepare("SELECT user_id, name, pw, lastchannel, texture, strftime('%s', last_active) FROM users WHERE server_id=?")
if err != nil {
return
}
rows, err := stmt.Query(server.Id)
if err != nil {
return
}
for rows.Next() {
var (
UserId int64
UserName string
SHA1Password string
LastChannel int
Texture []byte
LastActive int64
)
err = rows.Scan(&UserId, &UserName, &SHA1Password, &LastChannel, &Texture, &LastActive)
if err != nil {
continue
}
if UserId == 0 {
server.cfg.Set("SuperUserPassword", "sha1$$"+SHA1Password)
}
user, err := NewUser(uint32(UserId), UserName)
if err != nil {
return err
}
if len(Texture) > 0 {
key, err := blobStore.Put(Texture)
if err != nil {
return err
}
user.TextureBlob = key
}
user.LastActive = uint64(LastActive)
user.LastChannelId = LastChannel
server.Users[user.Id] = user
}
stmt, err = db.Prepare("SELECT key, value FROM user_info WHERE server_id=? AND user_id=?")
if err != nil {
return
}
// Populate users with any new-style UserInfo records
for uid, user := range server.Users {
rows, err = stmt.Query(server.Id, uid)
if err != nil {
return err
}
for rows.Next() {
var (
Key int
Value string
)
err = rows.Scan(&Key, &Value)
if err != nil {
return err
}
switch Key {
case UserInfoEmail:
user.Email = Value
case UserInfoComment:
key, err := blobStore.Put([]byte(Value))
if err != nil {
return err
}
user.CommentBlob = key
case UserInfoHash:
user.CertHash = Value
case UserInfoLastActive:
// not a kv-pair (trigger)
case UserInfoPassword:
// not a kv-pair
case UserInfoName:
// not a kv-pair
}
}
}
return
}
// Populate bans
func populateBans(server *Server, db *sql.DB) (err error) {
stmt, err := db.Prepare("SELECT base, mask, name, hash, reason, start, duration FROM bans WHERE server_id=?")
if err != nil {
return
}
rows, err := stmt.Query(server.Id)
if err != nil {
return err
}
for rows.Next() {
var (
Ban ban.Ban
IP []byte
StartDate string
Duration int64
)
err = rows.Scan(&IP, &Ban.Mask, &Ban.Username, &Ban.CertHash, &Ban.Reason, &StartDate, &Duration)
if err != nil {
return err
}
if len(IP) == 16 && IP[10] == 0xff && IP[11] == 0xff {
Ban.IP = net.IPv4(IP[12], IP[13], IP[14], IP[15])
} else {
Ban.IP = IP
}
Ban.SetISOStartDate(StartDate)
Ban.Duration = uint32(Duration)
server.Bans = append(server.Bans, Ban)
}
return
}

126
cmd/grumble/register.go Normal file
View file

@ -0,0 +1,126 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
// This file handles public server list registration
import (
"bytes"
"crypto/sha1"
"crypto/tls"
"encoding/hex"
"encoding/xml"
"io/ioutil"
"net/http"
)
type Register struct {
XMLName xml.Name `xml:"server"`
Version string `xml:"version"`
Release string `xml:"release"`
Name string `xml:"name"`
Host string `xml:"host"`
Password string `xml:"password"`
Port int `xml:"port"`
Url string `xml:"url"`
Digest string `xml:"digest"`
Users int `xml:"users"`
Channels int `xml:"channels"`
Location string `xml:"location"`
}
const registerUrl = "https://mumble.hive.no/register.cgi"
// Determines whether a server is public by checking whether the
// config values required for public registration are set.
//
// 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 {
return false
}
if len(server.cfg.StringValue("RegisterHost")) == 0 {
return false
}
if len(server.cfg.StringValue("RegisterPassword")) == 0 {
return false
}
if len(server.cfg.StringValue("RegisterWebUrl")) == 0 {
return false
}
return true
}
// Perform a public server registration update.
//
// When a Mumble server connects to the master server
// for registration, it connects using its server certificate
// as a client certificate for authentication purposes.
func (server *Server) RegisterPublicServer() {
if !server.IsPublic() {
return
}
// Fetch the server's certificates and put them in a tls.Config.
// We need the certificate chain to be able to use it in our client
// certificate chain to the registration server, and we also need to
// include a digest of the leaf certiifcate in the registration XML document
// we send off to the server.
config := &tls.Config{}
for _, cert := range server.tlscfg.Certificates {
config.Certificates = append(config.Certificates, cert)
}
hasher := sha1.New()
hasher.Write(config.Certificates[0].Certificate[0])
digest := hex.EncodeToString(hasher.Sum(nil))
// 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"),
Port: server.CurrentPort(),
Digest: digest,
Users: len(server.clients),
Channels: len(server.Channels),
Version: "1.2.4",
Release: "Grumble Git",
}
buf := bytes.NewBuffer(nil)
err := xml.NewEncoder(buf).Encode(reg)
if err != nil {
server.Printf("register: unable to marshal xml: %v", err)
return
}
// Post registration XML data to server asynchronously in its own goroutine
go func() {
tr := &http.Transport{
TLSClientConfig: config,
}
client := &http.Client{Transport: tr}
r, err := client.Post(registerUrl, "text/xml", ioutil.NopCloser(buf))
if err != nil {
server.Printf("register: unable to post registration request: %v", err)
return
}
bodyBytes, err := ioutil.ReadAll(r.Body)
if err == nil {
registerMsg := string(bodyBytes)
if r.StatusCode == 200 {
server.Printf("register: %v", registerMsg)
} else {
server.Printf("register: (status %v) %v", r.StatusCode, registerMsg)
}
} else {
server.Printf("register: unable to read post response: %v", err)
return
}
}()
}

1497
cmd/grumble/server.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
// +build darwin freebsd linux netbsd openbsd
package main
import (
"fmt"
"mumble.info/grumble/pkg/logtarget"
"os"
"os/signal"
"syscall"
)
func SignalHandler() {
sigchan := make(chan os.Signal, 10)
signal.Notify(sigchan, syscall.SIGUSR2, syscall.SIGTERM, syscall.SIGINT)
for sig := range sigchan {
if sig == syscall.SIGUSR2 {
err := logtarget.Target.Rotate()
if err != nil {
fmt.Fprintf(os.Stderr, "unable to rotate log file: %v", err)
}
continue
}
if sig == syscall.SIGINT || sig == syscall.SIGTERM {
os.Exit(0)
}
}
}

View file

@ -0,0 +1,8 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
func SignalHandler() {
}

71
cmd/grumble/user.go Normal file
View file

@ -0,0 +1,71 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"encoding/hex"
"errors"
)
// This file implements Server's handling of Users.
//
// Users are registered clients on the server.
type User struct {
Id uint32
Name string
Password string
CertHash string
Email string
TextureBlob string
CommentBlob string
LastChannelId int
LastActive uint64
}
// Create a new User
func NewUser(id uint32, name string) (user *User, err error) {
if id < 0 {
return nil, errors.New("Invalid user id")
}
if len(name) == 0 {
return nil, errors.New("Invalid username")
}
return &User{
Id: id,
Name: name,
}, nil
}
// Does the channel have comment?
func (user *User) HasComment() bool {
return len(user.CommentBlob) > 0
}
// Get the hash of the user's comment blob as a byte slice for transmitting via a protobuf message.
// Returns nil if there is no such blob.
func (user *User) CommentBlobHashBytes() (buf []byte) {
buf, err := hex.DecodeString(user.CommentBlob)
if err != nil {
return nil
}
return buf
}
// Does the user have a texture?
func (user *User) HasTexture() bool {
return len(user.TextureBlob) > 0
}
// Get the hash of the user's texture blob as a byte slice for transmitting via a protobuf message.
// Returns nil if there is no such blob.
func (user *User) TextureBlobHashBytes() (buf []byte) {
buf, err := hex.DecodeString(user.TextureBlob)
if err != nil {
return nil
}
return buf
}

6
cmd/grumble/version.go Normal file
View file

@ -0,0 +1,6 @@
package main
var (
version = "1.0~devel"
buildDate = "unknown"
)

152
cmd/grumble/voicetarget.go Normal file
View file

@ -0,0 +1,152 @@
// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import "mumble.info/grumble/pkg/acl"
// A VoiceTarget holds information about a single
// VoiceTarget entry of a Client.
type VoiceTarget struct {
sessions []uint32
channels []voiceTargetChannel
directCache map[uint32]*Client
fromChannelsCache map[uint32]*Client
}
type voiceTargetChannel struct {
id uint32
subChannels bool
links bool
onlyGroup string
}
// Add's a client's session to the VoiceTarget
func (vt *VoiceTarget) AddSession(session uint32) {
vt.sessions = append(vt.sessions, session)
}
// Add a channel to the VoiceTarget.
// If subchannels is true, any sent voice packets will also be sent to all subchannels.
// If links is true, any sent voice packets will also be sent to all linked channels.
// If group is a non-empty string, any sent voice packets will only be broadcast to members
// of that group who reside in the channel (or its children or linked channels).
func (vt *VoiceTarget) AddChannel(id uint32, subchannels bool, links bool, group string) {
vt.channels = append(vt.channels, voiceTargetChannel{
id: id,
subChannels: subchannels,
links: links,
onlyGroup: group,
})
}
// Checks whether the VoiceTarget is empty (has no targets)
func (vt *VoiceTarget) IsEmpty() bool {
return len(vt.sessions) == 0 && len(vt.channels) == 0
}
// Clear the VoiceTarget's cache.
func (vt *VoiceTarget) ClearCache() {
vt.directCache = nil
vt.fromChannelsCache = nil
}
// Send the contents of the VoiceBroadcast to all targets specified in the
// VoiceTarget.
func (vt *VoiceTarget) SendVoiceBroadcast(vb *VoiceBroadcast) {
buf := vb.buf
client := vb.client
server := client.server
direct := vt.directCache
fromChannels := vt.fromChannelsCache
if direct == nil || fromChannels == nil {
direct = make(map[uint32]*Client)
fromChannels = make(map[uint32]*Client)
for _, vtc := range vt.channels {
channel := server.Channels[int(vtc.id)]
if channel == nil {
continue
}
if !vtc.subChannels && !vtc.links && vtc.onlyGroup == "" {
if acl.HasPermission(&channel.ACL, client, acl.WhisperPermission) {
for _, target := range channel.clients {
fromChannels[target.Session()] = target
}
}
} else {
server.Printf("%v", vtc)
newchans := make(map[int]*Channel)
if vtc.links {
newchans = channel.AllLinks()
} else {
newchans[channel.Id] = channel
}
if vtc.subChannels {
subchans := channel.AllSubChannels()
for k, v := range subchans {
newchans[k] = v
}
}
for _, newchan := range newchans {
if acl.HasPermission(&newchan.ACL, client, acl.WhisperPermission) {
for _, target := range newchan.clients {
if vtc.onlyGroup == "" || acl.GroupMemberCheck(&newchan.ACL, &newchan.ACL, vtc.onlyGroup, target) {
fromChannels[target.Session()] = target
}
}
}
}
}
}
for _, session := range vt.sessions {
target := server.clients[session]
if target != nil {
if _, alreadyInFromChannels := fromChannels[target.Session()]; !alreadyInFromChannels {
direct[target.Session()] = target
}
}
}
// Make sure we don't send to ourselves.
delete(direct, client.Session())
delete(fromChannels, client.Session())
if vt.directCache == nil {
vt.directCache = direct
}
if vt.fromChannelsCache == nil {
vt.fromChannelsCache = fromChannels
}
}
kind := buf[0] & 0xe0
if len(fromChannels) > 0 {
for _, target := range fromChannels {
buf[0] = kind | 2
err := target.SendUDP(buf)
if err != nil {
target.Panicf("Unable to send UDP packet: %v", err.Error())
}
}
}
if len(direct) > 0 {
for _, target := range direct {
buf[0] = kind | 2
target.SendUDP(buf)
err := target.SendUDP(buf)
if err != nil {
target.Panicf("Unable to send UDP packet: %v", err.Error())
}
}
}
}