aboutsummaryrefslogtreecommitdiffstats
/*
 * Copyright (c) 2016-2017 ChatLounge IRC Network Development Team
 *     (http://www.chatlounge.net/)
 *
 *     Provides the TEMPLATEVHOST command for GroupServ.  (Un)set
 * optional vhost offers for users based on group membership if the
 * membership matches an existing template for the group.
 *
 *
 */

#include "atheme.h"
#include "groupserv.h"
#include "template.h"
#include "../hostserv/hostserv.h"

DECLARE_MODULE_V1
(
	"groupserv/templatevhost", false, _modinit, _moddeinit,
	PACKAGE_STRING,
	VENDOR_STRING
);

bool hostserv_loaded = false;

static bool *(*allow_vhost_change)(sourceinfo_t *si, myuser_t *target, bool shownotice) = NULL;
static unsigned int (*get_hostsvs_req_time)(void) = NULL;
static bool *(*get_hostsvs_limit_first_req)(void) = NULL;

static void gs_cmd_templatevhost(sourceinfo_t *si, int parc, char *parv[]);

command_t gs_templatevhost = { "TEMPLATEVHOST", N_("Manipulates available vhosts to group members based on template."),
		AC_NONE, 3, gs_cmd_templatevhost, { .path = "groupserv/templatevhost" } };

void _modinit(module_t *m)
{
	use_groupserv_main_symbols(m);

	if (module_request("hostserv/main"))
	{
		allow_vhost_change = module_locate_symbol("hostserv/main", "allow_vhost_change");
		get_hostsvs_req_time = module_locate_symbol("hostserv/main", "get_hostsvs_req_time");
		get_hostsvs_limit_first_req = module_locate_symbol("hostserv/main", "get_hostsvs_limit_first_req");
		hostserv_loaded = true;
	}
	else
		hostserv_loaded = false;

	service_named_bind_command("groupserv", &gs_templatevhost);
}

void _moddeinit(module_unload_intent_t intent)
{
	service_named_unbind_command("groupserv", &gs_templatevhost);
}

static void gs_cmd_templatevhost(sourceinfo_t *si, int parc, char *parv[])
{
	bool operoverride = false;
	metadata_t *md, *md2, *md3, *md4;
	mygroup_t *mg;
	mowgli_node_t *n;
	groupacs_t *ga;
	char *group = parv[0];
	char *target = parv[1];
	char *vhost = parv[2];
	char *p, *q, *r;
	char *s;
	char newstr[400], ss[60];
	int i = 0, j = 0;
	char templatevhostmask[128];
	char templatevhostmetadataname[128];
	char *templatevhostold;
	char templatename[128];
	char description[300];
	bool limit_first_req = get_hostsvs_limit_first_req();
	unsigned int request_time = get_hostsvs_req_time();

	if (!group)
	{
		command_fail(si, fault_nosuch_target, _("You must specify a group name."));
		return;
	}

	mg = mygroup_find(group);
	if (!mg)
	{
		command_fail(si, fault_nosuch_target, _("Group \2%s\2 is not registered."), group);
		return;
	}

	if (target)
	{
		snprintf(templatevhostmetadataname, sizeof templatevhostmetadataname,
			"private:templatevhost-%s", target);
	}

	if (!target)
	{
		/* No template name specified, so list the current settings. */

		if (!(mg->flags & MG_PUBACL) && !groupacs_sourceinfo_has_flag(mg, si, GA_ACLVIEW))
		{
			if (has_priv(si, PRIV_GROUP_AUSPEX))
				operoverride = true;
			else
			{
				command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
				return;
			}
		}

		if (metadata_find(mg, "private:close:closer") && !has_priv(si, PRIV_GROUP_AUSPEX))
		{
			command_fail(si, fault_noprivs, _("\2%s\2 is closed."), group);
			return;
		}

		md = metadata_find(mg, "private:templates");

		if (md != NULL)
		{
			command_success_nodata(si, "%-20s %s", _("Name"), _("V-Host Offer"));
			command_success_nodata(si, "%-20s %s", "--------------------", "----------");

			p = md->value;
			while (p != NULL)
			{
				struct tm tm;
				char moddate[64];
				time_t modtime;

				while (*p == ' ')
					p++;
				q = strchr(p, '=');
				if (q == NULL)
					break;
				r = strchr(q, ' ');
				snprintf(templatevhostmetadataname, sizeof templatevhostmetadataname,
					"private:templatevhost-%-0.*s", (int)(q - p), p);

				md2 = metadata_find(mg, templatevhostmetadataname);

				if (md2 != NULL)
				{
					snprintf(templatevhostmask, sizeof templatevhostmask,
						"%s", (char *)md2->value);

					while(templatevhostmask[i] != '|')
						i++;
				}
				else
				{
					snprintf(templatevhostmask, sizeof templatevhostmask, "<None>");
					i = 6;
				}

				while (p[j] != '=')
					j++;

				//*s = '\0';

				snprintf(templatename, 128, "%.*s", j, p);

				modtime = get_group_template_vhost_expiry(mg, templatename);

				tm = *localtime(&modtime);
				strftime(moddate, sizeof moddate, TIME_FORMAT, &tm);

				//command_success_nodata(si, "%-20.*s %s", (int)(q - p), p, md2 == NULL ? "<None>" : (char *)md2->value);
				if (md2 == NULL)
				{
					command_success_nodata(si, "%-20.*s <None>", (int)(q - p), p);
				}
				else
				{
					command_success_nodata(si, "%-20.*s %.*s [modified %s ago on %s]", (int)(q - p), p, i, templatevhostmask,
						time_ago(get_group_template_vhost_expiry(mg, templatename)),
						moddate);

					MOWGLI_ITER_FOREACH(n, mg->acs.head)
					{
						ga = n->data;

						if (templatename == NULL)
							continue;

						if (get_group_template_name(mg, ga->flags) == NULL)
							continue;

						if (!strcasecmp(templatename, get_group_template_name(mg, ga->flags)))
						{
							md4 = metadata_find(ga->mt, "private:usercloak");

							if (md4 == NULL)
								continue;

							md3 = metadata_find(ga->mt, "private:usercloak-timestamp");

							char *host = (char *)get_group_template_vhost_by_flags(mg, ga->flags);

							if (strstr(host, "$account"))
								replace(host, BUFSIZE, "$account", entity(ga->mt)->name);

							if (strcasecmp(md4->value, host))
								continue;

							if (md3 == NULL)
								command_success_nodata(si, "- %s (%s)", ga->mt->name);
							else
								command_success_nodata(si, "- %-18s [set %s ago]", ga->mt->name,
									time_ago(atoi(md3->value)));
						}
					}

				}
				//command_success_nodata(si, "%-20.*s %s", (int)(q - p), p, md2 == NULL ? "<None>" : strtok((char *)md2->value, "|"));
				//command_success_nodata(si, "Time: %s", strtok(NULL, "|"));

				//md2->value = s;

				p = r;

				i = 0; j = 0;
			}

			command_success_nodata(si, "%-20s %s", "--------------------", "----------");
			command_success_nodata(si, _("End of \2%s\2 TEMPLATEVHOST listing."), entity(mg)->name);
		}
		else
			command_success_nodata(si, _("No template vhost offers have been set on group: \2%s\2"), entity(mg)->name);

		if (operoverride)
			logcommand(si, CMDLOG_ADMIN, "TEMPLATEVHOST: \2%s\2 (oper override)", entity(mg)->name);
		else
			logcommand(si, CMDLOG_GET, "TEMPLATEVHOST: \2%s\2", entity(mg)->name);
	}
	else if (!vhost) /* Template name but no vhost, try to delete the template vhost. */
	{
		char oldvhost[80], *saveptr;

		// Code to check for GA_SET or oper override.
		if (!groupacs_sourceinfo_has_flag(mg, si, GA_SET) && !groupacs_sourceinfo_has_flag(mg, si, GA_FOUNDER))
		{
			if (has_priv(si, PRIV_GROUP_ADMIN))
				operoverride = true;
			else
			{
				command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
				return;
			}
		}

		md = metadata_find(mg, "private:templates");

		if (md == NULL)
		{
			command_fail(si, fault_nosuch_key, _("No templates have been set on: \2%s\2"), entity(mg)->name);
			return;
		}

		md = metadata_find(mg, templatevhostmetadataname);

		if (md == NULL)
		{
			command_success_nodata(si, _("No vhost offer has been set on template \2%s\2 in group: \2%s\2"),
				target, entity(mg)->name);
			return;
		}

		/* Remove any matching vhost settings for users; if so, remove them. */
		MOWGLI_ITER_FOREACH(n, mg->acs.head)
		{
			ga = n->data;
			md2 = metadata_find(ga->mt, "private:usercloak");

			if (md2 == NULL)
				continue;

			char usertemplatevhost[128];

			if (get_group_template_vhost_by_flags(mg, ga->flags) == NULL)
				continue;

			myuser_t *mu = myuser_find_by_nick(entity(ga->mt)->name);

			mowgli_strlcpy(usertemplatevhost, get_group_template_vhost_by_flags(mg, ga->flags), BUFSIZE);

			if (strstr(usertemplatevhost, "$account"))
				replace(usertemplatevhost, BUFSIZE, "$account", entity(ga->mt)->name);

			if (!strcasecmp(usertemplatevhost, (char *)md2->value))
			{
				hs_sethost_all(mu, NULL, get_source_name(si));
				// Send notice/memo to affected users.
				do_sethost_all(mu, NULL);
				logcommand(si, CMDLOG_ADMIN, "VHOST:REMOVE: \2%s\2 because the template vhost offer for \2%s\2 on \2%s\2 has been removed.",
					entity(ga->mt)->name, target, entity(mg)->name);
			}
		}

		mowgli_strlcpy(oldvhost, strtok_r((char *)md->value, "|", &saveptr), sizeof oldvhost);

		logcommand(si, CMDLOG_GET, "TEMPLATEVHOST: \2%s\2 (formerly \2%s\2) removed on \2%s\2 %s",
			target, oldvhost, entity(mg)->name, operoverride ? "(override)" : "");
		command_success_nodata(si, _("The vhost offer for the template \2%s\2 on the group \2%s\2 has been dropped (formerly: \2%s\2)."),
			target, entity(mg)->name, oldvhost);

		snprintf(description, sizeof description, "Vhost offer for template \2%s\2 removed, formerly: \2%s\2",
			target, oldvhost);
		notify_group_misc_change(si, mg, description);

		metadata_delete(mg, templatevhostmetadataname);
	}
	else /* Template name and vhost specified.  Set the template to the new vhost mask. */
	{
		char vhoststring[300];

		/* Check for GA_SET, GA_FOUNDER, or oper override. */
		if (!groupacs_sourceinfo_has_flag(mg, si, GA_SET) && !groupacs_sourceinfo_has_flag(mg, si, GA_FOUNDER))
		{
			if (has_priv(si, PRIV_ADMIN) || has_priv(si, PRIV_GROUP_ADMIN) || has_priv(si, PRIV_USER_VHOSTOVERRIDE))
				operoverride = true;
			else
			{
				command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
				return;
			}
		}

		md = metadata_find(mg, "private:templates");

		if (md == NULL)
		{
			command_fail(si, fault_nosuch_key, _("No templates have been set on: \2%s\2."), entity(mg)->name);
			return;
		}

		/* Check if the template exists. */
		if (!get_group_item(md->value, target))
		{
			command_fail(si, fault_nosuch_target, _("The template %s does not exist so you may not set a vhost offer for it."), target);
			return;
		}

		/* Check if the template vhost even exists. */

		md = metadata_find(mg, templatevhostmetadataname);

		snprintf(vhoststring, BUFSIZE, "%s|%u", vhost, (unsigned int)CURRTIME);

		/* Update existing users to the new vhost setting, if the user had an old matching vhost. */
		MOWGLI_ITER_FOREACH(n, mg->acs.head)
		{
			ga = n->data;
			md2 = metadata_find(ga->mt, "private:usercloak");

			if (md2 == NULL)
				continue;

			char usertemplatevhost[128];

			if (get_group_template_vhost_by_flags(mg, ga->flags) == NULL)
				continue;

			myuser_t *mu = myuser_find_by_nick(entity(ga->mt)->name);

			mowgli_strlcpy(usertemplatevhost, get_group_template_vhost_by_flags(mg, ga->flags), BUFSIZE);

			if (strstr(usertemplatevhost, "$account"))
				replace(usertemplatevhost, BUFSIZE, "$account", entity(ga->mt)->name);

			if (!strcasecmp(usertemplatevhost, (char *)md2->value))
			{
				char newusertemplatevhost[128];

				metadata_t *md_vhosttime = metadata_find(mu, "private:usercloak-timestamp");

				/* 86,400 seconds per day */
				if (limit_first_req && md_vhosttime == NULL && (CURRTIME - mu->registered > (request_time * 86400)))
				{
					hs_sethost_all(mu, NULL, get_source_name(si));
					// Send notice/memo to affected user.
					logcommand(si, CMDLOG_ADMIN, "VHOST:REMOVE: \2%s\2 by virtue of early template vhost offer change on: \2%s\2", entity(ga->mt)->name, entity(mg)->name);
					do_sethost_all(mu, NULL); // restore user vhost from user host
					continue;
				}

				time_t vhosttime = atoi(md_vhosttime->value);

				if (vhosttime + (request_time * 86400) > CURRTIME)
				{
					hs_sethost_all(mu, NULL, get_source_name(si));
					// Send notice/memo to affected user.
					logcommand(si, CMDLOG_ADMIN, "VHOST:REMOVE: \2%s\2 by virtue of early template vhost offer change on: \2%s\2", entity(ga->mt)->name, entity(mg)->name);
					do_sethost_all(mu, NULL); // restore user vhost from user host
					continue;
				}

				mowgli_strlcpy(newusertemplatevhost, vhost, BUFSIZE);

				if (strstr(newusertemplatevhost, "$account"))
					replace(newusertemplatevhost, BUFSIZE, "$account", entity(ga->mt)->name);

				hs_sethost_all(mu, newusertemplatevhost, get_source_name(si));
				// Send notice/memo to affected users (one per cycle).
				do_sethost_all(mu, newusertemplatevhost);
				logcommand(si, CMDLOG_ADMIN, "VHOST:REMOVE: \2%s\2 because the template vhost offer for \2%s\2 on \2%s\2 has been removed.",
					entity(ga->mt)->name, target, entity(mg)->name);
			}
		}

		if (md == NULL)
		{
			metadata_add(mg, templatevhostmetadataname, vhoststring);
			logcommand(si, CMDLOG_GET, "TEMPLATEVHOST: \2%s\2 for \2%s\2 added on \2%s\2 %s", vhost, target, entity(mg)->name,
				operoverride ? "(override)" : "");
			command_success_nodata(si, _("Added the vhost offer %s for the template %s in group: \2%s\2"), vhost, target, entity(mg)->name);

			snprintf(description, sizeof description, "Vhost offer for template \2%s\2 added: \2%s\2",
				target, vhost);
		}
		else
		{
			metadata_delete(mg, templatevhostmetadataname);
			metadata_add(mg, templatevhostmetadataname, vhoststring);
			logcommand(si, CMDLOG_GET, "TEMPLATEVHOST: \2%s\2 changed to \2%s\2 on \2%s\2 %s", target, vhost, entity(mg)->name,
				operoverride ? "(override)" : "");
			command_success_nodata(si, _("Changed the vhost offer for template %s to %s in group: \2%s\2"), target, vhost, entity(mg)->name);

			snprintf(description, sizeof description, "Vhost offer for template \2%s\2 changed to: \2%s\2",
				target, vhost);
		}

		notify_group_misc_change(si, mg, description);
	}
}