From: NurPech Date: Fri, 5 Apr 2013 20:50:53 +0000 (+0200) Subject: added some code for shitlist X-Git-Url: http://git.pk910.de/?p=srvx.git;a=commitdiff_plain;h=904bf25f927df4924d696be031510e848ce835e5 added some code for shitlist --- diff --git a/src/spamserv.c b/src/spamserv.c index 0a88cb2..c65ee84 100644 --- a/src/spamserv.c +++ b/src/spamserv.c @@ -1,2055 +1,2367 @@ -/* spamserv.c - anti spam service - * Copyright 2004 feigling - * - * 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 2 of the License, or - * (at your option) any later version. Important limitations are - * listed in the COPYING file that accompanies this software. - * - * 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, email srvx-maintainers@srvx.net. - * - * $Id: spamserv.c,v 1.08 2004/06/27 22:21:00 feigling Exp $ - */ - -#include "conf.h" -#include "spamserv.h" -#include "chanserv.h" -#include "global.h" -#include "modcmd.h" -#include "saxdb.h" -#include "timeq.h" -#include "gline.h" - -#define SPAMSERV_CONF_NAME "services/spamserv" - -#define KEY_EXCEPTIONS "exceptions" -#define KEY_FLAGS "flags" -#define KEY_INFO "info" -#define KEY_EXCEPTLEVEL "exceptlevel" -#define KEY_EXPIRY "expiry" - -#define KEY_DEBUG_CHANNEL "debug_channel" -#define KEY_GLOBAL_EXCEPTIONS "global_exceptions" -#define KEY_NETWORK_RULES "network_rules" -#define KEY_TRIGGER "trigger" -#define KEY_SHORT_BAN_DURATION "short_ban_duration" -#define KEY_LONG_BAN_DURATION "long_ban_duration" -#define KEY_GLINE_DURATION "gline_duration" -#define KEY_EXCEPTION_MAX "exception_max" -#define KEY_EXCEPTION_MIN_LEN "exception_min_len" -#define KEY_EXCEPTION_MAX_LEN "exception_max_len" -#define KEY_ADV_CHAN_MUST_EXIST "adv_chan_must_exist" -#define KEY_STRIP_MIRC_CODES "strip_mirc_codes" -#define KEY_ALLOW_MOVE_MERGE "allow_move_merge" - -#define SPAMSERV_FUNC(NAME) MODCMD_FUNC(NAME) -#define SPAMSERV_SYNTAX() svccmd_send_help(user, spamserv, cmd) -#define SPAMSERV_MIN_PARMS(N) do { \ -(void)argv; \ - if(argc < N) { \ - ss_reply(MSG_MISSING_PARAMS, argv[0]); \ - SPAMSERV_SYNTAX(); \ - return 0; } } while(0) - -struct userNode *spamserv; -static struct module *spamserv_module; -static struct service *spamserv_service; -static struct log_type *SS_LOG; -static unsigned long crc_table[256]; - -dict_t registered_channels_dict; -dict_t connected_users_dict; -dict_t killed_users_dict; - -#define spamserv_notice(target, format...) send_message(target , spamserv , ## format) -#define spamserv_debug(format...) do { if(spamserv_conf.debug_channel) send_channel_notice(spamserv_conf.debug_channel , spamserv , ## format); } while(0) -#define ss_reply(format...) send_message(user , spamserv , ## format) - -#define SET_SUBCMDS_SIZE 10 - -const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "FLOODSCAN", "JOINFLOODSCAN", "EXCEPTLEVEL","SCANCHANOPS", "SCANVOICED"}; - -static void spamserv_clear_spamNodes(struct chanNode *channel); -static void spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban); -static unsigned long crc32(const char *text); - -#define BINARY_OPTION(arguments...) return binary_option(arguments, user, channel, argc, argv); -#define MULTIPLE_OPTION(arguments...) return multiple_option(arguments, values, ArrayLength(values), user, channel, argc, argv); - -static const struct message_entry msgtab[] = { - { "SSMSG_CHANNEL_OPTIONS", "Channel Options:" }, - { "SSMSG_STRING_VALUE", "$b%s$b%s" }, - { "SSMSG_NUMERIC_VALUE", "$b%s$b%d - %s" }, - { "SSMSG_EASYNUMERIC_VALUE", "$b%s$b%d" }, - { "SSMSG_INVALID_NUM_SET", "$b'%d'$b is an invalid %s setting." }, - { "SSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." }, - { "SSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." }, - - { "SSMSG_NOT_REGISTERED", "$b%s$b has not been registered with $b$X$b." }, - { "SSMSG_NOT_REGISTERED_CS", "$b%s$b has not been registered with $b$C$b." }, - { "SSMSG_ALREADY_REGISTERED", "$b%s$b is already registered." }, - { "SSMSG_DEBUG_CHAN", "You may not register the debug channel." }, - { "SSMSG_SUSPENDED_CS", "$b$C$b access to $b%s$b has been temporarily suspended, thus you can't %s it." }, - { "SSMSG_SUSPENDED", "$b$X$b access to $b%s$b has been temporarily suspended." }, - { "SSMSG_NO_REGISTER", "Due to an error it was not possible to register $b%s$b." }, - { "SSMSG_REG_SUCCESS", "Channel $b%s$b registered." }, - { "SSMSG_UNREG_SUCCESS", "$b%s$b has been unregistered." }, - { "SSMSG_NO_ACCESS", "You lack sufficient access to use this command." }, - { "SSMSG_MUST_BE_OPER", "You must be an irc operator to set this option." }, - { "SSMSG_CONFIRM_UNREG", "To confirm this unregistration, you must append 'CONFIRM' to the end of your command. For example, 'unregister CONFIRM'." }, - - { "SSMSG_NO_EXCEPTIONS", "No words found in the exception list." }, - { "SSMSG_NO_SUCH_EXCEPTION", "Word $b%s$b not found in the exception list." }, - { "SSMSG_EXCEPTION_LIST", "The following words are in the exception list:" }, - { "SSMSG_EXCEPTION_ADDED", "Word $b%s$b added to the exception list." }, - { "SSMSG_EXCEPTION_DELETED", "Word $b%s$b deleted from the exception list." }, - { "SSMSG_EXCEPTION_IN_LIST", "The word $b%s$b is already in the exception list." }, - { "SSMSG_EXCEPTION_MAX", "The exception list has reached the maximum exceptions (max %lu). Delete a word to add another one." }, - { "SSMSG_EXCEPTION_TOO_SHORT", "The word must be at least %lu characters long." }, - { "SSMSG_EXCEPTION_TOO_LONG", "The word may not be longer than %lu characters." }, - - { "SSMSG_STATUS", "$bStatus:$b" }, - { "SSMSG_STATUS_USERS", "Total Users Online: %u" }, - { "SSMSG_STATUS_CHANNELS", "Registered Channels: %u" }, - { "SSMSG_STATUS_MEMORY", "$bMemory Information:$b" }, - { "SSMSG_STATUS_CHANNEL_LIST", "$bRegistered Channels:$b" }, - { "SSMSG_STATUS_NO_CHANNEL", "No channels registered." }, - { NULL, NULL } -}; - -#define SSMSG_DEBUG_KICK "Kicked user $b%s$b from $b%s$b, reason: %s" -#define SSMSG_DEBUG_BAN "Banned user $b%s$b from $b%s$b, reason: %s" -#define SSMSG_DEBUG_KILL "Killed user $b%s$b, last violation in $b%s$b" -#define SSMSG_DEBUG_GLINE "Glined user $b%s$b, host $b%s$b, last violation in $b%s$b" -#define SSMSG_DEBUG_RECONNECT "Killed user $b%s$b reconnected to the network" -#define SSMSG_SPAM "Spamming" -#define SSMSG_FLOOD "Flooding the channel/network" -#define SSMSG_ADV "Advertising" -#define SSMSG_JOINFLOOD "Join flooding the channel" -#define SSMSG_WARNING "%s is against the network rules" -#define SSMSG_WARNING_2 "You are violating the network rules" -#define SSMSG_WARNING_RULES "%s is against the network rules. Read the network rules at %s" -#define SSMSG_WARNING_RULES_2 "You are violating the network rules. Read the network rules at %s" - -static struct -{ - struct chanNode *debug_channel; - struct string_list *global_exceptions; - const char *network_rules; - unsigned char trigger; - unsigned long short_ban_duration; - unsigned long long_ban_duration; - unsigned long gline_duration; - unsigned long exception_max; - unsigned long exception_min_len; - unsigned long exception_max_len; - unsigned int adv_chan_must_exist : 1; - unsigned int strip_mirc_codes : 1; - unsigned int allow_move_merge : 1; -} spamserv_conf; - -/***********************************************/ -/* Channel */ -/***********************************************/ - -struct chanInfo* -get_chanInfo(const char *channelname) -{ - return dict_find(registered_channels_dict, channelname, 0); -} - -static void -spamserv_join_channel(struct chanNode *channel) -{ - struct mod_chanmode change; - mod_chanmode_init(&change); - change.argc = 1; - change.args[0].mode = MODE_CHANOP; - change.args[0].u.member = AddChannelUser(spamserv, channel); - mod_chanmode_announce(spamserv, channel, &change); -} - -static void -spamserv_part_channel(struct chanNode *channel, char *reason) -{ - /* we only have to clear the spamNodes because every other node expires on it's own */ - spamserv_clear_spamNodes(channel); - DelChannelUser(spamserv, channel, reason, 0); -} - -static struct chanInfo* -spamserv_register_channel(struct chanNode *channel, struct string_list *exceptions, unsigned int flags, char *info) -{ - struct chanInfo *cInfo = malloc(sizeof(struct chanInfo)); - - if(!cInfo) - { - log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for cInfo; channel: %s", channel->name); - return NULL; - } - - cInfo->channel = channel; - cInfo->exceptions = exceptions ? string_list_copy(exceptions) : alloc_string_list(1); - cInfo->flags = flags; - cInfo->exceptlevel = 400; - safestrncpy(cInfo->info, info, sizeof(cInfo->info)); - cInfo->suspend_expiry = 0; - dict_insert(registered_channels_dict, cInfo->channel->name, cInfo); - - return cInfo; -} - -static void -spamserv_unregister_channel(struct chanInfo *cInfo) -{ - if(!cInfo) - return; - - dict_remove(registered_channels_dict, cInfo->channel->name); - free_string_list(cInfo->exceptions); - free(cInfo); -} - -void -spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - - if(cInfo) - { - if(suspend) - { - cInfo->flags |= CHAN_SUSPENDED; - cInfo->suspend_expiry = expiry; - spamserv_part_channel(channel, reason); - } - else - { - if(CHECK_SUSPENDED(cInfo)) - { - cInfo->flags &= ~CHAN_SUSPENDED; - cInfo->suspend_expiry = 0; - } - } - } -} - -int -spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - - if(cInfo) - { - char reason[MAXLEN]; - - if(!spamserv_conf.allow_move_merge || get_chanInfo(target->name)) - { - if(move) - snprintf(reason, sizeof(reason), "unregistered due to a channel move to %s", target->name); - else - snprintf(reason, sizeof(reason), "unregistered due to a channel merge into %s", target->name); - - spamserv_cs_unregister(user, channel, manually, reason); - return 0; - } - - cInfo->channel = target; - - dict_remove(registered_channels_dict, channel->name); - dict_insert(registered_channels_dict, target->name, cInfo); - - if(move) - { - snprintf(reason, sizeof(reason), "Channel moved to %s by %s.", target->name, user->handle_info->handle); - } - else - { - spamserv_join_channel(target); - snprintf(reason, sizeof(reason), "%s merged into %s by %s.", channel->name, target->name, user->handle_info->handle); - } - - if(!CHECK_SUSPENDED(cInfo)) - spamserv_part_channel(channel, reason); - - if(move) - snprintf(reason, sizeof(reason), "$X (channel %s) moved to %s by %s.", channel->name, target->name, user->handle_info->handle); - else - snprintf(reason, sizeof(reason), "$X (channel %s) merged into %s by %s.", channel->name, target->name, user->handle_info->handle); - - global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); - return 1; - } - - return 0; -} - -void -spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - - if(cInfo) - { - char global[MAXLEN], partmsg[MAXLEN]; - - switch (type) - { - case manually: - snprintf(global, sizeof(global), "$X (channel %s) %s by %s.", channel->name, reason, user->handle_info->handle); - snprintf(partmsg, sizeof(partmsg), "%s %s by %s.", channel->name, reason, user->handle_info->handle); - break; - case expire: - snprintf(global, sizeof(global), "$X (channel %s) registration expired.", channel->name); - snprintf(partmsg, sizeof(partmsg), "%s registration expired.", channel->name); - break; - case lost_all_users: - snprintf(global, sizeof(global), "$X (channel %s) lost all users.", channel->name); - snprintf(partmsg, sizeof(partmsg), "%s lost all users.", channel->name); - break; - } - - if(!CHECK_SUSPENDED(cInfo)) - spamserv_part_channel(channel, partmsg); - - spamserv_unregister_channel(cInfo); - global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, global); - } -} - -/***********************************************/ -/* User */ -/***********************************************/ - -static struct userInfo* -get_userInfo(const char *nickname) -{ - return dict_find(connected_users_dict, nickname, 0); -} - -static void -spamserv_create_spamNode(struct chanNode *channel, struct userInfo *uInfo, char *text) -{ - struct spamNode *sNode = malloc(sizeof(struct spamNode)); - - if(!sNode) - { - log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for sNode; channel: %s; user: %s", channel->name, uInfo->user->nick); - return; - } - - sNode->channel = channel; - sNode->crc32 = crc32(text); - sNode->count = 1; - sNode->next = NULL; - - if(uInfo->spam) - { - struct spamNode *temp = uInfo->spam; - - while(temp->next) - temp = temp->next; - - sNode->prev = temp; - temp->next = sNode; - } - else - { - sNode->prev = NULL; - uInfo->spam = sNode; - } -} - -static void -spamserv_delete_spamNode(struct userInfo *uInfo, struct spamNode *sNode) -{ - if(!sNode) - return; - - if(sNode == uInfo->spam) - uInfo->spam = sNode->next; - - if(sNode->next) - sNode->next->prev = sNode->prev; - if(sNode->prev) - sNode->prev->next = sNode->next; - - free(sNode); -} - -static void -spamserv_clear_spamNodes(struct chanNode *channel) -{ - struct userInfo *uInfo; - struct spamNode *sNode; - unsigned int i; - - for(i = 0; i < channel->members.used; i++) - { - if((uInfo = get_userInfo(channel->members.list[i]->user->nick))) - { - if((sNode = uInfo->spam)) - { - for(; sNode; sNode = sNode->next) - if(sNode->channel == channel) - break; - - if(sNode) - spamserv_delete_spamNode(uInfo, sNode); - } - } - } -} - -static void -spamserv_create_floodNode(struct chanNode *channel, struct userNode *user, struct floodNode **uI_fNode) -{ - struct floodNode *fNode = malloc(sizeof(struct floodNode)); - - if(!fNode) - { - log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for fNode; channel: %s; user: %s", channel->name, user->nick); - return; - } - - fNode->channel = channel; - fNode->owner = user; - fNode->count = 1; - fNode->time = now; - fNode->next = NULL; - - if(*uI_fNode) - { - struct floodNode *temp = *uI_fNode; - - while(temp->next) - temp = temp->next; - - fNode->prev = temp; - temp->next = fNode; - } - else - { - fNode->prev = NULL; - *uI_fNode = fNode; - } -} - -static void -spamserv_delete_floodNode(struct floodNode **uI_fNode, struct floodNode *fNode) -{ - if(!fNode) - return; - - if(fNode == *uI_fNode) - *uI_fNode = fNode->next; - - if(fNode->next) - fNode->next->prev = fNode->prev; - if(fNode->prev) - fNode->prev->next = fNode->next; - - free(fNode); -} - -static void -spamserv_create_user(struct userNode *user) -{ - struct userInfo *uInfo = malloc(sizeof(struct userInfo)); - struct killNode *kNode = dict_find(killed_users_dict, irc_ntoa(&user->ip), 0); - - if(!uInfo) - { - log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for uInfo; nick: %s", user->nick); - return; - } - - if(kNode) - spamserv_debug(SSMSG_DEBUG_RECONNECT, user->nick); - - uInfo->user = user; - uInfo->spam = NULL; - uInfo->flood = NULL; - uInfo->joinflood = NULL; - uInfo->flags = kNode ? USER_KILLED : 0; - uInfo->warnlevel = kNode ? kNode->warnlevel : 0; - uInfo->lastadv = 0; - - dict_insert(connected_users_dict, user->nick, uInfo); - - if(kNode) - { - dict_remove(killed_users_dict, irc_ntoa(&user->ip)); - free(kNode); - } -} - -static void -spamserv_delete_user(struct userInfo *uInfo) -{ - if(!uInfo) - return; - - if(uInfo->spam) - while(uInfo->spam) - spamserv_delete_spamNode(uInfo, uInfo->spam); - - if(uInfo->flood) - while(uInfo->flood) - spamserv_delete_floodNode(&uInfo->flood, uInfo->flood); - - if(uInfo->joinflood) - while(uInfo->joinflood) - spamserv_delete_floodNode(&uInfo->joinflood, uInfo->joinflood); - - dict_remove(connected_users_dict, uInfo->user->nick); - free(uInfo); -} - -static void -spamserv_new_user_func(struct userNode *user) -{ - if(!IsLocal(user)) - spamserv_create_user(user); -} - -static void -spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why)) -{ - struct userInfo *uInfo = get_userInfo(user->nick); - struct killNode *kNode; - - if(killer == spamserv) - { - kNode = malloc(sizeof(struct killNode)); - - if(!kNode) - { - log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for killNode - nickname %s", user->nick); - spamserv_delete_user(uInfo); - return; - } - - if(uInfo->warnlevel > KILL_WARNLEVEL) - kNode->warnlevel = uInfo->warnlevel - KILL_WARNLEVEL; - else - kNode->warnlevel = 0; - - kNode->time = now; - - dict_insert(killed_users_dict, irc_ntoa(&user->ip), kNode); - } - - spamserv_delete_user(uInfo); -} - -static void -spamserv_nick_change_func(struct userNode *user, const char *old_nick) -{ - struct userInfo *uInfo = get_userInfo(old_nick); - - dict_remove(connected_users_dict, old_nick); - dict_insert(connected_users_dict, user->nick, uInfo); -} - -static int -spamserv_user_join(struct modeNode *mNode) -{ - struct chanNode *channel = mNode->channel; - struct userNode *user = mNode->user; - struct chanInfo *cInfo; - struct userInfo *uInfo; - struct floodNode *jfNode; - - if(user->uplink->burst || !(cInfo = get_chanInfo(channel->name)) || !CHECK_JOINFLOOD(cInfo) || !(uInfo = get_userInfo(user->nick))) - return 0; - - - if(!CHECK_CHANOPS(cInfo)) - { - //struct modeNode *mn = GetUserMode(channel, user); - //if(mn->modes & MODE_CHANOP) - // return; - if(check_user_level(channel, user, lvlGiveOps, 1, 0)) - return 0; - } - - if(!CHECK_VOICED(cInfo)) - { - if(check_user_level(channel, user, lvlGiveVoice, 1, 0)) - return 0; - } - - if(cInfo->exceptlevel == 0) - return 0; - if(cInfo->exceptlevel < 501) { - struct userData *uData; - if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) { - return 0; - } - } - - if(!(jfNode = uInfo->joinflood)) - { - spamserv_create_floodNode(channel, user, &uInfo->joinflood); - } - else - { - for(; jfNode; jfNode = jfNode->next) - if(jfNode->channel == channel) - break; - - if(!jfNode) - { - spamserv_create_floodNode(channel, user, &uInfo->joinflood); - } - else - { - jfNode->count++; - jfNode->time = now; - - if(jfNode->count > JOINFLOOD_MAX) - { - char reason[MAXLEN]; - - spamserv_delete_floodNode(&uInfo->joinflood, jfNode); - snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_JOINFLOOD, spamserv_conf.network_rules); - spamserv_punish(channel, user, JOINFLOOD_B_DURATION, reason, 1); - } - } - } - - return 0; -} - -static void -spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) -{ - struct userNode *user = mn->user; - struct chanNode *channel = mn->channel; - struct userInfo *uInfo; - struct spamNode *sNode; - struct floodNode *fNode; - - if(user->dead || !get_chanInfo(channel->name) || !(uInfo = get_userInfo(user->nick))) - return; - - if((sNode = uInfo->spam)) - { - for(; sNode; sNode = sNode->next) - if(sNode->channel == channel) - break; - - if(sNode) - spamserv_delete_spamNode(uInfo, sNode); - } - - if((fNode = uInfo->flood)) - { - for(; fNode; fNode = fNode->next) - if(fNode->channel == channel) - break; - - if(fNode) - spamserv_delete_floodNode(&uInfo->flood, fNode); - } -} - -/***********************************************/ -/* Other Stuff */ -/***********************************************/ - -static void -crc32_init(void) -{ - unsigned long crc; - int i, j; - - for(i = 0; i < 256; i++) - { - crc = i; - - for(j = 8; j > 0; j--) - { - if(crc & 1) - { - crc = (crc >> 1) ^ 0xEDB88320L; - } - else - { - crc >>= 1; - } - } - - crc_table[i] = crc; - } -} - -static unsigned long -crc32(const char *text) -{ - register unsigned long crc = 0xFFFFFFFF; - unsigned int c, i = 0; - - while((c = (unsigned int)text[i++]) != 0) - crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF]; - - return (crc^0xFFFFFFFF); -} - -static void -timeq_flood(UNUSED_ARG(void *data)) -{ - dict_iterator_t it; - struct userInfo *uInfo; - struct floodNode *fNode; - - for(it = dict_first(connected_users_dict); it; it = iter_next(it)) - { - uInfo = iter_data(it); - - if(!(fNode = uInfo->flood)) - continue; - - for(; fNode; fNode = fNode->next) - { - if(now - fNode->time > FLOOD_EXPIRE) - { - if(!(--fNode->count)) - spamserv_delete_floodNode(&uInfo->flood, fNode); - } - } - } - - timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL); -} - -static void -timeq_joinflood(UNUSED_ARG(void *data)) -{ - dict_iterator_t it; - struct userInfo *uInfo; - struct floodNode *fNode; - - for(it = dict_first(connected_users_dict); it; it = iter_next(it)) - { - uInfo = iter_data(it); - - if(!(fNode = uInfo->joinflood)) - continue; - - for(; fNode; fNode = fNode->next) - { - if(now - fNode->time > JOINFLOOD_EXPIRE) - { - if(!(--fNode->count)) - spamserv_delete_floodNode(&uInfo->joinflood, fNode); - } - } - } - - timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL); -} - -static void -timeq_adv(UNUSED_ARG(void *data)) -{ - dict_iterator_t it; - struct userInfo *uInfo; - - for(it = dict_first(connected_users_dict); it; it = iter_next(it)) - { - uInfo = iter_data(it); - - if(uInfo->lastadv && uInfo->lastadv - now > ADV_EXPIRE) - { - uInfo->lastadv = 0; - uInfo->flags &= ~USER_ADV_WARNED; - } - } - - timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL); -} - -static void -timeq_warnlevel(UNUSED_ARG(void *data)) -{ - dict_iterator_t it; - struct userInfo *uInfo; - - for(it = dict_first(connected_users_dict); it; it = iter_next(it)) - { - uInfo = iter_data(it); - - if(uInfo->warnlevel > 0) - uInfo->warnlevel--; - } - - timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL); -} - -static void -timeq_kill(UNUSED_ARG(void *data)) -{ - dict_iterator_t it; - struct killNode *kNode; - - for(it = dict_first(killed_users_dict); it; it = iter_next(it)) - { - kNode = iter_data(it); - - if(kNode->time - now > KILL_EXPIRE) - free(kNode); - } - - timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL); -} - -static int -binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[]) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - int value; - - if(argc > 1) - { - if(enabled_string(argv[1])) - { - cInfo->flags |= mask; - value = 1; - } - else if(disabled_string(argv[1])) - { - cInfo->flags &= ~mask; - value = 0; - } - else - { - spamserv_notice(user, "SSMSG_INVALID_BINARY", argv[1]); - return 0; - } - } - else - { - value = (cInfo->flags & mask) ? 1 : 0; - } - - spamserv_notice(user, "SSMSG_STRING_VALUE", name, value ? "Enabled." : "Disabled."); - return 1; -} - -struct valueData -{ - char *description; - char value; - int oper_only : 1; -}; - -static int -multiple_option(char *name, char *description, enum channelinfo info, struct valueData *values, int count, struct userNode *user, struct chanNode *channel, int argc, char *argv[]) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - int index; - - if(argc > 1) - { - index = atoi(argv[1]); - - if(index < 0 || index >= count) - { - spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, description); - - for(index = 0; index < count; index++) - spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description); - - return 0; - } - - if(values[index].oper_only && !IsOper(user)) - { - spamserv_notice(user, "SSMSG_MUST_BE_OPER"); - return 0; - } - - cInfo->info[info] = values[index].value; - } - else - { - for(index = 0; index < count && cInfo->info[info] != values[index].value; index++); - } - - spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description); - return 1; -} - -static int -show_exceptions(struct userNode *user, struct chanInfo *cInfo) -{ - struct helpfile_table table; - unsigned int i; - - if(!cInfo->exceptions->used) - { - spamserv_notice(user, "SSMSG_NO_EXCEPTIONS"); - return 0; - } - - spamserv_notice(user, "SSMSG_EXCEPTION_LIST"); - - table.length = 0; - table.width = 1; - table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS; - table.contents = alloca(cInfo->exceptions->used * sizeof(*table.contents)); - - for(i = 0; i < cInfo->exceptions->used; i++) - { - table.contents[table.length] = alloca(table.width * sizeof(**table.contents)); - table.contents[table.length][0] = cInfo->exceptions->list[i]; - table.length++; - } - - table_send(spamserv, user->nick, 0, NULL, table); - - return 1; -} - -static void -show_memory_usage(struct userNode *user) -{ - dict_iterator_t it; - struct helpfile_table table; - struct chanInfo *cInfo; - struct userInfo *uInfo; - struct spamNode *sNode; - struct floodNode *fNode; - double channel_size = 0, user_size, size; - unsigned int spamcount = 0, floodcount = 0, i, j; - char buffer[64]; - - for(it = dict_first(registered_channels_dict); it; it = iter_next(it)) - { - cInfo = iter_data(it); - - if(!cInfo->exceptions->used) - continue; - - for(i = 0; i < cInfo->exceptions->used; i++) - channel_size += strlen(cInfo->exceptions->list[i]) * sizeof(char); - } - - for(it = dict_first(connected_users_dict); it; it = iter_next(it)) - { - uInfo = iter_data(it); - - for(sNode = uInfo->spam; sNode; sNode = sNode->next, spamcount++); - for(fNode = uInfo->flood; fNode; fNode = fNode->next, floodcount++); - for(fNode = uInfo->joinflood; fNode; fNode = fNode->next, floodcount++); - } - - channel_size += dict_size(registered_channels_dict) * sizeof(struct chanInfo); - - user_size = dict_size(connected_users_dict) * sizeof(struct userInfo) + - dict_size(killed_users_dict) * sizeof(struct killNode) + - spamcount * sizeof(struct spamNode) + - floodcount * sizeof(struct floodNode); - - size = channel_size + user_size; - - ss_reply("SSMSG_STATUS_MEMORY"); - - table.length = 3; - table.width = 4; - table.flags = TABLE_NO_FREE | TABLE_NO_HEADERS | TABLE_PAD_LEFT; - table.contents = calloc(table.length, sizeof(char**)); - - // chanInfo - table.contents[0] = calloc(table.width, sizeof(char*)); - snprintf(buffer, sizeof(buffer), "Channel Memory Usage:"); - table.contents[0][0] = strdup(buffer); - snprintf(buffer, sizeof(buffer), " %g Byte; ", channel_size); - table.contents[0][1] = strdup(buffer); - snprintf(buffer, sizeof(buffer), "%g KiloByte; ", channel_size / 1024); - table.contents[0][2] = strdup(buffer); - snprintf(buffer, sizeof(buffer), "%g MegaByte", channel_size / 1024 / 1024); - table.contents[0][3] = strdup(buffer); - - // userInfo - table.contents[1] = calloc(table.width, sizeof(char*)); - snprintf(buffer, sizeof(buffer), "User Memory Usage :"); - table.contents[1][0] = strdup(buffer); - snprintf(buffer, sizeof(buffer), " %g Byte; ", user_size); - table.contents[1][1] = strdup(buffer); - snprintf(buffer, sizeof(buffer), "%g KiloByte; ", user_size / 1024); - table.contents[1][2] = strdup(buffer); - snprintf(buffer, sizeof(buffer), "%g MegaByte", user_size / 1024 / 1024); - table.contents[1][3] = strdup(buffer); - - // total memory usage - table.contents[2] = calloc(table.width, sizeof(char*)); - snprintf(buffer, sizeof(buffer), "Total Memory Usage :"); - table.contents[2][0] = strdup(buffer); - snprintf(buffer, sizeof(buffer), " %g Byte; ", size); - table.contents[2][1] = strdup(buffer); - snprintf(buffer, sizeof(buffer), "%g KiloByte; ", size / 1024); - table.contents[2][2] = strdup(buffer); - snprintf(buffer, sizeof(buffer), "%g MegaByte", size / 1024 / 1024); - table.contents[2][3] = strdup(buffer); - - table_send(spamserv, user->nick, 0, NULL, table); - - for(i = 0; i < table.length; i++) - { - for(j = 0; j < table.width; j++) - free((char*)table.contents[i][j]); - - free(table.contents[i]); - } - - free(table.contents); -} - -static void -show_registered_channels(struct userNode *user) -{ - struct helpfile_table table; - dict_iterator_t it; - - spamserv_notice(user, "SSMSG_STATUS_CHANNEL_LIST"); - - if(!dict_size(registered_channels_dict)) - { - spamserv_notice(user, "SSMSG_STATUS_NO_CHANNEL"); - return; - } - - table.length = 0; - table.width = 1; - table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS; - table.contents = alloca(dict_size(registered_channels_dict) * sizeof(*table.contents)); - - for(it = dict_first(registered_channels_dict); it; it = iter_next(it)) - { - struct chanInfo *cInfo = iter_data(it); - - table.contents[table.length] = alloca(table.width * sizeof(**table.contents)); - table.contents[table.length][0] = cInfo->channel->name; - table.length++; - } - - table_send(spamserv, user->nick, 0, NULL, table); -} - -/***********************************************/ -/* SpamServ_Func */ -/***********************************************/ - -static -SPAMSERV_FUNC(cmd_register) -{ - struct chanInfo *cInfo; - char reason[MAXLEN]; - - if(!channel || !channel->channel_info) - { - ss_reply("SSMSG_NOT_REGISTERED_CS", channel->name); - return 0; - } - - if(get_chanInfo(channel->name)) - { - ss_reply("SSMSG_ALREADY_REGISTERED", channel->name); - return 0; - } - - if(IsSuspended(channel->channel_info)) - { - ss_reply("SSMSG_SUSPENDED_CS", channel->name, "register"); - return 0; - } - - if(channel == spamserv_conf.debug_channel) - { - ss_reply("SSMSG_DEBUG_CHAN"); - return 0; - } - - if(!(cInfo = spamserv_register_channel(channel, spamserv_conf.global_exceptions, CHAN_FLAGS_DEFAULT , CHAN_INFO_DEFAULT))) - { - ss_reply("SSMSG_NO_REGISTER", channel->name); - return 0; - } - - spamserv_join_channel(cInfo->channel); - - snprintf(reason, sizeof(reason), "%s (channel %s) registered by %s.", spamserv->nick, channel->name, user->handle_info->handle); - global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); - ss_reply("SSMSG_REG_SUCCESS", channel->name); - - return 1; -} - -static -SPAMSERV_FUNC(cmd_unregister) -{ - struct chanInfo *cInfo; - struct chanData *cData; - struct userData *uData; - char reason[MAXLEN]; - - if(!channel || !(cData = channel->channel_info) || !(cInfo = get_chanInfo(channel->name))) - { - ss_reply("SSMSG_NOT_REGISTERED", channel->name); - return 0; - } - - if(!(uData = GetChannelUser(cData, user->handle_info)) || (uData->access < UL_OWNER)) - { - ss_reply("SSMSG_NO_ACCESS"); - return 0; - } - - if(!IsHelping(user)) - { - if(IsSuspended(cData)) - { - ss_reply("SSMSG_SUSPENDED_CS", channel->name, "unregister"); - return 0; - } - - if(argc < 2 || strcasecmp(argv[1], "CONFIRM")) - { - ss_reply("SSMSG_CONFIRM_UNREG"); - return 0; - } - } - - if(!CHECK_SUSPENDED(cInfo)) - { - snprintf(reason, sizeof(reason), "%s unregistered by %s.", spamserv->nick, user->handle_info->handle); - spamserv_part_channel(channel, reason); - } - - spamserv_unregister_channel(cInfo); - - snprintf(reason, sizeof(reason), "%s (channel %s) unregistered by %s.", spamserv->nick, channel->name, user->handle_info->handle); - global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); - ss_reply("SSMSG_UNREG_SUCCESS", channel->name); - - return 1; -} - -static -SPAMSERV_FUNC(cmd_status) -{ - ss_reply("SSMSG_STATUS"); - ss_reply("SSMSG_STATUS_USERS", dict_size(connected_users_dict)); - ss_reply("SSMSG_STATUS_CHANNELS", dict_size(registered_channels_dict)); - - if(IsOper(user) && argc > 1) - { - if(!irccasecmp(argv[1], "memory")) - show_memory_usage(user); - else if(!irccasecmp(argv[1], "channels")) - show_registered_channels(user); - } - - return 1; -} - -static -SPAMSERV_FUNC(cmd_addexception) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - struct userData *uData; - unsigned int i; - - if(!cInfo || !channel->channel_info) - { - ss_reply("SSMSG_NOT_REGISTERED", channel->name); - return 0; - } - - if(CHECK_SUSPENDED(cInfo)) - { - ss_reply("SSMSG_SUSPENDED", channel->name); - return 0; - } - - if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400)) - { - ss_reply("SSMSG_NO_ACCESS"); - return 0; - } - - if(argc < 2) - return show_exceptions(user, cInfo); - - if(cInfo->exceptions->used == spamserv_conf.exception_max && !IsOper(user)) - { - ss_reply("SSMSG_EXCEPTION_MAX", spamserv_conf.exception_max); - return 0; - } - - if(strlen(argv[1]) < spamserv_conf.exception_min_len) - { - ss_reply("SSMSG_EXCEPTION_TOO_SHORT", spamserv_conf.exception_min_len); - return 0; - } - else if(strlen(argv[1]) > spamserv_conf.exception_max_len) - { - ss_reply("SSMSG_EXCEPTION_TOO_LONG", spamserv_conf.exception_max_len); - return 0; - } - - for(i = 0; i < cInfo->exceptions->used; i++) - { - if(!irccasecmp(argv[1], cInfo->exceptions->list[i])) - { - ss_reply("SSMSG_EXCEPTION_IN_LIST", argv[1]); - return 0; - } - } - - string_list_append(cInfo->exceptions, strdup(argv[1])); - ss_reply("SSMSG_EXCEPTION_ADDED", argv[1]); - - return 1; -} - -static -SPAMSERV_FUNC(cmd_delexception) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - struct userData *uData; - unsigned int i; - int found = -1; - - if(!cInfo || !channel->channel_info) - { - ss_reply("SSMSG_NOT_REGISTERED", channel->name); - return 0; - } - - if(CHECK_SUSPENDED(cInfo)) - { - ss_reply("SSMSG_SUSPENDED", channel->name); - return 0; - } - - if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400)) - { - ss_reply("SSMSG_NO_ACCESS"); - return 0; - } - - if(argc < 2) - return show_exceptions(user, cInfo); - - for(i = 0; i < cInfo->exceptions->used; i++) - { - if(!irccasecmp(argv[1], cInfo->exceptions->list[i])) - { - found = i; - break; - } - } - - if(found == -1) - { - ss_reply("SSMSG_NO_SUCH_EXCEPTION", argv[1]); - return 0; - } - - string_list_delete(cInfo->exceptions, i); - ss_reply("SSMSG_EXCEPTION_DELETED", argv[1]); - - return 1; -} - -static -SPAMSERV_FUNC(cmd_set) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - struct svccmd *subcmd; - char cmd_name[MAXLEN]; - unsigned int i; - - if(!cInfo) - { - ss_reply("SSMSG_NOT_REGISTERED", channel->name); - return 0; - } - - if(CHECK_SUSPENDED(cInfo)) - { - ss_reply("SSMSG_SUSPENDED", channel->name); - return 0; - } - - if(!check_user_level(channel,user,lvlSetters,1,0)) - { - ss_reply("SSMSG_NO_ACCESS"); - return 0; - } - - if(argc < 2) - { - ss_reply("SSMSG_CHANNEL_OPTIONS"); - - for(i = 0; i < SET_SUBCMDS_SIZE; i++) - { - sprintf(cmd_name, "%s %s", cmd->name, set_subcommands[i]); - - if((subcmd = dict_find(cmd->parent->commands, cmd_name, NULL))) - subcmd->command->func(user, channel, 1, argv + 1, subcmd); - } - - return 1; - } - - sprintf(cmd_name, "%s %s", cmd->name, argv[1]); - subcmd = dict_find(cmd->parent->commands, cmd_name, NULL); - - if(!subcmd) - { - reply("SSMSG_INVALID_OPTION", argv[1], argv[0]); - return 0; - } - - return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd); -} - -static -SPAMSERV_FUNC(opt_spamlimit) -{ - struct valueData values[] = - { - {"Users may send the same message $b2$b times.", 'a', 0}, - {"Users may send the same message $b3$b times.", 'b', 0}, - {"Users may send the same message $b4$b times.", 'c', 0}, - {"Users may send the same message $b5$b times.", 'd', 0}, - {"Users may send the same message $b6$b times.", 'e', 0} - }; - - MULTIPLE_OPTION("SpamLimit ", "SpamLimit", ci_SpamLimit); -} - -static -SPAMSERV_FUNC(opt_advreaction) -{ - struct valueData values[] = - { - {"Kick on disallowed advertising.", 'k', 0}, - {"Kickban on disallowed advertising.", 'b', 0}, - {"Short timed ban on disallowed advertising.", 's', 0}, - {"Long timed ban on disallowed advertising.", 'l', 0}, - {"Kill on disallowed advertising.", 'd', 1} - }; - - MULTIPLE_OPTION("AdvReaction ", "AdvReaction", ci_AdvReaction); -} - -static -SPAMSERV_FUNC(opt_warnreaction) -{ - struct valueData values[] = - { - {"Kick after warning.", 'k', 0}, - {"Kickban after warning.", 'b', 0}, - {"Short timed ban after warning.", 's', 0}, - {"Long timed ban after warning.", 'l', 0}, - {"Kill after warning.", 'd', 1} - }; - - MULTIPLE_OPTION("WarnReaction ", "WarnReaction", ci_WarnReaction); -} - -static -SPAMSERV_FUNC(opt_advscan) -{ - BINARY_OPTION("AdvScan ", CHAN_ADV_SCAN); -} - -static -SPAMSERV_FUNC(opt_spamscan) -{ - BINARY_OPTION("SpamScan ", CHAN_SPAMSCAN); -} - -static -SPAMSERV_FUNC(opt_floodscan) -{ - BINARY_OPTION("FloodScan ", CHAN_FLOODSCAN); -} - -static -SPAMSERV_FUNC(opt_joinflood) -{ - BINARY_OPTION("JoinFloodScan ", CHAN_JOINFLOOD); -} - -static -SPAMSERV_FUNC(opt_scanops) -{ - BINARY_OPTION("ScanChanOps ", CHAN_SCAN_CHANOPS); -} - -static -SPAMSERV_FUNC(opt_scanvoiced) -{ - BINARY_OPTION("ScanVoiced ", CHAN_SCAN_VOICED); -} - -static -SPAMSERV_FUNC(opt_exceptlevel) -{ - struct chanInfo *cInfo = get_chanInfo(channel->name); - struct userData *uData; - int index; - if(argc > 1) - { - index = atoi(argv[1]); - if(index < 1 || index > 501) - { - spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, "ExceptLevel"); - return 0; - } - if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < cInfo->exceptlevel || index > uData->access)) - { - ss_reply("SSMSG_NO_ACCESS"); - return 0; - } - cInfo->exceptlevel=index; - } else { - index=cInfo->exceptlevel; - } - spamserv_notice(user, "SSMSG_EASYNUMERIC_VALUE", "ExceptLevel ", index); - return 1; -} - -static void -to_lower(char *message) -{ - unsigned int i, diff = 'a' - 'A'; - - for(i = 0; i < strlen(message); i++) - { - if((message[i] >= 'A') && (message[i] <= 'Z')) - message[i] = message[i] + diff; - } -} - -static char * -strip_mirc_codes(char *text) -{ - // taken from xchat and modified - int nc = 0, i = 0, col = 0, len = strlen(text); - static char new_str[MAXLEN]; - - while(len > 0) - { - if((col && isdigit(*text) && nc < 2) || - (col && *text == ',' && isdigit(*(text + 1)) && nc < 3)) - { - nc++; - - if(*text == ',') - nc = 0; - } - else - { - col = 0; - - switch(*text) - { - case '\003': - col = 1; - nc = 0; - break; - case '\002': - case '\022': - case '\026': - case '\031': - case '\037': - break; - default: - new_str[i] = *text; - i++; - } - } - - text++; - len--; - } - - new_str[i] = '\0'; - - return new_str; -} - -static int -is_in_exception_list(struct chanInfo *cInfo, char *message) -{ - unsigned int i; - - for(i = 0; i < cInfo->exceptions->used; i++) - if(strstr(message, cInfo->exceptions->list[i])) - return 1; - - return 0; -} - -static int -check_advertising(struct chanInfo *cInfo, char *message) -{ - unsigned int i = 0; - - if(spamserv_conf.strip_mirc_codes) - message = strip_mirc_codes(message); - - if(is_in_exception_list(cInfo, message)) - return 0; - - while(message[i] != 0) - { - if(message[i] == '#') - { - char channelname[CHANNELLEN]; - unsigned int j = 0; - - if(!spamserv_conf.adv_chan_must_exist) - return 1; - - /* only return 1, if the channel does exist */ - - while((message[i] != 0) && (message[i] != ' ')) - { - channelname[j] = message[i]; - i++; - j++; - } - - channelname[j] = '\0'; - - if(GetChannel(channelname)) - return 1; - } - else if((message[i] == 'w') && (message[i+1] == 'w') && (message[i+2] == 'w') && (message[i+3] == '.')) - return 1; - else if((message[i] == 'h') && (message[i+1] == 't') && (message[i+2] == 't') && (message[i+3] == 'p') && (message[i+4] == ':')) - return 1; - else if((message[i] == 'f') && (message[i+1] == 't') && (message[i+2] == 'p') && ((message[i+3] == '.') || (message[i+3] == ':'))) - return 1; - - i++; - } - - return 0; -} - -struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason); - -static void -spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban) -{ - if(ban) - { - struct mod_chanmode change; - char *hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT); - - sanitize_ircmask(hostmask); - - if(expires) - add_channel_ban(channel->channel_info, hostmask, spamserv->nick, now, now, now + expires, reason); - - mod_chanmode_init(&change); - change.argc = 1; - change.args[0].mode = MODE_BAN; - change.args[0].u.hostmask = hostmask; - mod_chanmode_announce(spamserv, channel, &change); - - free(hostmask); - - spamserv_debug(SSMSG_DEBUG_BAN, user->nick, channel->name, reason); - } - else - spamserv_debug(SSMSG_DEBUG_KICK, user->nick, channel->name, reason); - - KickChannelUser(user, channel, spamserv, reason); -} - -void -spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text) -{ - struct chanInfo *cInfo; - struct userInfo *uInfo; - struct spamNode *sNode; - struct floodNode *fNode; - unsigned int violation = 0; - char reason[MAXLEN]; - - /* make sure: spamserv is not disabled; srvx is running; spamserv is in the chan; chan is regged, user does exist */ - if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick))) - return; - - if(!CHECK_CHANOPS(cInfo)) - { - struct modeNode *mn = GetUserMode(channel, user); - if(mn && mn->modes & MODE_CHANOP) - return; - //if(check_user_level(channel, user, lvlGiveOps, 1, 0)) - // return; - } - - if(!CHECK_VOICED(cInfo)) - { - struct modeNode *mn = GetUserMode(channel, user); - if(mn && (mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP)) - return; - } - - if(cInfo->exceptlevel == 0) - return; - if(cInfo->exceptlevel < 501) { - struct userData *uData; - if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) { - return; - } - } - - to_lower(text); - - if(CHECK_SPAM(cInfo)) - { - if(!(sNode = uInfo->spam)) - { - spamserv_create_spamNode(channel, uInfo, text); - } - else - { - for(; sNode; sNode = sNode->next) - if(sNode->channel == channel) - break; - - if(!sNode) - { - spamserv_create_spamNode(channel, uInfo, text); - } - else - { - unsigned long crc = crc32(text); - - if(crc == sNode->crc32) - { - unsigned int spamlimit = 2; - sNode->count++; - - switch(cInfo->info[ci_SpamLimit]) - { - case 'a': spamlimit = 2; break; - case 'b': spamlimit = 3; break; - case 'c': spamlimit = 4; break; - case 'd': spamlimit = 5; break; - case 'e': spamlimit = 6; break; - } - - if(sNode->count == spamlimit) - { - uInfo->warnlevel += SPAM_WARNLEVEL; - - if(uInfo->warnlevel < MAX_WARNLEVEL) - spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); - } - else if(sNode->count > spamlimit) - { - switch(cInfo->info[ci_WarnReaction]) - { - case 'k': uInfo->flags |= USER_KICK; break; - case 'b': uInfo->flags |= USER_KICKBAN; break; - case 's': uInfo->flags |= USER_SHORT_TBAN; break; - case 'l': uInfo->flags |= USER_LONG_TBAN; break; - case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break; - } - - spamserv_delete_spamNode(uInfo, sNode); - uInfo->warnlevel += SPAM_WARNLEVEL; - violation = 1; - } - } - else - { - sNode->crc32 = crc; - sNode->count = 1; - } - } - } - } - - if(CHECK_FLOOD(cInfo)) - { - if(!(fNode = uInfo->flood)) - { - spamserv_create_floodNode(channel, user, &uInfo->flood); - } - else - { - for(; fNode; fNode = fNode->next) - if(fNode->channel == channel) - break; - - if(!fNode) - { - spamserv_create_floodNode(channel, user, &uInfo->flood); - } - else - { - if(((now - fNode->time) < FLOOD_EXPIRE)) - { - fNode->count++; - - if(fNode->count == FLOOD_MAX_LINES - 1) - { - uInfo->warnlevel += FLOOD_WARNLEVEL; - - if(uInfo->warnlevel < MAX_WARNLEVEL) - spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); - } - else if(fNode->count > FLOOD_MAX_LINES) - { - switch(cInfo->info[ci_WarnReaction]) - { - case 'k': uInfo->flags |= USER_KICK; break; - case 'b': uInfo->flags |= USER_KICKBAN; break; - case 's': uInfo->flags |= USER_SHORT_TBAN; break; - case 'l': uInfo->flags |= USER_LONG_TBAN; break; - case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break; - } - - spamserv_delete_floodNode(&uInfo->flood, fNode); - uInfo->warnlevel += FLOOD_WARNLEVEL; - violation = 2; - } - } - - fNode->time = now; - } - } - } - - if(CHECK_ADV(cInfo) && check_advertising(cInfo, text)) - { - if(CHECK_ADV_WARNED(uInfo)) - { - switch(cInfo->info[ci_AdvReaction]) - { - case 'k': uInfo->flags |= USER_KICK; break; - case 'b': uInfo->flags |= USER_KICKBAN; break; - case 's': uInfo->flags |= USER_SHORT_TBAN; break; - case 'l': uInfo->flags |= USER_LONG_TBAN; break; - case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break; - } - - uInfo->warnlevel += ADV_WARNLEVEL; - violation = 3; - } - else - { - uInfo->flags |= USER_ADV_WARNED; - uInfo->lastadv = now; - uInfo->warnlevel += ADV_WARNLEVEL; - - if(uInfo->warnlevel < MAX_WARNLEVEL) - spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); - } - } - - if(!CHECK_WARNED(uInfo) && !CHECK_KILL(uInfo) && !CHECK_GLINE(uInfo) && uInfo->warnlevel == MAX_WARNLEVEL) - { - uInfo->flags |= USER_WARNED; - snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); - irc_notice(spamserv, user->numeric, reason); - irc_privmsg(spamserv, user->numeric, reason); - } - else if(uInfo->warnlevel > MAX_WARNLEVEL) - { - if(CHECK_KILLED(uInfo)) - uInfo->flags |= USER_GLINE; - else - uInfo->flags |= USER_KILL; - - violation = 5; - } - - if(!violation) - return; - - switch(violation) - { - case 1: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); break; - case 2: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); break; - case 3: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); break; - default: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); break; - } - - if(CHECK_GLINE(uInfo)) - { - int size = strlen(user->hostname) + 3; - char *mask = alloca(size); - snprintf(mask, size, "*@%s", user->hostname); - gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 0, 1); - spamserv_debug(SSMSG_DEBUG_GLINE, user->nick, user->hostname, channel->name); - } - else if(CHECK_KILL(uInfo)) - { - DelUser(user, spamserv, 1, reason); - spamserv_debug(SSMSG_DEBUG_KILL, user->nick, channel->name); - } - else if(CHECK_LONG_TBAN(uInfo)) - { - spamserv_punish(channel, user, spamserv_conf.long_ban_duration, reason, 1); - } - else if(CHECK_SHORT_TBAN(uInfo)) - { - spamserv_punish(channel, user, spamserv_conf.short_ban_duration, reason, 1); - } - else if(CHECK_KICKBAN(uInfo)) - { - spamserv_punish(channel, user, 0, reason, 1); - } - else if(CHECK_KICK(uInfo)) - { - spamserv_punish(channel, user, 0, reason, 0); - } -} - -static int -spamserv_saxdb_read(struct dict *database) -{ - dict_iterator_t it; - struct record_data *hir; - struct chanNode *channel; - struct chanInfo *cInfo; - struct string_list *strlist; - unsigned int flags,exceptlevel; - char *str, *info; - unsigned long expiry; - - for(it = dict_first(database); it; it = iter_next(it)) - { - hir = iter_data(it); - - if(hir->type != RECDB_OBJECT) - { - log_module(SS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it)); - continue; - } - - channel = GetChannel(iter_key(it)); - strlist = database_get_data(hir->d.object, KEY_EXCEPTIONS, RECDB_STRING_LIST); - - str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING); - flags = str ? atoi(str) : 0; - - info = database_get_data(hir->d.object, KEY_INFO, RECDB_QSTRING); - str = database_get_data(hir->d.object, KEY_EXCEPTLEVEL, RECDB_QSTRING); - exceptlevel = str ? atoi(str) : 400; - - str = database_get_data(hir->d.object, KEY_EXPIRY, RECDB_QSTRING); - expiry = str ? strtoul(str, NULL, 0) : 0; - - if(channel && info) - { - if((cInfo = spamserv_register_channel(channel, strlist, flags, info))) - { - /* if the channel is suspended and expiry = 0 it means: channel will - never expire ! it does NOT mean, the channel is not suspended */ - if(CHECK_SUSPENDED(cInfo) && expiry && (expiry < now)) - { - cInfo->flags &= ~CHAN_SUSPENDED; - spamserv_join_channel(cInfo->channel); - } - else if(!CHECK_SUSPENDED(cInfo)) - spamserv_join_channel(cInfo->channel); - else - cInfo->suspend_expiry = expiry; - cInfo->exceptlevel=exceptlevel; - } - } - else - log_module(SS_LOG, LOG_ERROR, "Couldn't register channel %s. Channel or info invalid.", iter_key(it)); - } - - return 0; -} - -static int -spamserv_saxdb_write(struct saxdb_context *ctx) -{ - dict_iterator_t it; - - for(it = dict_first(registered_channels_dict); it; it = iter_next(it)) - { - struct chanInfo *cInfo = iter_data(it); - - saxdb_start_record(ctx, cInfo->channel->name, 1); - - if(cInfo->exceptions->used) - saxdb_write_string_list(ctx, KEY_EXCEPTIONS, cInfo->exceptions); - - if(cInfo->flags) - saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags); - - saxdb_write_string(ctx, KEY_INFO, cInfo->info); - saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel); - - if(cInfo->suspend_expiry) - saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry); - - saxdb_end_record(ctx); - } - return 0; -} - -static void -spamserv_conf_read(void) -{ - dict_t conf_node; - const char *str; - - if(!(conf_node = conf_get_data(SPAMSERV_CONF_NAME, RECDB_OBJECT))) - { - log_module(SS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", SPAMSERV_CONF_NAME); - return; - } - - str = database_get_data(conf_node, KEY_DEBUG_CHANNEL, RECDB_QSTRING); - - if(str) - { - spamserv_conf.debug_channel = AddChannel(str, now, "+tinms", NULL); - - if(spamserv_conf.debug_channel) - spamserv_join_channel(spamserv_conf.debug_channel); - } - else - { - spamserv_conf.debug_channel = NULL; - } - - spamserv_conf.global_exceptions = database_get_data(conf_node, KEY_GLOBAL_EXCEPTIONS, RECDB_STRING_LIST); - - str = database_get_data(conf_node, KEY_NETWORK_RULES, RECDB_QSTRING); - spamserv_conf.network_rules = str ? str : NULL; - - str = database_get_data(conf_node, KEY_TRIGGER, RECDB_QSTRING); - spamserv_conf.trigger = str ? str[0] : 0; - - str = database_get_data(conf_node, KEY_SHORT_BAN_DURATION, RECDB_QSTRING); - spamserv_conf.short_ban_duration = str ? ParseInterval(str) : ParseInterval("15m"); - - str = database_get_data(conf_node, KEY_LONG_BAN_DURATION, RECDB_QSTRING); - spamserv_conf.long_ban_duration = str ? ParseInterval(str) : ParseInterval("1h"); - - str = database_get_data(conf_node, KEY_GLINE_DURATION, RECDB_QSTRING); - spamserv_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h"); - - str = database_get_data(conf_node, KEY_EXCEPTION_MAX, RECDB_QSTRING); - spamserv_conf.exception_max = str ? strtoul(str, NULL, 0) : 10; - - str = database_get_data(conf_node, KEY_EXCEPTION_MIN_LEN, RECDB_QSTRING); - spamserv_conf.exception_min_len = str ? strtoul(str, NULL, 0) : 4; - - str = database_get_data(conf_node, KEY_EXCEPTION_MAX_LEN, RECDB_QSTRING); - spamserv_conf.exception_max_len = str ? strtoul(str, NULL, 0) : 15; - - str = database_get_data(conf_node, KEY_ADV_CHAN_MUST_EXIST, RECDB_QSTRING); - spamserv_conf.adv_chan_must_exist = str ? enabled_string(str) : 1; - - str = database_get_data(conf_node, KEY_STRIP_MIRC_CODES, RECDB_QSTRING); - spamserv_conf.strip_mirc_codes = str ? enabled_string(str) : 0; - - str = database_get_data(conf_node, KEY_ALLOW_MOVE_MERGE, RECDB_QSTRING); - spamserv_conf.allow_move_merge = str ? enabled_string(str) : 0; -} - -static void -spamserv_db_cleanup(void) -{ - dict_iterator_t it; - - while((it = dict_first(registered_channels_dict))) - { - spamserv_unregister_channel(iter_data(it)); - } - - while((it = dict_first(killed_users_dict))) - { - free(iter_data(it)); - } - - dict_delete(registered_channels_dict); - dict_delete(connected_users_dict); - dict_delete(killed_users_dict); -} - -void -init_spamserv(const char *nick) -{ - if(!nick) - return; - - const char *modes = conf_get_data("services/spamserv/modes", RECDB_QSTRING); - spamserv = AddLocalUser(nick, nick, NULL, "Anti Spam Services", modes); - spamserv_service = service_register(spamserv); - service_register(spamserv)->trigger = spamserv_conf.trigger; - - conf_register_reload(spamserv_conf_read); - - SS_LOG = log_register_type("SpamServ", "file:spamserv.log"); - - registered_channels_dict = dict_new(); - connected_users_dict = dict_new(); - killed_users_dict = dict_new(); - - reg_new_user_func(spamserv_new_user_func); - reg_del_user_func(spamserv_del_user_func); - reg_nick_change_func(spamserv_nick_change_func); - reg_join_func(spamserv_user_join); - reg_part_func(spamserv_user_part); - - timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL); - timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL); - timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL); - timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL); - timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL); - - spamserv_module = module_register("SpamServ", SS_LOG, "spamserv.help", NULL); - modcmd_register(spamserv_module, "REGISTER", cmd_register, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+acceptchan,+helping", NULL); - modcmd_register(spamserv_module, "UNREGISTER", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+loghostmask", NULL); - modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "DELEXCEPTION", cmd_delexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL); - modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET WARNREACTION", opt_warnreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET ADVSCAN", opt_advscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET SPAMSCAN", opt_spamscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET FLOODSCAN", opt_floodscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET JOINFLOODSCAN", opt_joinflood, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET SCANCHANOPS", opt_scanops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET SCANVOICED", opt_scanvoiced, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - modcmd_register(spamserv_module, "SET EXCEPTLEVEL", opt_exceptlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); - - saxdb_register("SpamServ", spamserv_saxdb_read, spamserv_saxdb_write); - reg_exit_func(spamserv_db_cleanup); - message_register_table(msgtab); - crc32_init(); -} +/* spamserv.c - anti spam service + * Copyright 2004 feigling + * + * 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 2 of the License, or + * (at your option) any later version. Important limitations are + * listed in the COPYING file that accompanies this software. + * + * 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, email srvx-maintainers@srvx.net. + * + * $Id: spamserv.c,v 1.08 2004/06/27 22:21:00 feigling Exp $ + */ + +#include "conf.h" +#include "spamserv.h" +#include "chanserv.h" +#include "global.h" +#include "modcmd.h" +#include "saxdb.h" +#include "timeq.h" +#include "gline.h" + +#define SPAMSERV_CONF_NAME "services/spamserv" + +#define KEY_EXCEPTIONS "exceptions" +#define KEY_FLAGS "flags" +#define KEY_INFO "info" +#define KEY_EXCEPTLEVEL "exceptlevel" +#define KEY_EXPIRY "expiry" +#define KEY_LASTBADWORDID "last_badword_id" +#define KEY_BADWORDS "badwords" + +#define KEY_BADWORD_MASK "mask" +#define KEY_BADWORD_TRIGGERED "count" +#define KEY_BADWORD_ACTION "action" +#define KEY_BADWORDID "badwordid" + +#define KEY_DEBUG_CHANNEL "debug_channel" +#define KEY_GLOBAL_EXCEPTIONS "global_exceptions" +#define KEY_NETWORK_RULES "network_rules" +#define KEY_TRIGGER "trigger" +#define KEY_SHORT_BAN_DURATION "short_ban_duration" +#define KEY_LONG_BAN_DURATION "long_ban_duration" +#define KEY_GLINE_DURATION "gline_duration" +#define KEY_EXCEPTION_MAX "exception_max" +#define KEY_EXCEPTION_MIN_LEN "exception_min_len" +#define KEY_EXCEPTION_MAX_LEN "exception_max_len" +#define KEY_ADV_CHAN_MUST_EXIST "adv_chan_must_exist" +#define KEY_STRIP_MIRC_CODES "strip_mirc_codes" +#define KEY_ALLOW_MOVE_MERGE "allow_move_merge" + +#define SPAMSERV_FUNC(NAME) MODCMD_FUNC(NAME) +#define SPAMSERV_SYNTAX() svccmd_send_help(user, spamserv, cmd) +#define SPAMSERV_MIN_PARMS(N) do { \ +(void)argv; \ + if(argc < N) { \ + ss_reply(MSG_MISSING_PARAMS, argv[0]); \ + SPAMSERV_SYNTAX(); \ + return 0; } } while(0) + +struct userNode *spamserv; +static struct module *spamserv_module; +static struct service *spamserv_service; +static struct log_type *SS_LOG; +static unsigned long crc_table[256]; + +dict_t registered_channels_dict; +dict_t connected_users_dict; +dict_t killed_users_dict; + +#define spamserv_notice(target, format...) send_message(target , spamserv , ## format) +#define spamserv_debug(format...) do { if(spamserv_conf.debug_channel) send_channel_notice(spamserv_conf.debug_channel , spamserv , ## format); } while(0) +#define ss_reply(format...) send_message(user , spamserv , ## format) + +#define SET_SUBCMDS_SIZE 10 + +const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "FLOODSCAN", "JOINFLOODSCAN", "EXCEPTLEVEL","SCANCHANOPS", "SCANVOICED"}; + +static void spamserv_clear_spamNodes(struct chanNode *channel); +static void spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban); +static unsigned long crc32(const char *text); + +#define BINARY_OPTION(arguments...) return binary_option(arguments, user, channel, argc, argv); +#define MULTIPLE_OPTION(arguments...) return multiple_option(arguments, values, ArrayLength(values), user, channel, argc, argv); + +static const struct message_entry msgtab[] = { + { "SSMSG_CHANNEL_OPTIONS", "Channel Options:" }, + { "SSMSG_STRING_VALUE", "$b%s$b%s" }, + { "SSMSG_NUMERIC_VALUE", "$b%s$b%d - %s" }, + { "SSMSG_EASYNUMERIC_VALUE", "$b%s$b%d" }, + { "SSMSG_INVALID_NUM_SET", "$b'%d'$b is an invalid %s setting." }, + { "SSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." }, + { "SSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." }, + + { "SSMSG_NOT_REGISTERED", "$b%s$b has not been registered with $b$X$b." }, + { "SSMSG_NOT_REGISTERED_CS", "$b%s$b has not been registered with $b$C$b." }, + { "SSMSG_ALREADY_REGISTERED", "$b%s$b is already registered." }, + { "SSMSG_DEBUG_CHAN", "You may not register the debug channel." }, + { "SSMSG_SUSPENDED_CS", "$b$C$b access to $b%s$b has been temporarily suspended, thus you can't %s it." }, + { "SSMSG_SUSPENDED", "$b$X$b access to $b%s$b has been temporarily suspended." }, + { "SSMSG_NO_REGISTER", "Due to an error it was not possible to register $b%s$b." }, + { "SSMSG_REG_SUCCESS", "Channel $b%s$b registered." }, + { "SSMSG_UNREG_SUCCESS", "$b%s$b has been unregistered." }, + { "SSMSG_NO_ACCESS", "You lack sufficient access to use this command." }, + { "SSMSG_MUST_BE_OPER", "You must be an irc operator to set this option." }, + { "SSMSG_CONFIRM_UNREG", "To confirm this unregistration, you must append 'CONFIRM' to the end of your command. For example, 'unregister CONFIRM'." }, + + { "SSMSG_NO_EXCEPTIONS", "No words found in the exception list." }, + { "SSMSG_NO_SUCH_EXCEPTION", "Word $b%s$b not found in the exception list." }, + { "SSMSG_EXCEPTION_LIST", "The following words are in the exception list:" }, + { "SSMSG_EXCEPTION_ADDED", "Word $b%s$b added to the exception list." }, + { "SSMSG_EXCEPTION_DELETED", "Word $b%s$b deleted from the exception list." }, + { "SSMSG_EXCEPTION_IN_LIST", "The word $b%s$b is already in the exception list." }, + { "SSMSG_EXCEPTION_MAX", "The exception list has reached the maximum exceptions (max %lu). Delete a word to add another one." }, + { "SSMSG_EXCEPTION_TOO_SHORT", "The word must be at least %lu characters long." }, + { "SSMSG_EXCEPTION_TOO_LONG", "The word may not be longer than %lu characters." }, + + { "SSMSG_STATUS", "$bStatus:$b" }, + { "SSMSG_STATUS_USERS", "Total Users Online: %u" }, + { "SSMSG_STATUS_CHANNELS", "Registered Channels: %u" }, + { "SSMSG_STATUS_MEMORY", "$bMemory Information:$b" }, + { "SSMSG_STATUS_CHANNEL_LIST", "$bRegistered Channels:$b" }, + { "SSMSG_STATUS_NO_CHANNEL", "No channels registered." }, + + { "SSMSG_BADWORD_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" }, + { "SSMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." }, + { "SSMSG_BADWORD_SET_DONE", "Done." }, + { "SSMSG_BADWORD_SET_INVALID", "Invalid Option for setting %s" }, + { "SSMSG_BADWORD_SET", "Settings for BadWord entry $b%s$b" }, + { "SSMSG_BADWORD_SET_MASK", "$bMASK$b: %s" }, + { "SSMSG_BADWORD_SET_ACTION", "$bACTION$b: %s" }, + { NULL, NULL } +}; + +#define SSMSG_DEBUG_KICK "Kicked user $b%s$b from $b%s$b, reason: %s" +#define SSMSG_DEBUG_BAN "Banned user $b%s$b from $b%s$b, reason: %s" +#define SSMSG_DEBUG_KILL "Killed user $b%s$b, last violation in $b%s$b" +#define SSMSG_DEBUG_GLINE "Glined user $b%s$b, host $b%s$b, last violation in $b%s$b" +#define SSMSG_DEBUG_RECONNECT "Killed user $b%s$b reconnected to the network" +#define SSMSG_SPAM "Spamming" +#define SSMSG_FLOOD "Flooding the channel/network" +#define SSMSG_ADV "Advertising" +#define SSMSG_JOINFLOOD "Join flooding the channel" +#define SSMSG_WARNING "%s is against the network rules" +#define SSMSG_WARNING_2 "You are violating the network rules" +#define SSMSG_WARNING_RULES "%s is against the network rules. Read the network rules at %s" +#define SSMSG_WARNING_RULES_2 "You are violating the network rules. Read the network rules at %s" + +static struct +{ + struct chanNode *debug_channel; + struct string_list *global_exceptions; + const char *network_rules; + unsigned char trigger; + unsigned long short_ban_duration; + unsigned long long_ban_duration; + unsigned long gline_duration; + unsigned long exception_max; + unsigned long exception_min_len; + unsigned long exception_max_len; + unsigned int adv_chan_must_exist : 1; + unsigned int strip_mirc_codes : 1; + unsigned int allow_move_merge : 1; +} spamserv_conf; + +/***********************************************/ +/* Channel */ +/***********************************************/ + +struct chanInfo* +get_chanInfo(const char *channelname) +{ + return dict_find(registered_channels_dict, channelname, 0); +} + +static void +spamserv_join_channel(struct chanNode *channel) +{ + struct mod_chanmode change; + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].mode = MODE_CHANOP; + change.args[0].u.member = AddChannelUser(spamserv, channel); + mod_chanmode_announce(spamserv, channel, &change); +} + +static void +spamserv_part_channel(struct chanNode *channel, char *reason) +{ + /* we only have to clear the spamNodes because every other node expires on it's own */ + spamserv_clear_spamNodes(channel); + DelChannelUser(spamserv, channel, reason, 0); +} + +static struct chanInfo* +spamserv_register_channel(struct chanNode *channel, struct string_list *exceptions, unsigned int flags, char *info) +{ + struct chanInfo *cInfo = malloc(sizeof(struct chanInfo)); + + if(!cInfo) + { + log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for cInfo; channel: %s", channel->name); + return NULL; + } + + cInfo->channel = channel; + cInfo->exceptions = exceptions ? string_list_copy(exceptions) : alloc_string_list(1); + cInfo->flags = flags; + cInfo->exceptlevel = 400; + safestrncpy(cInfo->info, info, sizeof(cInfo->info)); + cInfo->suspend_expiry = 0; + cInfo->badwords = dict_new(); + cInfo->last_badword_id = 0; + dict_insert(registered_channels_dict, cInfo->channel->name, cInfo); + + return cInfo; +} + +static void +spamserv_unregister_channel(struct chanInfo *cInfo) +{ + if(!cInfo) + return; + + dict_remove(registered_channels_dict, cInfo->channel->name); + free_string_list(cInfo->exceptions); + free(cInfo); +} + +void +spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + + if(cInfo) + { + if(suspend) + { + cInfo->flags |= CHAN_SUSPENDED; + cInfo->suspend_expiry = expiry; + spamserv_part_channel(channel, reason); + } + else + { + if(CHECK_SUSPENDED(cInfo)) + { + cInfo->flags &= ~CHAN_SUSPENDED; + cInfo->suspend_expiry = 0; + } + } + } +} + +int +spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + + if(cInfo) + { + char reason[MAXLEN]; + + if(!spamserv_conf.allow_move_merge || get_chanInfo(target->name)) + { + if(move) + snprintf(reason, sizeof(reason), "unregistered due to a channel move to %s", target->name); + else + snprintf(reason, sizeof(reason), "unregistered due to a channel merge into %s", target->name); + + spamserv_cs_unregister(user, channel, manually, reason); + return 0; + } + + cInfo->channel = target; + + dict_remove(registered_channels_dict, channel->name); + dict_insert(registered_channels_dict, target->name, cInfo); + + if(move) + { + snprintf(reason, sizeof(reason), "Channel moved to %s by %s.", target->name, user->handle_info->handle); + } + else + { + spamserv_join_channel(target); + snprintf(reason, sizeof(reason), "%s merged into %s by %s.", channel->name, target->name, user->handle_info->handle); + } + + if(!CHECK_SUSPENDED(cInfo)) + spamserv_part_channel(channel, reason); + + if(move) + snprintf(reason, sizeof(reason), "$X (channel %s) moved to %s by %s.", channel->name, target->name, user->handle_info->handle); + else + snprintf(reason, sizeof(reason), "$X (channel %s) merged into %s by %s.", channel->name, target->name, user->handle_info->handle); + + global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); + return 1; + } + + return 0; +} + +void +spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + + if(cInfo) + { + char global[MAXLEN], partmsg[MAXLEN]; + + switch (type) + { + case manually: + snprintf(global, sizeof(global), "$X (channel %s) %s by %s.", channel->name, reason, user->handle_info->handle); + snprintf(partmsg, sizeof(partmsg), "%s %s by %s.", channel->name, reason, user->handle_info->handle); + break; + case expire: + snprintf(global, sizeof(global), "$X (channel %s) registration expired.", channel->name); + snprintf(partmsg, sizeof(partmsg), "%s registration expired.", channel->name); + break; + case lost_all_users: + snprintf(global, sizeof(global), "$X (channel %s) lost all users.", channel->name); + snprintf(partmsg, sizeof(partmsg), "%s lost all users.", channel->name); + break; + } + + if(!CHECK_SUSPENDED(cInfo)) + spamserv_part_channel(channel, partmsg); + + spamserv_unregister_channel(cInfo); + global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, global); + } +} + +/***********************************************/ +/* User */ +/***********************************************/ + +static struct userInfo* +get_userInfo(const char *nickname) +{ + return dict_find(connected_users_dict, nickname, 0); +} + +static void +spamserv_create_spamNode(struct chanNode *channel, struct userInfo *uInfo, char *text) +{ + struct spamNode *sNode = malloc(sizeof(struct spamNode)); + + if(!sNode) + { + log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for sNode; channel: %s; user: %s", channel->name, uInfo->user->nick); + return; + } + + sNode->channel = channel; + sNode->crc32 = crc32(text); + sNode->count = 1; + sNode->next = NULL; + + if(uInfo->spam) + { + struct spamNode *temp = uInfo->spam; + + while(temp->next) + temp = temp->next; + + sNode->prev = temp; + temp->next = sNode; + } + else + { + sNode->prev = NULL; + uInfo->spam = sNode; + } +} + +static void +spamserv_delete_spamNode(struct userInfo *uInfo, struct spamNode *sNode) +{ + if(!sNode) + return; + + if(sNode == uInfo->spam) + uInfo->spam = sNode->next; + + if(sNode->next) + sNode->next->prev = sNode->prev; + if(sNode->prev) + sNode->prev->next = sNode->next; + + free(sNode); +} + +static void +spamserv_clear_spamNodes(struct chanNode *channel) +{ + struct userInfo *uInfo; + struct spamNode *sNode; + unsigned int i; + + for(i = 0; i < channel->members.used; i++) + { + if((uInfo = get_userInfo(channel->members.list[i]->user->nick))) + { + if((sNode = uInfo->spam)) + { + for(; sNode; sNode = sNode->next) + if(sNode->channel == channel) + break; + + if(sNode) + spamserv_delete_spamNode(uInfo, sNode); + } + } + } +} + +static void +spamserv_create_floodNode(struct chanNode *channel, struct userNode *user, struct floodNode **uI_fNode) +{ + struct floodNode *fNode = malloc(sizeof(struct floodNode)); + + if(!fNode) + { + log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for fNode; channel: %s; user: %s", channel->name, user->nick); + return; + } + + fNode->channel = channel; + fNode->owner = user; + fNode->count = 1; + fNode->time = now; + fNode->next = NULL; + + if(*uI_fNode) + { + struct floodNode *temp = *uI_fNode; + + while(temp->next) + temp = temp->next; + + fNode->prev = temp; + temp->next = fNode; + } + else + { + fNode->prev = NULL; + *uI_fNode = fNode; + } +} + +static void +spamserv_delete_floodNode(struct floodNode **uI_fNode, struct floodNode *fNode) +{ + if(!fNode) + return; + + if(fNode == *uI_fNode) + *uI_fNode = fNode->next; + + if(fNode->next) + fNode->next->prev = fNode->prev; + if(fNode->prev) + fNode->prev->next = fNode->next; + + free(fNode); +} + +static void +spamserv_create_user(struct userNode *user) +{ + struct userInfo *uInfo = malloc(sizeof(struct userInfo)); + struct killNode *kNode = dict_find(killed_users_dict, irc_ntoa(&user->ip), 0); + + if(!uInfo) + { + log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for uInfo; nick: %s", user->nick); + return; + } + + if(kNode) + spamserv_debug(SSMSG_DEBUG_RECONNECT, user->nick); + + uInfo->user = user; + uInfo->spam = NULL; + uInfo->flood = NULL; + uInfo->joinflood = NULL; + uInfo->flags = kNode ? USER_KILLED : 0; + uInfo->warnlevel = kNode ? kNode->warnlevel : 0; + uInfo->lastadv = 0; + + dict_insert(connected_users_dict, user->nick, uInfo); + + if(kNode) + { + dict_remove(killed_users_dict, irc_ntoa(&user->ip)); + free(kNode); + } +} + +static void +spamserv_delete_user(struct userInfo *uInfo) +{ + if(!uInfo) + return; + + if(uInfo->spam) + while(uInfo->spam) + spamserv_delete_spamNode(uInfo, uInfo->spam); + + if(uInfo->flood) + while(uInfo->flood) + spamserv_delete_floodNode(&uInfo->flood, uInfo->flood); + + if(uInfo->joinflood) + while(uInfo->joinflood) + spamserv_delete_floodNode(&uInfo->joinflood, uInfo->joinflood); + + dict_remove(connected_users_dict, uInfo->user->nick); + free(uInfo); +} + +static void +spamserv_new_user_func(struct userNode *user) +{ + if(!IsLocal(user)) + spamserv_create_user(user); +} + +static void +spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why)) +{ + struct userInfo *uInfo = get_userInfo(user->nick); + struct killNode *kNode; + + if(killer == spamserv) + { + kNode = malloc(sizeof(struct killNode)); + + if(!kNode) + { + log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for killNode - nickname %s", user->nick); + spamserv_delete_user(uInfo); + return; + } + + if(uInfo->warnlevel > KILL_WARNLEVEL) + kNode->warnlevel = uInfo->warnlevel - KILL_WARNLEVEL; + else + kNode->warnlevel = 0; + + kNode->time = now; + + dict_insert(killed_users_dict, irc_ntoa(&user->ip), kNode); + } + + spamserv_delete_user(uInfo); +} + +static void +spamserv_nick_change_func(struct userNode *user, const char *old_nick) +{ + struct userInfo *uInfo = get_userInfo(old_nick); + + dict_remove(connected_users_dict, old_nick); + dict_insert(connected_users_dict, user->nick, uInfo); +} + +static int +spamserv_user_join(struct modeNode *mNode) +{ + struct chanNode *channel = mNode->channel; + struct userNode *user = mNode->user; + struct chanInfo *cInfo; + struct userInfo *uInfo; + struct floodNode *jfNode; + + if(user->uplink->burst || !(cInfo = get_chanInfo(channel->name)) || !CHECK_JOINFLOOD(cInfo) || !(uInfo = get_userInfo(user->nick))) + return 0; + + + if(!CHECK_CHANOPS(cInfo)) + { + //struct modeNode *mn = GetUserMode(channel, user); + //if(mn->modes & MODE_CHANOP) + // return; + if(check_user_level(channel, user, lvlGiveOps, 1, 0)) + return 0; + } + + if(!CHECK_VOICED(cInfo)) + { + if(check_user_level(channel, user, lvlGiveVoice, 1, 0)) + return 0; + } + + if(cInfo->exceptlevel == 0) + return 0; + if(cInfo->exceptlevel < 501) { + struct userData *uData; + if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) { + return 0; + } + } + + if(!(jfNode = uInfo->joinflood)) + { + spamserv_create_floodNode(channel, user, &uInfo->joinflood); + } + else + { + for(; jfNode; jfNode = jfNode->next) + if(jfNode->channel == channel) + break; + + if(!jfNode) + { + spamserv_create_floodNode(channel, user, &uInfo->joinflood); + } + else + { + jfNode->count++; + jfNode->time = now; + + if(jfNode->count > JOINFLOOD_MAX) + { + char reason[MAXLEN]; + + spamserv_delete_floodNode(&uInfo->joinflood, jfNode); + snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_JOINFLOOD, spamserv_conf.network_rules); + spamserv_punish(channel, user, JOINFLOOD_B_DURATION, reason, 1); + } + } + } + + return 0; +} + +static void +spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) +{ + struct userNode *user = mn->user; + struct chanNode *channel = mn->channel; + struct userInfo *uInfo; + struct spamNode *sNode; + struct floodNode *fNode; + + if(user->dead || !get_chanInfo(channel->name) || !(uInfo = get_userInfo(user->nick))) + return; + + if((sNode = uInfo->spam)) + { + for(; sNode; sNode = sNode->next) + if(sNode->channel == channel) + break; + + if(sNode) + spamserv_delete_spamNode(uInfo, sNode); + } + + if((fNode = uInfo->flood)) + { + for(; fNode; fNode = fNode->next) + if(fNode->channel == channel) + break; + + if(fNode) + spamserv_delete_floodNode(&uInfo->flood, fNode); + } +} + +static struct badword* +add_badword(struct chanInfo *cInfo, const char *badword_mask, unsigned int triggered, unsigned int action, const char *id) +{ + struct badword *badword; + + badword = calloc(1, sizeof(*badword)); + if (!badword) + return NULL; + + if(!id) { + cInfo->last_badword_id++; + badword->id = strtab(cInfo->last_badword_id); + } else + badword->id = strdup(id); + badword->badword_mask = strdup(badword_mask); + badword->triggered = triggered; + badword->action = action; + dict_insert(cInfo->badwords, badword->id, badword); + return badword; +} + +/***********************************************/ +/* Other Stuff */ +/***********************************************/ + +static void +crc32_init(void) +{ + unsigned long crc; + int i, j; + + for(i = 0; i < 256; i++) + { + crc = i; + + for(j = 8; j > 0; j--) + { + if(crc & 1) + { + crc = (crc >> 1) ^ 0xEDB88320L; + } + else + { + crc >>= 1; + } + } + + crc_table[i] = crc; + } +} + +static unsigned long +crc32(const char *text) +{ + register unsigned long crc = 0xFFFFFFFF; + unsigned int c, i = 0; + + while((c = (unsigned int)text[i++]) != 0) + crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF]; + + return (crc^0xFFFFFFFF); +} + +static void +timeq_flood(UNUSED_ARG(void *data)) +{ + dict_iterator_t it; + struct userInfo *uInfo; + struct floodNode *fNode; + + for(it = dict_first(connected_users_dict); it; it = iter_next(it)) + { + uInfo = iter_data(it); + + if(!(fNode = uInfo->flood)) + continue; + + for(; fNode; fNode = fNode->next) + { + if(now - fNode->time > FLOOD_EXPIRE) + { + if(!(--fNode->count)) + spamserv_delete_floodNode(&uInfo->flood, fNode); + } + } + } + + timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL); +} + +static void +timeq_joinflood(UNUSED_ARG(void *data)) +{ + dict_iterator_t it; + struct userInfo *uInfo; + struct floodNode *fNode; + + for(it = dict_first(connected_users_dict); it; it = iter_next(it)) + { + uInfo = iter_data(it); + + if(!(fNode = uInfo->joinflood)) + continue; + + for(; fNode; fNode = fNode->next) + { + if(now - fNode->time > JOINFLOOD_EXPIRE) + { + if(!(--fNode->count)) + spamserv_delete_floodNode(&uInfo->joinflood, fNode); + } + } + } + + timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL); +} + +static void +timeq_adv(UNUSED_ARG(void *data)) +{ + dict_iterator_t it; + struct userInfo *uInfo; + + for(it = dict_first(connected_users_dict); it; it = iter_next(it)) + { + uInfo = iter_data(it); + + if(uInfo->lastadv && uInfo->lastadv - now > ADV_EXPIRE) + { + uInfo->lastadv = 0; + uInfo->flags &= ~USER_ADV_WARNED; + } + } + + timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL); +} + +static void +timeq_warnlevel(UNUSED_ARG(void *data)) +{ + dict_iterator_t it; + struct userInfo *uInfo; + + for(it = dict_first(connected_users_dict); it; it = iter_next(it)) + { + uInfo = iter_data(it); + + if(uInfo->warnlevel > 0) + uInfo->warnlevel--; + } + + timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL); +} + +static void +timeq_kill(UNUSED_ARG(void *data)) +{ + dict_iterator_t it; + struct killNode *kNode; + + for(it = dict_first(killed_users_dict); it; it = iter_next(it)) + { + kNode = iter_data(it); + + if(kNode->time - now > KILL_EXPIRE) + free(kNode); + } + + timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL); +} + +static int +binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[]) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + int value; + + if(argc > 1) + { + if(enabled_string(argv[1])) + { + cInfo->flags |= mask; + value = 1; + } + else if(disabled_string(argv[1])) + { + cInfo->flags &= ~mask; + value = 0; + } + else + { + spamserv_notice(user, "SSMSG_INVALID_BINARY", argv[1]); + return 0; + } + } + else + { + value = (cInfo->flags & mask) ? 1 : 0; + } + + spamserv_notice(user, "SSMSG_STRING_VALUE", name, value ? "Enabled." : "Disabled."); + return 1; +} + +struct valueData +{ + char *description; + char value; + int oper_only : 1; +}; + +static int +multiple_option(char *name, char *description, enum channelinfo info, struct valueData *values, int count, struct userNode *user, struct chanNode *channel, int argc, char *argv[]) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + int index; + + if(argc > 1) + { + index = atoi(argv[1]); + + if(index < 0 || index >= count) + { + spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, description); + + for(index = 0; index < count; index++) + spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description); + + return 0; + } + + if(values[index].oper_only && !IsOper(user)) + { + spamserv_notice(user, "SSMSG_MUST_BE_OPER"); + return 0; + } + + cInfo->info[info] = values[index].value; + } + else + { + for(index = 0; index < count && cInfo->info[info] != values[index].value; index++); + } + + spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description); + return 1; +} + +static int +show_exceptions(struct userNode *user, struct chanInfo *cInfo) +{ + struct helpfile_table table; + unsigned int i; + + if(!cInfo->exceptions->used) + { + spamserv_notice(user, "SSMSG_NO_EXCEPTIONS"); + return 0; + } + + spamserv_notice(user, "SSMSG_EXCEPTION_LIST"); + + table.length = 0; + table.width = 1; + table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS; + table.contents = alloca(cInfo->exceptions->used * sizeof(*table.contents)); + + for(i = 0; i < cInfo->exceptions->used; i++) + { + table.contents[table.length] = alloca(table.width * sizeof(**table.contents)); + table.contents[table.length][0] = cInfo->exceptions->list[i]; + table.length++; + } + + table_send(spamserv, user->nick, 0, NULL, table); + + return 1; +} + +static void +show_memory_usage(struct userNode *user) +{ + dict_iterator_t it; + struct helpfile_table table; + struct chanInfo *cInfo; + struct userInfo *uInfo; + struct spamNode *sNode; + struct floodNode *fNode; + double channel_size = 0, user_size, size; + unsigned int spamcount = 0, floodcount = 0, i, j; + char buffer[64]; + + for(it = dict_first(registered_channels_dict); it; it = iter_next(it)) + { + cInfo = iter_data(it); + + if(!cInfo->exceptions->used) + continue; + + for(i = 0; i < cInfo->exceptions->used; i++) + channel_size += strlen(cInfo->exceptions->list[i]) * sizeof(char); + } + + for(it = dict_first(connected_users_dict); it; it = iter_next(it)) + { + uInfo = iter_data(it); + + for(sNode = uInfo->spam; sNode; sNode = sNode->next, spamcount++); + for(fNode = uInfo->flood; fNode; fNode = fNode->next, floodcount++); + for(fNode = uInfo->joinflood; fNode; fNode = fNode->next, floodcount++); + } + + channel_size += dict_size(registered_channels_dict) * sizeof(struct chanInfo); + + user_size = dict_size(connected_users_dict) * sizeof(struct userInfo) + + dict_size(killed_users_dict) * sizeof(struct killNode) + + spamcount * sizeof(struct spamNode) + + floodcount * sizeof(struct floodNode); + + size = channel_size + user_size; + + ss_reply("SSMSG_STATUS_MEMORY"); + + table.length = 3; + table.width = 4; + table.flags = TABLE_NO_FREE | TABLE_NO_HEADERS | TABLE_PAD_LEFT; + table.contents = calloc(table.length, sizeof(char**)); + + // chanInfo + table.contents[0] = calloc(table.width, sizeof(char*)); + snprintf(buffer, sizeof(buffer), "Channel Memory Usage:"); + table.contents[0][0] = strdup(buffer); + snprintf(buffer, sizeof(buffer), " %g Byte; ", channel_size); + table.contents[0][1] = strdup(buffer); + snprintf(buffer, sizeof(buffer), "%g KiloByte; ", channel_size / 1024); + table.contents[0][2] = strdup(buffer); + snprintf(buffer, sizeof(buffer), "%g MegaByte", channel_size / 1024 / 1024); + table.contents[0][3] = strdup(buffer); + + // userInfo + table.contents[1] = calloc(table.width, sizeof(char*)); + snprintf(buffer, sizeof(buffer), "User Memory Usage :"); + table.contents[1][0] = strdup(buffer); + snprintf(buffer, sizeof(buffer), " %g Byte; ", user_size); + table.contents[1][1] = strdup(buffer); + snprintf(buffer, sizeof(buffer), "%g KiloByte; ", user_size / 1024); + table.contents[1][2] = strdup(buffer); + snprintf(buffer, sizeof(buffer), "%g MegaByte", user_size / 1024 / 1024); + table.contents[1][3] = strdup(buffer); + + // total memory usage + table.contents[2] = calloc(table.width, sizeof(char*)); + snprintf(buffer, sizeof(buffer), "Total Memory Usage :"); + table.contents[2][0] = strdup(buffer); + snprintf(buffer, sizeof(buffer), " %g Byte; ", size); + table.contents[2][1] = strdup(buffer); + snprintf(buffer, sizeof(buffer), "%g KiloByte; ", size / 1024); + table.contents[2][2] = strdup(buffer); + snprintf(buffer, sizeof(buffer), "%g MegaByte", size / 1024 / 1024); + table.contents[2][3] = strdup(buffer); + + table_send(spamserv, user->nick, 0, NULL, table); + + for(i = 0; i < table.length; i++) + { + for(j = 0; j < table.width; j++) + free((char*)table.contents[i][j]); + + free(table.contents[i]); + } + + free(table.contents); +} + +static void +show_registered_channels(struct userNode *user) +{ + struct helpfile_table table; + dict_iterator_t it; + + spamserv_notice(user, "SSMSG_STATUS_CHANNEL_LIST"); + + if(!dict_size(registered_channels_dict)) + { + spamserv_notice(user, "SSMSG_STATUS_NO_CHANNEL"); + return; + } + + table.length = 0; + table.width = 1; + table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS; + table.contents = alloca(dict_size(registered_channels_dict) * sizeof(*table.contents)); + + for(it = dict_first(registered_channels_dict); it; it = iter_next(it)) + { + struct chanInfo *cInfo = iter_data(it); + + table.contents[table.length] = alloca(table.width * sizeof(**table.contents)); + table.contents[table.length][0] = cInfo->channel->name; + table.length++; + } + + table_send(spamserv, user->nick, 0, NULL, table); +} + +/***********************************************/ +/* SpamServ_Func */ +/***********************************************/ + +static +SPAMSERV_FUNC(cmd_register) +{ + struct chanInfo *cInfo; + char reason[MAXLEN]; + + if(!channel || !channel->channel_info) + { + ss_reply("SSMSG_NOT_REGISTERED_CS", channel->name); + return 0; + } + + if(get_chanInfo(channel->name)) + { + ss_reply("SSMSG_ALREADY_REGISTERED", channel->name); + return 0; + } + + if(IsSuspended(channel->channel_info)) + { + ss_reply("SSMSG_SUSPENDED_CS", channel->name, "register"); + return 0; + } + + if(channel == spamserv_conf.debug_channel) + { + ss_reply("SSMSG_DEBUG_CHAN"); + return 0; + } + + if(!(cInfo = spamserv_register_channel(channel, spamserv_conf.global_exceptions, CHAN_FLAGS_DEFAULT , CHAN_INFO_DEFAULT))) + { + ss_reply("SSMSG_NO_REGISTER", channel->name); + return 0; + } + + spamserv_join_channel(cInfo->channel); + + snprintf(reason, sizeof(reason), "%s (channel %s) registered by %s.", spamserv->nick, channel->name, user->handle_info->handle); + global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); + ss_reply("SSMSG_REG_SUCCESS", channel->name); + + return 1; +} + +static +SPAMSERV_FUNC(cmd_unregister) +{ + struct chanInfo *cInfo; + struct chanData *cData; + struct userData *uData; + char reason[MAXLEN]; + + if(!channel || !(cData = channel->channel_info) || !(cInfo = get_chanInfo(channel->name))) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(!(uData = GetChannelUser(cData, user->handle_info)) || (uData->access < UL_OWNER)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + if(!IsHelping(user)) + { + if(IsSuspended(cData)) + { + ss_reply("SSMSG_SUSPENDED_CS", channel->name, "unregister"); + return 0; + } + + if(argc < 2 || strcasecmp(argv[1], "CONFIRM")) + { + ss_reply("SSMSG_CONFIRM_UNREG"); + return 0; + } + } + + if(!CHECK_SUSPENDED(cInfo)) + { + snprintf(reason, sizeof(reason), "%s unregistered by %s.", spamserv->nick, user->handle_info->handle); + spamserv_part_channel(channel, reason); + } + + spamserv_unregister_channel(cInfo); + + snprintf(reason, sizeof(reason), "%s (channel %s) unregistered by %s.", spamserv->nick, channel->name, user->handle_info->handle); + global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); + ss_reply("SSMSG_UNREG_SUCCESS", channel->name); + + return 1; +} + +static +SPAMSERV_FUNC(cmd_status) +{ + ss_reply("SSMSG_STATUS"); + ss_reply("SSMSG_STATUS_USERS", dict_size(connected_users_dict)); + ss_reply("SSMSG_STATUS_CHANNELS", dict_size(registered_channels_dict)); + + if(IsOper(user) && argc > 1) + { + if(!irccasecmp(argv[1], "memory")) + show_memory_usage(user); + else if(!irccasecmp(argv[1], "channels")) + show_registered_channels(user); + } + + return 1; +} + +static +SPAMSERV_FUNC(cmd_addexception) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + unsigned int i; + + if(!cInfo || !channel->channel_info) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + if(argc < 2) + return show_exceptions(user, cInfo); + + if(cInfo->exceptions->used == spamserv_conf.exception_max && !IsOper(user)) + { + ss_reply("SSMSG_EXCEPTION_MAX", spamserv_conf.exception_max); + return 0; + } + + if(strlen(argv[1]) < spamserv_conf.exception_min_len) + { + ss_reply("SSMSG_EXCEPTION_TOO_SHORT", spamserv_conf.exception_min_len); + return 0; + } + else if(strlen(argv[1]) > spamserv_conf.exception_max_len) + { + ss_reply("SSMSG_EXCEPTION_TOO_LONG", spamserv_conf.exception_max_len); + return 0; + } + + for(i = 0; i < cInfo->exceptions->used; i++) + { + if(!irccasecmp(argv[1], cInfo->exceptions->list[i])) + { + ss_reply("SSMSG_EXCEPTION_IN_LIST", argv[1]); + return 0; + } + } + + string_list_append(cInfo->exceptions, strdup(argv[1])); + ss_reply("SSMSG_EXCEPTION_ADDED", argv[1]); + + return 1; +} + +static +SPAMSERV_FUNC(cmd_delexception) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + unsigned int i; + int found = -1; + + if(!cInfo || !channel->channel_info) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + if(argc < 2) + return show_exceptions(user, cInfo); + + for(i = 0; i < cInfo->exceptions->used; i++) + { + if(!irccasecmp(argv[1], cInfo->exceptions->list[i])) + { + found = i; + break; + } + } + + if(found == -1) + { + ss_reply("SSMSG_NO_SUCH_EXCEPTION", argv[1]); + return 0; + } + + string_list_delete(cInfo->exceptions, i); + ss_reply("SSMSG_EXCEPTION_DELETED", argv[1]); + + return 1; +} + +static +SPAMSERV_FUNC(cmd_set) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct svccmd *subcmd; + char cmd_name[MAXLEN]; + unsigned int i; + + if(!cInfo) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!check_user_level(channel,user,lvlSetters,1,0)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + if(argc < 2) + { + ss_reply("SSMSG_CHANNEL_OPTIONS"); + + for(i = 0; i < SET_SUBCMDS_SIZE; i++) + { + sprintf(cmd_name, "%s %s", cmd->name, set_subcommands[i]); + + if((subcmd = dict_find(cmd->parent->commands, cmd_name, NULL))) + subcmd->command->func(user, channel, 1, argv + 1, subcmd); + } + + return 1; + } + + sprintf(cmd_name, "%s %s", cmd->name, argv[1]); + subcmd = dict_find(cmd->parent->commands, cmd_name, NULL); + + if(!subcmd) + { + reply("SSMSG_INVALID_OPTION", argv[1], argv[0]); + return 0; + } + + return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd); +} + +static +SPAMSERV_FUNC(opt_spamlimit) +{ + struct valueData values[] = + { + {"Users may send the same message $b2$b times.", 'a', 0}, + {"Users may send the same message $b3$b times.", 'b', 0}, + {"Users may send the same message $b4$b times.", 'c', 0}, + {"Users may send the same message $b5$b times.", 'd', 0}, + {"Users may send the same message $b6$b times.", 'e', 0} + }; + + MULTIPLE_OPTION("SpamLimit ", "SpamLimit", ci_SpamLimit); +} + +static +SPAMSERV_FUNC(opt_advreaction) +{ + struct valueData values[] = + { + {"Kick on disallowed advertising.", 'k', 0}, + {"Kickban on disallowed advertising.", 'b', 0}, + {"Short timed ban on disallowed advertising.", 's', 0}, + {"Long timed ban on disallowed advertising.", 'l', 0}, + {"Kill on disallowed advertising.", 'd', 1} + }; + + MULTIPLE_OPTION("AdvReaction ", "AdvReaction", ci_AdvReaction); +} + +static +SPAMSERV_FUNC(opt_warnreaction) +{ + struct valueData values[] = + { + {"Kick after warning.", 'k', 0}, + {"Kickban after warning.", 'b', 0}, + {"Short timed ban after warning.", 's', 0}, + {"Long timed ban after warning.", 'l', 0}, + {"Kill after warning.", 'd', 1} + }; + + MULTIPLE_OPTION("WarnReaction ", "WarnReaction", ci_WarnReaction); +} + +static +SPAMSERV_FUNC(opt_advscan) +{ + BINARY_OPTION("AdvScan ", CHAN_ADV_SCAN); +} + +static +SPAMSERV_FUNC(opt_spamscan) +{ + BINARY_OPTION("SpamScan ", CHAN_SPAMSCAN); +} + +static +SPAMSERV_FUNC(opt_floodscan) +{ + BINARY_OPTION("FloodScan ", CHAN_FLOODSCAN); +} + +static +SPAMSERV_FUNC(opt_joinflood) +{ + BINARY_OPTION("JoinFloodScan ", CHAN_JOINFLOOD); +} + +static +SPAMSERV_FUNC(opt_scanops) +{ + BINARY_OPTION("ScanChanOps ", CHAN_SCAN_CHANOPS); +} + +static +SPAMSERV_FUNC(opt_scanvoiced) +{ + BINARY_OPTION("ScanVoiced ", CHAN_SCAN_VOICED); +} + +static +SPAMSERV_FUNC(opt_exceptlevel) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + int index; + if(argc > 1) + { + index = atoi(argv[1]); + if(index < 1 || index > 501) + { + spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, "ExceptLevel"); + return 0; + } + if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < cInfo->exceptlevel || index > uData->access)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + cInfo->exceptlevel=index; + } else { + index=cInfo->exceptlevel; + } + spamserv_notice(user, "SSMSG_EASYNUMERIC_VALUE", "ExceptLevel ", index); + return 1; +} + +static SPAMSERV_FUNC(cmd_addbad) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + + if(!cInfo) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!check_user_level(channel,user,lvlSetters,1,0)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + dict_iterator_t it; + char *mask = unsplit_string(argv + 1, argc - 1, NULL); + for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) { + struct badword *badword = iter_data(it); + if(match_ircglob(mask,badword->badword_mask)) { + reply("SSMSG_BADWORD_ALREADY_ADDED", mask, badword->id); + return 1; + } + } + + struct badword *new_badword = add_badword(cInfo, mask, 0, BADACTION_KICK, NULL); + for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) { + struct badword *badword = iter_data(it); + if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) { + dict_remove(cInfo->badwords, badword->id); + } + } + + reply("SSMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id); + return 1; +} + +static SPAMSERV_FUNC(cmd_delbad) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + + if(!cInfo) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!check_user_level(channel,user,lvlSetters,1,0)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } +} + +static SPAMSERV_FUNC(cmd_setbad) +{ + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + struct badword *badword; + + if(!cInfo) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!check_user_level(channel,user,lvlSetters,1,0)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + if ((badword = dict_find(cInfo->badwords, argv[1], NULL))) { + if (argc > 3) { + unsigned int ii; + char *setting = argv[2]; + char *value = argv[3]; + for( ii = 0; setting[ ii ]; ii++) + setting[ ii ] = toupper( setting[ ii ] ); + for( ii = 0; value[ ii ]; ii++) + value[ ii ] = toupper( value[ ii ] ); + if(!strcmp("MASK",setting)) { + free(badword->badword_mask); + badword->badword_mask = strdup(argv[3]); + badword->triggered = 0; + reply("SSMSG_BADWORD_SET_DONE"); + } + else if(!strcmp("ACTION",setting)) { + if (!strcmp("1",value) || !strcmp("KICK",value)) { + badword->action = BADACTION_KICK; + reply("SSMSG_BADWORD_SET_DONE"); + } else if (!strcmp("2",value) || !strcmp("BAN",value)) { + badword->action = BADACTION_BAN; + reply("SSMSG_BADWORD_SET_DONE"); + } else if (!strcmp("3",value) || !strcmp("KILL",value)) { + badword->action = BADACTION_KILL; + reply("SSMSG_BADWORD_SET_DONE"); + } else if (!strcmp("4",value) || !strcmp("GLINE",value)) { + badword->action = BADACTION_GLINE; + reply("SSMSG_BADWORD_SET_DONE"); + } else { + reply("SSMSG_BADWORD_SET_INVALID", setting); + } + } else { + reply("SSMSG_BADWORD_SETTING_INVALID", setting); + } + + } else { + reply("SSMSG_BADWORD_SET", badword->id); + reply("SSMSG_BADWORD_SET_MASK", badword->badword_mask); + switch(badword->action) { + case BADACTION_KICK: + reply("SSMSG_BADWORD_SET_ACTION", "KICK"); + break; + case BADACTION_BAN: + reply("SSMSG_BADWORD_SET_ACTION", "BAN"); + break; + case BADACTION_KILL: + reply("SSMSG_BADWORD_SET_ACTION", "KILL"); + break; + case BADACTION_GLINE: + reply("SSMSG_BADWORD_SET_ACTION", "GLINE"); + break; + default: + reply("SSMSG_BADWORD_SET_ACTION", "*undef*"); + } + } + } else { + reply("SSMSG_BADWORD_NOT_FOUND", argv[1]); + return 0; + } + return 1; +} + +int +ss_badwords_sort(const void *pa, const void *pb) +{ + struct badword *a = *(struct badword**)pa; + struct badword *b = *(struct badword**)pb; + + return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0); +} + +static SPAMSERV_FUNC(cmd_listbad) +{ + struct helpfile_table tbl; + struct chanInfo *cInfo = get_chanInfo(channel->name); + struct userData *uData; + struct badword **badwords; + unsigned int count = 0, ii = 0; + + if(!cInfo) + { + ss_reply("SSMSG_NOT_REGISTERED", channel->name); + return 0; + } + + if(CHECK_SUSPENDED(cInfo)) + { + ss_reply("SSMSG_SUSPENDED", channel->name); + return 0; + } + + if(!check_user_level(channel,user,lvlSetters,1,0)) + { + ss_reply("SSMSG_NO_ACCESS"); + return 0; + } + + dict_iterator_t it; + for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) { + count++; + } + + tbl.length = count+1; + tbl.width = 4; + tbl.flags = 0; + tbl.flags = TABLE_NO_FREE; + tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0])); + tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0])); + tbl.contents[0][0] = "#"; + tbl.contents[0][1] = "Badword"; + tbl.contents[0][2] = "Action"; + tbl.contents[0][3] = "(Triggered)"; + if(!count) + { + table_send(cmd->parent->bot, user->nick, 0, NULL, tbl); + reply("MSG_NONE"); + free(tbl.contents[0]); + free(tbl.contents); + return 0; + } + badwords = alloca(count * sizeof(badwords[0])); + for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) { + struct badword *bw = iter_data(it); + badwords[ii++] = bw; + } + + qsort(badwords, count, sizeof(badwords[0]), ss_badwords_sort); + for (ii = 1; ii <= count; ii++) { + struct badword *bw = badwords[ii-1]; + tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0])); + tbl.contents[ii][0] = strdup(bw->id); + tbl.contents[ii][1] = strdup(bw->badword_mask); + switch(bw->action) { + case BADACTION_KICK: + tbl.contents[ii][2] = "KICK"; + break; + case BADACTION_BAN: + tbl.contents[ii][2] = "BAN"; + break; + case BADACTION_KILL: + tbl.contents[ii][2] = "KILL"; + break; + case BADACTION_GLINE: + tbl.contents[ii][2] = "GLINE"; + break; + default: + tbl.contents[ii][2] = "*undef*"; + } + tbl.contents[ii][3] = strtab(bw->triggered); + } + + table_send(cmd->parent->bot, user->nick, 0, NULL, tbl); + for(ii = 1; ii < tbl.length; ++ii) + { + free(tbl.contents[ii]); + } + free(tbl.contents[0]); + free(tbl.contents); + return 1; +} + +static void +to_lower(char *message) +{ + unsigned int i, diff = 'a' - 'A'; + + for(i = 0; i < strlen(message); i++) + { + if((message[i] >= 'A') && (message[i] <= 'Z')) + message[i] = message[i] + diff; + } +} + +static char * +strip_mirc_codes(char *text) +{ + // taken from xchat and modified + int nc = 0, i = 0, col = 0, len = strlen(text); + static char new_str[MAXLEN]; + + while(len > 0) + { + if((col && isdigit(*text) && nc < 2) || + (col && *text == ',' && isdigit(*(text + 1)) && nc < 3)) + { + nc++; + + if(*text == ',') + nc = 0; + } + else + { + col = 0; + + switch(*text) + { + case '\003': + col = 1; + nc = 0; + break; + case '\002': + case '\022': + case '\026': + case '\031': + case '\037': + break; + default: + new_str[i] = *text; + i++; + } + } + + text++; + len--; + } + + new_str[i] = '\0'; + + return new_str; +} + +static int +is_in_exception_list(struct chanInfo *cInfo, char *message) +{ + unsigned int i; + + for(i = 0; i < cInfo->exceptions->used; i++) + if(strstr(message, cInfo->exceptions->list[i])) + return 1; + + return 0; +} + +static int +check_advertising(struct chanInfo *cInfo, char *message) +{ + unsigned int i = 0; + + if(spamserv_conf.strip_mirc_codes) + message = strip_mirc_codes(message); + + if(is_in_exception_list(cInfo, message)) + return 0; + + while(message[i] != 0) + { + if(message[i] == '#') + { + char channelname[CHANNELLEN]; + unsigned int j = 0; + + if(!spamserv_conf.adv_chan_must_exist) + return 1; + + /* only return 1, if the channel does exist */ + + while((message[i] != 0) && (message[i] != ' ')) + { + channelname[j] = message[i]; + i++; + j++; + } + + channelname[j] = '\0'; + + if(GetChannel(channelname)) + return 1; + } + else if((message[i] == 'w') && (message[i+1] == 'w') && (message[i+2] == 'w') && (message[i+3] == '.')) + return 1; + else if((message[i] == 'h') && (message[i+1] == 't') && (message[i+2] == 't') && (message[i+3] == 'p') && (message[i+4] == ':')) + return 1; + else if((message[i] == 'f') && (message[i+1] == 't') && (message[i+2] == 'p') && ((message[i+3] == '.') || (message[i+3] == ':'))) + return 1; + + i++; + } + + return 0; +} + +struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason); + +static void +spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban) +{ + if(ban) + { + struct mod_chanmode change; + char *hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT); + + sanitize_ircmask(hostmask); + + if(expires) + add_channel_ban(channel->channel_info, hostmask, spamserv->nick, now, now, now + expires, reason); + + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].mode = MODE_BAN; + change.args[0].u.hostmask = hostmask; + mod_chanmode_announce(spamserv, channel, &change); + + free(hostmask); + + spamserv_debug(SSMSG_DEBUG_BAN, user->nick, channel->name, reason); + } + else + spamserv_debug(SSMSG_DEBUG_KICK, user->nick, channel->name, reason); + + KickChannelUser(user, channel, spamserv, reason); +} + +void +spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text) +{ + struct chanInfo *cInfo; + struct userInfo *uInfo; + struct spamNode *sNode; + struct floodNode *fNode; + unsigned int violation = 0; + char reason[MAXLEN]; + + /* make sure: spamserv is not disabled; srvx is running; spamserv is in the chan; chan is regged, user does exist */ + if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick))) + return; + + if(!CHECK_CHANOPS(cInfo)) + { + struct modeNode *mn = GetUserMode(channel, user); + if(mn && mn->modes & MODE_CHANOP) + return; + //if(check_user_level(channel, user, lvlGiveOps, 1, 0)) + // return; + } + + if(!CHECK_VOICED(cInfo)) + { + struct modeNode *mn = GetUserMode(channel, user); + if(mn && (mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP)) + return; + } + + if(cInfo->exceptlevel == 0) + return; + if(cInfo->exceptlevel < 501) { + struct userData *uData; + if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) { + return; + } + } + + to_lower(text); + + if(CHECK_SPAM(cInfo)) + { + if(!(sNode = uInfo->spam)) + { + spamserv_create_spamNode(channel, uInfo, text); + } + else + { + for(; sNode; sNode = sNode->next) + if(sNode->channel == channel) + break; + + if(!sNode) + { + spamserv_create_spamNode(channel, uInfo, text); + } + else + { + unsigned long crc = crc32(text); + + if(crc == sNode->crc32) + { + unsigned int spamlimit = 2; + sNode->count++; + + switch(cInfo->info[ci_SpamLimit]) + { + case 'a': spamlimit = 2; break; + case 'b': spamlimit = 3; break; + case 'c': spamlimit = 4; break; + case 'd': spamlimit = 5; break; + case 'e': spamlimit = 6; break; + } + + if(sNode->count == spamlimit) + { + uInfo->warnlevel += SPAM_WARNLEVEL; + + if(uInfo->warnlevel < MAX_WARNLEVEL) + spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); + } + else if(sNode->count > spamlimit) + { + switch(cInfo->info[ci_WarnReaction]) + { + case 'k': uInfo->flags |= USER_KICK; break; + case 'b': uInfo->flags |= USER_KICKBAN; break; + case 's': uInfo->flags |= USER_SHORT_TBAN; break; + case 'l': uInfo->flags |= USER_LONG_TBAN; break; + case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break; + } + + spamserv_delete_spamNode(uInfo, sNode); + uInfo->warnlevel += SPAM_WARNLEVEL; + violation = 1; + } + } + else + { + sNode->crc32 = crc; + sNode->count = 1; + } + } + } + } + + if(CHECK_FLOOD(cInfo)) + { + if(!(fNode = uInfo->flood)) + { + spamserv_create_floodNode(channel, user, &uInfo->flood); + } + else + { + for(; fNode; fNode = fNode->next) + if(fNode->channel == channel) + break; + + if(!fNode) + { + spamserv_create_floodNode(channel, user, &uInfo->flood); + } + else + { + if(((now - fNode->time) < FLOOD_EXPIRE)) + { + fNode->count++; + + if(fNode->count == FLOOD_MAX_LINES - 1) + { + uInfo->warnlevel += FLOOD_WARNLEVEL; + + if(uInfo->warnlevel < MAX_WARNLEVEL) + spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); + } + else if(fNode->count > FLOOD_MAX_LINES) + { + switch(cInfo->info[ci_WarnReaction]) + { + case 'k': uInfo->flags |= USER_KICK; break; + case 'b': uInfo->flags |= USER_KICKBAN; break; + case 's': uInfo->flags |= USER_SHORT_TBAN; break; + case 'l': uInfo->flags |= USER_LONG_TBAN; break; + case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break; + } + + spamserv_delete_floodNode(&uInfo->flood, fNode); + uInfo->warnlevel += FLOOD_WARNLEVEL; + violation = 2; + } + } + + fNode->time = now; + } + } + } + + if(CHECK_ADV(cInfo) && check_advertising(cInfo, text)) + { + if(CHECK_ADV_WARNED(uInfo)) + { + switch(cInfo->info[ci_AdvReaction]) + { + case 'k': uInfo->flags |= USER_KICK; break; + case 'b': uInfo->flags |= USER_KICKBAN; break; + case 's': uInfo->flags |= USER_SHORT_TBAN; break; + case 'l': uInfo->flags |= USER_LONG_TBAN; break; + case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break; + } + + uInfo->warnlevel += ADV_WARNLEVEL; + violation = 3; + } + else + { + uInfo->flags |= USER_ADV_WARNED; + uInfo->lastadv = now; + uInfo->warnlevel += ADV_WARNLEVEL; + + if(uInfo->warnlevel < MAX_WARNLEVEL) + spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); + } + } + + if(!CHECK_WARNED(uInfo) && !CHECK_KILL(uInfo) && !CHECK_GLINE(uInfo) && uInfo->warnlevel == MAX_WARNLEVEL) + { + uInfo->flags |= USER_WARNED; + snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); + irc_notice(spamserv, user->numeric, reason); + irc_privmsg(spamserv, user->numeric, reason); + } + else if(uInfo->warnlevel > MAX_WARNLEVEL) + { + if(CHECK_KILLED(uInfo)) + uInfo->flags |= USER_GLINE; + else + uInfo->flags |= USER_KILL; + + violation = 5; + } + + if(!violation) + return; + + switch(violation) + { + case 1: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); break; + case 2: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); break; + case 3: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); break; + default: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); break; + } + + if(CHECK_GLINE(uInfo)) + { + int size = strlen(user->hostname) + 3; + char *mask = alloca(size); + snprintf(mask, size, "*@%s", user->hostname); + gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 0, 1); + spamserv_debug(SSMSG_DEBUG_GLINE, user->nick, user->hostname, channel->name); + } + else if(CHECK_KILL(uInfo)) + { + DelUser(user, spamserv, 1, reason); + spamserv_debug(SSMSG_DEBUG_KILL, user->nick, channel->name); + } + else if(CHECK_LONG_TBAN(uInfo)) + { + spamserv_punish(channel, user, spamserv_conf.long_ban_duration, reason, 1); + } + else if(CHECK_SHORT_TBAN(uInfo)) + { + spamserv_punish(channel, user, spamserv_conf.short_ban_duration, reason, 1); + } + else if(CHECK_KICKBAN(uInfo)) + { + spamserv_punish(channel, user, 0, reason, 1); + } + else if(CHECK_KICK(uInfo)) + { + spamserv_punish(channel, user, 0, reason, 0); + } +} + +static int +spamserv_saxdb_read(struct dict *database) +{ + dict_iterator_t it, itbad; + struct dict *object; + struct record_data *hir, hirbad; + struct chanNode *channel; + struct chanInfo *cInfo; + struct string_list *strlist; + unsigned int flags,exceptlevel,badwordid,action,triggered; + char *str, *info, *mask; + unsigned long expiry; + + for(it = dict_first(database); it; it = iter_next(it)) + { + hir = iter_data(it); + + if(hir->type != RECDB_OBJECT) + { + log_module(SS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it)); + continue; + } + + channel = GetChannel(iter_key(it)); + strlist = database_get_data(hir->d.object, KEY_EXCEPTIONS, RECDB_STRING_LIST); + + str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING); + flags = str ? atoi(str) : 0; + + info = database_get_data(hir->d.object, KEY_INFO, RECDB_QSTRING); + str = database_get_data(hir->d.object, KEY_EXCEPTLEVEL, RECDB_QSTRING); + exceptlevel = str ? atoi(str) : 400; + + str = database_get_data(hir->d.object, KEY_EXPIRY, RECDB_QSTRING); + expiry = str ? strtoul(str, NULL, 0) : 0; + + if(channel && info) + { + if((cInfo = spamserv_register_channel(channel, strlist, flags, info))) + { + /* if the channel is suspended and expiry = 0 it means: channel will + never expire ! it does NOT mean, the channel is not suspended */ + if(CHECK_SUSPENDED(cInfo) && expiry && (expiry < now)) + { + cInfo->flags &= ~CHAN_SUSPENDED; + spamserv_join_channel(cInfo->channel); + } + else if(!CHECK_SUSPENDED(cInfo)) + spamserv_join_channel(cInfo->channel); + else + cInfo->suspend_expiry = expiry; + cInfo->exceptlevel=exceptlevel; + cInfo->badwords = dict_new(); + + } + } + else + log_module(SS_LOG, LOG_ERROR, "Couldn't register channel %s. Channel or info invalid.", iter_key(it)); + } + + return 0; +} + +static int +spamserv_saxdb_write(struct saxdb_context *ctx) +{ + dict_iterator_t it; + + for(it = dict_first(registered_channels_dict); it; it = iter_next(it)) + { + struct chanInfo *cInfo = iter_data(it); + + saxdb_start_record(ctx, cInfo->channel->name, 1); + + if(cInfo->exceptions->used) + saxdb_write_string_list(ctx, KEY_EXCEPTIONS, cInfo->exceptions); + + if(cInfo->flags) + saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags); + + saxdb_write_string(ctx, KEY_INFO, cInfo->info); + saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel); + + if(cInfo->suspend_expiry) + saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry); + + saxdb_start_record(ctx, KEY_BADWORDS, 1); + saxdb_write_int(ctx, KEY_LASTBADWORDID, cInfo->last_badword_id); + dict_iterator_t itbad; + for (itbad = dict_first(cInfo->badwords); itbad; itbad = iter_next(itbad)) { + struct badword *badword = iter_data(itbad); + saxdb_start_record(ctx, badword->id, 1); + saxdb_write_string(ctx, KEY_BADWORDID, badword->id); + saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask); + saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action); + saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered); + saxdb_end_record(ctx); + } + saxdb_end_record(ctx); + saxdb_end_record(ctx); + } + return 0; +} + +static void +spamserv_conf_read(void) +{ + dict_t conf_node; + const char *str; + + if(!(conf_node = conf_get_data(SPAMSERV_CONF_NAME, RECDB_OBJECT))) + { + log_module(SS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", SPAMSERV_CONF_NAME); + return; + } + + str = database_get_data(conf_node, KEY_DEBUG_CHANNEL, RECDB_QSTRING); + + if(str) + { + spamserv_conf.debug_channel = AddChannel(str, now, "+tinms", NULL); + + if(spamserv_conf.debug_channel) + spamserv_join_channel(spamserv_conf.debug_channel); + } + else + { + spamserv_conf.debug_channel = NULL; + } + + spamserv_conf.global_exceptions = database_get_data(conf_node, KEY_GLOBAL_EXCEPTIONS, RECDB_STRING_LIST); + + str = database_get_data(conf_node, KEY_NETWORK_RULES, RECDB_QSTRING); + spamserv_conf.network_rules = str ? str : NULL; + + str = database_get_data(conf_node, KEY_TRIGGER, RECDB_QSTRING); + spamserv_conf.trigger = str ? str[0] : 0; + + str = database_get_data(conf_node, KEY_SHORT_BAN_DURATION, RECDB_QSTRING); + spamserv_conf.short_ban_duration = str ? ParseInterval(str) : ParseInterval("15m"); + + str = database_get_data(conf_node, KEY_LONG_BAN_DURATION, RECDB_QSTRING); + spamserv_conf.long_ban_duration = str ? ParseInterval(str) : ParseInterval("1h"); + + str = database_get_data(conf_node, KEY_GLINE_DURATION, RECDB_QSTRING); + spamserv_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h"); + + str = database_get_data(conf_node, KEY_EXCEPTION_MAX, RECDB_QSTRING); + spamserv_conf.exception_max = str ? strtoul(str, NULL, 0) : 10; + + str = database_get_data(conf_node, KEY_EXCEPTION_MIN_LEN, RECDB_QSTRING); + spamserv_conf.exception_min_len = str ? strtoul(str, NULL, 0) : 4; + + str = database_get_data(conf_node, KEY_EXCEPTION_MAX_LEN, RECDB_QSTRING); + spamserv_conf.exception_max_len = str ? strtoul(str, NULL, 0) : 15; + + str = database_get_data(conf_node, KEY_ADV_CHAN_MUST_EXIST, RECDB_QSTRING); + spamserv_conf.adv_chan_must_exist = str ? enabled_string(str) : 1; + + str = database_get_data(conf_node, KEY_STRIP_MIRC_CODES, RECDB_QSTRING); + spamserv_conf.strip_mirc_codes = str ? enabled_string(str) : 0; + + str = database_get_data(conf_node, KEY_ALLOW_MOVE_MERGE, RECDB_QSTRING); + spamserv_conf.allow_move_merge = str ? enabled_string(str) : 0; +} + +static void +spamserv_db_cleanup(void) +{ + dict_iterator_t it; + + while((it = dict_first(registered_channels_dict))) + { + spamserv_unregister_channel(iter_data(it)); + } + + while((it = dict_first(killed_users_dict))) + { + free(iter_data(it)); + } + + dict_delete(registered_channels_dict); + dict_delete(connected_users_dict); + dict_delete(killed_users_dict); +} + +void +init_spamserv(const char *nick) +{ + if(!nick) + return; + + const char *modes = conf_get_data("services/spamserv/modes", RECDB_QSTRING); + spamserv = AddLocalUser(nick, nick, NULL, "Anti Spam Services", modes); + spamserv_service = service_register(spamserv); + service_register(spamserv)->trigger = spamserv_conf.trigger; + + conf_register_reload(spamserv_conf_read); + + SS_LOG = log_register_type("SpamServ", "file:spamserv.log"); + + registered_channels_dict = dict_new(); + connected_users_dict = dict_new(); + killed_users_dict = dict_new(); + + reg_new_user_func(spamserv_new_user_func); + reg_del_user_func(spamserv_del_user_func); + reg_nick_change_func(spamserv_nick_change_func); + reg_join_func(spamserv_user_join); + reg_part_func(spamserv_user_part); + + timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL); + timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL); + timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL); + timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL); + timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL); + + spamserv_module = module_register("SpamServ", SS_LOG, "spamserv.help", NULL); + modcmd_register(spamserv_module, "REGISTER", cmd_register, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+acceptchan,+helping", NULL); + modcmd_register(spamserv_module, "UNREGISTER", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+loghostmask", NULL); + modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "DELEXCEPTION", cmd_delexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL); + modcmd_register(spamserv_module, "ADDBAD", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "DELBAD", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SETBAD", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "LISTBAD", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET WARNREACTION", opt_warnreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET ADVSCAN", opt_advscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET SPAMSCAN", opt_spamscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET FLOODSCAN", opt_floodscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET JOINFLOODSCAN", opt_joinflood, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET SCANCHANOPS", opt_scanops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET SCANVOICED", opt_scanvoiced, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + modcmd_register(spamserv_module, "SET EXCEPTLEVEL", opt_exceptlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + + saxdb_register("SpamServ", spamserv_saxdb_read, spamserv_saxdb_write); + reg_exit_func(spamserv_db_cleanup); + message_register_table(msgtab); + crc32_init(); +} diff --git a/src/spamserv.h b/src/spamserv.h index b6b6ac6..ec38889 100644 --- a/src/spamserv.h +++ b/src/spamserv.h @@ -1,172 +1,187 @@ -/* spamserv.h - anti spam service - * Copyright 2004 feigling - * - * 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 2 of the License, or - * (at your option) any later version. Important limitations are - * listed in the COPYING file that accompanies this software. - * - * 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, email srvx-maintainers@srvx.net. - * - * $Id: spamserv.h,v 1.3 2004/06/27 22:20:00 feigling Exp $ - */ - -#ifndef _spamserv_h -#define _spamserv_h - -#ifdef HAVE_ARPA_INET_H -#include -#endif - -/***********************************************/ -/* Channel */ -/***********************************************/ - -enum channelinfo -{ - ci_SpamLimit = 0, - ci_AdvReaction, - ci_WarnReaction, - ci_Max -}; - -#define CHAN_INFO_SIZE (ci_Max + 1) -#define CHAN_INFO_DEFAULT "bls" - -#define CHAN_SPAMSCAN 0x00000001 -#define CHAN_FLOODSCAN 0x00000002 -#define CHAN_JOINFLOOD 0x00000004 -#define CHAN_ADV_SCAN 0x00000008 -#define CHAN_SCAN_CHANOPS 0x00000010 -#define CHAN_SCAN_VOICED 0x00000020 -#define CHAN_SUSPENDED 0x00000040 - -#define CHAN_FLAGS_DEFAULT (CHAN_SPAMSCAN | CHAN_FLOODSCAN | CHAN_JOINFLOOD) - -#define CHECK_SPAM(x) ((x)->flags & CHAN_SPAMSCAN) -#define CHECK_FLOOD(x) ((x)->flags & CHAN_FLOODSCAN) -#define CHECK_JOINFLOOD(x) ((x)->flags & CHAN_JOINFLOOD) -#define CHECK_ADV(x) ((x)->flags & CHAN_ADV_SCAN) -#define CHECK_CHANOPS(x) ((x)->flags & CHAN_SCAN_CHANOPS) -#define CHECK_VOICED(x) ((x)->flags & CHAN_SCAN_VOICED) -#define CHECK_SUSPENDED(x) ((x)->flags & CHAN_SUSPENDED) - -struct chanInfo -{ - struct chanNode *channel; - struct string_list *exceptions; - unsigned int flags : 30; - unsigned int exceptlevel; - char info[CHAN_INFO_SIZE]; - unsigned long suspend_expiry; -}; - -/***********************************************/ -/* User */ -/***********************************************/ - -#define USER_KICK 0x00000001 -#define USER_KICKBAN 0x00000002 -#define USER_SHORT_TBAN 0x00000004 -#define USER_LONG_TBAN 0x00000008 -#define USER_KILL 0x00000010 -#define USER_GLINE 0x00000020 -#define USER_WARNED 0x00000040 -#define USER_KILLED 0x00000080 -#define USER_ADV_WARNED 0x00000100 - -#define CHECK_KICK(x) ((x)->flags & USER_KICK) -#define CHECK_KICKBAN(x) ((x)->flags & USER_KICKBAN) -#define CHECK_SHORT_TBAN(x) ((x)->flags & USER_SHORT_TBAN) -#define CHECK_LONG_TBAN(x) ((x)->flags & USER_LONG_TBAN) -#define CHECK_KILL(x) ((x)->flags & USER_KILL) -#define CHECK_GLINE(x) ((x)->flags & USER_GLINE) -#define CHECK_WARNED(x) ((x)->flags & USER_WARNED) -#define CHECK_KILLED(x) ((x)->flags & USER_KILLED) -#define CHECK_ADV_WARNED(x) ((x)->flags & USER_ADV_WARNED) - -#define SPAM_WARNLEVEL 1 - -#define FLOOD_TIMEQ_FREQ 5 -#define FLOOD_EXPIRE 5 -#define FLOOD_WARNLEVEL 1 -#define FLOOD_MAX_LINES 8 - -#define JOINFLOOD_TIMEQ_FREQ 225 -#define JOINFLOOD_EXPIRE 450 -#define JOINFLOOD_MAX 3 -#define JOINFLOOD_B_DURATION 900 - -#define ADV_TIMEQ_FREQ 300 -#define ADV_EXPIRE 900 -#define ADV_WARNLEVEL 2 - -#define WARNLEVEL_TIMEQ_FREQ 1800 -#define MAX_WARNLEVEL 6 - -#define KILL_TIMEQ_FREQ 450 -#define KILL_EXPIRE 1800 -#define KILL_WARNLEVEL 3 - -struct spamNode -{ - struct chanNode *channel; - unsigned long crc32; - unsigned int count; - struct spamNode *prev; - struct spamNode *next; -}; - -struct floodNode -{ - struct chanNode *channel; - struct userNode *owner; - unsigned int count; - time_t time; - struct floodNode *prev; - struct floodNode *next; -}; - -struct killNode -{ - unsigned int warnlevel; - time_t time; -}; - -struct userInfo -{ - struct userNode *user; - struct spamNode *spam; - struct floodNode *flood; - struct floodNode *joinflood; - unsigned int flags : 30; - unsigned int warnlevel; - time_t lastadv; -}; - -/***********************************************/ -/* Other Stuff */ -/***********************************************/ - -enum cs_unreg -{ - manually, - expire, - lost_all_users -}; - -void init_spamserv(const char *nick); -struct chanInfo *get_chanInfo(const char *channelname); -void spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text); -void spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason); -int spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move); -void spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason); - -#endif \ No newline at end of file +/* spamserv.h - anti spam service + * Copyright 2004 feigling + * + * 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 2 of the License, or + * (at your option) any later version. Important limitations are + * listed in the COPYING file that accompanies this software. + * + * 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, email srvx-maintainers@srvx.net. + * + * $Id: spamserv.h,v 1.3 2004/06/27 22:20:00 feigling Exp $ + */ + +#ifndef _spamserv_h +#define _spamserv_h + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +/***********************************************/ +/* Channel */ +/***********************************************/ + +enum channelinfo +{ + ci_SpamLimit = 0, + ci_AdvReaction, + ci_WarnReaction, + ci_Max +}; + +#define CHAN_INFO_SIZE (ci_Max + 1) +#define CHAN_INFO_DEFAULT "bls" + +#define CHAN_SPAMSCAN 0x00000001 +#define CHAN_FLOODSCAN 0x00000002 +#define CHAN_JOINFLOOD 0x00000004 +#define CHAN_ADV_SCAN 0x00000008 +#define CHAN_SCAN_CHANOPS 0x00000010 +#define CHAN_SCAN_VOICED 0x00000020 +#define CHAN_SUSPENDED 0x00000040 + +#define CHAN_FLAGS_DEFAULT (CHAN_SPAMSCAN | CHAN_FLOODSCAN | CHAN_JOINFLOOD) + +#define CHECK_SPAM(x) ((x)->flags & CHAN_SPAMSCAN) +#define CHECK_FLOOD(x) ((x)->flags & CHAN_FLOODSCAN) +#define CHECK_JOINFLOOD(x) ((x)->flags & CHAN_JOINFLOOD) +#define CHECK_ADV(x) ((x)->flags & CHAN_ADV_SCAN) +#define CHECK_CHANOPS(x) ((x)->flags & CHAN_SCAN_CHANOPS) +#define CHECK_VOICED(x) ((x)->flags & CHAN_SCAN_VOICED) +#define CHECK_SUSPENDED(x) ((x)->flags & CHAN_SUSPENDED) + +#define BADACTION_KICK 0 +#define BADACTION_BAN 1 +#define BADACTION_KILL 2 +#define BADACTION_GLINE 3 + +struct chanInfo +{ + struct chanNode *channel; + struct string_list *exceptions; + dict_t badwords; + unsigned int flags : 30; + unsigned int exceptlevel; + char info[CHAN_INFO_SIZE]; + unsigned long suspend_expiry; + unsigned int last_badword_id; +}; + +/***********************************************/ +/* User */ +/***********************************************/ + +#define USER_KICK 0x00000001 +#define USER_KICKBAN 0x00000002 +#define USER_SHORT_TBAN 0x00000004 +#define USER_LONG_TBAN 0x00000008 +#define USER_KILL 0x00000010 +#define USER_GLINE 0x00000020 +#define USER_WARNED 0x00000040 +#define USER_KILLED 0x00000080 +#define USER_ADV_WARNED 0x00000100 + +#define CHECK_KICK(x) ((x)->flags & USER_KICK) +#define CHECK_KICKBAN(x) ((x)->flags & USER_KICKBAN) +#define CHECK_SHORT_TBAN(x) ((x)->flags & USER_SHORT_TBAN) +#define CHECK_LONG_TBAN(x) ((x)->flags & USER_LONG_TBAN) +#define CHECK_KILL(x) ((x)->flags & USER_KILL) +#define CHECK_GLINE(x) ((x)->flags & USER_GLINE) +#define CHECK_WARNED(x) ((x)->flags & USER_WARNED) +#define CHECK_KILLED(x) ((x)->flags & USER_KILLED) +#define CHECK_ADV_WARNED(x) ((x)->flags & USER_ADV_WARNED) + +#define SPAM_WARNLEVEL 1 + +#define FLOOD_TIMEQ_FREQ 5 +#define FLOOD_EXPIRE 5 +#define FLOOD_WARNLEVEL 1 +#define FLOOD_MAX_LINES 8 + +#define JOINFLOOD_TIMEQ_FREQ 225 +#define JOINFLOOD_EXPIRE 450 +#define JOINFLOOD_MAX 3 +#define JOINFLOOD_B_DURATION 900 + +#define ADV_TIMEQ_FREQ 300 +#define ADV_EXPIRE 900 +#define ADV_WARNLEVEL 2 + +#define WARNLEVEL_TIMEQ_FREQ 1800 +#define MAX_WARNLEVEL 6 + +#define KILL_TIMEQ_FREQ 450 +#define KILL_EXPIRE 1800 +#define KILL_WARNLEVEL 3 + +struct spamNode +{ + struct chanNode *channel; + unsigned long crc32; + unsigned int count; + struct spamNode *prev; + struct spamNode *next; +}; + +struct floodNode +{ + struct chanNode *channel; + struct userNode *owner; + unsigned int count; + time_t time; + struct floodNode *prev; + struct floodNode *next; +}; + +struct killNode +{ + unsigned int warnlevel; + time_t time; +}; + +struct userInfo +{ + struct userNode *user; + struct spamNode *spam; + struct floodNode *flood; + struct floodNode *joinflood; + unsigned int flags : 30; + unsigned int warnlevel; + time_t lastadv; +}; + +struct badword { + char *id; + char *badword_mask; + unsigned int triggered : 29; + unsigned int action : 3; +}; + +/***********************************************/ +/* Other Stuff */ +/***********************************************/ + +enum cs_unreg +{ + manually, + expire, + lost_all_users +}; + +void init_spamserv(const char *nick); +struct chanInfo *get_chanInfo(const char *channelname); +void spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text); +void spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason); +int spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move); +void spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason); +static struct badword *add_badword(struct chanInfo *cInfo, const char *badword_mask, unsigned int triggered, unsigned int action, const char *id); + +#endif