unrealircd/src/modules/chanmodes/delayjoin.c

407 lines
11 KiB
C

/*
* Channel mode +D/+d: delayed join
* except from opers, U-lines and servers.
* Copyright 2014 Travis Mcarthur <Heero> and UnrealIRCd Team
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"chanmodes/delayjoin", /* Name of module */
"5.0", /* Version */
"delayed join (+D,+d)", /* Short description of module */
"UnrealIRCd Team",
"unrealircd-6",
};
#define MOD_DATA_STR "delayjoin"
#define MOD_DATA_INVISIBLE "1"
static long UMODE_PRIVDEAF = 0;
static Cmode *CmodeDelayed = NULL;
static Cmode *CmodePostDelayed = NULL;
static Cmode_t EXTMODE_DELAYED;
static Cmode_t EXTMODE_POST_DELAYED;
int visible_in_channel(Client *client, Channel *channel);
int moded_check_part(Client *client, Channel *channel);
int moded_join(Client *client, Channel *channel);
int moded_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
int moded_quit(Client *client, MessageTag *mtags, const char *comment);
int delayjoin_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what);
int moded_chanmode(Client *client, Channel *channel,
MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode);
int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, const char *text, SendType sendtype);
const char *moded_serialize(ModData *m);
void moded_unserialize(const char *str, ModData *m);
MOD_INIT()
{
CmodeInfo req;
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1);
memset(&req, 0, sizeof(req));
req.paracount = 0;
req.is_ok = extcmode_default_requirechop;
req.letter = 'D';
CmodeDelayed = CmodeAdd(modinfo->handle, req, &EXTMODE_DELAYED);
memset(&req, 0, sizeof(req));
req.paracount = 0;
req.is_ok = delayjoin_is_ok;
req.letter = 'd';
req.local = 1;
CmodePostDelayed = CmodeAdd(modinfo->handle, req, &EXTMODE_POST_DELAYED);
memset(&mreq, 0, sizeof(mreq));
mreq.name = MOD_DATA_STR;
mreq.serialize = moded_serialize;
mreq.unserialize = moded_unserialize;
mreq.sync = 0;
mreq.type = MODDATATYPE_MEMBER;
if (!ModDataAdd(modinfo->handle, mreq))
abort();
if (!CmodeDelayed || !CmodePostDelayed)
{
/* I use config_error() here because it's printed to stderr in case of a load
* on cmd line, and to all opers in case of a /rehash.
*/
config_error("delayjoin: Could not add channel mode '+D' or '+d': %s", ModuleGetErrorStr(modinfo->handle));
return MOD_FAILED;
}
HookAdd(modinfo->handle, HOOKTYPE_VISIBLE_IN_CHANNEL, 0, visible_in_channel);
HookAdd(modinfo->handle, HOOKTYPE_JOIN_DATA, 0, moded_join);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PART, 0, moded_part);
HookAdd(modinfo->handle, HOOKTYPE_REMOTE_PART, 0, moded_part);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, moded_quit);
HookAdd(modinfo->handle, HOOKTYPE_REMOTE_QUIT, 0, moded_quit);
HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CHANMODE, 0, moded_chanmode);
HookAdd(modinfo->handle, HOOKTYPE_PRE_REMOTE_CHANMODE, 0, moded_chanmode);
HookAdd(modinfo->handle, HOOKTYPE_PRE_CHANMSG, 0, moded_prechanmsg);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
void set_post_delayed(Channel *channel)
{
MessageTag *mtags = NULL;
channel->mode.mode |= EXTMODE_POST_DELAYED;
new_message(&me, NULL, &mtags);
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +d", me.name, channel->name);
free_message_tags(mtags);
}
void clear_post_delayed(Channel *channel)
{
MessageTag *mtags = NULL;
channel->mode.mode &= ~EXTMODE_POST_DELAYED;
new_message(&me, NULL, &mtags);
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s -d", me.name, channel->name);
free_message_tags(mtags);
}
bool moded_member_invisible(Member* m, Channel *channel)
{
ModDataInfo *md;
if (!m)
return false;
md = findmoddata_byname(MOD_DATA_STR, MODDATATYPE_MEMBER);
if (!md)
return false;
if (!moddata_member(m, md).str)
return false;
return true;
}
bool moded_user_invisible(Client *client, Channel *channel)
{
return moded_member_invisible(find_member_link(channel->members, client), channel);
}
bool channel_has_invisible_users(Channel *channel)
{
Member* i;
for (i = channel->members; i; i = i->next)
{
if (moded_member_invisible(i, channel))
{
return true;
}
}
return false;
}
bool channel_is_post_delayed(Channel *channel)
{
if (channel->mode.mode & EXTMODE_POST_DELAYED)
return true;
return false;
}
bool channel_is_delayed(Channel *channel)
{
if (channel->mode.mode & EXTMODE_DELAYED)
return true;
return false;
}
void clear_user_invisible(Channel *channel, Client *client)
{
Member *i;
ModDataInfo *md;
bool should_clear = true, found_member = false;
md = findmoddata_byname(MOD_DATA_STR, MODDATATYPE_MEMBER);
if (!md)
return;
for (i = channel->members; i; i = i->next)
{
if (i->client == client)
{
if (md)
memset(&moddata_member(i, md), 0, sizeof(ModData));
found_member = true;
if (!should_clear)
break;
}
else if (moddata_member(i, md).str)
{
should_clear = false;
if (found_member)
break;
}
}
if (should_clear && (channel->mode.mode & EXTMODE_POST_DELAYED))
{
clear_post_delayed(channel);
}
}
void clear_user_invisible_announce(Channel *channel, Client *client, MessageTag *recv_mtags)
{
Member *i;
MessageTag *mtags = NULL;
char joinbuf[512];
char exjoinbuf[512];
long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
clear_user_invisible(channel, client);
ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s",
client->name, client->user->username, GetHost(client), channel->name);
ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s",
client->name, client->user->username, GetHost(client), channel->name,
IsLoggedIn(client) ? client->user->account : "*",
client->info);
new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->name);
for (i = channel->members; i; i = i->next)
{
Client *acptr = i->client;
if (!check_channel_access(acptr, channel, "hoaq") && acptr != client && MyConnect(acptr))
{
if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN))
sendto_one(acptr, mtags, "%s", exjoinbuf);
else
sendto_one(acptr, mtags, "%s", joinbuf);
}
}
free_message_tags(mtags);
}
void set_user_invisible(Channel *channel, Client *client)
{
Member *m = find_member_link(channel->members, client);
ModDataInfo *md;
if (!m)
return;
md = findmoddata_byname(MOD_DATA_STR, MODDATATYPE_MEMBER);
if (!md || !md->unserialize)
return;
md->unserialize(MOD_DATA_INVISIBLE, &moddata_member(m, md));
}
int delayjoin_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
{
return EX_ALWAYS_DENY;
}
int visible_in_channel(Client *client, Channel *channel)
{
return (channel_is_delayed(channel) || channel_is_post_delayed(channel)) && moded_user_invisible(client, channel);
}
int moded_join(Client *client, Channel *channel)
{
if (channel_is_delayed(channel))
set_user_invisible(channel, client);
return 0;
}
int moded_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment)
{
if (channel_is_delayed(channel) || channel_is_post_delayed(channel))
clear_user_invisible(channel, client);
return 0;
}
int moded_quit(Client *client, MessageTag *mtags, const char *comment)
{
Membership *membership;
Channel *channel;
for (membership = client->user->channel; membership; membership=membership->next)
{
channel = membership->channel;
/* Identical to moded_part() */
if (channel_is_delayed(channel) || channel_is_post_delayed(channel))
clear_user_invisible(channel, client);
}
return 0;
}
int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode)
{
long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
// Handle case where we just unset +D but have invisible users
if (!channel_is_delayed(channel) && !channel_is_post_delayed(channel) && channel_has_invisible_users(channel))
set_post_delayed(channel);
else if (channel_is_delayed(channel) && channel_is_post_delayed(channel))
clear_post_delayed(channel);
if ((channel_is_delayed(channel) || channel_is_post_delayed(channel)))
{
ParseMode pm;
int ret;
for (ret = parse_chanmode(&pm, modebuf, parabuf); ret; ret = parse_chanmode(&pm, NULL, NULL))
{
if (pm.what == MODE_ADD && (pm.modechar == 'o' || pm.modechar == 'h' || pm.modechar == 'a' || pm.modechar == 'q' || pm.modechar == 'v'))
{
Member* i;
Client *user = find_client(pm.param,NULL);
if (!user)
continue;
if (moded_user_invisible(user, channel))
clear_user_invisible_announce(channel, user, recv_mtags);
if (pm.modechar == 'v' || !MyConnect(user))
continue;
/* Our user 'user' just got ops (oaq) - send the joins for all the users (s)he doesn't know about */
for (i = channel->members; i; i = i->next)
{
if (i->client == user)
continue;
if (moded_user_invisible(i->client, channel))
{
MessageTag *mtags = NULL;
new_message_special(i->client, recv_mtags, &mtags, ":%s JOIN %s", i->client->name, channel->name);
if (HasCapabilityFast(user, CAP_EXTENDED_JOIN))
{
sendto_one(user, mtags, ":%s!%s@%s JOIN %s %s :%s",
i->client->name, i->client->user->username, GetHost(i->client),
channel->name,
IsLoggedIn(i->client) ? i->client->user->account : "*",
i->client->info);
} else {
sendto_one(user, mtags, ":%s!%s@%s JOIN :%s", i->client->name, i->client->user->username, GetHost(i->client), channel->name);
}
free_message_tags(mtags);
}
}
}
if (pm.what == MODE_DEL && (pm.modechar == 'o' || pm.modechar == 'h' || pm.modechar == 'a' || pm.modechar == 'q' || pm.modechar == 'v'))
{
Member* i;
Client *user = find_client(pm.param,NULL);
if (!user)
continue;
if (moded_user_invisible(user, channel))
clear_user_invisible_announce(channel, user, recv_mtags);
if (pm.modechar == 'v' || !MyConnect(user))
continue;
/* Our user 'user' just lost ops (oaq) - send the parts for all users (s)he won't see anymore */
for (i = channel->members; i; i = i->next)
{
if (i->client == user)
continue;
if (moded_user_invisible(i->client, channel))
{
MessageTag *mtags = NULL;
new_message_special(i->client, recv_mtags, &mtags, ":%s PART %s", i->client->name, channel->name);
sendto_one(user, mtags, ":%s!%s@%s PART :%s", i->client->name, i->client->user->username, GetHost(i->client), channel->name);
free_message_tags(mtags);
}
}
}
}
}
return 0;
}
int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, const char *text, SendType sendtype)
{
if ((channel_is_delayed(channel) || channel_is_post_delayed(channel)) && (moded_user_invisible(client, channel)))
clear_user_invisible_announce(channel, client, mtags);
return 0;
}
const char *moded_serialize(ModData *m)
{
return m->i ? "1" : "0";
}
void moded_unserialize(const char *str, ModData *m)
{
m->i = atoi(str);
}