/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
   Copyright (C) 2009-2011, Stefan Hacker <dd0t@users.sourceforge.net>

   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.
   - Neither the name of the Mumble Developers nor the names of its
     contributors may be used to endorse or promote products derived from this
     software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "murmur_pch.h"

#include "User.h"
#include "Channel.h"
#include "ACL.h"
#include "Group.h"
#include "Message.h"
#include "ServerDB.h"
#include "Connection.h"
#include "Server.h"
#include "ServerUser.h"
#include "Version.h"

#define MSG_SETUP(st) \
	if (uSource->sState != st) { \
		return; \
	} \
	uSource->bwr.resetIdleSeconds()

#define MSG_SETUP_NO_UNIDLE(st) \
	if (uSource->sState != st) \
		return

#define VICTIM_SETUP \
	ServerUser *pDstServerUser = uSource; \
	if (msg.has_session()) \
		pDstServerUser = qhUsers.value(msg.session()); \
	if (! pDstServerUser) \
		return; \
	Q_UNUSED(pDstServerUser)

#define PERM_DENIED(who, where, what) \
	{ \
		MumbleProto::PermissionDenied mppd; \
		mppd.set_permission(static_cast<int>(what)); \
		mppd.set_channel_id(where->iId); \
		mppd.set_session(who->uiSession); \
		mppd.set_type(MumbleProto::PermissionDenied_DenyType_Permission); \
		sendMessage(uSource, mppd); \
		log(uSource, QString("%1 not allowed to %2 in %3").arg(who->qsName).arg(ChanACL::permName(what)).arg(where->qsName)); \
	}

#define PERM_DENIED_TYPE(type) \
	{ \
		MumbleProto::PermissionDenied mppd; \
		mppd.set_type(MumbleProto::PermissionDenied_DenyType_##type); \
		sendMessage(uSource, mppd); \
	}

#define PERM_DENIED_FALLBACK(type,version,text) \
	{ \
		MumbleProto::PermissionDenied mppd; \
		mppd.set_type(MumbleProto::PermissionDenied_DenyType_##type); \
		if (uSource->uiVersion < version) \
			mppd.set_reason(u8(text)); \
		sendMessage(uSource, mppd); \
	}

#define PERM_DENIED_HASH(user) \
	{ \
		MumbleProto::PermissionDenied mppd; \
		mppd.set_type(MumbleProto::PermissionDenied_DenyType_MissingCertificate); \
		if (user) \
			mppd.set_session(user->uiSession); \
		sendMessage(uSource, mppd); \
	}

void Server::msgAuthenticate(ServerUser *uSource, MumbleProto::Authenticate &msg) {
	if ((msg.tokens_size() > 0) || (uSource->sState == ServerUser::Authenticated)) {
		QStringList qsl;
		for (int i=0;i<msg.tokens_size();++i)
			qsl << u8(msg.tokens(i));
		{
			QMutexLocker qml(&qmCache);
			uSource->qslAccessTokens = qsl;
		}
		clearACLCache(uSource);
	}
	MSG_SETUP(ServerUser::Connected);

	Channel *root = qhChannels.value(0);
	Channel *c;

	uSource->qsName = u8(msg.username());

	bool ok = false;
	bool nameok = validateUserName(uSource->qsName);
	QString pw = u8(msg.password());

	// Fetch ID and stored username.
	// Since this may call DBus, which may recall our dbus messages, this function needs
	// to support re-entrancy, and also to support the fact that sessions may go away.
	int id = authenticate(uSource->qsName, pw, uSource->uiSession, uSource->qslEmail, uSource->qsHash, uSource->bVerified, uSource->peerCertificateChain());

	uSource->iId = id >= 0 ? id : -1;

	QString reason;
	MumbleProto::Reject_RejectType rtType = MumbleProto::Reject_RejectType_None;

	if (id==-2 && ! nameok) {
		reason = "Invalid username";
		rtType = MumbleProto::Reject_RejectType_InvalidUsername;
	} else if (id==-1) {
		reason = "Wrong certificate or password for existing user";
		rtType = MumbleProto::Reject_RejectType_WrongUserPW;
	} else if (id==-2 && ! qsPassword.isEmpty() && qsPassword != pw) {
		reason = "Invalid server password";
		rtType = MumbleProto::Reject_RejectType_WrongServerPW;
	} else {
		ok = true;
	}

	ServerUser *uOld = NULL;
	foreach(ServerUser *u, qhUsers) {
		if (u == uSource)
			continue;
		if (((u->iId>=0) && (u->iId == uSource->iId)) ||
		        (u->qsName == uSource->qsName)) {
			uOld = u;
			break;
		}
	}

	// Allow reuse of name from same IP
	if (ok && uOld && (uSource->iId == -1)) {
		if ((uOld->peerAddress() != uSource->peerAddress()) && (uSource->qsHash.isEmpty() || (uSource->qsHash != uOld->qsHash))) {
			reason = "Username already in use";
			rtType = MumbleProto::Reject_RejectType_UsernameInUse;
			ok = false;
		}
	}

	if ((id != 0) && (qhUsers.count() > iMaxUsers)) {
		reason = QString::fromLatin1("Server is full (max %1 users)").arg(iMaxUsers);
		rtType = MumbleProto::Reject_RejectType_ServerFull;
		ok = false;
	}

	if ((id != 0) && (uSource->qsHash.isEmpty() && bCertRequired)) {
		reason = QString::fromLatin1("A certificate is required to connect to this server");
		rtType = MumbleProto::Reject_RejectType_NoCertificate;
		ok = false;
	}

	if (! ok) {
		log(uSource, QString("Rejected connection from %1: %2").
			arg(addressToString(uSource->peerAddress(),
			uSource->peerPort()), reason));
		MumbleProto::Reject mpr;
		mpr.set_reason(u8(reason));
		mpr.set_type(rtType);
		sendMessage(uSource, mpr);
		uSource->disconnectSocket();
		return;
	}

	startThread();

	// Kick ghost
	if (uOld) {
		log(uSource, "Disconnecting ghost");
		uOld->disconnectSocket(true);
	}

	// Setup UDP encryption
	uSource->csCrypt.genKey();

	MumbleProto::CryptSetup mpcrypt;
	mpcrypt.set_key(std::string(reinterpret_cast<const char *>(uSource->csCrypt.raw_key), AES_BLOCK_SIZE));
	mpcrypt.set_server_nonce(std::string(reinterpret_cast<const char *>(uSource->csCrypt.encrypt_iv), AES_BLOCK_SIZE));
	mpcrypt.set_client_nonce(std::string(reinterpret_cast<const char *>(uSource->csCrypt.decrypt_iv), AES_BLOCK_SIZE));
	sendMessage(uSource, mpcrypt);

	bool fake_celt_support = false;
	if (msg.celt_versions_size() > 0) {
		for (int i=0;i < msg.celt_versions_size(); ++i)
			uSource->qlCodecs.append(msg.celt_versions(i));
	} else {
		uSource->qlCodecs.append(static_cast<qint32>(0x8000000b));
		fake_celt_support = true;
	}
	uSource->bOpus = msg.opus();
	recheckCodecVersions(uSource);

	MumbleProto::CodecVersion mpcv;
	mpcv.set_alpha(iCodecAlpha);
	mpcv.set_beta(iCodecBeta);
	mpcv.set_prefer_alpha(bPreferAlpha);
	mpcv.set_opus(bOpus);
	sendMessage(uSource, mpcv);

	if (!bOpus && uSource->bOpus && fake_celt_support) {
		sendTextMessage(NULL, uSource, false, QLatin1String("<strong>WARNING:</strong> Your client doesn't support the CELT codec, you won't be able to talk to or hear most clients. Please make sure your client was built with CELT support."));
	}

	// Transmit channel tree
	QQueue<Channel *> q;
	QSet<Channel *> chans;
	q << root;
	MumbleProto::ChannelState mpcs;
	while (! q.isEmpty()) {
		c = q.dequeue();
		chans.insert(c);

		mpcs.Clear();

		mpcs.set_channel_id(c->iId);
		if (c->cParent)
			mpcs.set_parent(c->cParent->iId);
		if (c->iId == 0)
			mpcs.set_name(u8(qsRegName.isEmpty() ? QLatin1String("Root") : qsRegName));
		else
			mpcs.set_name(u8(c->qsName));

		mpcs.set_position(c->iPosition);

		if ((uSource->uiVersion >= 0x010202) && ! c->qbaDescHash.isEmpty())
			mpcs.set_description_hash(blob(c->qbaDescHash));
		else if (! c->qsDesc.isEmpty())
			mpcs.set_description(u8(c->qsDesc));

		sendMessage(uSource, mpcs);

		foreach(c, c->qlChannels)
			q.enqueue(c);
	}

	// Transmit links
	foreach(c, chans) {
		if (c->qhLinks.count() > 0) {
			mpcs.Clear();
			mpcs.set_channel_id(c->iId);

			foreach(Channel *l, c->qhLinks.keys())
				mpcs.add_links(l->iId);
			sendMessage(uSource, mpcs);
		}
	}

	// Transmit user profile
	MumbleProto::UserState mpus;

	Channel *lc;
	if (bRememberChan)
		lc = qhChannels.value(readLastChannel(uSource->iId));
	else
		lc = qhChannels.value(iDefaultChan);

	if (! lc || ! hasPermission(uSource, lc, ChanACL::Enter)) {
		lc = qhChannels.value(iDefaultChan);
		if (! lc || ! hasPermission(uSource, lc, ChanACL::Enter)) {
			lc = root;
		}
	}

	userEnterChannel(uSource, lc, mpus);

	uSource->sState = ServerUser::Authenticated;
	mpus.set_session(uSource->uiSession);
	mpus.set_name(u8(uSource->qsName));
	if (uSource->iId >= 0) {
		mpus.set_user_id(uSource->iId);

		hashAssign(uSource->qbaTexture, uSource->qbaTextureHash, getUserTexture(uSource->iId));

		if (! uSource->qbaTextureHash.isEmpty())
			mpus.set_texture_hash(blob(uSource->qbaTextureHash));
		else if (! uSource->qbaTexture.isEmpty())
			mpus.set_texture(blob(uSource->qbaTexture));

		const QMap<int, QString> &info = getRegistration(uSource->iId);
		if (info.contains(ServerDB::User_Comment)) {
			hashAssign(uSource->qsComment, uSource->qbaCommentHash, info.value(ServerDB::User_Comment));
			if (! uSource->qbaCommentHash.isEmpty())
				mpus.set_comment_hash(blob(uSource->qbaCommentHash));
			else if (! uSource->qsComment.isEmpty())
				mpus.set_comment(u8(uSource->qsComment));
		}
	}
	if (! uSource->qsHash.isEmpty())
		mpus.set_hash(u8(uSource->qsHash));
	if (uSource->cChannel->iId != 0)
		mpus.set_channel_id(uSource->cChannel->iId);

	sendAll(mpus, 0x010202);

	if ((uSource->qbaTexture.length() >= 4) && (qFromBigEndian<unsigned int>(reinterpret_cast<const unsigned char *>(uSource->qbaTexture.constData())) == 600 * 60 * 4))
		mpus.set_texture(blob(uSource->qbaTexture));
	if (! uSource->qsComment.isEmpty())
		mpus.set_comment(u8(uSource->qsComment));
	sendAll(mpus, ~ 0x010202);

	// Transmit other users profiles
	foreach(ServerUser *u, qhUsers) {
		if (u->sState != ServerUser::Authenticated)
			continue;

		if (u == uSource)
			continue;

		mpus.Clear();
		mpus.set_session(u->uiSession);
		mpus.set_name(u8(u->qsName));
		if (u->iId >= 0)
			mpus.set_user_id(u->iId);
		if (uSource->uiVersion >= 0x010202) {
			if (! u->qbaTextureHash.isEmpty())
				mpus.set_texture_hash(blob(u->qbaTextureHash));
			else if (! u->qbaTexture.isEmpty())
				mpus.set_texture(blob(u->qbaTexture));
		} else if ((uSource->qbaTexture.length() >= 4) && (qFromBigEndian<unsigned int>(reinterpret_cast<const unsigned char *>(uSource->qbaTexture.constData())) == 600 * 60 * 4)) {
			mpus.set_texture(blob(u->qbaTexture));
		}
		if (u->cChannel->iId != 0)
			mpus.set_channel_id(u->cChannel->iId);
		if (u->bDeaf)
			mpus.set_deaf(true);
		else if (u->bMute)
			mpus.set_mute(true);
		if (u->bSuppress)
			mpus.set_suppress(true);
		if (u->bPrioritySpeaker)
			mpus.set_priority_speaker(true);
		if (u->bRecording)
			mpus.set_recording(true);
		if (u->bSelfDeaf)
			mpus.set_self_deaf(true);
		else if (u->bSelfMute)
			mpus.set_self_mute(true);
		if ((uSource->uiVersion >= 0x010202) && ! u->qbaCommentHash.isEmpty())
			mpus.set_comment_hash(blob(u->qbaCommentHash));
		else if (! u->qsComment.isEmpty())
			mpus.set_comment(u8(u->qsComment));
		if (! u->qsHash.isEmpty())
			mpus.set_hash(u8(u->qsHash));

		sendMessage(uSource, mpus);
	}

	// Send syncronisation packet
	MumbleProto::ServerSync mpss;
	mpss.set_session(uSource->uiSession);
	if (! qsWelcomeText.isEmpty())
		mpss.set_welcome_text(u8(qsWelcomeText));
	mpss.set_max_bandwidth(iMaxBandwidth);

	if (uSource->iId == 0) {
		mpss.set_permissions(ChanACL::All);
	} else {
		QMutexLocker qml(&qmCache);
		ChanACL::hasPermission(uSource, root, ChanACL::Enter, &acCache);
		mpss.set_permissions(acCache.value(uSource)->value(root));
	}

	sendMessage(uSource, mpss);

	MumbleProto::ServerConfig mpsc;
	mpsc.set_allow_html(bAllowHTML);
	mpsc.set_message_length(iMaxTextMessageLength);
	mpsc.set_image_message_length(iMaxImageMessageLength);
	sendMessage(uSource, mpsc);

	MumbleProto::SuggestConfig mpsug;
	if (! qvSuggestVersion.isNull())
		mpsug.set_version(qvSuggestVersion.toUInt());
	if (! qvSuggestPositional.isNull())
		mpsug.set_positional(qvSuggestPositional.toBool());
	if (! qvSuggestPushToTalk.isNull())
		mpsug.set_push_to_talk(qvSuggestPushToTalk.toBool());
	if (mpsug.ByteSize() > 0) {
		sendMessage(uSource, mpsug);
	}

	log(uSource, "Authenticated");

	emit userConnected(uSource);
}

void Server::msgBanList(ServerUser *uSource, MumbleProto::BanList &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	if (! hasPermission(uSource, qhChannels.value(0), ChanACL::Ban)) {
		PERM_DENIED(uSource, qhChannels.value(0), ChanACL::Ban);
		return;
	}
	if (msg.query()) {
		msg.clear_query();
		msg.clear_bans();
		foreach(const Ban &b, qlBans) {
			MumbleProto::BanList_BanEntry *be = msg.add_bans();
			be->set_address(b.haAddress.toStdString());
			be->set_mask(b.iMask);
			be->set_name(u8(b.qsUsername));
			be->set_hash(u8(b.qsHash));
			be->set_reason(u8(b.qsReason));
			be->set_start(u8(b.qdtStart.toString(Qt::ISODate)));
			be->set_duration(b.iDuration);
		}
		sendMessage(uSource, msg);
	} else {
		qlBans.clear();
		for (int i=0;i < msg.bans_size(); ++i) {
			const MumbleProto::BanList_BanEntry &be = msg.bans(i);

			Ban b;
			b.haAddress = be.address();
			b.iMask = be.mask();
			b.qsUsername = u8(be.name());
			b.qsHash = u8(be.hash());
			b.qsReason = u8(be.reason());
			if (be.has_start()) {
				b.qdtStart = QDateTime::fromString(u8(be.start()), Qt::ISODate);
				b.qdtStart.setTimeSpec(Qt::UTC);
			} else {
				b.qdtStart = QDateTime::currentDateTime().toUTC();
			}
			b.iDuration = be.duration();
			if (b.isValid())
				qlBans << b;
		}
		saveBans();
		log(uSource, "Updated banlist");
	}
}

void Server::msgReject(ServerUser *, MumbleProto::Reject &) {
}

void Server::msgServerSync(ServerUser *, MumbleProto::ServerSync &) {
}

void Server::msgPermissionDenied(ServerUser *, MumbleProto::PermissionDenied &) {
}

void Server::msgUDPTunnel(ServerUser *uSource, MumbleProto::UDPTunnel &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);

	const std::string &str = msg.packet();
	int len = static_cast<int>(str.length());
	if (len < 1)
		return;
	QReadLocker rl(&qrwlUsers);
	processMsg(uSource, str.data(), len);
}

void Server::msgUserState(ServerUser *uSource, MumbleProto::UserState &msg) {
	MSG_SETUP(ServerUser::Authenticated);
	VICTIM_SETUP;

	Channel *root = qhChannels.value(0);

	/*
		First check all permissions involved
	*/
	if ((pDstServerUser->iId == 0) && (uSource->iId != 0)) {
		PERM_DENIED_TYPE(SuperUser);
		return;
	}

	msg.set_session(pDstServerUser->uiSession);
	msg.set_actor(uSource->uiSession);

	if (msg.has_channel_id()) {
		Channel *c = qhChannels.value(msg.channel_id());
		if (!c || (c == pDstServerUser->cChannel))
			return;

		if ((uSource != pDstServerUser) && (! hasPermission(uSource, pDstServerUser->cChannel, ChanACL::Move))) {
			PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::Move);
			return;
		}

		if (! hasPermission(uSource, c, ChanACL::Move) && ! hasPermission(pDstServerUser, c, ChanACL::Enter)) {
			PERM_DENIED(pDstServerUser, c, ChanACL::Enter);
			return;
		}
		if (iMaxUsersPerChannel && (c->qlUsers.count() >= iMaxUsersPerChannel)) {
			PERM_DENIED_FALLBACK(ChannelFull, 0x010201, QLatin1String("Channel is full"));
			return;
		}
	}

	if (msg.has_mute() || msg.has_deaf() || msg.has_suppress() || msg.has_priority_speaker()) {
		if (pDstServerUser->iId == 0) {
			PERM_DENIED_TYPE(SuperUser);
			return;
		}
		if (uSource->cChannel->bTemporary) {
			PERM_DENIED_TYPE(TemporaryChannel);
			return;
		}
		if (! hasPermission(uSource, pDstServerUser->cChannel, ChanACL::MuteDeafen) || msg.suppress()) {
			PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::MuteDeafen);
			return;
		}
	}

	QString comment;

	if (msg.has_comment()) {
		bool changed = false;
		comment = u8(msg.comment());
		if (uSource != pDstServerUser) {
			if (! hasPermission(uSource, root, ChanACL::Move)) {
				PERM_DENIED(uSource, root, ChanACL::Move);
				return;
			}
			if (comment.length() > 0) {
				PERM_DENIED_TYPE(TextTooLong);
				return;
			}
		}


		if (! isTextAllowed(comment, changed)) {
			PERM_DENIED_TYPE(TextTooLong);
			return;
		}
		if (changed)
			msg.set_comment(u8(comment));
	}

	if (msg.has_texture()) {
		if (iMaxImageMessageLength > 0 && (msg.texture().length() > static_cast<unsigned int>(iMaxImageMessageLength))) {
			PERM_DENIED_TYPE(TextTooLong);
			return;
		}
	}


	if (msg.has_user_id()) {
		ChanACL::Perm p = (uSource == pDstServerUser) ? ChanACL::SelfRegister : ChanACL::Register;
		if ((pDstServerUser->iId >= 0) || ! hasPermission(uSource, root, p)) {
			PERM_DENIED(uSource, root, p);
			return;
		}
		if (pDstServerUser->qsHash.isEmpty()) {
			PERM_DENIED_HASH(pDstServerUser);
			return;
		}
	}

	// Prevent self-targeting state changes from being applied to others
	if ((pDstServerUser != uSource) && (msg.has_self_deaf() || msg.has_self_mute() || msg.has_texture() || msg.has_plugin_context() || msg.has_plugin_identity() || msg.has_recording()))
		return;

	/*
		-------------------- Permission checks done. Now act --------------------
	*/
	bool bBroadcast = false;

	if (msg.has_texture()) {
		QByteArray qba = blob(msg.texture());
		if (uSource->iId > 0) {
			// For registered users store the texture we just received in the database
			if (! setTexture(uSource->iId, qba))
				return;
		} else {
			// For unregistered users or SuperUser only get the hash
			hashAssign(uSource->qbaTexture, uSource->qbaTextureHash, qba);
		}

		// The texture will be sent out later in this function
		bBroadcast = true;
	}

	if (msg.has_self_deaf()) {
		uSource->bSelfDeaf = msg.self_deaf();
		if (uSource->bSelfDeaf)
			msg.set_self_mute(true);
		bBroadcast = true;
	}

	if (msg.has_self_mute()) {
		uSource->bSelfMute = msg.self_mute();
		if (! uSource->bSelfMute) {
			msg.set_self_deaf(false);
			uSource->bSelfDeaf = false;
		}
		bBroadcast = true;
	}

	if (msg.has_plugin_context()) {
		uSource->ssContext = msg.plugin_context();
		// Make sure to clear this from the packet so we don't broadcast it
		msg.clear_plugin_context();
	}

	if (msg.has_plugin_identity()) {
		uSource->qsIdentity = u8(msg.plugin_identity());
		// Make sure to clear this from the packet so we don't broadcast it
		msg.clear_plugin_identity();
	}

	if (! comment.isNull()) {
		hashAssign(pDstServerUser->qsComment, pDstServerUser->qbaCommentHash, comment);

		if (pDstServerUser->iId >= 0) {
			QMap<int, QString> info;
			info.insert(ServerDB::User_Comment, pDstServerUser->qsComment);
			setInfo(pDstServerUser->iId, info);
		}
		bBroadcast = true;
	}



	if (msg.has_mute() || msg.has_deaf() || msg.has_suppress() || msg.has_priority_speaker()) {
		if (msg.has_deaf()) {
			pDstServerUser->bDeaf = msg.deaf();
			if (pDstServerUser->bDeaf)
				msg.set_mute(true);
		}
		if (msg.has_mute()) {
			pDstServerUser->bMute = msg.mute();
			if (! pDstServerUser->bMute) {
				msg.set_deaf(false);
				pDstServerUser->bDeaf = false;
			}
		}
		if (msg.has_suppress())
			pDstServerUser->bSuppress = msg.suppress();

		if (msg.has_priority_speaker())
			pDstServerUser->bPrioritySpeaker = msg.priority_speaker();

		log(uSource, QString("Changed speak-state of %1 (%2 %3 %4 %5)").arg(QString(*pDstServerUser),
		        QString::number(pDstServerUser->bMute),
		        QString::number(pDstServerUser->bDeaf),
		        QString::number(pDstServerUser->bSuppress),
		        QString::number(pDstServerUser->bPrioritySpeaker)));

		bBroadcast = true;
	}

	if (msg.has_recording() && (pDstServerUser->bRecording != msg.recording())) {
		pDstServerUser->bRecording = msg.recording();

		MumbleProto::TextMessage mptm;
		mptm.add_tree_id(0);
		if (pDstServerUser->bRecording)
			mptm.set_message(u8(QString(QLatin1String("User '%1' started recording")).arg(pDstServerUser->qsName)));
		else
			mptm.set_message(u8(QString(QLatin1String("User '%1' stopped recording")).arg(pDstServerUser->qsName)));

		sendAll(mptm, ~ 0x010203);

		bBroadcast = true;
	}

	if (msg.has_channel_id()) {
		Channel *c = qhChannels.value(msg.channel_id());

		userEnterChannel(pDstServerUser, c, msg);
		log(uSource, QString("Moved %1 to %2").arg(QString(*pDstServerUser), QString(*c)));
		bBroadcast = true;
	}

	bool bDstAclChanged = false;
	if (msg.has_user_id()) {
		// Handle user (Self-)Registration
		QMap<int, QString> info;

		info.insert(ServerDB::User_Name, pDstServerUser->qsName);
		info.insert(ServerDB::User_Hash, pDstServerUser->qsHash);
		if (! pDstServerUser->qslEmail.isEmpty())
			info.insert(ServerDB::User_Email, pDstServerUser->qslEmail.first());
		int id = registerUser(info);
		if (id > 0) {
			pDstServerUser->iId = id;
			setLastChannel(pDstServerUser);
			msg.set_user_id(id);
			bDstAclChanged = true;
		} else {
			// Registration failed
			msg.clear_user_id();
		}
		bBroadcast = true;
	}

	if (bBroadcast) {
		// Texture handling for clients < 1.2.2.
		// Send the texture data in the message.
		if (msg.has_texture() && (pDstServerUser->qbaTexture.length() >= 4) && (qFromBigEndian<unsigned int>(reinterpret_cast<const unsigned char *>(pDstServerUser->qbaTexture.constData())) != 600 * 60 * 4)) {
			// This is a new style texture, don't send it because the client doesn't handle it correctly / crashes.
			msg.clear_texture();
			sendAll(msg, ~ 0x010202);
			msg.set_texture(blob(pDstServerUser->qbaTexture));
		} else {
			// This is an old style texture, empty texture or there was no texture in this packet,
			// send the message unchanged.
			sendAll(msg, ~ 0x010202);
		}

		// Texture / comment handling for clients >= 1.2.2.
		// Send only a hash of the texture / comment text. The client will request the actual data if necessary.
		if (msg.has_texture() && ! pDstServerUser->qbaTextureHash.isEmpty()) {
			msg.clear_texture();
			msg.set_texture_hash(blob(pDstServerUser->qbaTextureHash));
		}
		if (msg.has_comment() && ! pDstServerUser->qbaCommentHash.isEmpty()) {
			msg.clear_comment();
			msg.set_comment_hash(blob(pDstServerUser->qbaCommentHash));
		}

		sendAll(msg, 0x010202);

		if (bDstAclChanged)
			clearACLCache(pDstServerUser);
	}

	emit userStateChanged(pDstServerUser);
}

void Server::msgUserRemove(ServerUser *uSource, MumbleProto::UserRemove &msg) {
	MSG_SETUP(ServerUser::Authenticated);
	VICTIM_SETUP;

	msg.set_actor(uSource->uiSession);

	bool ban = msg.has_ban() && msg.ban();

	Channel *c = qhChannels.value(0);
	QFlags<ChanACL::Perm> perm = ban ? ChanACL::Ban : (ChanACL::Ban|ChanACL::Kick);

	if ((pDstServerUser->iId ==0) || ! hasPermission(uSource, c, perm)) {
		PERM_DENIED(uSource, c, perm);
		return;
	}

	if (ban) {
		Ban b;
		b.haAddress = pDstServerUser->haAddress;
		b.iMask = 128;
		b.qsReason = u8(msg.reason());
		b.qsUsername = pDstServerUser->qsName;
		b.qsHash = pDstServerUser->qsHash;
		b.qdtStart = QDateTime::currentDateTime().toUTC();
		b.iDuration = 0;
		qlBans << b;
		saveBans();
	}

	sendAll(msg);
	if (ban)
		log(uSource, QString("Kickbanned %1 (%2)").arg(QString(*pDstServerUser), u8(msg.reason())));
	else
		log(uSource, QString("Kicked %1 (%2)").arg(QString(*pDstServerUser), u8(msg.reason())));
	pDstServerUser->disconnectSocket();
}

void Server::msgChannelState(ServerUser *uSource, MumbleProto::ChannelState &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	Channel *c = NULL;
	Channel *p = NULL;

	// If this message relates to an existing channel check if the id is really valid
	if (msg.has_channel_id()) {
		c = qhChannels.value(msg.channel_id());
		if (! c)
			return;
	}

	// Check if the parent exists
	if (msg.has_parent()) {
		p = qhChannels.value(msg.parent());
		if (! p)
			return;
	}

	msg.clear_links();

	QString qsName;
	QString qsDesc;
	if (msg.has_description()) {
		qsDesc = u8(msg.description());
		bool changed = false;
		if (! isTextAllowed(qsDesc, changed)) {
			PERM_DENIED_TYPE(TextTooLong);
			return;
		}
		if (changed)
			msg.set_description(u8(qsDesc));
	}

	if (msg.has_name()) {
		// If we are sent a channel name this means we want to create this channel so
		// check if the name is valid and not already in use.
		qsName = u8(msg.name());

		if (! validateChannelName(qsName)) {
			PERM_DENIED_TYPE(ChannelName);
			return;
		}

		QRegExp re2("\\w");
		if (re2.indexIn(qsName) == -1) {
			PERM_DENIED_TYPE(ChannelName);
			return;
		}

		if (p || (c && c->iId != 0)) {
			Channel *cp = p ? p : c->cParent;
			foreach(Channel *sibling, cp->qlChannels) {
				if (sibling->qsName == qsName) {
					PERM_DENIED_TYPE(ChannelName);
					return;
				}
			}
		}
	}

	if(p) {
		// Having a parent channel given means we either want to create
		// a channel in or move a channel into this parent.

		if (!canNest(p, c)) {
			PERM_DENIED_FALLBACK(NestingLimit, 0x010204, QLatin1String("Channel nesting limit reached"));
			return;
		}
	}

	if (! c) {
		// If we don't have a channel handle up to now we want to create a new channel
		// so check if the user has enough rights and we got everything we need.
		if (! p || qsName.isNull())
			return;

		ChanACL::Perm perm = msg.temporary() ? ChanACL::MakeTempChannel : ChanACL::MakeChannel;
		if (! hasPermission(uSource, p, perm)) {
			PERM_DENIED(uSource, p, perm);
			return;
		}

		if ((uSource->iId < 0) && uSource->qsHash.isEmpty()) {
			PERM_DENIED_HASH(uSource);
			return;
		}

		if (p->bTemporary) {
			PERM_DENIED_TYPE(TemporaryChannel);
			return;
		}

		c = addChannel(p, qsName, msg.temporary(), msg.position());
		hashAssign(c->qsDesc, c->qbaDescHash, qsDesc);

		if (uSource->iId >= 0) {
			Group *g = new Group(c, "admin");
			g->qsAdd << uSource->iId;
		}

		if (! hasPermission(uSource, c, ChanACL::Write)) {
			ChanACL *a = new ChanACL(c);
			a->bApplyHere=true;
			a->bApplySubs=false;
			if (uSource->iId >= 0)
				a->iUserId=uSource->iId;
			else
				a->qsGroup=QLatin1Char('$') + uSource->qsHash;
			a->pDeny=ChanACL::None;
			a->pAllow=ChanACL::Write | ChanACL::Traverse;

			clearACLCache();
		}
		updateChannel(c);

		msg.set_channel_id(c->iId);
		log(uSource, QString("Added channel %1 under %2").arg(QString(*c), QString(*p)));
		emit channelCreated(c);

		sendAll(msg, ~ 0x010202);
		if (! c->qbaDescHash.isEmpty()) {
			msg.clear_description();
			msg.set_description_hash(blob(c->qbaDescHash));
		}
		sendAll(msg, 0x010202);

		if (c->bTemporary) {
			// If a temporary channel has been created move the creator right in there
			MumbleProto::UserState mpus;
			mpus.set_session(uSource->uiSession);
			mpus.set_channel_id(c->iId);
			userEnterChannel(uSource, c, mpus);
			sendAll(mpus);
			emit userStateChanged(uSource);
		}
	} else {
		// The message is related to an existing channel c so check if the user is allowed to modify it
		// and perform the modifications
		if (! qsName.isNull()) {
			if (! hasPermission(uSource, c, ChanACL::Write) || (c->iId == 0)) {
				PERM_DENIED(uSource, c, ChanACL::Write);
				return;
			}
		}
		if (! qsDesc.isNull()) {
			if (! hasPermission(uSource, c, ChanACL::Write)) {
				PERM_DENIED(uSource, c, ChanACL::Write);
				return;
			}
		}
		if (msg.has_position()) {
			if (! hasPermission(uSource, c, ChanACL::Write)) {
				PERM_DENIED(uSource, c, ChanACL::Write);
				return;
			}
		}
		if (p) {
			// If we received a parent channel check if it differs from the old one and is not
			// Temporary. If that is the case check if the user has enough rights and if the
			// channel name is not used in the target location. Abort otherwise.
			if (p == c->cParent)
				return;

			Channel *ip = p;
			while (ip) {
				if (ip == c)
					return;
				ip = ip->cParent;
			}

			if (p->bTemporary) {
				PERM_DENIED_TYPE(TemporaryChannel);
				return;
			}

			if (! hasPermission(uSource, c, ChanACL::Write)) {
				PERM_DENIED(uSource, c, ChanACL::Write);
				return;
			}

			if (! hasPermission(uSource, p, ChanACL::MakeChannel)) {
				PERM_DENIED(uSource, p, ChanACL::MakeChannel);
				return;
			}

			QString name = qsName.isNull() ? c->qsName : qsName;

			foreach(Channel *sibling, p->qlChannels) {
				if (sibling->qsName == name) {
					PERM_DENIED_TYPE(ChannelName);
					return;
				}
			}
		}
		QList<Channel *> qlAdd;
		QList<Channel *> qlRemove;

		if (msg.links_add_size() || msg.links_remove_size()) {
			if (! hasPermission(uSource, c, ChanACL::LinkChannel)) {
				PERM_DENIED(uSource, c, ChanACL::LinkChannel);
				return;
			}
			if (msg.links_remove_size()) {
				for (int i=0;i < msg.links_remove_size(); ++i) {
					unsigned int link = msg.links_remove(i);
					Channel *l = qhChannels.value(link);
					if (! l)
						return;
					qlRemove << l;
				}
			}
			if (msg.links_add_size()) {
				for (int i=0;i < msg.links_add_size(); ++i) {
					unsigned int link = msg.links_add(i);
					Channel *l = qhChannels.value(link);
					if (! l)
						return;
					if (! hasPermission(uSource, l, ChanACL::LinkChannel)) {
						PERM_DENIED(uSource, l, ChanACL::LinkChannel);
						return;
					}
					qlAdd << l;
				}
			}
		}

		// All permission checks done -- the update is good.

		if (p) {
			log(uSource, QString("Moved channel %1 from %2 to %3").arg(QString(*c),
			        QString(* c->cParent),
			        QString(*p)));

			c->cParent->removeChannel(c);
			p->addChannel(c);
		}
		if (! qsName.isNull()) {
			log(uSource, QString("Renamed channel %1 to %2").arg(QString(*c),
			        QString(qsName)));
			c->qsName = qsName;
		}
		if (! qsDesc.isNull())
			hashAssign(c->qsDesc, c->qbaDescHash, qsDesc);

		if (msg.has_position())
			c->iPosition = msg.position();

		foreach(Channel *l, qlAdd) {
			addLink(c, l);
		}
		foreach(Channel *l, qlRemove) {
			removeLink(c, l);
		}

		updateChannel(c);
		emit channelStateChanged(c);

		sendAll(msg, ~ 0x010202);
		if (msg.has_description() && ! c->qbaDescHash.isEmpty()) {
			msg.clear_description();
			msg.set_description_hash(blob(c->qbaDescHash));
		}
		sendAll(msg, 0x010202);
	}
}

void Server::msgChannelRemove(ServerUser *uSource, MumbleProto::ChannelRemove &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	Channel *c = qhChannels.value(msg.channel_id());
	if (!c)
		return;

	if (! hasPermission(uSource, c, ChanACL::Write) || (c->iId == 0)) {
		PERM_DENIED(uSource, c, ChanACL::Write);
		return;
	}

	log(uSource, QString("Removed channel %1").arg(*c));

	removeChannel(c);
}

void Server::msgTextMessage(ServerUser *uSource, MumbleProto::TextMessage &msg) {
	MSG_SETUP(ServerUser::Authenticated);
	QMutexLocker qml(&qmCache);

	TextMessage tm; // for signal userTextMessage

	QSet<ServerUser *> users;
	QQueue<Channel *> q;

	QString text = u8(msg.message());
	bool changed = false;

	if (! isTextAllowed(text, changed)) {
		PERM_DENIED_TYPE(TextTooLong);
		return;
	}
	if (text.isEmpty())
		return;
	if (changed)
		msg.set_message(u8(text));

	tm.qsText = text;

	{ // Happy easter
		char m[29] = {0117, 0160, 0145, 0156, 040, 0164, 0150, 0145, 040, 0160, 0157, 0144, 040, 0142, 0141, 0171, 040, 0144, 0157, 0157, 0162, 0163, 054, 040, 0110, 0101, 0114, 056, 0};
		if (msg.channel_id_size() == 1 && msg.channel_id(0) == 0 && msg.message() == m) {
			PERM_DENIED_TYPE(H9K);
			return;
		}
	}

	msg.set_actor(uSource->uiSession);
	for (int i=0;i<msg.channel_id_size(); ++i) {
		unsigned int id = msg.channel_id(i);

		Channel *c = qhChannels.value(id);
		if (! c)
			return;

		if (! ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
			PERM_DENIED(uSource, c, ChanACL::TextMessage);
			return;
		}

		foreach(User *p, c->qlUsers)
			users.insert(static_cast<ServerUser *>(p));

		tm.qlChannels.append(id);
	}

	for (int i=0;i<msg.tree_id_size(); ++i) {
		unsigned int id = msg.tree_id(i);

		Channel *c = qhChannels.value(id);
		if (! c)
			return;

		if (! ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
			PERM_DENIED(uSource, c, ChanACL::TextMessage);
			return;
		}

		q.enqueue(c);

		tm.qlTrees.append(id);
	}

	while (! q.isEmpty()) {
		Channel *c = q.dequeue();
		if (ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
			foreach(Channel *sub, c->qlChannels)
				q.enqueue(sub);
			foreach(User *p, c->qlUsers)
				users.insert(static_cast<ServerUser *>(p));
		}
	}

	for (int i=0;i < msg.session_size(); ++i) {
		unsigned int session = msg.session(i);
		ServerUser *u = qhUsers.value(session);
		if (u) {
			if (! ChanACL::hasPermission(uSource, u->cChannel, ChanACL::TextMessage, &acCache)) {
				PERM_DENIED(uSource, u->cChannel, ChanACL::TextMessage);
				return;
			}
			users.insert(u);
		}

		tm.qlSessions.append(session);
	}

	users.remove(uSource);

	foreach(ServerUser *u, users)
		sendMessage(u, msg);

	emit userTextMessage(uSource, tm);
}

void Server::msgACL(ServerUser *uSource, MumbleProto::ACL &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	Channel *c = qhChannels.value(msg.channel_id());
	if (!c)
		return;

	if (! hasPermission(uSource, c, ChanACL::Write) && !(c->cParent && hasPermission(uSource, c->cParent, ChanACL::Write))) {
		PERM_DENIED(uSource, c, ChanACL::Write);
		return;
	}

	if (msg.has_query() && msg.query()) {
		QStack<Channel *> chans;
		Channel *p;
		ChanACL *acl;

		QSet<int> qsId;

		msg.clear_groups();
		msg.clear_acls();
		msg.clear_query();
		msg.set_inherit_acls(c->bInheritACL);

		p = c;
		while (p) {
			chans.push(p);
			if ((p==c) || p->bInheritACL)
				p = p->cParent;
			else
				p = NULL;
		}

		while (! chans.isEmpty()) {
			p = chans.pop();
			foreach(acl, p->qlACL) {
				if ((p == c) || (acl->bApplySubs)) {
					MumbleProto::ACL_ChanACL *mpacl = msg.add_acls();

					mpacl->set_inherited(p != c);
					mpacl->set_apply_here(acl->bApplyHere);
					mpacl->set_apply_subs(acl->bApplySubs);
					if (acl->iUserId >= 0) {
						mpacl->set_user_id(acl->iUserId);
						qsId.insert(acl->iUserId);
					} else
						mpacl->set_group(u8(acl->qsGroup));
					mpacl->set_grant(acl->pAllow);
					mpacl->set_deny(acl->pDeny);
				}
			}
		}

		p = c->cParent;
		QSet<QString> allnames=Group::groupNames(c);
		foreach(const QString &name, allnames) {
			Group *g = c->qhGroups.value(name);
			Group *pg = p ? Group::getGroup(p, name) : NULL;

			MumbleProto::ACL_ChanGroup *group = msg.add_groups();
			group->set_name(u8(name));
			group->set_inherit(g ? g->bInherit : true);
			group->set_inheritable(g ? g->bInheritable : true);
			group->set_inherited((pg != NULL) && pg->bInheritable);
			if (g) {
				foreach(int id, g->qsAdd) {
					qsId.insert(id);
					group->add_add(id);
				}
				foreach(int id, g->qsRemove) {
					qsId.insert(id);
					group->add_remove(id);
				}
			}
			if (pg)
				foreach(int id, pg->members()) {
					qsId.insert(id);
					group->add_inherited_members(id);
				}
		}

		sendMessage(uSource, msg);

		MumbleProto::QueryUsers mpqu;
		foreach(int id, qsId) {
			QString uname=getUserName(id);
			if (! uname.isEmpty()) {
				mpqu.add_ids(id);
				mpqu.add_names(u8(uname));
			}
		}
		if (mpqu.ids_size())
			sendMessage(uSource, mpqu);
	} else {
		Group *g;
		ChanACL *a;

		QHash<QString, QSet<int> > hOldTemp;

		foreach(g, c->qhGroups) {
			hOldTemp.insert(g->qsName, g->qsTemporary);
			delete g;
		}

		foreach(a, c->qlACL)
			delete a;

		c->qhGroups.clear();
		c->qlACL.clear();

		c->bInheritACL = msg.inherit_acls();

		for (int i=0;i<msg.groups_size(); ++i) {
			const MumbleProto::ACL_ChanGroup &group = msg.groups(i);
			g = new Group(c, u8(group.name()));
			g->bInherit = group.inherit();
			g->bInheritable = group.inheritable();
			for (int j=0;j<group.add_size();++j)
				if (!getUserName(group.add(j)).isEmpty())
					g->qsAdd << group.add(j);
			for (int j=0;j<group.remove_size();++j)
				if (!getUserName(group.remove(j)).isEmpty())
					g->qsRemove << group.remove(j);
			g->qsTemporary = hOldTemp.value(g->qsName);
		}

		for (int i=0;i<msg.acls_size(); ++i) {
			const MumbleProto::ACL_ChanACL &mpacl = msg.acls(i);
			if (mpacl.has_user_id() && getUserName(mpacl.user_id()).isEmpty())
				continue;

			a = new ChanACL(c);
			a->bApplyHere=mpacl.apply_here();
			a->bApplySubs=mpacl.apply_subs();
			if (mpacl.has_user_id())
				a->iUserId=mpacl.user_id();
			else
				a->qsGroup=u8(mpacl.group());
			a->pDeny=static_cast<ChanACL::Permissions>(mpacl.deny())  & ChanACL::All;
			a->pAllow=static_cast<ChanACL::Permissions>(mpacl.grant()) & ChanACL::All;
		}

		clearACLCache();

		if (! hasPermission(uSource, c, ChanACL::Write) && ((uSource->iId >= 0) || !uSource->qsHash.isEmpty())) {
			a = new ChanACL(c);
			a->bApplyHere=true;
			a->bApplySubs=false;
			if (uSource->iId >= 0)
				a->iUserId=uSource->iId;
			else
				a->qsGroup=QLatin1Char('$') + uSource->qsHash;
			a->iUserId=uSource->iId;
			a->pDeny=ChanACL::None;
			a->pAllow=ChanACL::Write | ChanACL::Traverse;

			clearACLCache();
		}

		updateChannel(c);
		log(uSource, QString("Updated ACL in channel %1").arg(*c));
	}
}

void Server::msgQueryUsers(ServerUser *uSource, MumbleProto::QueryUsers &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	MumbleProto::QueryUsers reply;

	for (int i=0;i<msg.ids_size();++i) {
		int id = msg.ids(i);
		if (id >= 0) {
			const QString &name = getUserName(id);
			if (! name.isEmpty()) {
				reply.add_ids(id);
				reply.add_names(u8(name));
			}
		}
	}

	for (int i=0;i<msg.names_size();++i) {
		QString name = u8(msg.names(i));
		int id = getUserID(name);
		if (id >= 0) {
			name = getUserName(id);
			reply.add_ids(id);
			reply.add_names(u8(name));
		}
	}

	sendMessage(uSource, reply);
}

void Server::msgPing(ServerUser *uSource, MumbleProto::Ping &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
	CryptState &cs=uSource->csCrypt;

	cs.uiRemoteGood = msg.good();
	cs.uiRemoteLate = msg.late();
	cs.uiRemoteLost = msg.lost();
	cs.uiRemoteResync = msg.resync();

	uSource->dUDPPingAvg = msg.udp_ping_avg();
	uSource->dUDPPingVar = msg.udp_ping_var();
	uSource->uiUDPPackets = msg.udp_packets();
	uSource->dTCPPingAvg = msg.tcp_ping_avg();
	uSource->dTCPPingVar = msg.tcp_ping_var();
	uSource->uiTCPPackets = msg.tcp_packets();

	quint64 ts = msg.timestamp();

	msg.Clear();
	msg.set_timestamp(ts);
	msg.set_good(cs.uiGood);
	msg.set_late(cs.uiLate);
	msg.set_lost(cs.uiLost);
	msg.set_resync(cs.uiResync);

	sendMessage(uSource, msg);
}

void Server::msgCryptSetup(ServerUser *uSource, MumbleProto::CryptSetup &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
	if (! msg.has_client_nonce()) {
		log(uSource, "Requested crypt-nonce resync");
		msg.set_server_nonce(std::string(reinterpret_cast<const char *>(uSource->csCrypt.encrypt_iv), AES_BLOCK_SIZE));
		sendMessage(uSource, msg);
	} else {
		const std::string &str = msg.client_nonce();
		if (str.size()  == AES_BLOCK_SIZE) {
			uSource->csCrypt.uiResync++;
			memcpy(uSource->csCrypt.decrypt_iv, str.data(), AES_BLOCK_SIZE);
		}
	}
}

void Server::msgContextActionModify(ServerUser *, MumbleProto::ContextActionModify &) {
}

void Server::msgContextAction(ServerUser *uSource, MumbleProto::ContextAction &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	unsigned int session = msg.has_session() ? msg.session() : 0;
	int id = msg.has_channel_id() ? static_cast<int>(msg.channel_id()) : -1;

	if (session && ! qhUsers.contains(session))
		return;
	if ((id >= 0) && ! qhChannels.contains(id))
		return;
	emit contextAction(uSource, u8(msg.action()), session, id);
}

void Server::msgVersion(ServerUser *uSource, MumbleProto::Version &msg) {
	if (msg.has_version())
		uSource->uiVersion=msg.version();
	if (msg.has_release())
		uSource->qsRelease = u8(msg.release());
	if (msg.has_os()) {
		uSource->qsOS = u8(msg.os());
		if (msg.has_os_version())
			uSource->qsOSVersion = u8(msg.os_version());
	}

	log(uSource, QString("Client version %1 (%2: %3)").arg(MumbleVersion::toString(uSource->uiVersion)).arg(uSource->qsOS).arg(uSource->qsRelease));
}

void Server::msgUserList(ServerUser *uSource, MumbleProto::UserList &msg) {
	MSG_SETUP(ServerUser::Authenticated);

	if (! hasPermission(uSource, qhChannels.value(0), ChanACL::Register)) {
		PERM_DENIED(uSource, qhChannels.value(0), ChanACL::Register);
		return;
	}

	if (msg.users_size() == 0) {
		// Query mode.
		QMap<int, QString> users = getRegisteredUsers();
		QMap<int, QString>::const_iterator i;
		for (i = users.constBegin(); i != users.constEnd(); ++i) {
			if (i.key() > 0) {
				::MumbleProto::UserList_User *u = msg.add_users();
				u->set_user_id(i.key());
				u->set_name(u8(i.value()));
			}
		}
		sendMessage(uSource, msg);
	} else {
		for (int i=0;i < msg.users_size(); ++i) {
			const MumbleProto::UserList_User &u = msg.users(i);

			int id = u.user_id();
			if (id == 0)
				continue;

			if (! u.has_name()) {
				log(uSource, QString::fromLatin1("Unregistered user %1").arg(id));
				unregisterUser(id);
			} else {
				const QString &name = u8(u.name());
				if (validateUserName(name)) {
					log(uSource, QString::fromLatin1("Renamed user %1 to '%2'").arg(QString::number(id), name));

					QMap<int, QString> info;
					info.insert(ServerDB::User_Name, name);
					setInfo(id, info);
				} else {
					MumbleProto::PermissionDenied mppd;
					mppd.set_type(MumbleProto::PermissionDenied_DenyType_UserName);
					if (uSource->uiVersion < 0x010201)
						mppd.set_reason(u8(QString::fromLatin1("%1 is not a valid username").arg(name)));
					else
						mppd.set_name(u8(name));
					sendMessage(uSource, mppd);
				}
			}
		}
	}
}

void Server::msgVoiceTarget(ServerUser *uSource, MumbleProto::VoiceTarget &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);

	int target = msg.id();
	if ((target < 1) || (target >= 0x1f))
		return;

	QWriteLocker lock(&qrwlUsers);

	uSource->qmTargetCache.remove(target);

	int count = msg.targets_size();
	if (count == 0) {
		uSource->qmTargets.remove(target);
	} else {
		WhisperTarget wt;
		for (int i=0;i<count;++i) {
			const MumbleProto::VoiceTarget_Target &t = msg.targets(i);
			for (int j=0;j<t.session_size(); ++j) {
				unsigned int s = t.session(j);
				if (qhUsers.contains(s))
					wt.qlSessions << s;
			}
			if (t.has_channel_id()) {
				unsigned int id = t.channel_id();
				if (qhChannels.contains(id)) {
					WhisperTarget::Channel wtc;
					wtc.iId = id;
					wtc.bChildren = t.children();
					wtc.bLinks = t.links();
					if (t.has_group())
						wtc.qsGroup = u8(t.group());
					wt.qlChannels << wtc;
				}
			}
		}
		if (wt.qlSessions.isEmpty() && wt.qlChannels.isEmpty())
			uSource->qmTargets.remove(target);
		else
			uSource->qmTargets.insert(target, wt);
	}
}

void Server::msgPermissionQuery(ServerUser *uSource, MumbleProto::PermissionQuery &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);

	Channel *c = qhChannels.value(msg.channel_id());
	if (!c)
		return;

	sendClientPermission(uSource, c, true);
}

void Server::msgCodecVersion(ServerUser *, MumbleProto::CodecVersion &) {
}

void Server::msgUserStats(ServerUser*uSource, MumbleProto::UserStats &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
	VICTIM_SETUP;
	const CryptState &cs = pDstServerUser->csCrypt;
	const BandwidthRecord &bwr = pDstServerUser->bwr;
	const QList<QSslCertificate> &certs = pDstServerUser->peerCertificateChain();

	bool extend = (uSource == pDstServerUser) || hasPermission(uSource, qhChannels.value(0), ChanACL::Register);

	if (! extend && ! hasPermission(uSource, pDstServerUser->cChannel, ChanACL::Enter)) {
		PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::Enter);
		return;
	}

	bool details = extend;
	bool local = extend || (pDstServerUser->cChannel == uSource->cChannel);

	if (msg.stats_only())
		details = false;

	msg.Clear();
	msg.set_session(pDstServerUser->uiSession);

	if (details) {
		foreach(const QSslCertificate &cert, certs) {
			const QByteArray &der = cert.toDer();
			msg.add_certificates(blob(der));
		}
		msg.set_strong_certificate(pDstServerUser->bVerified);
	}

	if (local) {
		MumbleProto::UserStats_Stats *mpusss;

		mpusss = msg.mutable_from_client();
		mpusss->set_good(cs.uiGood);
		mpusss->set_late(cs.uiLate);
		mpusss->set_lost(cs.uiLost);
		mpusss->set_resync(cs.uiResync);

		mpusss = msg.mutable_from_server();
		mpusss->set_good(cs.uiRemoteGood);
		mpusss->set_late(cs.uiRemoteLate);
		mpusss->set_lost(cs.uiRemoteLost);
		mpusss->set_resync(cs.uiRemoteResync);
	}

	msg.set_udp_packets(pDstServerUser->uiUDPPackets);
	msg.set_tcp_packets(pDstServerUser->uiTCPPackets);
	msg.set_udp_ping_avg(pDstServerUser->dUDPPingAvg);
	msg.set_udp_ping_var(pDstServerUser->dUDPPingVar);
	msg.set_tcp_ping_avg(pDstServerUser->dTCPPingAvg);
	msg.set_tcp_ping_var(pDstServerUser->dTCPPingVar);

	if (details) {
		MumbleProto::Version *mpv;

		mpv = msg.mutable_version();
		if (pDstServerUser->uiVersion)
			mpv->set_version(pDstServerUser->uiVersion);
		if (! pDstServerUser->qsRelease.isEmpty())
			mpv->set_release(u8(pDstServerUser->qsRelease));
		if (! pDstServerUser->qsOS.isEmpty()) {
			mpv->set_os(u8(pDstServerUser->qsOS));
			if (! pDstServerUser->qsOSVersion.isEmpty())
				mpv->set_os_version(u8(pDstServerUser->qsOSVersion));
		}

		foreach(int v, pDstServerUser->qlCodecs)
			msg.add_celt_versions(v);
		msg.set_opus(pDstServerUser->bOpus);

		msg.set_address(pDstServerUser->haAddress.toStdString());
	}

	if (local)
		msg.set_bandwidth(bwr.bandwidth());
	msg.set_onlinesecs(bwr.onlineSeconds());
	if (local)
		msg.set_idlesecs(bwr.idleSeconds());

	sendMessage(uSource, msg);
}

void Server::msgRequestBlob(ServerUser *uSource, MumbleProto::RequestBlob &msg) {
	MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);

	int ntextures = msg.session_texture_size();
	int ncomments = msg.session_comment_size();
	int ndescriptions = msg.channel_description_size();

	if (ndescriptions) {
		MumbleProto::ChannelState mpcs;
		for (int i=0;i<ndescriptions;++i) {
			int id = msg.channel_description(i);
			Channel *c = qhChannels.value(id);
			if (c && ! c->qsDesc.isEmpty()) {
				mpcs.set_channel_id(id);
				mpcs.set_description(u8(c->qsDesc));
				sendMessage(uSource, mpcs);
			}
		}
	}
	if (ntextures || ncomments) {
		MumbleProto::UserState mpus;
		for (int i=0;i<ntextures;++i) {
			int session = msg.session_texture(i);
			ServerUser *su = qhUsers.value(session);
			if (su && ! su->qbaTexture.isEmpty()) {
				mpus.set_session(session);
				mpus.set_texture(blob(su->qbaTexture));
				sendMessage(uSource, mpus);
			}
		}
		if (ntextures)
			mpus.clear_texture();
		for (int i=0;i<ncomments;++i) {
			int session = msg.session_comment(i);
			ServerUser *su = qhUsers.value(session);
			if (su && ! su->qsComment.isEmpty()) {
				mpus.set_session(session);
				mpus.set_comment(u8(su->qsComment));
				sendMessage(uSource, mpus);
			}
		}
	}
}

void Server::msgServerConfig(ServerUser *, MumbleProto::ServerConfig &) {
}

void Server::msgSuggestConfig(ServerUser *, MumbleProto::SuggestConfig &) {
}
