// 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" "encoding/binary" "goprotobuf.googlecode.com/hg/proto" "grumble/blobstore" "grumble/cryptstate" "grumble/mumbleproto" "io" "log" "net" "packetdatastream" "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 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 // 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 } // 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 } // 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 } // 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() } } // 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: mumbleproto.NewReject_RejectType(rejectType), 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: mumbleproto.NewPermissionDenied_DenyType(denyType), } 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 Permission) { pd := &mumbleproto.PermissionDenied{ Permission: proto.Uint32(uint32(what)), ChannelId: proto.Uint32(uint32(where.Id)), Session: proto.Uint32(who.Session), Type: mumbleproto.NewPermissionDenied_DenyType(mumbleproto.PermissionDenied_Permission), } err := c.sendMessage(pd) if err != nil { c.Panicf("%v", err.Error()) return } } // Send permission denied fallback func (c *Client) sendPermissionDeniedFallback(denyType mumbleproto.PermissionDenied_DenyType, version uint32, text string) { // fixme(mkrautz): Do fallback kind of stuff... c.sendPermissionDeniedType(denyType) } // UDP receiver. func (client *Client) udpreceiver() { 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: kind := buf[0] & 0xe0 target := buf[0] & 0x1f var counter uint8 outbuf := make([]byte, 1024) incoming := packetdatastream.New(buf[1 : 1+(len(buf)-1)]) outgoing := packetdatastream.New(outbuf[1 : 1+(len(outbuf)-1)]) _ = incoming.GetUint32() for { counter = incoming.Next8() incoming.Skip(int(counter & 0x7f)) if !((counter&0x80) != 0 && incoming.IsValid()) { break } } outgoing.PutUint32(client.Session) outgoing.PutBytes(buf[1 : 1+(len(buf)-1)]) outbuf[0] = kind 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)+4) 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 { msgData, err = proto.Marshal(msg) 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 } // Receiver Goroutine func (client *Client) receiver() { 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 { client.sendMessage(&mumbleproto.Version{ Version: proto.Uint32(0x10203), Release: proto.String("Grumble"), }) // fixme(mkrautz): Re-add OS information... Does it break anything? It seems like // the client discards the version message if there is no OS information in it. 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 } client.Printf("version = 0x%x", client.Version) client.Printf("os = %s %s", client.OSName, client.OSVersion) client.Printf("client = %s", client.ClientName) 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.Temporary { 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() { goodElapsed := time.Seconds() - client.crypt.LastGoodTime if goodElapsed > 5 { requestElapsed := time.Seconds() - client.lastResync if requestElapsed > 5 { client.lastResync = time.Seconds() cryptsetup := &mumbleproto.CryptSetup{} err := client.sendMessage(cryptsetup) if err != nil { client.Panicf("%v", err) } } } }