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

	bool bNoBroadcast = false;
	Channel *root = qhChannels.value(0);

	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 (! 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 && (msg.texture().length() > 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;
		}
	}

	if ((pDstServerUser != uSource) && (msg.has_self_deaf() || msg.has_self_mute() || msg.has_texture() || msg.has_plugin_context() || msg.has_plugin_identity()))
		return;

	// Permission checks done. Now enact this.

	if (msg.has_texture()) {
		QByteArray qba = blob(msg.texture());
		if (uSource->iId > 0) {
			if (! setTexture(uSource->iId, qba))
				return;
		} else {
			hashAssign(uSource->qbaTexture, uSource->qbaTextureHash, qba);
		}
	}

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

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

	if (msg.has_plugin_context()) {
		bNoBroadcast = true;
		uSource->ssContext = msg.plugin_context();
	}

	if (msg.has_plugin_identity()) {
		bNoBroadcast = true;
		uSource->qsIdentity = u8(msg.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);
		}
	}



	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)));
	}

	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)));
	}

	if (msg.has_user_id()) {
		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;
			msg.set_user_id(id);
		} else {
			msg.clear_user_id();
		}
	}

	if (! bNoBroadcast) {
		if (msg.has_texture() && (pDstServerUser->qbaTexture.length() >= 4) && (qFromBigEndian<unsigned int>(reinterpret_cast<const unsigned char *>(pDstServerUser->qbaTexture.constData())) != 600 * 60 * 4)) {
			msg.clear_texture();
			sendAll(msg, ~ 0x010202);
			msg.set_texture(blob(pDstServerUser->qbaTexture));
		} else {
			sendAll(msg, ~ 0x010202);
		}
		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);
	}

	emit userStateChanged(pDstServerUser);
}