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