/*
* Copyright (c) 2016-2017 ChatLounge IRC Network Development Team
* (http://www.chatlounge.net/)
*
* Provides the TEMPLATE command for GroupServ, similar to
* the way templates for ChanServ flags work.
*
*/
#include "atheme.h"
#include "groupserv.h"
#include "template.h"
DECLARE_MODULE_V1
(
"groupserv/template", false, _modinit, _moddeinit,
PACKAGE_STRING,
VENDOR_STRING
);
static void gs_cmd_template(sourceinfo_t *si, int parc, char *parv[]);
command_t gs_template = { "TEMPLATE", N_("Manipulates predefined sets of flags."),
AC_NONE, 3, gs_cmd_template, { .path = "groupserv/template" } };
void _modinit(module_t *m)
{
use_groupserv_main_symbols(m);
service_named_bind_command("groupserv", &gs_template);
}
void _moddeinit(module_unload_intent_t intent)
{
service_named_unbind_command("groupserv", &gs_template);
}
static int display_template(const char *key, void *data, void *privdata)
{
sourceinfo_t *si = privdata;
default_group_template_t *def_t = (default_group_template_t *)data;
command_success_nodata(si, "%-20s %s", key, gflags_tostr(ga_flags, def_t->flags));
return 0;
}
static void list_generic_flags(sourceinfo_t *si)
{
command_success_nodata(si, "%-20s %s", _("Name"), _("Flags"));
command_success_nodata(si, "%-20s %s", "--------------------", "-----");
mowgli_patricia_foreach(global_group_template_dict, display_template, si);
command_success_nodata(si, "%-20s %s", "--------------------", "-----");
command_success_nodata(si, _("End of network wide template list."));
}
static void gs_cmd_template(sourceinfo_t *si, int parc, char *parv[])
{
metadata_t *md;
bool operoverride = false, changegroupacs = false;
size_t l;
char *group = parv[0];
char *target = parv[1];
mygroup_t *mg;
unsigned int oldflags, newflags = 0, addflags, removeflags, restrictflags;
char *p, *q, *r;
char ss[40], newstr[400], description[300];
bool found, denied;
if (!group)
{
//list_generic_flags(si);
//logcommand(si, CMDLOG_GET, "TEMPLATE");
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);
}
if (!target)
{
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"), _("Flags"));
command_success_nodata(si, "%-20s %s", "--------------------", "-----");
p = md->value;
while (p != NULL)
{
while (*p == ' ')
p++;
q = strchr(p, '=');
if (q == NULL)
break;
r = strchr(q, ' ');
command_success_nodata(si, "%-20.*s %.*s", (int)(q - p), p, r != NULL ? (int)(r - q - 1) : (int)strlen(q + 1), q + 1);
p = r;
}
command_success_nodata(si, "%-20s %s", "--------------------", "-----");
command_success_nodata(si, _("End of \2%s\2 TEMPLATE listing."), entity(mg)->name);
}
else
command_success_nodata(si, _("No templates set on group: \2%s\2"), entity(mg)->name);
if (operoverride)
logcommand(si, CMDLOG_ADMIN, "TEMPLATE: \2%s\2 (oper override)", entity(mg)->name);
else
logcommand(si, CMDLOG_GET, "TEMPLATE: \2%s\2", entity(mg)->name);
}
else
{
char *flagstr = parv[2];
if (!si->smu)
{
command_fail(si, fault_noprivs, _("You are not logged in."));
return;
}
/* founder may always set flags -- jilles */
restrictflags = groupacs_sourceinfo_flags(mg, si);
if (restrictflags & GA_FOUNDER)
restrictflags = GA_ALL_ALL;
else
{
if (!(restrictflags & GA_FLAGS))
{
command_fail(si, fault_noprivs, _("You are not authorized to execute this command."));
return;
}
restrictflags = allow_gflags(mg, restrictflags);
}
if (metadata_find(mg, "private:close:closer"))
{
command_fail(si, fault_noprivs, _("\2%s\2 is closed."), group);
return;
}
if (!target || !flagstr)
{
command_fail(si, fault_needmoreparams, _("Usage: TEMPLATE %s [target flags]"), group);
return;
}
if (*target == '+' || *target == '-' || *target == '=')
{
command_fail(si, fault_badparams, _("Invalid template name \2%s\2."), target);
return;
}
l = strlen(target);
if (*flagstr == '!' && (flagstr[1] == '+' || flagstr[1] == '-' || flagstr[1] == '='))
{
changegroupacs = true;
flagstr++;
}
if (*flagstr == '+' || *flagstr == '-' || *flagstr == '=')
{
gflags_make_bitmasks(flagstr, &addflags, &removeflags);
if (addflags == 0 && removeflags == 0)
{
command_fail(si, fault_badparams, _("No valid flags given."));
return;
}
/* Prevent adding +b to group templates. Rationale:
*
* +b serves no purpose outside of public groups anyone can join.
* However, adding a template with +b could be used for griefing
* other users who are using a group template-specific vhost offer,
* which would be visible in all channels.
*
* Maybe implement the restriction in TEMPLATEVHOST instead?
* - Ben
*/
if (addflags & GA_BAN)
{
command_fail(si, fault_badparams, _("You may not add +b to group templates."));
return;
}
}
else
{
/* allow copying templates as well */
addflags = get_group_template_flags(mg, flagstr);
if (addflags == 0)
{
command_fail(si, fault_nosuch_key, _("Invalid template name given."));
return;
}
removeflags = GA_ALL_ALL & ~addflags;
}
/* if adding +F, also add +f */
if (addflags & GA_FOUNDER)
addflags |= GA_FLAGS, removeflags &= ~GA_FLAGS;
/* if removing +f, also remove +F */
else if (removeflags & GA_FLAGS)
removeflags |= GA_FOUNDER, addflags &= ~GA_FOUNDER;
found = denied = false;
oldflags = 0;
md = metadata_find(mg, "private:templates");
if (md != NULL)
{
p = md->value;
mowgli_strlcpy(newstr, p, sizeof newstr);
while (p != NULL)
{
while (*p == ' ')
p++;
q = strchr(p, '=');
if (q == NULL)
break;
r = strchr(q, ' ');
if (r != NULL && r < q)
break;
mowgli_strlcpy(ss, q, sizeof ss);
if (r != NULL && r - q < (int)(sizeof ss - 1))
{
ss[r - q] = '\0';
}
if ((size_t)(q - p) == l && !strncasecmp(target, p, l))
{
found = true;
oldflags = gs_flags_parser(ss, 0, 0);
oldflags &= GA_ALL_ALL;
addflags &= ~oldflags;
removeflags &= oldflags & ~addflags;
/* no change? */
if ((addflags | removeflags) == 0)
break;
/* attempting to add bad flag? */
/* attempting to remove bad flag? */
/* attempting to manipulate something with more privs? */
if (~restrictflags & addflags ||
~restrictflags & removeflags ||
~restrictflags & oldflags)
{
denied = true;
break;
}
newflags = (oldflags | addflags) & ~removeflags;
if (newflags == 0)
{
if (p == md->value)
/* removing first entry,
* zap the space after it */
mowgli_strlcpy(newstr, r != NULL ? r + 1 : "", sizeof newstr);
else
{
/* otherwise, zap the space before it */
p--;
mowgli_strlcpy(newstr + (p - md->value), r != NULL ? r : "", sizeof newstr - (p - md->value));
}
}
else
snprintf(newstr + (p - md->value), sizeof newstr - (p - md->value), "%s=%s%s", target, bitmask_to_gflags(newflags), r != NULL ? r : "");
break;
}
p = r;
}
}
if (!found)
{
removeflags = 0;
newflags = addflags;
if ((addflags | removeflags) == 0)
;
else if (~restrictflags & addflags ||
~restrictflags & removeflags ||
~restrictflags & oldflags)
denied = true;
else if (md != NULL)
snprintf(newstr + strlen(newstr), sizeof newstr - strlen(newstr), " %s=%s", target, bitmask_to_gflags(newflags));
else
snprintf(newstr, sizeof newstr, "%s=%s", target, bitmask_to_gflags(newflags));
}
if (oldflags == 0 && has_ctrl_chars(target))
{
command_fail(si, fault_badparams, _("Invalid template name \2%s\2."), target);
return;
}
if ((addflags | removeflags) == 0)
{
if (oldflags != 0)
command_fail(si, fault_nochange, _("Template \2%s\2 on \2%s\2 unchanged."), target, entity(mg)->name);
else
command_fail(si, fault_nosuch_key, _("No such template \2%s\2 on \2%s\2."), target, entity(mg)->name);
return;
}
if (denied)
{
command_fail(si, fault_noprivs, _("You are not allowed to set \2%s\2 on template \2%s\2 in \2%s\2."), bitmask_to_gflags2(addflags, removeflags), target, entity(mg)->name);
return;
}
if (strlen(newstr) >= 300)
{
command_fail(si, fault_toomany, _("Sorry, too many templates on \2%s\2."), entity(mg)->name);
return;
}
p = md != NULL ? md->value : NULL;
while (p != NULL)
{
while (*p == ' ')
p++;
q = strchr(p, '=');
if (q == NULL)
break;
r = strchr(q, ' ');
snprintf(ss,sizeof ss,"%.*s",r != NULL ? (int)(r - q - 1) : (int)strlen(q + 1), q + 1);
if (gs_flags_parser(ss, 0, 0) == newflags)
{
command_fail(si, fault_alreadyexists, _("The template \2%.*s\2 already has flags \2%.*s\2."), (int)(q - p), p, r != NULL ? (int)(r - q - 1) : (int)strlen(q + 1), q + 1);
return;
}
p = r;
}
if (newstr[0] == '\0')
metadata_delete(mg, "private:templates");
else
metadata_add(mg, "private:templates", newstr);
if (oldflags == 0)
{
command_success_nodata(si, _("Added template \2%s\2 with flags \2%s\2 in \2%s\2."),
target, bitmask_to_gflags(newflags), entity(mg)->name);
snprintf(description, sizeof description, "Added template \2%s\2 (new flags: %s)",
target, bitmask_to_gflags(newflags));
}
else if (newflags == 0)
{
command_success_nodata(si, _("Removed template \2%s\2 from \2%s\2."),
target, entity(mg)->name);
snprintf(description, sizeof description, "Removed template \2%s\2",
target);
}
else
{
command_success_nodata(si, _("Changed template \2%s\2 to \2%s\2 in \2%s\2."),
target, bitmask_to_gflags(newflags), entity(mg)->name);
snprintf(description, sizeof description, "Changed template \2%s\2 (new flags: %s)",
target, bitmask_to_gflags(newflags));
}
notify_group_misc_change(si, mg, description);
flagstr = bitmask_to_gflags2(addflags, removeflags);
if (changegroupacs)
{
mowgli_node_t *n, *tn;
groupacs_t *ga;
int changes = 0, founderskipped = 0;
char flagstr2[128];
MOWGLI_ITER_FOREACH_SAFE(n, tn, mg->acs.head)
{
ga = n->data;
if (ga->flags != oldflags)
continue;
if ((addflags | removeflags) & GA_FOUNDER)
{
founderskipped++;
continue;
}
changes++;
groupacs_modify_simple(ga, newflags, ~newflags);
myuser_t *tmu = myuser_find(ga->mt->name);
notify_target_acl_change(si, tmu, mg, flagstr, newflags);
notify_group_acl_change(si, tmu, mg, flagstr, newflags);
groupacs_close(ga);
}
logcommand(si, CMDLOG_SET, "TEMPLATE: \2%s\2 \2%s\2 !\2%s\2 (\2%d\2 change%s)", entity(mg)->name, target, flagstr, changes, changes == 1 ? "" : "s");
mowgli_strlcpy(flagstr2, flagstr, sizeof flagstr2);
command_success_nodata(si, _("%d access entr%s updated accordingly."), changes, changes == 1 ? "y" : "ies");
if (founderskipped)
command_success_nodata(si, _("Not updating %d access entries involving founder status. Please do it manually."), founderskipped);
}
else
logcommand(si, CMDLOG_SET, "TEMPLATE: \2%s\2 \2%s\2 \2%s\2", entity(mg)->name, target, flagstr);
}
}