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)
				g->qsAdd << group.add(j);
			for (int j=0;j<group.remove_size();++j)
				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);
			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));
	}
}