mirror of
https://github.com/mumble-voip/grumble.git
synced 2025-12-24 11:41:56 -08:00
grumble: move grumble cmd to 'cmd/grumble' instead of the source tree root.
This commit is contained in:
parent
8302bb2b08
commit
ef533d8f2b
19 changed files with 2 additions and 2 deletions
97
cmd/grumble/args.go
Normal file
97
cmd/grumble/args.go
Normal 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
131
cmd/grumble/channel.go
Normal 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
648
cmd/grumble/client.go
Normal 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
861
cmd/grumble/freeze.go
Normal 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
|
||||
}
|
||||
57
cmd/grumble/freeze_unix.go
Normal file
57
cmd/grumble/freeze_unix.go
Normal 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
|
||||
}
|
||||
59
cmd/grumble/freeze_windows.go
Normal file
59
cmd/grumble/freeze_windows.go
Normal 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
82
cmd/grumble/gencert.go
Normal 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
220
cmd/grumble/grumble.go
Normal 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
1609
cmd/grumble/message.go
Normal file
File diff suppressed because it is too large
Load diff
529
cmd/grumble/murmurdb.go
Normal file
529
cmd/grumble/murmurdb.go
Normal 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
126
cmd/grumble/register.go
Normal 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
1497
cmd/grumble/server.go
Normal file
File diff suppressed because it is too large
Load diff
32
cmd/grumble/signal_unix.go
Normal file
32
cmd/grumble/signal_unix.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
8
cmd/grumble/signal_windows.go
Normal file
8
cmd/grumble/signal_windows.go
Normal 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
71
cmd/grumble/user.go
Normal 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
6
cmd/grumble/version.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package main
|
||||
|
||||
var (
|
||||
version = "1.0~devel"
|
||||
buildDate = "unknown"
|
||||
)
|
||||
152
cmd/grumble/voicetarget.go
Normal file
152
cmd/grumble/voicetarget.go
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue