diff --git a/cs_aquiet.c b/cs_aquiet.c new file mode 100644 index 0000000..e3a9d54 --- /dev/null +++ b/cs_aquiet.c @@ -0,0 +1,861 @@ +/* + * Copyright (c) 2005 William Pitcock, et al. + * Rights to this code are as documented in doc/LICENSE. + * + * This file contains code for the CService AQUIET functions. + * + */ + +//Quora's AQUIET module +//Mainly code stolen from other parts of atheme + +#include "atheme.h" + +static void cs_cmd_aquiet(sourceinfo_t *si, int parc, char *parv[]); +static void cs_cmd_aquiet_add(sourceinfo_t *si, int parc, char *parv[]); +static void cs_cmd_aquiet_del(sourceinfo_t *si, int parc, char *parv[]); +static void cs_cmd_aquiet_list(sourceinfo_t *si, int parc, char *parv[]); +static void cs_cmd_aquiet_onjoin(hook_channel_joinpart_t *hdata); + +static void aquiet_timeout_check(void *arg); +static void aquietdel_list_create(void *arg); + +int aquiet_flag; + +DECLARE_MODULE_V1 +( + "contrib/cs_aquiet", false, _modinit, _moddeinit, + PACKAGE_STRING, + "Atheme Development Group and Quora" +); + +command_t cs_aquiet = { "AQUIET", N_("Manipulates a channel's AQUIET list."), + AC_NONE, 4, cs_cmd_aquiet, { .path = "cservice/aquiet" } }; +command_t cs_aquiet_add = { "ADD", N_("Adds a channel AQUIET."), + AC_NONE, 4, cs_cmd_aquiet_add, { .path = "" } }; +command_t cs_aquiet_del = { "DEL", N_("Deletes a channel AQUIET."), + AC_NONE, 3, cs_cmd_aquiet_del, { .path = "" } }; +command_t cs_aquiet_list = { "LIST", N_("Displays a channel's AQUIET list."), + AC_NONE, 2, cs_cmd_aquiet_list, { .path = "" } }; + +typedef struct { + time_t expiration; + + myentity_t *entity; + mychan_t *chan; + + char host[NICKLEN + USERLEN + HOSTLEN + 4]; + + mowgli_node_t node; +} aquiet_timeout_t; + +time_t aquietdel_next; +mowgli_list_t aquietdel_list; +mowgli_patricia_t *cs_aquiet_cmds; +mowgli_eventloop_timer_t *aquiet_timeout_check_timer = NULL; + +static aquiet_timeout_t *aquiet_add_timeout(mychan_t *mc, myentity_t *mt, const char *host, time_t expireson); + +mowgli_heap_t *aquiet_timeout_heap; + +void _modinit(module_t *m) +{ + service_named_bind_command("chanserv", &cs_aquiet); + + if ((aquiet_flag = flags_associate('Q', 0, false, "quieted")) == 0) { + slog(LG_ERROR, "contrib/cs_aquiet: Inserting +Q into flagset failed."); + } + + cs_aquiet_cmds = mowgli_patricia_create(strcasecanon); + + /* Add sub-commands */ + command_add(&cs_aquiet_add, cs_aquiet_cmds); + command_add(&cs_aquiet_del, cs_aquiet_cmds); + command_add(&cs_aquiet_list, cs_aquiet_cmds); + + hook_add_event("channel_join"); + hook_add_channel_join(cs_cmd_aquiet_onjoin); + + aquiet_timeout_heap = mowgli_heap_create(sizeof(aquiet_timeout_t), 512, BH_NOW); + + if (aquiet_timeout_heap == NULL) { + m->mflags = MODTYPE_FAIL; + return; + } + + mowgli_timer_add_once(base_eventloop, "aquietdel_list_create", aquietdel_list_create, NULL, 0); +} + +void _moddeinit(module_unload_intent_t intent) +{ + service_named_unbind_command("chanserv", &cs_aquiet); + + /* Delete sub-commands */ + command_delete(&cs_aquiet_add, cs_aquiet_cmds); + command_delete(&cs_aquiet_del, cs_aquiet_cmds); + command_delete(&cs_aquiet_list, cs_aquiet_cmds); + + hook_del_channel_join(cs_cmd_aquiet_onjoin); + + mowgli_heap_destroy(aquiet_timeout_heap); + mowgli_patricia_destroy(cs_aquiet_cmds, NULL, NULL); +} + +static void clear_bans_matching_entity(mychan_t *mc, myentity_t *mt) +{ + mowgli_node_t *n; + myuser_t *tmu; + + if (mc->chan == NULL) + return; + + if (!isuser(mt)) + return; + + tmu = user(mt); + + MOWGLI_ITER_FOREACH(n, tmu->logins.head) + { + user_t *tu; + mowgli_node_t *it, *itn; + + char hostbuf2[BUFSIZE]; + + tu = n->data; + + snprintf(hostbuf2, BUFSIZE, "%s!%s@%s", tu->nick, tu->user, tu->vhost); + for (it = next_matching_ban(mc->chan, tu, 'q', mc->chan->bans.head); it != NULL; it = next_matching_ban(mc->chan, tu, 'q', itn)) + { + chanban_t *cb; + + itn = it->next; + cb = it->data; + + modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask); + chanban_delete(cb); + } + } + + modestack_flush_channel(mc->chan); +} + +static void cs_cmd_aquiet(sourceinfo_t *si, int parc, char *parv[]) +{ + char *chan; + char *cmd; + command_t *c; + + if (parc < 2) + { + command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AQUIET"); + command_fail(si, fault_needmoreparams, _("Syntax: AQUIET <#channel> [parameters]")); + return; + } + + if (parv[0][0] == '#') + chan = parv[0], cmd = parv[1]; + else if (parv[1][0] == '#') + cmd = parv[0], chan = parv[1]; + else + { + command_fail(si, fault_badparams, STR_INVALID_PARAMS, "AQUIET"); + command_fail(si, fault_badparams, _("Syntax: AQUIET <#channel> [parameters]")); + return; + } + + c = command_find(cs_aquiet_cmds, cmd); + if (c == NULL) + { + command_fail(si, fault_badparams, _("Invalid command. Use \2/%s%s help\2 for a command listing."), (ircd->uses_rcommand == false) ? "msg " : "", chansvs.me->disp); + return; + } + + parv[1] = chan; + command_exec(si->service, si, c, parc - 1, parv + 1); +} + +void cs_cmd_aquiet_add(sourceinfo_t *si, int parc, char *parv[]) +{ + myentity_t *mt; + mychan_t *mc; + hook_channel_acl_req_t req; + chanacs_t *ca, *ca2; + char *chan = parv[0]; + long duration; + char expiry[512]; + char *s; + char *target; + char *uname; + char *token; + char *treason, reason[BUFSIZE]; + + target = parv[1]; + token = strtok(parv[2], " "); + + if (!target) + { + command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AQUIET"); + command_fail(si, fault_needmoreparams, _("Syntax: AQUIET <#channel> ADD [!P|!T ] [reason]")); + return; + } + + mc = mychan_find(chan); + if (!mc) + { + command_fail(si, fault_nosuch_target, _("Channel \2%s\2 is not registered."), chan); + return; + } + + if (metadata_find(mc, "private:close:closer")) + { + command_fail(si, fault_noprivs, _("\2%s\2 is closed."), chan); + return; + } + + /* A duration, reason or duration and reason. */ + if (token) + { + if (!strcasecmp(token, "!P")) /* A duration [permanent] */ + { + duration = 0; + treason = strtok(NULL, ""); + + if (treason) + mowgli_strlcpy(reason, treason, BUFSIZE); + else + reason[0] = 0; + } + else if (!strcasecmp(token, "!T")) /* A duration [temporary] */ + { + s = strtok(NULL, " "); + treason = strtok(NULL, ""); + + if (treason) + mowgli_strlcpy(reason, treason, BUFSIZE); + else + reason[0] = 0; + + if (s) + { + duration = (atol(s) * 60); + while (isdigit(*s)) + s++; + if (*s == 'h' || *s == 'H') + duration *= 60; + else if (*s == 'd' || *s == 'D') + duration *= 1440; + else if (*s == 'w' || *s == 'W') + duration *= 10080; + else if (*s == '\0') + ; + else + duration = 0; + + if (duration == 0) + { + command_fail(si, fault_badparams, _("Invalid duration given.")); + command_fail(si, fault_badparams, _("Syntax: AQUIET <#channel> ADD [!P|!T ] [reason]")); + return; + } + } + else + { + command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AQUIET ADD"); + command_fail(si, fault_needmoreparams, _("Syntax: AQUIET <#channel> ADD [!P|!T ] [reason]")); + return; + } + } + else + { + duration = chansvs.akick_time; + mowgli_strlcpy(reason, token, BUFSIZE); + treason = strtok(NULL, ""); + + if (treason) + { + mowgli_strlcat(reason, " ", BUFSIZE); + mowgli_strlcat(reason, treason, BUFSIZE); + } + } + } + else + { /* No reason and no duration */ + duration = chansvs.akick_time; + reason[0] = 0; + } + + if ((chanacs_source_flags(mc, si) & (CA_FLAGS | CA_REMOVE)) != (CA_FLAGS | CA_REMOVE)) + { + command_fail(si, fault_noprivs, _("You are not authorized to perform this operation.")); + return; + } + + mt = myentity_find_ext(target); + if (!mt) + { + uname = pretty_mask(target); + if (uname == NULL) + uname = target; + + /* we might be adding a hostmask */ + if (!validhostmask(uname)) + { + command_fail(si, fault_badparams, _("\2%s\2 is neither a nickname nor a hostmask."), uname); + return; + } + + uname = collapse(uname); + + ca = chanacs_find_host_literal(mc, uname, 0); + if (ca != NULL) + { + if (ca->level & aquiet_flag) + command_fail(si, fault_nochange, _("\2%s\2 is already on the AQUIET list for \2%s\2"), uname, mc->name); + else + command_fail(si, fault_alreadyexists, _("\2%s\2 already has flags \2%s\2 on \2%s\2"), uname, bitmask_to_flags(ca->level), mc->name); + return; + } + + ca = chanacs_find_host(mc, uname, aquiet_flag); + if (ca != NULL) + { + command_fail(si, fault_nochange, _("The more general mask \2%s\2 is already on the AQUIET list for \2%s\2"), ca->host, mc->name); + return; + } + + /* new entry */ + ca2 = chanacs_open(mc, NULL, uname, true, entity(si->smu)); + if (chanacs_is_table_full(ca2)) + { + command_fail(si, fault_toomany, _("Channel %s access list is full."), mc->name); + chanacs_close(ca2); + return; + } + + req.ca = ca2; + req.oldlevel = ca2->level; + + chanacs_modify_simple(ca2, aquiet_flag, 0); + + req.newlevel = ca2->level; + + if (reason[0]) + metadata_add(ca2, "reason", reason); + + if (duration > 0) + { + aquiet_timeout_t *timeout; + time_t expireson = ca2->tmodified+duration; + + snprintf(expiry, sizeof expiry, "%ld", (duration > 0L ? expireson : 0L)); + metadata_add(ca2, "expires", expiry); + + verbose(mc, "\2%s\2 added \2%s\2 to the AQUIET list, expires in %s.", get_source_name(si), uname,timediff(duration)); + logcommand(si, CMDLOG_SET, "AQUIET:ADD: \2%s\2 on \2%s\2, expires in %s.", uname, mc->name,timediff(duration)); + command_success_nodata(si, _("AQUIET on \2%s\2 was successfully added for \2%s\2 and will expire in %s."), uname, mc->name,timediff(duration) ); + + timeout = aquiet_add_timeout(mc, NULL, uname, expireson); + + if (aquietdel_next == 0 || aquietdel_next > timeout->expiration) + { + if (aquietdel_next != 0) + mowgli_timer_destroy(base_eventloop, aquiet_timeout_check_timer); + + aquietdel_next = timeout->expiration; + aquiet_timeout_check_timer = mowgli_timer_add_once(base_eventloop, "aquiet_timeout_check", aquiet_timeout_check, NULL, aquietdel_next - CURRTIME); + } + } + else + { + verbose(mc, "\2%s\2 added \2%s\2 to the AQUIET list.", get_source_name(si), uname); + logcommand(si, CMDLOG_SET, "AQUIET:ADD: \2%s\2 on \2%s\2", uname, mc->name); + + command_success_nodata(si, _("AQUIET on \2%s\2 was successfully added to the AQUIET list for \2%s\2."), uname, mc->name); + } + + hook_call_channel_acl_change(&req); + chanacs_close(ca2); + + return; + } + else + { + if ((ca = chanacs_find_literal(mc, mt, 0x0))) + { + if (ca->level & aquiet_flag) + command_fail(si, fault_nochange, _("\2%s\2 is already on the AQUIET list for \2%s\2"), mt->name, mc->name); + else + command_fail(si, fault_alreadyexists, _("\2%s\2 already has flags \2%s\2 on \2%s\2"), mt->name, bitmask_to_flags(ca->level), mc->name); + return; + } + + /* new entry */ + ca2 = chanacs_open(mc, mt, NULL, true, entity(si->smu)); + if (chanacs_is_table_full(ca2)) + { + command_fail(si, fault_toomany, _("Channel %s access list is full."), mc->name); + chanacs_close(ca2); + return; + } + + req.ca = ca2; + req.oldlevel = ca2->level; + + chanacs_modify_simple(ca2, aquiet_flag, 0); + + req.newlevel = ca2->level; + + if (reason[0]) + metadata_add(ca2, "reason", reason); + + if (duration > 0) + { + aquiet_timeout_t *timeout; + time_t expireson = ca2->tmodified+duration; + + snprintf(expiry, sizeof expiry, "%ld", (duration > 0L ? ca2->tmodified+duration : 0L)); + metadata_add(ca2, "expires", expiry); + + command_success_nodata(si, _("AQUIET on \2%s\2 was successfully added for \2%s\2 and will expire in %s."), mt->name, mc->name, timediff(duration)); + verbose(mc, "\2%s\2 added \2%s\2 to the AQUIET list, expires in %s.", get_source_name(si), mt->name, timediff(duration)); + logcommand(si, CMDLOG_SET, "AQUIET:ADD: \2%s\2 on \2%s\2, expires in %s", mt->name, mc->name, timediff(duration)); + + timeout = aquiet_add_timeout(mc, mt, mt->name, expireson); + + if (aquietdel_next == 0 || aquietdel_next > timeout->expiration) + { + if (aquietdel_next != 0) + mowgli_timer_destroy(base_eventloop, aquiet_timeout_check_timer); + + aquietdel_next = timeout->expiration; + aquiet_timeout_check_timer = mowgli_timer_add_once(base_eventloop, "aquiet_timeout_check", aquiet_timeout_check, NULL, aquietdel_next - CURRTIME); + } + } + else + { + command_success_nodata(si, _("AQUIET on \2%s\2 was successfully added to the AQUIET list for \2%s\2."), mt->name, mc->name); + + verbose(mc, "\2%s\2 added \2%s\2 to the AQUIET list.", get_source_name(si), mt->name); + logcommand(si, CMDLOG_SET, "AQUIET:ADD: \2%s\2 on \2%s\2", mt->name, mc->name); + } + + hook_call_channel_acl_change(&req); + chanacs_close(ca2); + return; + } +} + +void cs_cmd_aquiet_del(sourceinfo_t *si, int parc, char *parv[]) +{ + myentity_t *mt; + mychan_t *mc; + hook_channel_acl_req_t req; + chanacs_t *ca; + mowgli_node_t *n, *tn; + char *chan = parv[0]; + char *uname = parv[1]; + + if (!chan || !uname) + { + command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AQUIET"); + command_fail(si, fault_needmoreparams, _("Syntax: AQUIET <#channel> DEL ")); + return; + } + + mc = mychan_find(chan); + if (!mc) + { + command_fail(si, fault_nosuch_target, _("Channel \2%s\2 is not registered."), chan); + return; + } + + if (metadata_find(mc, "private:close:closer")) + { + command_fail(si, fault_noprivs, _("\2%s\2 is closed."), chan); + return; + } + + aquiet_timeout_t *timeout; + chanban_t *cb; + + if ((chanacs_source_flags(mc, si) & (CA_FLAGS | CA_REMOVE)) != (CA_FLAGS | CA_REMOVE)) + { + command_fail(si, fault_noprivs, _("You are not authorized to perform this operation.")); + return; + } + + mt = myentity_find_ext(uname); + if (!mt) + { + /* we might be deleting a hostmask */ + ca = chanacs_find_host_literal(mc, uname, aquiet_flag); + if (ca == NULL) + { + ca = chanacs_find_host(mc, uname, aquiet_flag); + if (ca != NULL) + command_fail(si, fault_nosuch_key, _("\2%s\2 is not on the AQUIET list for \2%s\2, however \2%s\2 is."), uname, mc->name, ca->host); + else + command_fail(si, fault_nosuch_key, _("\2%s\2 is not on the AQUIET list for \2%s\2."), uname, mc->name); + return; + } + + req.ca = ca; + req.oldlevel = ca->level; + + chanacs_modify_simple(ca, 0, aquiet_flag); + + req.newlevel = ca->level; + + hook_call_channel_acl_change(&req); + chanacs_close(ca); + + verbose(mc, "\2%s\2 removed \2%s\2 from the AQUIET list.", get_source_name(si), uname); + logcommand(si, CMDLOG_SET, "AQUIET:DEL: \2%s\2 on \2%s\2", uname, mc->name); + command_success_nodata(si, _("\2%s\2 has been removed from the AQUIET list for \2%s\2."), uname, mc->name); + + MOWGLI_ITER_FOREACH_SAFE(n, tn, aquietdel_list.head) + { + timeout = n->data; + if (!match(timeout->host, uname) && timeout->chan == mc) + { + mowgli_node_delete(&timeout->node, &aquietdel_list); + mowgli_heap_free(aquiet_timeout_heap, timeout); + } + } + + if (mc->chan != NULL && (cb = chanban_find(mc->chan, uname, 'q'))) + { + modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask); + chanban_delete(cb); + } + + return; + } + + if (!(ca = chanacs_find_literal(mc, mt, aquiet_flag))) + { + command_fail(si, fault_nosuch_key, _("\2%s\2 is not on the AQUIET list for \2%s\2."), mt->name, mc->name); + return; + } + + clear_bans_matching_entity(mc, mt); + + MOWGLI_ITER_FOREACH_SAFE(n, tn, aquietdel_list.head) + { + timeout = n->data; + if (timeout->entity == mt && timeout->chan == mc) + { + mowgli_node_delete(&timeout->node, &aquietdel_list); + mowgli_heap_free(aquiet_timeout_heap, timeout); + } + } + + req.ca = ca; + req.oldlevel = ca->level; + + chanacs_modify_simple(ca, 0, aquiet_flag); + + req.newlevel = ca->level; + + hook_call_channel_acl_change(&req); + chanacs_close(ca); + + command_success_nodata(si, _("\2%s\2 has been removed from the AQUIET list for \2%s\2."), mt->name, mc->name); + logcommand(si, CMDLOG_SET, "AQUIET:DEL: \2%s\2 on \2%s\2", mt->name, mc->name); + verbose(mc, "\2%s\2 removed \2%s\2 from the AQUIET list.", get_source_name(si), mt->name); + + return; +} + +void cs_cmd_aquiet_list(sourceinfo_t *si, int parc, char *parv[]) +{ + mychan_t *mc; + chanacs_t *ca; + metadata_t *md, *md2; + mowgli_node_t *n, *tn; + bool operoverride = false; + char *chan = parv[0]; + char expiry[512]; + + if (!chan) + { + command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AQUIET"); + command_fail(si, fault_needmoreparams, _("Syntax: AQUIET <#channel> LIST")); + return; + } + + /* make sure they're registered, logged in + * and the founder of the channel before + * we go any further. + */ + if (!si->smu) + { + /* if they're opers and just want to LIST, they don't have to log in */ + if (!(has_priv(si, PRIV_CHAN_AUSPEX))) + { + command_fail(si, fault_noprivs, _("You are not logged in.")); + return; + } + } + + mc = mychan_find(chan); + if (!mc) + { + command_fail(si, fault_nosuch_target, _("Channel \2%s\2 is not registered."), chan); + return; + } + + if (metadata_find(mc, "private:close:closer")) + { + command_fail(si, fault_noprivs, _("\2%s\2 is closed."), chan); + return; + } + + int i = 0; + + if (!chanacs_source_has_flag(mc, si, CA_ACLVIEW)) + { + if (has_priv(si, PRIV_CHAN_AUSPEX)) + operoverride = true; + else + { + command_fail(si, fault_noprivs, _("You are not authorized to perform this operation.")); + return; + } + } + command_success_nodata(si, _("AQUIET list for \2%s\2:"), mc->name); + + MOWGLI_ITER_FOREACH_SAFE(n, tn, mc->chanacs.head) + { + time_t expires_on = 0; + char *ago; + long time_left = 0; + + ca = (chanacs_t *)n->data; + + if (ca->level == aquiet_flag) + { + char buf[BUFSIZE], *buf_iter; + + md = metadata_find(ca, "reason"); + + /* check if it's a temporary aquiet */ + if ((md2 = metadata_find(ca, "expires"))) + { + snprintf(expiry, sizeof expiry, "%s", md2->value); + expires_on = (time_t)atol(expiry); + time_left = difftime(expires_on, CURRTIME); + } + ago = ca->tmodified ? time_ago(ca->tmodified) : "?"; + + buf_iter = buf; + buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("%d: \2%s\2 (\2%s\2) ["), + ++i, ca->entity != NULL ? ca->entity->name : ca->host, + md != NULL ? md->value : _("no AQUIET reason specified")); + + if (ca->setter) + buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("setter: %s"), + ca->setter); + + if (expires_on > 0) + buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("%sexpires: %s"), + ca->setter != NULL ? ", " : "", timediff(time_left)); + + if (ca->tmodified) + buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("%smodified: %s"), + expires_on > 0 || ca->setter != NULL ? ", " : "", ago); + + mowgli_strlcat(buf, "]", sizeof buf); + + command_success_nodata(si, "%s", buf); + } + + } + + command_success_nodata(si, _("Total of \2%d\2 %s in \2%s\2's AQUIET list."), i, (i == 1) ? "entry" : "entries", mc->name); + + if (operoverride) + logcommand(si, CMDLOG_ADMIN, "AQUIET:LIST: \2%s\2 (oper override)", mc->name); + else + logcommand(si, CMDLOG_GET, "AQUIET:LIST: \2%s\2", mc->name); +} + +void aquiet_timeout_check(void *arg) +{ + mowgli_node_t *n, *tn; + aquiet_timeout_t *timeout; + chanacs_t *ca; + mychan_t *mc; + + chanban_t *cb; + aquietdel_next = 0; + + MOWGLI_ITER_FOREACH_SAFE(n, tn, aquietdel_list.head) + { + timeout = n->data; + mc = timeout->chan; + + if (timeout->expiration > CURRTIME) + { + aquietdel_next = timeout->expiration; + aquiet_timeout_check_timer = mowgli_timer_add_once(base_eventloop, "aquiet_timeout_check", aquiet_timeout_check, NULL, aquietdel_next - CURRTIME); + break; + } + + ca = NULL; + + if (timeout->entity == NULL) + { + if ((ca = chanacs_find_host_literal(mc, timeout->host, aquiet_flag)) && mc->chan != NULL && (cb = chanban_find(mc->chan, ca->host, 'b'))) + { + modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask); + chanban_delete(cb); + } + } + else + { + ca = chanacs_find_literal(mc, timeout->entity, aquiet_flag); + if (ca == NULL) + { + mowgli_node_delete(&timeout->node, &aquietdel_list); + mowgli_heap_free(aquiet_timeout_heap, timeout); + + continue; + } + + clear_bans_matching_entity(mc, timeout->entity); + } + + if (ca) + { + chanacs_modify_simple(ca, 0, aquiet_flag); + chanacs_close(ca); + } + + mowgli_node_delete(&timeout->node, &aquietdel_list); + mowgli_heap_free(aquiet_timeout_heap, timeout); + } +} + +static aquiet_timeout_t *aquiet_add_timeout(mychan_t *mc, myentity_t *mt, const char *host, time_t expireson) +{ + mowgli_node_t *n; + aquiet_timeout_t *timeout, *timeout2; + + timeout = mowgli_heap_alloc(aquiet_timeout_heap); + + timeout->entity = mt; + timeout->chan = mc; + timeout->expiration = expireson; + + mowgli_strlcpy(timeout->host, host, sizeof timeout->host); + + MOWGLI_ITER_FOREACH_PREV(n, aquietdel_list.tail) + { + timeout2 = n->data; + if (timeout2->expiration <= timeout->expiration) + break; + } + if (n == NULL) + mowgli_node_add_head(timeout, &timeout->node, &aquietdel_list); + else if (n->next == NULL) + mowgli_node_add(timeout, &timeout->node, &aquietdel_list); + else + mowgli_node_add_before(timeout, &timeout->node, &aquietdel_list, n->next); + + return timeout; +} + +void aquietdel_list_create(void *arg) +{ + mychan_t *mc; + mowgli_node_t *n, *tn; + chanacs_t *ca; + metadata_t *md; + time_t expireson; + + mowgli_patricia_iteration_state_t state; + + MOWGLI_PATRICIA_FOREACH(mc, &state, mclist) + { + MOWGLI_ITER_FOREACH_SAFE(n, tn, mc->chanacs.head) + { + ca = (chanacs_t *)n->data; + + if (!(ca->level & aquiet_flag)) + continue; + + md = metadata_find(ca, "expires"); + + if (!md) + continue; + + expireson = atol(md->value); + + if (CURRTIME > expireson) + { + chanacs_modify_simple(ca, 0, aquiet_flag); + chanacs_close(ca); + } + else + { + /* overcomplicate the logic here a tiny bit */ + if (ca->host == NULL && ca->entity != NULL) + aquiet_add_timeout(mc, ca->entity, entity(ca->entity)->name, expireson); + else if (ca->host != NULL && ca->entity == NULL) + aquiet_add_timeout(mc, NULL, ca->host, expireson); + } + } + } +} + +//Handle channel joins +static void cs_cmd_aquiet_onjoin(hook_channel_joinpart_t *hdata) { + chanuser_t *cu = hdata->cu; + user_t *u; + channel_t *chan; + mychan_t *mc; + unsigned int flags; + bool noop; + bool secure; + bool guard; + metadata_t *md; + chanacs_t *ca2; + + //Ignore botservs + + if (cu == NULL || is_internal_client(cu->user)) { + return; + } + + u = cu->user; + chan = cu->chan; + + //Verify if the channel is registered + mc = mychan_find(chan->name); + + if (mc == NULL) { + return; + } + + //Get the flags populated so we can work with them + flags = chanacs_user_flags(mc, u); + + if(flags & aquiet_flag && !(flags & CA_EXEMPT)) { + //If the user has already earned a spot on the AQUIET list + ca2 = chanacs_find_host_by_user(mc, u, aquiet_flag); + + if (ca2 != NULL) { + //If the user is not quieted already + chanban_add(chan, ca2->host, 'q'); + modestack_mode_param(chansvs.nick, chan, MTYPE_ADD, 'q', ca2->host); + modestack_flush_channel(chan); + } + } +} + +/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs + * vim:ts=8 + * vim:sw=8 + * vim:noexpandtab + */