/* * IRC - Internet Relay Chat, src/modules/invite.c * (C) 2004 The UnrealIRCd Team * * See file AUTHORS in IRC package for additional names of * the programmers. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "unrealircd.h" #define MSG_INVITE "INVITE" #define CLIENT_INVITES(client) (moddata_local_client(client, userInvitesMD).ptr) #define CHANNEL_INVITES(channel) (moddata_channel(channel, channelInvitesMD).ptr) ModDataInfo *userInvitesMD; ModDataInfo *channelInvitesMD; long CAP_INVITE_NOTIFY = 0L; int invite_always_notify = 0; CMD_FUNC(cmd_invite); void invite_free(ModData *md); int invite_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); int invite_config_run(ConfigFile *cf, ConfigEntry *ce, int type); void add_invite(Client *from, Client *to, Channel *channel, MessageTag *mtags); void del_invite(Client *client, Channel *channel); static int invite_channel_destroy(Channel *channel, int *should_destroy); int invite_user_quit(Client *client, MessageTag *mtags, const char *comment); int invite_user_join(Client *client, Channel *channel, MessageTag *mtags); int invite_is_invited(Client *client, Channel *channel, int *invited); ModuleHeader MOD_HEADER = { "invite", "5.0", "command /invite", "UnrealIRCd Team", "unrealircd-6", }; MOD_TEST() { HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, invite_config_test); return MOD_SUCCESS; } MOD_INIT() { ClientCapabilityInfo cap; ClientCapability *c; ModDataInfo mreq; MARK_AS_OFFICIAL_MODULE(modinfo); CommandAdd(modinfo->handle, MSG_INVITE, cmd_invite, MAXPARA, CMD_USER|CMD_SERVER); memset(&cap, 0, sizeof(cap)); cap.name = "invite-notify"; c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_INVITE_NOTIFY); if (!c) { config_error("[%s] Failed to request invite-notify cap: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle)); return MOD_FAILED; } memset(&mreq, 0 , sizeof(mreq)); mreq.type = MODDATATYPE_LOCAL_CLIENT; mreq.name = "invite", mreq.free = invite_free; userInvitesMD = ModDataAdd(modinfo->handle, mreq); if (!userInvitesMD) { config_error("[%s] Failed to request user invite moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle)); return MOD_FAILED; } memset(&mreq, 0 , sizeof(mreq)); mreq.type = MODDATATYPE_CHANNEL; mreq.name = "invite", mreq.free = invite_free; channelInvitesMD = ModDataAdd(modinfo->handle, mreq); if (!channelInvitesMD) { config_error("[%s] Failed to request channel invite moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle)); return MOD_FAILED; } invite_always_notify = 0; /* the default */ HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, invite_config_run); HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_DESTROY, 1000000, invite_channel_destroy); HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, invite_user_quit); HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, invite_user_join); HookAdd(modinfo->handle, HOOKTYPE_IS_INVITED, 0, invite_is_invited); return MOD_SUCCESS; } MOD_LOAD() { return MOD_SUCCESS; } MOD_UNLOAD() { return MOD_SUCCESS; } void invite_free(ModData *md) { Link **inv, *tmp; if (!md->ptr) return; // was not set for (inv = (Link **)md->ptr; (tmp = *inv); inv = &tmp->next) { *inv = tmp->next; free_link(tmp); } } int invite_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) { int errors = 0; if (type != CONFIG_SET) return 0; if (!ce || !ce->name || strcmp(ce->name, "normal-user-invite-notification")) return 0; if (!ce->value) { config_error_empty(ce->file->filename, ce->line_number, "set", ce->name); errors++; } *errs = errors; return errors ? -1 : 1; } int invite_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { ConfigEntry *cep; if (type != CONFIG_SET) return 0; if (!ce || !ce->name || strcmp(ce->name, "normal-user-invite-notification")) return 0; invite_always_notify = config_checkval(ce->value, CFG_YESNO); return 1; } static int invite_channel_destroy(Channel *channel, int *should_destroy) { Link *lp; while ((lp = CHANNEL_INVITES(channel))) del_invite(lp->value.client, channel); return 0; } int invite_user_quit(Client *client, MessageTag *mtags, const char *comment) { Link *lp; /* Clean up invitefield */ while ((lp = CLIENT_INVITES(client))) del_invite(client, lp->value.channel); return 0; } int invite_user_join(Client *client, Channel *channel, MessageTag *mtags) { del_invite(client, channel); return 0; } /* Send the user their list of active invites */ void send_invite_list(Client *client) { Link *inv; for (inv = CLIENT_INVITES(client); inv; inv = inv->next) { sendnumeric(client, RPL_INVITELIST, inv->value.channel->name); } sendnumeric(client, RPL_ENDOFINVITELIST); } int invite_is_invited(Client *client, Channel *channel, int *invited) { Link *lp; if (!MyConnect(client)) return 0; // not handling invite lists for remote clients for (lp = CLIENT_INVITES(client); lp; lp = lp->next) if (lp->value.channel == channel) { *invited = 1; return 0; } return 0; } void invite_process(Client *client, Client *target, Channel *channel, MessageTag *recv_mtags, int override) { MessageTag *mtags = NULL; new_message(client, recv_mtags, &mtags); /* broadcast to other servers */ sendto_server(client, 0, 0, mtags, ":%s INVITE %s %s %d", client->id, target->id, channel->name, override); /* send chanops notifications */ if (IsUser(client) && (check_channel_access(client, channel, "oaq") || IsULine(client) || ValidatePermissionsForPath("channel:override:invite:self",client,NULL,channel,NULL) || invite_always_notify )) { if (override == 1) { sendto_channel(channel, &me, NULL, "o", 0, SEND_LOCAL, mtags, ":%s NOTICE @%s :OperOverride -- %s invited him/herself into the channel.", me.name, channel->name, client->name); } if (override == 0) { sendto_channel(channel, &me, NULL, "o", CAP_INVITE_NOTIFY | CAP_INVERT, SEND_LOCAL, mtags, ":%s NOTICE @%s :%s invited %s into the channel.", me.name, channel->name, client->name, target->name); } /* always send IRCv3 invite-notify if possible */ sendto_channel(channel, client, NULL, "o", CAP_INVITE_NOTIFY, SEND_LOCAL, mtags, ":%s INVITE %s %s", client->name, target->name, channel->name); } /* add to list and notify the person who got invited */ if (MyConnect(target)) { if (IsUser(client) && (check_channel_access(client, channel, "oaq") || IsULine(client) || ValidatePermissionsForPath("channel:override:invite:self",client,NULL,channel,NULL) )) { add_invite(client, target, channel, mtags); } if (!is_silenced(client, target)) { sendto_prefix_one(target, client, mtags, ":%s INVITE %s :%s", client->name, target->name, channel->name); } } free_message_tags(mtags); } void invite_operoverride_msg(Client *client, Channel *channel, char *override_mode, char *override_mode_text) { unreal_log(ULOG_INFO, "operoverride", "OPEROVERRIDE_INVITE", client, "OperOverride: $client.details invited him/herself into $channel (Overriding $override_mode_text)", log_data_string("override_type", "join"), log_data_string("override_mode", override_mode), log_data_string("override_mode_text", override_mode_text), log_data_channel("channel", channel)); } /* ** cmd_invite ** parv[1] - user to invite ** parv[2] - channel name ** parv[3] - override (S2S only) */ CMD_FUNC(cmd_invite) { Client *target = NULL; Channel *channel = NULL; int override = 0; int i = 0; int params_ok = 0; Hook *h; if (parc >= 3 && *parv[1] != '\0') { params_ok = 1; target = find_user(parv[1], NULL); channel = find_channel(parv[2]); } if (!MyConnect(client)) /*** remote invite ***/ { if (!params_ok) return; /* the client or channel may be already gone */ if (!target) { sendnumeric(client, ERR_NOSUCHNICK, parv[1]); return; } if (!channel) { sendnumeric(client, ERR_NOSUCHCHANNEL, parv[2]); return; } if (parc >= 4 && !BadPtr(parv[3])) { override = atoi(parv[3]); } /* no further checks */ invite_process(client, target, channel, recv_mtags, override); return; } /*** local invite ***/ /* the client requested own invite list */ if (parc == 1) { send_invite_list(client); return; } /* notify user about bad parameters */ if (!params_ok) { sendnumeric(client, ERR_NEEDMOREPARAMS, "INVITE"); return; } if (!target) { sendnumeric(client, ERR_NOSUCHNICK, parv[1]); return; } if (!channel) { sendnumeric(client, ERR_NOSUCHCHANNEL, parv[2]); return; } /* proceed with the command */ for (h = Hooks[HOOKTYPE_PRE_INVITE]; h; h = h->next) { i = (*(h->func.intfunc))(client,target,channel,&override); if (i == HOOK_DENY) return; if (i == HOOK_ALLOW) break; } if (!IsMember(client, channel) && !IsULine(client)) { if (ValidatePermissionsForPath("channel:override:invite:notinchannel",client,NULL,channel,NULL) && client == target) { override = 1; } else { sendnumeric(client, ERR_NOTONCHANNEL, parv[2]); return; } } if (IsMember(target, channel)) { sendnumeric(client, ERR_USERONCHANNEL, parv[1], parv[2]); return; } if (has_channel_mode(channel, 'i')) { if (!check_channel_access(client, channel, "oaq") && !IsULine(client)) { if (ValidatePermissionsForPath("channel:override:invite:invite-only",client,NULL,channel,NULL) && client == target) { override = 1; } else { sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name); return; } } else if (!IsMember(client, channel) && !IsULine(client)) { if (ValidatePermissionsForPath("channel:override:invite:invite-only",client,NULL,channel,NULL) && client == target) { override = 1; } else { sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name); return; } } } if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN && !strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN) && !check_channel_access(client, channel, "oaq") && !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL)) { sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name); return; } if (target_limit_exceeded(client, target, target->name)) return; if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL) && flood_limit_exceeded(client, FLD_INVITE)) { sendnumeric(client, RPL_TRYAGAIN, "INVITE"); return; } if (!override) { sendnumeric(client, RPL_INVITING, target->name, channel->name); if (target->user->away) { sendnumeric(client, RPL_AWAY, target->name, target->user->away); } } else { /* Send OperOverride messages */ char override_what = '\0'; if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL)) invite_operoverride_msg(client, channel, "b", "ban"); else if (has_channel_mode(channel, 'i')) invite_operoverride_msg(client, channel, "i", "invite only"); else if (has_channel_mode(channel, 'l')) invite_operoverride_msg(client, channel, "l", "user limit"); else if (has_channel_mode(channel, 'k')) invite_operoverride_msg(client, channel, "k", "key"); else if (has_channel_mode(channel, 'z')) invite_operoverride_msg(client, channel, "z", "secure only"); #ifdef OPEROVERRIDE_VERIFY else if (channel->mode.mode & MODE_SECRET || channel->mode.mode & MODE_PRIVATE) override = -1; #endif else return; } /* allowed to proceed */ invite_process(client, target, channel, recv_mtags, override); } /** Register an invite from someone to a channel - so they can bypass +i etc. * @param from The person sending the invite * @param to The person who is invited to join * @param channel The channel * @param mtags Message tags associated with this INVITE command */ void add_invite(Client *from, Client *to, Channel *channel, MessageTag *mtags) { Link *inv, *tmp; del_invite(to, channel); /* If too many invite entries then delete the oldest one */ if (link_list_length(CLIENT_INVITES(to)) >= MAXCHANNELSPERUSER) { for (tmp = CLIENT_INVITES(to); tmp->next; tmp = tmp->next) ; del_invite(to, tmp->value.channel); } /* We get pissy over too many invites per channel as well now, * since otherwise mass-inviters could take up some major * resources -Donwulff */ if (link_list_length(CHANNEL_INVITES(channel)) >= MAXCHANNELSPERUSER) { for (tmp = CHANNEL_INVITES(channel); tmp->next; tmp = tmp->next) ; del_invite(tmp->value.client, channel); } /* * add client to the beginning of the channel invite list */ inv = make_link(); inv->value.client = to; inv->next = CHANNEL_INVITES(channel); CHANNEL_INVITES(channel) = inv; /* * add channel to the beginning of the client invite list */ inv = make_link(); inv->value.channel = channel; inv->next = CLIENT_INVITES(to); CLIENT_INVITES(to) = inv; RunHook(HOOKTYPE_INVITE, from, to, channel, mtags); } /** Delete a previous invite of someone to a channel. * @param client The client who was invited * @param channel The channel to which the person was invited */ void del_invite(Client *client, Channel *channel) { Link **inv, *tmp; for (inv = (Link **)&CHANNEL_INVITES(channel); (tmp = *inv); inv = &tmp->next) if (tmp->value.client == client) { *inv = tmp->next; free_link(tmp); break; } for (inv = (Link **)&CLIENT_INVITES(client); (tmp = *inv); inv = &tmp->next) if (tmp->value.channel == channel) { *inv = tmp->next; free_link(tmp); break; } }