From: root Date: Thu, 2 Feb 2012 21:44:45 +0000 (+0100) Subject: Merge remote branch 'upstream/master' X-Git-Url: http://git.pk910.de/?p=srvx.git;a=commitdiff_plain;h=fffe459760943b6ed8e0bb736c87ef424ce38fdc;hp=4e6b0e24b2f4b6e00df70bd5c2badda8d56fab77 Merge remote branch 'upstream/master' Conflicts: src/proto-p10.c --- diff --git a/INSTALL b/INSTALL index c31fd17..e74bb49 100644 --- a/INSTALL +++ b/INSTALL @@ -49,6 +49,14 @@ You may also have trouble unless your compiler's C preprocessor supports ISO C99 varadic macros. gcc is the compiler we use for almost all our testing, and we recommend it for use with srvx. +PreInstall: +---------- +$ aclocal +$ autoconf +$ autoreconf -f -i -Wall,no-obsolete +$ cp /usr/share/libtool/* ./ +$ automake + Quick Install: ---------- $ ./configure diff --git a/configure.in b/configure.in index 5b350a9..a3a8851 100644 --- a/configure.in +++ b/configure.in @@ -3,7 +3,7 @@ dnl Process this file with autoconf to create a configure script. dnl General initialization. AC_PREREQ(2.64) AC_INIT([srvx],[1.4.0-rc3],[srvx-bugs@lists.sourceforge.net]) -CODENAME=surge +CODENAME=wgn AC_CONFIG_HEADERS(src/config.h) AC_CONFIG_SRCDIR(src/opserv.c) dnl AM_CANONICAL_TARGET must be before AM_INIT_AUTOMAKE() or autoconf whines @@ -24,6 +24,7 @@ AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_GCC_TRADITIONAL +AC_PROG_RANLIB dnl Look for a git client AC_CHECK_PROGS(GIT, [git]) diff --git a/src/Makefile.am b/src/Makefile.am index adcb551..cdf6ae8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,7 +11,9 @@ noinst_DATA = \ opserv.help \ saxdb.help \ mail.help \ + spamserv.help \ mod-helpserv.help \ + mod-watchdog.help \ mod-memoserv.help \ mod-qserver.help \ mod-snoop.help \ @@ -53,6 +55,7 @@ EXTRA_srvx_SOURCES = \ proto-p10.c \ mod-blacklist.c \ mod-helpserv.c \ + mod-watchdog.c \ mod-memoserv.c \ mod-qserver.c \ mod-snoop.c \ @@ -86,6 +89,7 @@ srvx_SOURCES = \ recdb.c recdb.h \ sar.c sar.h \ saxdb.c saxdb.h \ + spamserv.c spamserv.h \ timeq.c timeq.h \ tools.c diff --git a/src/chanserv.c b/src/chanserv.c index 2ba265f..7512279 100644 --- a/src/chanserv.c +++ b/src/chanserv.c @@ -22,9 +22,10 @@ #include "conf.h" #include "global.h" #include "modcmd.h" -#include "opserv.h" /* for opserv_bad_channel() */ +#include "opserv.h" /* for opserv_bad_channel() and devnull management */ #include "nickserv.h" /* for oper_outranks() */ #include "saxdb.h" +#include "spamserv.h" #include "timeq.h" #define CHANSERV_CONF_NAME "services/chanserv" @@ -56,6 +57,10 @@ #define KEY_NODELETE_LEVEL "nodelete_level" #define KEY_MAX_USERINFO_LENGTH "max_userinfo_length" #define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout" +#define KEY_INVITED_INTERVAL "invite_timeout" +#define KEY_NEW_CHANNEL_AUTHED "new_channel_authed_join" +#define KEY_NEW_CHANNEL_UNAUTHED "new_channel_unauthed_join" +#define KEY_NEW_CHANNEL_MSG "new_channel_message" /* ChanServ database */ #define KEY_CHANNELS "channels" @@ -99,15 +104,27 @@ #define KEY_USERS "users" #define KEY_BANS "bans" #define KEY_MAX "max" +#define KEY_MAX_TIME "max_time" #define KEY_NOTES "notes" #define KEY_TOPIC_MASK "topic_mask" #define KEY_OWNER_TRANSFER "owner_transfer" +#define KEY_EXPIRE "expire" /* User data */ #define KEY_LEVEL "level" #define KEY_INFO "info" #define KEY_SEEN "seen" +/* Votes */ +#define KEY_VOTE "vote" +#define KEY_VOTE_START "votestart" +#define KEY_VOTE_OPTIONS "voptions" +#define KEY_VOTE_OPTION_NAME "voptionname" +#define KEY_VOTE_VOTED "vvoted" +#define KEY_VOTE_VOTEDFOR "vvotefor" +#define KEY_VOTE_OPTION_ID "voptionid" +#define KEY_VOTE_OPTION_VOTED "voptionvoted" + /* Ban data */ #define KEY_OWNER "owner" #define KEY_REASON "reason" @@ -192,7 +209,7 @@ static const struct message_entry msgtab[] = { /* User management */ { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %d." }, { "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." }, - { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." }, + { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be lower than maximum (%d)." }, { "CSMSG_DELETED_USERS", "Deleted accounts matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list." }, { "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." }, { "CSMSG_INCORRECT_ACCESS", "%s has access $b%d$b, not %s." }, @@ -265,6 +282,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_SET_USERINFO", "$bUserInfo $b %d" }, { "CSMSG_SET_GIVE_VOICE", "$bGiveVoice $b %d" }, { "CSMSG_SET_TOPICSNARF", "$bTopicSnarf $b %d" }, + { "CSMSG_SET_VOTE", "$bVote $b %d" }, { "CSMSG_SET_INVITEME", "$bInviteMe $b %d" }, { "CSMSG_SET_ENFOPS", "$bEnfOps $b %d" }, { "CSMSG_SET_GIVE_OPS", "$bGiveOps $b %d" }, @@ -278,6 +296,8 @@ static const struct message_entry msgtab[] = { { "CSMSG_SET_CTCPREACTION", "$bCTCPReaction$b %d - %s" }, { "CSMSG_SET_TOPICREFRESH", "$bTopicRefresh$b %d - %s" }, { "CSMSG_SET_UNREVIEWED", "$bUnreviewed $b %s" }, + { "CSMSG_SET_EXPIRE", "$bExpire $b %s" }, + { "CSMSG_SET_EXPIRE_OFF", "$bExpire $b off" }, { "CSMSG_USET_NOAUTOOP", "$bNoAutoOp $b %s" }, { "CSMSG_USET_NOAUTOVOICE", "$bNoAutoVoice $b %s" }, { "CSMSG_USET_AUTOINVITE", "$bAutoInvite $b %s" }, @@ -325,6 +345,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_ACCESS_SEARCH_HEADER", "%s users from level %d to %d matching %s:" }, { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." }, { "CSMSG_CHANGED_ACCESS", "%s now has access $b%d$b in %s." }, + { "CSMSG_TOTAL_USERS", "There are $b%d$b users in %s." }, /* Channel note list */ { "CSMSG_NOTELIST_HEADER", "Notes for $b%s$b:" }, @@ -367,6 +388,9 @@ static const struct message_entry msgtab[] = { { "CSMSG_UC_H_TITLE", "network helper" }, { "CSMSG_LC_H_TITLE", "support helper" }, { "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." }, + { "CSMSG_MYACCESS_COUNT", "%s has access in $b%d$b channels and is owner of $b%d$b channel(s)." }, + { "CSMSG_MYACCESS_COUNT_1", "%s has access in $b%d$b channel and is owner of $b%d$b channel(s)." }, + /* Seen information */ { "CSMSG_NEVER_SEEN", "%s has never been seen in $b%s$b." }, @@ -384,6 +408,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_CHANNEL_MODES", "$bMode Lock: $b%s" }, { "CSMSG_CHANNEL_NOTE", "$b%s:%*s$b%s" }, { "CSMSG_CHANNEL_MAX", "$bRecord Visitors: $b%i" }, + { "CSMSG_CHANNEL_MAX_TIME", "$bRecord Visitors: $b%i (%s ago.)" }, { "CSMSG_CHANNEL_OWNER", "$bOwner: $b%s" }, { "CSMSG_CHANNEL_BANS", "$bBan Count: $b%i" }, { "CSMSG_CHANNEL_USERS", "$bTotal User Count: $b%i" }, @@ -452,6 +477,32 @@ static const struct message_entry msgtab[] = { { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" }, { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" }, +/* Vote */ + { "CSMSG_ADDVOTE_DONE", "Vote added. Use $baddoption$b to add at least 2 vote options and then $bstartvote$b to start the voting." }, + { "CSMSG_ADDVOTE_FULL", "There is already a vote in this channel. Use $bdelvote$b to delete it." }, + { "CSMSG_DELVOTE_DONE", "Vote deleted." }, + { "CSMSG_NO_VOTE", "There is no vote in this channel." }, + { "CSMSG_ADDOPTION_DONE", "Vote option added with id %i (%i - %i)." }, + { "CSMSG_DELOPTION_DONE", "Vote option deleted." }, + { "CSMSG_DELOPTION_NONE", "Vote option does not exist." }, + { "CSMSG_VOTE_NEED_OPTIONS", "There must be at least 2 options in a vote." }, + { "CSMSG_VOTE_NOT_STARTED", "The vote is not started. Use $bstartvote$b to allow voting." }, + { "CSMSG_VOTE_QUESTION", "Question: %s" }, + { "CSMSG_VOTE_OPTION", "$b%i$b: %s ($b%i$b votes)" }, + { "CSMSG_VOTERES_QUESTION", "Question: %s" }, + { "CSMSG_VOTERES_OPTION", "%i: %s (%i votes)" }, + { "CSMSG_STARTVOTE_TOP", "%s has started a voting:" }, + { "CSMSG_STARTVOTE_QUESTION", "Question: %s" }, + { "CSMSG_STARTVOTE_OPTION", "%i: %s" }, + { "CSMSG_STARTVOTE_ACCESS", "All channel users with at least %i access can vote." }, + { "CSMSG_STARTVOTE_HOWTO", "To vote for an option, use vote ID. To see the available options and the current votes, use vote without parameters." }, + { "CSMSG_STARTVOTE_RUNNING", "The vote is already running." }, + { "CSMSG_VOTE_VOTED", "You have already voted." }, + { "CSMSG_VOTE_INVALID", "Vote option does not exist." }, + { "CSMSG_VOTE_DONE", "You voted for $b%s$b." }, + { "CSMSG_ENDVOTE_DONE", "The vote has been finished." }, + { "CSMSG_ENDVOTE_STOPPED", "The vote is not running." }, + /* Other things */ { "CSMSG_EVENT_SEARCH_RESULTS", "The following channel events were found:" }, { NULL, NULL } @@ -498,6 +549,8 @@ static struct unsigned long db_backup_frequency; unsigned long channel_expire_frequency; unsigned long dnr_expire_frequency; + + unsigned long invited_timeout; unsigned long info_delay; unsigned long adjust_delay; @@ -526,6 +579,10 @@ static struct const char *irc_operator_epithet; const char *network_helper_epithet; const char *support_helper_epithet; + + const char *new_channel_authed; + const char *new_channel_unauthed; + const char *new_channel_msg; } chanserv_conf; struct listData @@ -540,6 +597,12 @@ struct listData struct helpfile_table table; }; +struct ChanUser +{ + struct userNode *user; + struct chanNode *chan; +}; + enum note_access_type { NOTE_SET_CHANNEL_ACCESS, @@ -609,7 +672,8 @@ static const struct { { "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9, 0, 0 }, { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 }, { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 }, - { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 } + { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }, + { "CSMSG_SET_VOTE", "vote", 100, ~0, 0, 0 } }; struct charOptionValues { @@ -656,9 +720,9 @@ struct chanData *channelList; static struct module *chanserv_module; static unsigned int userCount; -#define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0) #define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0) #define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1) +static void unregister_channel(struct chanData *channel, const char *reason); unsigned short user_level_from_name(const char *name, unsigned short clamp_level) @@ -850,6 +914,15 @@ chanserv_create_note_type(const char *name) return ntype; } +static void +free_vote_options(void *data) +{ + struct vote_option *vOpt = data; + free(vOpt->name); + free(vOpt->option_str); + free(vOpt); +} + static void chanserv_deref_note_type(void *data) { @@ -1017,6 +1090,65 @@ static MODCMD_FUNC(cmd_removenote) { return 1; } +static void +chanserv_expire_channel(void *data) +{ + struct chanData *channel = data; + char reason[MAXLEN]; + sprintf(reason, "channel expired."); + channel->expiry = 0; + spamserv_cs_unregister(NULL, channel->channel, expire, NULL); + unregister_channel(channel, reason); +} + +static MODCMD_FUNC(chan_opt_expire) +{ + struct chanData *cData = channel->channel_info; + unsigned long value = cData->expiry; + + if(argc > 1) + { + if((!IsOper(user) || !user->handle_info || (user->handle_info->opserv_level < chanserv_conf.nodelete_level))) + { + reply("MSG_SETTING_PRIVILEGED", argv[0]); + return 0; + } + unsigned long expiry,duration; + + /* The two directions can have different ACLs. */ + if(!strcmp(argv[1], "0")) + expiry = 0; + else if((duration = ParseInterval(argv[1]))) + expiry = now + duration; + else + { + reply("MSG_INVALID_DURATION", argv[1]); + return 0; + } + + if (expiry != value) + { + if(value) { + //unset old timer + timeq_del(value, chanserv_expire_channel, cData, 0); + } + value = expiry; + cData->expiry = value; + if(value > 0) { + //New timer! + timeq_add(expiry, chanserv_expire_channel, cData); + } + } + } + + if(cData->expiry > now) { + char expirestr[INTERVALLEN]; + reply("CSMSG_SET_EXPIRE", intervalString(expirestr, cData->expiry - now, user->handle_info)); + } else + reply("CSMSG_SET_EXPIRE_OFF"); + return 1; +} + static int mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *change) { @@ -1112,6 +1244,8 @@ register_channel(struct chanNode *cNode, char *registrar) channel->channel = cNode; LockChannel(cNode); cNode->channel_info = channel; + + channel->vote = NULL; return channel; } @@ -1149,8 +1283,6 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned return ud; } -static void unregister_channel(struct chanData *channel, const char *reason); - void del_channel_user(struct userData *user, int do_gc) { @@ -1175,13 +1307,15 @@ del_channel_user(struct userData *user, int do_gc) free(user->info); free(user); - if(do_gc && !channel->users && !IsProtected(channel)) + if(do_gc && !channel->users && !IsProtected(channel)) { + spamserv_cs_unregister(NULL, channel->channel, lost_all_users, NULL); unregister_channel(channel, "lost all users."); + } } static void expire_ban(void *data); -static struct banData* +struct banData* add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason) { struct banData *bd; @@ -1349,6 +1483,8 @@ unregister_channel(struct chanData *channel, const char *reason) if(cNode) cNode->channel_info = NULL; } + if(channel->expiry) + timeq_del(channel->expiry, chanserv_expire_channel, channel, 0); channel->channel->channel_info = NULL; dict_delete(channel->notes); @@ -1389,6 +1525,7 @@ expire_channels(void *data) /* Unregister the channel */ log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name); + spamserv_cs_unregister(NULL, channel->channel, expire, NULL); unregister_channel(channel, "registration expired."); } @@ -2098,6 +2235,7 @@ static CHANSERV_FUNC(cmd_register) /* Initialize the channel's max user record. */ cData->max = channel->members.used; + cData->max_time = 0; if(handle != user->handle_info) reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name); @@ -2147,7 +2285,7 @@ static CHANSERV_FUNC(cmd_unregister) return 0; } - if(IsProtected(cData)) + if(IsProtected(cData) && !IsOper(user)) { reply("CSMSG_UNREG_NODELETE", channel->name); return 0; @@ -2172,11 +2310,39 @@ static CHANSERV_FUNC(cmd_unregister) sprintf(reason, "unregistered by %s.", user->handle_info->handle); name = strdup(channel->name); unregister_channel(cData, reason); + spamserv_cs_unregister(user, channel, manually, "unregistered"); reply("CSMSG_UNREG_SUCCESS", name); free(name); return 1; } +static void +ss_cs_join_channel(struct chanNode *channel, int spamserv_join) +{ + extern struct userNode *spamserv; + struct mod_chanmode *change; + + if(spamserv && spamserv_join && get_chanInfo(channel->name)) + { + change = mod_chanmode_alloc(2); + change->argc = 2; + change->args[0].mode = MODE_CHANOP; + change->args[0].u.member = AddChannelUser(chanserv, channel); + change->args[1].mode = MODE_CHANOP; + change->args[1].u.member = AddChannelUser(spamserv, channel); + } + else + { + change = mod_chanmode_alloc(1); + change->argc = 1; + change->args[0].mode = MODE_CHANOP; + change->args[0].u.member = AddChannelUser(chanserv, channel); + } + + mod_chanmode_announce(chanserv, channel, change); + mod_chanmode_free(change); +} + static CHANSERV_FUNC(cmd_move) { struct mod_chanmode change; @@ -2185,6 +2351,7 @@ static CHANSERV_FUNC(cmd_move) struct userData *uData; char reason[MAXLEN]; struct do_not_register *dnr; + int chanserv_join = 0, spamserv_join; REQUIRE_PARAMS(2); @@ -2226,7 +2393,7 @@ static CHANSERV_FUNC(cmd_move) { target = AddChannel(argv[1], now, NULL, NULL); if(!IsSuspended(channel->channel_info)) - AddChannelUser(chanserv, target); + chanserv_join = 1; } else if(target->channel_info) { @@ -2240,12 +2407,7 @@ static CHANSERV_FUNC(cmd_move) return 0; } else if(!IsSuspended(channel->channel_info)) - { - change.argc = 1; - change.args[0].mode = MODE_CHANOP; - change.args[0].u.member = AddChannelUser(chanserv, target); - mod_chanmode_announce(chanserv, target, &change); - } + chanserv_join = 1; if(off_channel > 0) { @@ -2269,7 +2431,10 @@ static CHANSERV_FUNC(cmd_move) for(uData = target->channel_info->users; uData; uData = uData->next) scan_user_presence(uData, NULL); - reply("CSMSG_MOVE_SUCCESS", target->name); + spamserv_join = spamserv_cs_move_merge(user, channel, target, 1); + + if(chanserv_join) + ss_cs_join_channel(target, spamserv_join); sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle); if(!IsSuspended(target->channel_info)) @@ -2281,6 +2446,7 @@ static CHANSERV_FUNC(cmd_move) UnlockChannel(channel); LockChannel(target); global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); + reply("CSMSG_MOVE_SUCCESS", target->name); return 1; } @@ -2461,8 +2627,10 @@ merge_data(struct chanData *source, struct chanData *target) target->ownerTransfer = source->ownerTransfer; if(source->may_opchan) target->may_opchan = 1; - if(source->max > target->max) + if(source->max > target->max) { target->max = source->max; + target->max_time = source->max_time; + } } static void @@ -2522,6 +2690,7 @@ static CHANSERV_FUNC(cmd_merge) /* Merge the channel structures and associated data. */ merge_channel(channel->channel_info, target->channel_info); + spamserv_cs_move_merge(user, channel, target, 0); sprintf(reason, "merged into %s by %s.", target->name, user->handle_info->handle); unregister_channel(channel->channel_info, reason); reply("CSMSG_MERGE_SUCCESS", target->name); @@ -3067,6 +3236,44 @@ static CHANSERV_FUNC(cmd_devoice) return modify_users(CSFUNC_ARGS, NULL, MODE_REMOVE|MODE_VOICE, "CSMSG_DEVOICED_USERS"); } +static CHANSERV_FUNC(cmd_opme) +{ + struct mod_chanmode change; + const char *errmsg; + + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].u.member = GetUserMode(channel, user); + if(!change.args[0].u.member) + { + if(argc) + reply("MSG_CHANNEL_ABSENT", channel->name); + return 0; + } + + struct devnull_class *devnull; + if(user->handle_info->devnull && (devnull = devnull_get(user->handle_info->devnull)) && (devnull->modes & DEVNULL_MODE_OPME)) + { + change.args[0].mode = MODE_CHANOP; + errmsg = "CSMSG_ALREADY_OPPED"; + } + else + { + if(argc) + reply("CSMSG_NO_ACCESS"); + return 0; + } + change.args[0].mode &= ~change.args[0].u.member->modes; + if(!change.args[0].mode) + { + if(argc) + reply(errmsg, channel->name); + return 0; + } + modcmd_chanmode_announce(&change); + return 1; +} + static int bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims) { @@ -3104,6 +3311,20 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c offset = (action & ACTION_ADD_TIMED_BAN) ? 3 : 2; REQUIRE_PARAMS(offset); + if(argc > offset && IsNetServ(user)) + { + if(*argv[offset] == '$') { + struct userNode *hib; + const char *accountnameb = argv[offset] + 1; + if(!(hib = GetUserH(accountnameb))) + { + reply("MSG_HANDLE_UNKNOWN", accountnameb); + return 0; + } + user=hib; + offset++; + } + } if(argc > offset) { reason = unsplit_string(argv + offset, argc - offset, NULL); @@ -3440,7 +3661,7 @@ static CHANSERV_FUNC(cmd_addtimedban) return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_BAN | ACTION_ADD_TIMED_BAN); } -static struct mod_chanmode * +struct mod_chanmode * find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask) { struct mod_chanmode *change; @@ -3637,6 +3858,8 @@ static CHANSERV_FUNC(cmd_myaccess) static struct string_buffer sbuf; struct handle_info *target_handle; struct userData *uData; + int ccount = 0; + int ocount = 0; if(argc < 2) target_handle = user->handle_info; @@ -3661,13 +3884,18 @@ static CHANSERV_FUNC(cmd_myaccess) for(uData = target_handle->channels; uData; uData = uData->u_next) { struct chanData *cData = uData->channel; + ccount++; unsigned int base_len; if(uData->access > UL_OWNER) continue; + if(uData->access == UL_OWNER) + ocount++; + if(IsProtected(cData) && (target_handle != user->handle_info) - && !GetTrueChannelAccess(cData, user->handle_info)) + && !GetTrueChannelAccess(cData, user->handle_info) + && !IsNetworkHelper(user)) continue; sbuf.used = 0; string_buffer_append_printf(&sbuf, "[%s (%d,", cData->channel->name, uData->access); @@ -3693,6 +3921,12 @@ static CHANSERV_FUNC(cmd_myaccess) send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list); } + if(ccount == 1) { + reply("CSMSG_MYACCESS_COUNT_1", target_handle->handle, ccount, ocount); + } else { + reply("CSMSG_MYACCESS_COUNT", target_handle->handle, ccount, ocount); + } + return 1; } @@ -3928,6 +4162,7 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg } free(lData.table.contents[0]); free(lData.table.contents); + reply("CSMSG_TOTAL_USERS",(lData.table.length - 1),channel->name); return 1; } @@ -4182,7 +4417,7 @@ static CHANSERV_FUNC(cmd_mode) base_oplevel = 1; else base_oplevel = 1 + UL_OWNER - uData->access; - change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, base_oplevel); + change = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS, base_oplevel); if(!change) { reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL)); @@ -4205,9 +4440,27 @@ static CHANSERV_FUNC(cmd_mode) return 1; } +static void +chanserv_del_invite_mark(void *data) +{ + struct ChanUser *chanuser = data; + struct chanNode *channel = chanuser->chan; + unsigned int i; + if(!channel) return; + for(i = 0; i < channel->invited.used; i++) + { + if(channel->invited.list[i] == chanuser->user) { + userList_remove(&channel->invited, chanuser->user); + } + } + free(chanuser); +} + static CHANSERV_FUNC(cmd_invite) { struct userNode *invite; + struct ChanUser *chanuser; + unsigned int i; if(argc > 1) { @@ -4225,6 +4478,14 @@ static CHANSERV_FUNC(cmd_invite) reply("CSMSG_ALREADY_PRESENT", invite->nick, channel->name); return 0; } + + for(i = 0; i < channel->invited.used; i++) + { + if(channel->invited.list[i] == invite) { + reply("CSMSG_ALREADY_INVITED", invite->nick, channel->name); + return 0; + } + } if(user != invite) { @@ -4240,6 +4501,12 @@ static CHANSERV_FUNC(cmd_invite) if(argc > 1) reply("CSMSG_INVITED_USER", argv[1], channel->name); + userList_append(&channel->invited, invite); + chanuser = calloc(1, sizeof(*chanuser)); + chanuser->user=invite; + chanuser->chan=channel; + timeq_add(now + chanserv_conf.invited_timeout, chanserv_del_invite_mark, chanuser); + return 1; } @@ -4260,6 +4527,28 @@ static CHANSERV_FUNC(cmd_inviteme) return 1; } +static CHANSERV_FUNC(cmd_invitemeall) +{ + struct handle_info *target = user->handle_info; + struct userData *uData; + + if(!target->channels) + { + reply("CSMSG_SQUAT_ACCESS", target->handle); + return 1; + } + + for(uData = target->channels; uData; uData = uData->u_next) + { + struct chanData *cData = uData->channel; + if(uData->access >= cData->lvlOpts[lvlInviteMe]) + { + irc_invite(cmd->parent->bot, user, cData->channel); + } + } + return 1; +} + static void show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended *suspended) { @@ -4348,7 +4637,11 @@ static CHANSERV_FUNC(cmd_info) reply("CSMSG_CHANNEL_NOTE", iter_key(it), padding > 0 ? padding : 1, "", note->note); } - reply("CSMSG_CHANNEL_MAX", cData->max); + if(cData->max_time) { + reply("CSMSG_CHANNEL_MAX_TIME", cData->max, intervalString(buffer, now - cData->max_time, user->handle_info)); + } else { + reply("CSMSG_CHANNEL_MAX", cData->max); + } for(owner = cData->users; owner; owner = owner->next) if(owner->access == UL_OWNER) reply("CSMSG_CHANNEL_OWNER", owner->handle->handle); @@ -4417,6 +4710,8 @@ send_staff_list(struct userNode *to, struct userList *list, int skip_flags) continue; if(IsBot(user)) continue; + if(IsInvi(user)) + continue; table.contents[table.length] = alloca(table.width*sizeof(**table.contents)); if(IsAway(user)) { @@ -4552,8 +4847,16 @@ static CHANSERV_FUNC(cmd_resync) { if(!(mn->modes & MODE_CHANOP)) { - changes->args[used].mode = MODE_CHANOP; - changes->args[used++].u.member = mn; + if(!uData || IsUserAutoOp(uData)) + { + changes->args[used].mode = MODE_CHANOP; + changes->args[used++].u.member = mn; + if(!(mn->modes & MODE_VOICE)) + { + changes->args[used].mode = MODE_VOICE; + changes->args[used++].u.member = mn; + } + } } } else if(!cData->lvlOpts[lvlGiveVoice] @@ -4564,7 +4867,7 @@ static CHANSERV_FUNC(cmd_resync) changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE); changes->args[used++].u.member = mn; } - if(!(mn->modes & MODE_VOICE)) + if(!(mn->modes & MODE_VOICE) && (!uData || IsUserAutoOp(uData))) { changes->args[used].mode = MODE_VOICE; changes->args[used++].u.member = mn; @@ -4930,12 +5233,8 @@ chanserv_expire_suspension(void *data) /* If appropriate, re-join ChanServ to the channel. */ if(!IsOffChannel(suspended->cData)) { - struct mod_chanmode change; - mod_chanmode_init(&change); - change.argc = 1; - change.args[0].mode = MODE_CHANOP; - change.args[0].u.member = AddChannelUser(chanserv, channel); - mod_chanmode_announce(chanserv, channel, &change); + spamserv_cs_suspend(channel, 0, 0, NULL); + ss_cs_join_channel(channel, 1); } /* Mark everyone currently in the channel as present. */ @@ -5021,6 +5320,7 @@ static CHANSERV_FUNC(cmd_csuspend) /* Mark the channel as suspended, then part. */ channel->channel_info->flags |= CHANNEL_SUSPENDED; + spamserv_cs_suspend(channel, expiry, 1, suspended->reason); DelChannelUser(chanserv, channel, suspended->reason, 0); reply("CSMSG_SUSPENDED", channel->name); sprintf(reason, "%s suspended by %s.", channel->name, suspended->suspender); @@ -5370,7 +5670,7 @@ static MODCMD_FUNC(chan_opt_modes) { memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes)); } - else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, 0))) + else if(!(new_modes = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS|(IsOper(user) && IsHelping(user) ? MCP_OPERMODE : 0), 0))) { reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL)); return 0; @@ -5698,6 +5998,11 @@ static MODCMD_FUNC(chan_opt_topicsnarf) return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS); } +static MODCMD_FUNC(chan_opt_vote) +{ + return channel_level_option(lvlVote, CSFUNC_ARGS); +} + static MODCMD_FUNC(chan_opt_inviteme) { return channel_level_option(lvlInviteMe, CSFUNC_ARGS); @@ -6189,6 +6494,363 @@ static MODCMD_FUNC(cmd_deleteme) return 1; } +static CHANSERV_FUNC(cmd_addvote) +{ + struct chanData *cData = channel->channel_info; + struct userData *uData, *target; + struct handle_info *hi; + if (!cData) return 0; + REQUIRE_PARAMS(2); + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if (cData->vote) { + reply("CSMSG_ADDVOTE_FULL"); + return 0; + } + char *msg; + msg = unsplit_string(argv + 1, argc - 1, NULL); + cData->vote = strdup(msg); + cData->vote_start=0; + dict_delete(cData->vote_options); + cData->vote_options = dict_new(); + dict_set_free_data(cData->vote_options, free_vote_options); + for(uData = channel->channel_info->users; uData; uData = uData->next) + { + uData->voted = 0; + uData->votefor = 0; + } + reply("CSMSG_ADDVOTE_DONE"); + return 1; +} + +static CHANSERV_FUNC(cmd_delvote) +{ + struct chanData *cData = channel->channel_info; + struct userData *target; + struct handle_info *hi; + if (!cData) return 0; + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if (!cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + free(cData->vote); + cData->vote = NULL; + reply("CSMSG_DELVOTE_DONE"); + return 1; +} + +static CHANSERV_FUNC(cmd_addoption) +{ + struct chanData *cData = channel->channel_info; + struct userData *target; + struct handle_info *hi; + if (!cData) return 0; + REQUIRE_PARAMS(2); + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if (!cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + + char *msg; + + msg = unsplit_string(argv + 1, argc - 1, NULL); + + dict_iterator_t it; + unsigned int lastid = 1; + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + struct vote_option *cvOpt = iter_data(it); + if(cvOpt->option_id > lastid) + lastid = cvOpt->option_id; + } + struct vote_option *vOpt; + vOpt = calloc(1, sizeof(*vOpt)); + vOpt->name = strdup(msg); + vOpt->option_id = (lastid + 1); + char str[50]; + sprintf(str,"%i",(lastid + 1)); + vOpt->option_str = strdup(str); + vOpt->voted = 0; + dict_insert(cData->vote_options,vOpt->option_str,vOpt); + + reply("CSMSG_ADDOPTION_DONE",dict_size(cData->vote_options),lastid,(lastid + 1)); + return 1; +} + +static CHANSERV_FUNC(cmd_deloption) +{ + struct chanData *cData = channel->channel_info; + struct userData *uData, *target; + struct handle_info *hi; + if (!cData) return 0; + REQUIRE_PARAMS(2); + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if (!cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + if(cData->vote_start) { + if(dict_size(cData->vote_options) < 3) { + reply("CSMSG_VOTE_NEED_OPTIONS"); + return 0; + } + } + + int find_id = atoi(argv[1]); + int ii = 0; + unsigned int found = 0; + dict_iterator_t it; + + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + ii++; + if (find_id == ii) { + struct vote_option *vOpt = iter_data(it); + found = vOpt->option_id; + char str[50]; + sprintf(str,"%i",vOpt->option_id); + dict_remove(cData->vote_options, str); + } + } + + if(found > 0) { + for(uData = channel->channel_info->users; uData; uData = uData->next) { + if(uData->votefor == found) { + uData->voted = 0; + uData->votefor = 0; + } + } + reply("CSMSG_DELOPTION_DONE"); + return 1; + } else { + reply("CSMSG_DELOPTION_NONE"); + return 0; + } +} + +static CHANSERV_FUNC(cmd_vote) +{ + struct chanData *cData = channel->channel_info; + struct userData *target; + struct handle_info *hi; + unsigned int votedfor = 0; + char *votedfor_str = NULL; + + if (!cData || !cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + if(argc > 1 && cData->vote_start) { + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(!check_user_level(channel, user, lvlVote, 1, 0)) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if(target->voted) { + reply("CSMSG_VOTE_VOTED"); + return 0; + } + int find_id = atoi(argv[1]); + int ii = 0; + dict_iterator_t it; + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + ii++; + if (find_id == ii) { + struct vote_option *vOpt = iter_data(it); + vOpt->voted++; + target->voted = 1; + target->votefor = vOpt->option_id; + votedfor = vOpt->option_id; + votedfor_str = vOpt->name; + } + } + if(votedfor == 0) { + reply("CSMSG_VOTE_INVALID"); + return 0; + } + } + if (!cData->vote_start) { + reply("CSMSG_VOTE_NOT_STARTED"); + } + reply("CSMSG_VOTE_QUESTION",cData->vote); + + unsigned int voteid = 0; + dict_iterator_t it; + + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + struct vote_option *vOpt = iter_data(it); + voteid++; + reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted); + } + if(argc > 1 && cData->vote_start && votedfor_str) { + reply("CSMSG_VOTE_DONE",votedfor_str); + } + return 1; +} + +static CHANSERV_FUNC(cmd_startvote) +{ + struct chanData *cData = channel->channel_info; + struct userData *target; + struct handle_info *hi; + if (!cData) return 0; + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if (!cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + if(cData->vote_start) { + reply("CSMSG_STARTVOTE_RUNNING"); + return 0; + } + if(dict_size(cData->vote_options) < 2) { + reply("CSMSG_VOTE_NEED_OPTIONS"); + return 0; + } + cData->vote_start = 1; + char response[MAXLEN]; + sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_TOP"), user->nick); + irc_privmsg(cmd->parent->bot, channel->name, response); + sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_QUESTION"), cData->vote); + irc_privmsg(cmd->parent->bot, channel->name, response); + unsigned int voteid = 0; + dict_iterator_t it; + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + struct vote_option *vOpt = iter_data(it); + voteid++; + sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_OPTION"), voteid, vOpt->name); + irc_privmsg(cmd->parent->bot, channel->name, response); + } + sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_ACCESS"), cData->lvlOpts[lvlVote]); //Todo + irc_privmsg(cmd->parent->bot, channel->name, response); + sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_HOWTO")); //Todo + irc_privmsg(cmd->parent->bot, channel->name, response); + return 1; +} + +static CHANSERV_FUNC(cmd_endvote) +{ + struct chanData *cData = channel->channel_info; + struct userData *target; + struct handle_info *hi; + if (!cData) return 0; + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + if (!cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + if(!cData->vote_start) { + reply("CSMSG_ENDVOTE_STOPPED"); + return 0; + } + cData->vote_start = 0; + reply("CSMSG_ENDVOTE_DONE"); + return 1; +} + +static CHANSERV_FUNC(cmd_voteresults) +{ + struct chanData *cData = channel->channel_info; + struct userData *target; + struct handle_info *hi; + if (!cData) return 0; + if (!cData->vote) { + reply("CSMSG_NO_VOTE"); + return 0; + } + if (argc > 1 && !irccasecmp(argv[1], "*")) { + hi = user->handle_info; + if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) + { + reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); + return 0; + } + if(target->access < 300) { + reply("CSMSG_NO_ACCESS"); + return 0; + } + char response[MAXLEN]; + sprintf(response, user_find_message(user, "CSMSG_VOTERES_QUESTION"), cData->vote); + irc_privmsg(cmd->parent->bot, channel->name, response); + unsigned int voteid = 0; + dict_iterator_t it; + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + struct vote_option *vOpt = iter_data(it); + voteid++; + sprintf(response, user_find_message(user, "CSMSG_VOTERES_OPTION"), voteid, vOpt->name, vOpt->voted); + irc_privmsg(cmd->parent->bot, channel->name, response); + } + } else { + reply("CSMSG_VOTE_QUESTION",cData->vote); + unsigned int voteid = 0; + dict_iterator_t it; + for (it = dict_first(cData->vote_options); it; it = iter_next(it)) { + struct vote_option *vOpt = iter_data(it); + voteid++; + reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted); + } + } + return 1; +} + static void chanserv_refresh_topics(UNUSED_ARG(void *data)) { @@ -6389,6 +7051,16 @@ handle_new_channel(struct chanNode *channel) SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1); } +void handle_new_channel_created(char *chan, struct userNode *user) { + if(user->handle_info && chanserv_conf.new_channel_authed) { + send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_authed); + } else if(!user->handle_info && chanserv_conf.new_channel_unauthed) { + send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_unauthed); + } + if(chanserv_conf.new_channel_msg) + send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_msg); +} + /* Welcome to my worst nightmare. Warning: Read (or modify) the code below at your own risk. */ static int @@ -6403,13 +7075,23 @@ handle_join(struct modeNode *mNode) struct handle_info *handle; unsigned int modes = 0, info = 0; char *greeting; + unsigned int i = 0; if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info)) return 0; cData = channel->channel_info; - if(channel->members.used > cData->max) + if(channel->members.used > cData->max) { cData->max = channel->members.used; + cData->max_time = now; + } + + for(i = 0; i < channel->invited.used; i++) + { + if(channel->invited.list[i] == user) { + userList_remove(&channel->invited, user); + } + } /* Check for bans. If they're joining through a ban, one of two * cases applies: @@ -6975,6 +7657,8 @@ chanserv_conf_read(void) chanserv_conf.channel_expire_delay = str ? ParseInterval(str) : 86400*30; str = database_get_data(conf_node, KEY_DNR_EXPIRE_FREQ, RECDB_QSTRING); chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600; + str = database_get_data(conf_node, KEY_INVITED_INTERVAL, RECDB_QSTRING); + chanserv_conf.invited_timeout = str ? ParseInterval(str) : 600*2; str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING); chanserv_conf.nodelete_level = str ? atoi(str) : 1; str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING); @@ -7002,12 +7686,18 @@ chanserv_conf_read(void) chanserv_conf.network_helper_epithet = str ? str : "a wannabe tyrant"; str = database_get_data(conf_node, KEY_SUPPORT_HELPER_EPITHET, RECDB_QSTRING); chanserv_conf.support_helper_epithet = str ? str : "a wannabe tyrant"; + str = database_get_data(conf_node, KEY_NEW_CHANNEL_AUTHED, RECDB_QSTRING); + chanserv_conf.new_channel_authed = (str && *str) ? str : NULL; + str = database_get_data(conf_node, KEY_NEW_CHANNEL_UNAUTHED, RECDB_QSTRING); + chanserv_conf.new_channel_unauthed = (str && *str) ? str : NULL; + str = database_get_data(conf_node, KEY_NEW_CHANNEL_MSG, RECDB_QSTRING); + chanserv_conf.new_channel_msg = (str && *str) ? str : NULL; str = database_get_data(conf_node, "default_modes", RECDB_QSTRING); if(!str) str = "+nt"; safestrncpy(mode_line, str, sizeof(mode_line)); ii = split_line(mode_line, 0, ArrayLength(modes), modes); - if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0)) + if((change = mod_chanmode_parse(NULL, NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0)) && (change->argc < 2)) { chanserv_conf.default_modes = *change; @@ -7028,7 +7718,7 @@ chanserv_conf_read(void) /* multiple choice options */ "CtcpReaction", "Protect", "Toys", "TopicRefresh", /* binary options */ - "DynLimit", "NoDelete", + "DynLimit", "NoDelete", "expire", "Vote", /* delimiter */ NULL }; @@ -7123,12 +7813,33 @@ chanserv_note_type_read(const char *key, struct record_data *rd) ntype->max_length = str ? strtoul(str, NULL, 0) : 400; } +static void +vote_option_read_helper(const char *key, struct record_data *rd, struct chanData *chan) +{ + struct vote_option *vOpt; + char *str; + + if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) + { + log_module(CS_LOG, LOG_ERROR, "Invalid vote option in %s.", chan->channel->name); + return; + } + + vOpt = calloc(1, sizeof(*vOpt)); + vOpt->name = strdup(database_get_data(rd->d.object, KEY_VOTE_OPTION_NAME, RECDB_QSTRING)); + str = database_get_data(rd->d.object, KEY_VOTE_OPTION_VOTED, RECDB_QSTRING); + vOpt->voted = str ? atoi(str) : 0; + vOpt->option_id = str ? atoi(key) : 0; + vOpt->option_str = strdup(key); + dict_insert(chan->vote_options,vOpt->option_str,vOpt); +} + static void user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) { struct handle_info *handle; struct userData *uData; - char *seen, *inf, *flags; + char *seen, *inf, *flags, *voted, *votefor; unsigned long last_seen; unsigned short access_level; @@ -7149,6 +7860,8 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING); last_seen = seen ? strtoul(seen, NULL, 0) : now; flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING); + voted = database_get_data(rd->d.object, KEY_VOTE_VOTED, RECDB_QSTRING); + votefor = database_get_data(rd->d.object, KEY_VOTE_VOTEDFOR, RECDB_QSTRING); handle = get_handle_info(key); if(!handle) { @@ -7158,6 +7871,13 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) uData = add_channel_user(chan, handle, access_level, last_seen, inf); uData->flags = flags ? strtoul(flags, NULL, 0) : 0; + if(chan->vote) { + uData->voted = voted ? strtoul(voted, NULL, 0) : 0; + uData->votefor = votefor ? strtoul(votefor, NULL, 0) : 0; + } else { + uData->voted = 0; + uData->votefor = 0; + } } static void @@ -7309,6 +8029,20 @@ chanserv_channel_read(const char *key, struct record_data *hir) cData->chOpts[chOpt] = ((count <= charOptions[chOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[charOptions[chOpt].old_idx]; } + if((str = database_get_data(hir->d.object, KEY_EXPIRE, RECDB_QSTRING))) + { + cData->expiry = atoi(str); + if(cData->expiry > 0) { + if(cData->expiry > now) { + timeq_add(cData->expiry, chanserv_expire_channel, cData); + } else { + timeq_add(1, chanserv_expire_channel, cData); + } + } + } else { + cData->expiry = 0; + } + if((obj = database_get_data(hir->d.object, KEY_SUSPENDED, RECDB_OBJECT))) { suspended = chanserv_read_suspended(obj); @@ -7361,6 +8095,8 @@ chanserv_channel_read(const char *key, struct record_data *hir) cData->ownerTransfer = str ? strtoul(str, NULL, 0) : 0; str = database_get_data(channel, KEY_MAX, RECDB_QSTRING); cData->max = str ? atoi(str) : 0; + str = database_get_data(channel, KEY_MAX_TIME, RECDB_QSTRING); + cData->max_time = str ? atoi(str) : 0; str = database_get_data(channel, KEY_GREETING, RECDB_QSTRING); cData->greeting = str ? strdup(str) : NULL; str = database_get_data(channel, KEY_USER_GREETING, RECDB_QSTRING); @@ -7370,10 +8106,24 @@ chanserv_channel_read(const char *key, struct record_data *hir) str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING); cData->topic = str ? strdup(str) : NULL; + str = database_get_data(channel, KEY_VOTE, RECDB_QSTRING); + if(str) { + cData->vote = str ? strdup(str) : NULL; + dict_delete(cData->vote_options); + cData->vote_options = dict_new(); + dict_set_free_data(cData->vote_options, free_vote_options); + str = database_get_data(channel, KEY_VOTE_START, RECDB_QSTRING); + cData->vote_start = str ? atoi(str) : 0; + obj = database_get_data(channel, KEY_VOTE_OPTIONS, RECDB_OBJECT); + for(it = dict_first(obj); it; it = iter_next(it)) { + vote_option_read_helper(iter_key(it), iter_data(it), cData); + } + } + if(!IsSuspended(cData) && (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING)) && (argc = split_line(str, 0, ArrayLength(argv), argv)) - && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) { + && (modes = mod_chanmode_parse(cNode, NULL, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) { cData->modes = *modes; if(off_channel > 0) cData->modes.modes_set |= MODE_REGISTERED; @@ -7496,6 +8246,10 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData) saxdb_write_int(ctx, KEY_SEEN, uData->seen); if(uData->flags) saxdb_write_int(ctx, KEY_FLAGS, uData->flags); + if(uData->channel->vote && uData->voted) + saxdb_write_int(ctx, KEY_VOTE_VOTED, uData->voted); + if(uData->channel->vote && uData->votefor) + saxdb_write_int(ctx, KEY_VOTE_VOTEDFOR, uData->votefor); if(uData->info) saxdb_write_string(ctx, KEY_INFO, uData->info); saxdb_end_record(ctx); @@ -7551,11 +8305,13 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel) int high_present; enum levelOption lvlOpt; enum charOption chOpt; + dict_iterator_t it; saxdb_start_record(ctx, channel->channel->name, 1); saxdb_write_int(ctx, KEY_REGISTERED, channel->registered); saxdb_write_int(ctx, KEY_MAX, channel->max); + saxdb_write_int(ctx, KEY_MAX_TIME, channel->max_time); if(channel->topic) saxdb_write_string(ctx, KEY_TOPIC, channel->topic); if(channel->registrar) @@ -7568,6 +8324,29 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel) saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask); if(channel->suspended) chanserv_write_suspended(ctx, "suspended", channel->suspended); + if(channel->expiry) + saxdb_write_int(ctx, KEY_EXPIRE, channel->expiry); + + if(channel->vote) { + saxdb_write_string(ctx, KEY_VOTE, channel->vote); + if(channel->vote_start) + saxdb_write_int(ctx, KEY_VOTE_START, channel->vote_start); + if (dict_size(channel->vote_options)) { + saxdb_start_record(ctx, KEY_VOTE_OPTIONS, 1); + for (it = dict_first(channel->vote_options); it; it = iter_next(it)) { + struct vote_option *vOpt = iter_data(it); + char str[50]; + sprintf(str,"%i",vOpt->option_id); + saxdb_start_record(ctx, str, 0); + if(vOpt->voted) + saxdb_write_int(ctx, KEY_VOTE_OPTION_VOTED, vOpt->voted); + if(vOpt->name) + saxdb_write_string(ctx, KEY_VOTE_OPTION_NAME, vOpt->name); + saxdb_end_record(ctx); + } + saxdb_end_record(ctx); + } + } saxdb_start_record(ctx, KEY_OPTIONS, 0); saxdb_write_int(ctx, KEY_FLAGS, channel->flags); @@ -7812,6 +8591,7 @@ init_chanserv(const char *nick) DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", "flags", "+never_csuspend", NULL); DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL); DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL); + DEFINE_COMMAND(invitemeall, 1, MODCMD_REQUIRE_AUTHED, NULL); DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "master", NULL); DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL); DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL); @@ -7858,7 +8638,18 @@ init_chanserv(const char *nick) DEFINE_COMMAND(8ball, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL); DEFINE_COMMAND(d, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL); DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL); - + + DEFINE_COMMAND(addvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(delvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(addoption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(deloption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(vote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(startvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(endvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + DEFINE_COMMAND(voteresults, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL); + + DEFINE_COMMAND(opme, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL); + /* Channel options */ DEFINE_CHANNEL_OPTION(defaulttopic); DEFINE_CHANNEL_OPTION(topicmask); @@ -7875,6 +8666,7 @@ init_chanserv(const char *nick) DEFINE_CHANNEL_OPTION(userinfo); DEFINE_CHANNEL_OPTION(dynlimit); DEFINE_CHANNEL_OPTION(topicsnarf); + DEFINE_CHANNEL_OPTION(vote); DEFINE_CHANNEL_OPTION(nodelete); DEFINE_CHANNEL_OPTION(toys); DEFINE_CHANNEL_OPTION(setters); @@ -7883,6 +8675,7 @@ init_chanserv(const char *nick) DEFINE_CHANNEL_OPTION(ctcpreaction); DEFINE_CHANNEL_OPTION(inviteme); DEFINE_CHANNEL_OPTION(unreviewed); + modcmd_register(chanserv_module, "set expire", chan_opt_expire, 1, 0, "flags", "+helping", NULL); modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL); modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, "flags", "+oper", NULL); if(off_channel > 1) diff --git a/src/chanserv.h b/src/chanserv.h index a84a86c..22fb717 100644 --- a/src/chanserv.h +++ b/src/chanserv.h @@ -45,6 +45,7 @@ enum levelOption { lvlUserInfo, lvlInviteMe, lvlTopicSnarf, + lvlVote, NUM_LEVEL_OPTIONS }; @@ -83,6 +84,7 @@ struct chanData unsigned long visited; unsigned long limitAdjusted; unsigned long ownerTransfer; + unsigned long expiry; char *topic; char *greeting; @@ -90,9 +92,14 @@ struct chanData char *registrar; char *topic_mask; + char *vote; + unsigned int vote_start; + dict_t vote_options; + unsigned int flags : 30; unsigned int may_opchan : 1; unsigned int max; + unsigned int max_time; unsigned int last_refresh; unsigned short banCount; unsigned short userCount; @@ -127,6 +134,9 @@ struct userData unsigned int present : 1; unsigned int flags : USER_FLAGS_SIZE; + unsigned short voted; + unsigned int votefor; + /* linked list of userDatas for a chanData */ struct userData *prev; struct userData *next; @@ -162,6 +172,14 @@ struct suspended struct suspended *previous; }; +struct vote_option +{ + char *name; + unsigned int option_id; + char *option_str; + unsigned int voted; +}; + struct do_not_register { char chan_name[CHANNELLEN+1]; @@ -171,11 +189,16 @@ struct do_not_register char reason[1]; }; +#define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0) +struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended); +struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason); void init_chanserv(const char *nick); void del_channel_user(struct userData *user, int do_gc); struct channelList *chanserv_support_channels(void); unsigned short user_level_from_name(const char *name, unsigned short clamp_level); struct do_not_register *chanserv_is_dnr(const char *chan_name, struct handle_info *handle); int check_user_level(struct chanNode *channel, struct userNode *user, enum levelOption opt, int allow_override, int exempt_owner); +struct mod_chanmode *find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask); +void handle_new_channel_created(char *chan, struct userNode *user); #endif diff --git a/src/hash.c b/src/hash.c index c214cff..a55ad9b 100644 --- a/src/hash.c +++ b/src/hash.c @@ -382,6 +382,7 @@ AddChannel(const char *name, unsigned long time_, const char *modes, char *banli strcpy(cNode->name, name); banList_init(&cNode->banlist); modeList_init(&cNode->members); + userList_init(&cNode->invited); mod_chanmode(NULL, cNode, argv, nn, MCP_FROM_SERVER); dict_insert(channels, cNode->name, cNode); cNode->timestamp = time_; @@ -469,6 +470,7 @@ DelChannel(struct chanNode *channel) modeList_clean(&channel->members); banList_clean(&channel->banlist); + userList_clean(&channel->invited); free(channel); } @@ -663,9 +665,9 @@ ChannelUserKicked(struct userNode* kicker, struct userNode* victim, struct chanN unsigned int n; struct modeNode *mn; - if (!victim || !channel || IsService(victim) || !GetUserMode(channel, victim)) + if (!victim || !channel || !GetUserMode(channel, victim)) return; - + /* Update the kicker's idle time (kicker may be null if it was a server) */ if (kicker && (mn = GetUserMode(channel, kicker))) mn->idle_since = now; diff --git a/src/hash.h b/src/hash.h index 8d3cb21..dd22ad3 100644 --- a/src/hash.h +++ b/src/hash.h @@ -43,24 +43,37 @@ #define MODE_REGISTERED 0x8000 /* Bahamut +r */ #define MODE_APASS 0x10000 /* +A adminpass */ #define MODE_UPASS 0x20000 /* +U userpass */ +#define MODE_NONOTICES 0x40000 /* +N */ +#define MODE_NOAMSGS 0x80000 /* +M */ +#define MODE_ALTCHAN 0x100000 /* +F */ +#define MODE_ACCESS 0x200000 /* +a */ +#define MODE_NOFLOOD 0x400000 /* +f */ +#define MODE_AUDITORIUM 0x800000 /* +u */ +#define MODE_SSLCHAN 0x1000000 /* +S */ #define MODE_REMOVE 0x80000000 -#define FLAGS_OPER 0x0001 /* global operator +o */ -#define FLAGS_INVISIBLE 0x0004 /* invisible +i */ -#define FLAGS_WALLOP 0x0008 /* receives wallops +w */ -#define FLAGS_DEAF 0x0020 /* deaf +d */ -#define FLAGS_SERVICE 0x0040 /* cannot be kicked, killed or deoped +k */ -#define FLAGS_GLOBAL 0x0080 /* receives global messages +g */ -#define FLAGS_NOCHAN 0x0100 /* hide channels in whois +n */ +#define FLAGS_OPER 0x0001 /* +o global operator */ +#define FLAGS_INVISIBLE 0x0004 /* +i invisible */ +#define FLAGS_WALLOP 0x0008 /* +w receives wallops */ +#define FLAGS_DEAF 0x0020 /* +d deaf */ +#define FLAGS_SERVICE 0x0040 /* +k cannot be kicked, killed or deoped */ +#define FLAGS_GLOBAL 0x0080 /* +g receives global messages */ +#define FLAGS_NOCHAN 0x0100 /* +n hide channels in whois */ #define FLAGS_PERSISTENT 0x0200 /* for reserved nicks, this isn't just one-shot */ #define FLAGS_GAGGED 0x0400 /* for gagged users */ #define FLAGS_AWAY 0x0800 /* for away users */ #define FLAGS_STAMPED 0x1000 /* for users who have been stamped */ -#define FLAGS_HIDDEN_HOST 0x2000 /* user's host is masked by their account */ -#define FLAGS_REGNICK 0x4000 /* user owns their current nick */ +#define FLAGS_HIDDEN_HOST 0x2000 /* +x user's host is masked by their account */ +#define FLAGS_REGNICK 0x4000 /* +r user owns their current nick */ #define FLAGS_REGISTERING 0x8000 /* user has issued account register command, is waiting for email cookie */ #define FLAGS_DUMMY 0x10000 /* user is not announced to other servers */ -#define FLAGS_NOIDLE 0x20000 /* hide idle time in whois +I */ +#define FLAGS_NOIDLE 0x20000 /* +I hide idle time in whois */ +#define FLAGS_NETSERV 0x40000 /* +S */ +#define FLAGS_SECURITYSERV 0x80000 /* +D */ +#define FLAGS_XTRAOP 0x100000 /* +X */ +#define FLAGS_HIDDENOPER 0x200000 /* +H */ +#define FLAGS_SERVERNOTICE 0x400000 /* +s */ +#define FLAGS_SEENOIDLE 0x800000 /* +t */ #define IsOper(x) ((x)->modes & FLAGS_OPER) #define IsService(x) ((x)->modes & FLAGS_SERVICE) @@ -78,15 +91,22 @@ #define IsRegistering(x) ((x)->modes & FLAGS_REGISTERING) #define IsDummy(x) ((x)->modes & FLAGS_DUMMY) #define IsNoIdle(x) ((x)->modes & FLAGS_NOIDLE) +#define IsSecurityServ(x) ((x)->modes & FLAGS_SECURITYSERV) +#define IsNetServ(x) ((x)->modes & FLAGS_NETSERV) +#define IsXtraOp(x) ((x)->modes & FLAGS_XTRAOP) +#define IsServerNotice(x) ((x)->modes & FLAGS_SERVERNOTICE) +#define IsHiddenOper(x) ((x)->modes & FLAGS_HIDDENOPER) +#define IsSeeNoIdle(x) ((x)->modes & FLAGS_SEENOIDLE) #define IsFakeHost(x) ((x)->fakehost[0] != '\0') #define IsFakeIdent(x) ((x)->fakeident[0] != '\0') #define IsLocal(x) ((x)->uplink == self) +#define NOFLOODLEN 15 #define NICKLEN 30 #define USERLEN 10 #define HOSTLEN 63 #define REALLEN 50 -#define TOPICLEN 250 +#define TOPICLEN 500 #define CHANNELLEN 200 #define MAXOPLEVEL 999 @@ -131,8 +151,11 @@ struct userNode { struct chanNode { chan_mode_t modes; unsigned int limit; + unsigned int access; unsigned int locks; char key[KEYLEN + 1]; + char altchan[CHANNELLEN + 1]; + char noflood[NOFLOODLEN + 1]; char upass[KEYLEN + 1]; char apass[KEYLEN + 1]; unsigned long timestamp; /* creation time */ @@ -144,6 +167,7 @@ struct chanNode { struct modeList members; struct banList banlist; struct policer join_policer; + struct userList invited; unsigned int join_flooded : 1; unsigned int bad_channel : 1; diff --git a/src/helpfile.c b/src/helpfile.c index de67b2c..aaf0d80 100644 --- a/src/helpfile.c +++ b/src/helpfile.c @@ -23,6 +23,7 @@ #include "log.h" #include "modcmd.h" #include "nickserv.h" +#include "spamserv.h" #if defined(HAVE_DIRENT_H) #include @@ -41,7 +42,7 @@ static const struct message_entry msgtab[] = { #define DEFAULT_LINE_SIZE MAX_LINE_SIZE #define DEFAULT_TABLE_SIZE 80 -extern struct userNode *global, *chanserv, *opserv, *nickserv; +extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv; struct userNode *message_dest; struct userNode *message_source; struct language *lang_C; @@ -529,12 +530,24 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle case 'N': value = nickserv ? nickserv->nick : "NickServ"; break; + case 'X': + value = spamserv ? spamserv->nick : "SpamServ"; + break; case 's': value = self->name; break; - case 'H': + case 'A': value = handle ? handle->handle : "Account"; break; + case 'U': + value = message_dest ? message_dest->nick : "Nick"; + break; + case 'I': + value = message_dest ? (IsFakeIdent(message_dest) ? message_dest->fakeident : message_dest->ident) : "Ident"; + break; + case 'H': + value = message_dest ? (IsFakeHost(message_dest) ? message_dest->fakehost : message_dest->hostname) : "Hostname"; + break; #define SEND_LINE(TRUNCED) do { \ line[pos] = 0; \ if (pos > 0) { \ diff --git a/src/main-common.c b/src/main-common.c index 229088b..85b9dd3 100644 --- a/src/main-common.c +++ b/src/main-common.c @@ -53,6 +53,7 @@ static const struct message_entry msgtab[] = { { "MSG_STUPID_ACCESS_CHANGE", "Please ask someone $belse$b to demote you." }, { "MSG_NO_SEARCH_ACCESS", "You do not have enough access to search based on $b%s$b." }, { "MSG_INVALID_CRITERIA", "$b%s$b is an invalid search criteria." }, + { "MSG_INVALID_FIELD", "$b%s$b is an invalid search field." }, { "MSG_MATCH_COUNT", "Found $b%u$b matches." }, { "MSG_NO_MATCHES", "Nothing matched the criteria of your search." }, { "MSG_TOPIC_UNKNOWN", "No help on that topic." }, @@ -441,6 +442,11 @@ conf_globals(void) if (info && (info[0] == '.')) info = NULL; init_chanserv(info); + + info = conf_get_data("services/spamserv/nick", RECDB_QSTRING); + if (info && (info[0] == '.')) + info = NULL; + init_spamserv(info); god_policer_params = policer_params_new(); if ((dict = conf_get_data("policers/commands-god", RECDB_OBJECT))) { diff --git a/src/main.c b/src/main.c index 6db2482..4b8c714 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,7 @@ #include "global.h" #include "modules.h" #include "opserv.h" +#include "spamserv.h" #ifdef HAVE_GETOPT_H #include diff --git a/src/mod-helpserv.c b/src/mod-helpserv.c index c9eec1b..7f645a9 100644 --- a/src/mod-helpserv.c +++ b/src/mod-helpserv.c @@ -53,6 +53,7 @@ const char *helpserv_module_deps[] = { NULL }; #define KEY_NICK "nick" #define KEY_DB_BADCHANS "badchans" #define KEY_HELP_CHANNEL "help_channel" +#define KEY_PUBLIC_CHANNEL "public_channel" #define KEY_PAGE_DEST "page_dest" #define KEY_CMDWORD "cmdword" #define KEY_PERSIST_LENGTH "persist_length" @@ -85,6 +86,7 @@ const char *helpserv_module_deps[] = { NULL }; #define KEY_PRIVMSG_ONLY "privmsg_only" #define KEY_REQ_ON_JOIN "req_on_join" #define KEY_AUTO_VOICE "auto_voice" +#define KEY_AUTO_JOIN "auto_join" #define KEY_AUTO_DEVOICE "auto_devoice" #define KEY_LAST_ACTIVE "last_active" @@ -170,7 +172,9 @@ static const struct message_entry msgtab[] = { { "HSMSG_SET_PRIVMSGONLY", "$bPrivmsgOnly $b %s" }, { "HSMSG_SET_REQONJOIN", "$bReqOnJoin $b %s" }, { "HSMSG_SET_AUTOVOICE", "$bAutoVoice $b %s" }, + { "HSMSG_SET_AUTOJOIN", "$bAutoJoin $b %s" }, { "HSMSG_SET_AUTODEVOICE", "$bAutoDevoice $b %s" }, + { "HSMSG_SET_PUBLICCHAN", "$bPublicChan $b %s" }, { "HSMSG_PAGE_NOTICE", "notice" }, { "HSMSG_PAGE_PRIVMSG", "privmsg" }, { "HSMSG_PAGE_ONOTICE", "onotice" }, @@ -500,6 +504,7 @@ struct helpserv_bot { struct userNode *helpserv; struct chanNode *helpchan; + struct chanNode *publicchan; struct chanNode *page_targets[PGSRC_COUNT]; enum page_type page_types[PGSRC_COUNT]; @@ -521,6 +526,7 @@ struct helpserv_bot { unsigned int privmsg_only : 1; unsigned int req_on_join : 1; unsigned int auto_voice : 1; + unsigned int auto_join : 1; unsigned int auto_devoice : 1; unsigned int helpchan_empty : 1; @@ -1060,8 +1066,11 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, con helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST"); return; } - if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) { - helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name); + if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (!hs->publicchan || (hs->publicchan && !GetUserMode(hs->publicchan, user)))) { + if(hs->publicchan) + helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->publicchan->name); + else + helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name); return; } @@ -1902,7 +1911,11 @@ static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct user req->helper = GetHSUser(hs, user->handle_info); assert(req->helper); req->assigned = now; - + + if (req->user && hs->auto_join) { + irc_svsjoin(hs->helpserv,req->user,hs->helpchan); + } + if (old_helper) { helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle); req->helper->reassigned_to[0]++; @@ -1941,7 +1954,7 @@ static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct user if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user))) mod_chanmode_announce(hs->helpserv, hs->helpchan, &change); } - + return 1; } @@ -2695,7 +2708,6 @@ static struct helpserv_bot *register_helpserv(const char *nick, const char *help dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist); } helpserv_botlist_append(botlist, hs); - return hs; } @@ -2703,7 +2715,7 @@ static HELPSERV_FUNC(cmd_register) { char *nick, *helpchan, reason[MAXLEN]; struct handle_info *handle; - REQUIRE_PARMS(4); + REQUIRE_PARMS(3); nick = argv[1]; if (!is_valid_nick(nick)) { helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick); @@ -2764,7 +2776,7 @@ static void helpserv_free_bot(void *data) { } static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) { - char reason[MAXLEN], channame[CHANNELLEN], botname[NICKLEN]; + char reason[MAXLEN], channame[CHANNELLEN], *botname; struct helpserv_botlist *botlist; size_t len; @@ -2772,8 +2784,7 @@ static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, helpserv_botlist_remove(botlist, bot); if (!botlist->used) dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name); - len = strlen(bot->helpserv->nick) + 1; - safestrncpy(botname, bot->helpserv->nick, len); + botname=bot->helpserv->nick; len = strlen(bot->helpchan->name) + 1; safestrncpy(channame, bot->helpchan->name, len); snprintf(reason, sizeof(reason), quit_fmt, actor); @@ -3291,23 +3302,72 @@ static HELPSERV_OPTION(opt_auto_voice) { OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE"); } +static HELPSERV_OPTION(opt_auto_join) { + OPTION_BINARY(hs->auto_join, "HSMSG_SET_AUTOJOIN"); +} + static HELPSERV_OPTION(opt_auto_devoice) { OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE"); } +static HELPSERV_OPTION(opt_publicchan) { + char *publicchan; + int changed=0; + if (argc > 0) { + publicchan = argv[0]; + if(strcmp(publicchan, "*")) { + if (!IsChannelName(publicchan)) { + helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan); + HELPSERV_SYNTAX(); + return 0; + } + if (opserv_bad_channel(publicchan)) { + helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan); + return 0; + } + } + if (!hs->publicchan || (hs->publicchan && irccasecmp(hs->publicchan->name, publicchan))) { + if(hs->publicchan) { + //there is another public chan o.O + //part + DelChannelUser(hs->helpserv, hs->publicchan, "unregistered.", 0); + hs->publicchan = NULL; + } + changed = 1; + if(strcmp(publicchan, "*")) { + if (!(hs->publicchan = GetChannel(publicchan))) { + hs->publicchan = AddChannel(publicchan, now, NULL, NULL); + AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP; + } else { + struct mod_chanmode change; + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].mode = MODE_CHANOP; + change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan); + mod_chanmode_announce(hs->helpserv, hs->publicchan, &change); + } + } + } + } else { + changed = 0; + } + helpserv_notice(user, "HSMSG_SET_PUBLICCHAN", (hs->publicchan) ? hs->publicchan->name : user_find_message(user,"MSG_NONE")); \ + return changed; +} + static HELPSERV_FUNC(cmd_set) { helpserv_option_func_t *opt; if (argc < 2) { unsigned int i; helpserv_option_func_t *display[] = { - opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status, + opt_publicchan, opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status, opt_pagetype, opt_alert_page_type, opt_status_page_type, opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed, opt_idle_delay, opt_whine_delay, opt_whine_interval, opt_empty_interval, opt_stale_delay, opt_request_persistence, opt_helper_persistence, opt_notification, opt_id_wrap, - opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, + opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, opt_auto_join, opt_auto_devoice }; @@ -3594,6 +3654,7 @@ helpserv_bot_write(const char *key, void *data, void *extra) { /* Other settings and state */ saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name); + if(hs->publicchan) saxdb_write_string(ctx, KEY_PUBLIC_CHANNEL, hs->publicchan->name); slist = alloc_string_list(PGSRC_COUNT); for (pagesrc=0; pagesrcpage_targets[pagesrc]; @@ -3629,6 +3690,7 @@ helpserv_bot_write(const char *key, void *data, void *extra) { saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only); saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join); saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice); + saxdb_write_int(ctx, KEY_AUTO_JOIN, hs->auto_join); saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice); saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active); @@ -3649,7 +3711,7 @@ helpserv_saxdb_write(struct saxdb_context *ctx) { static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) { struct record_data *br = data, *raw_record; struct helpserv_bot *hs; - char *registrar, *helpchannel_name, *str; + char *registrar, *helpchannel_name, *publicchannel_name, *str; dict_t users, requests; enum page_source pagesrc; enum message_type msgtype; @@ -3670,6 +3732,25 @@ static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra hs = register_helpserv(key, helpchannel_name, registrar); + publicchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_PUBLIC_CHANNEL, RECDB_QSTRING); + if (publicchannel_name) { + if(!IsChannelName(publicchannel_name)) { + log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key); + return 0; + } else { + if (!(hs->publicchan = GetChannel(publicchannel_name))) { + hs->publicchan = AddChannel(publicchannel_name, now, NULL, NULL); + AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP; + } else { + struct mod_chanmode change; + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].mode = MODE_CHANOP; + change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan); + mod_chanmode_announce(hs->helpserv, hs->publicchan, &change); + } + } + } raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL); switch (raw_record ? raw_record->type : RECDB_INVALID) { case RECDB_QSTRING: @@ -3736,6 +3817,8 @@ static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra hs->req_on_join = str ? enabled_string(str) : 0; str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING); hs->auto_voice = str ? enabled_string(str) : 0; + str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_JOIN, RECDB_QSTRING); + hs->auto_join = str ? enabled_string(str) : 0; str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING); hs->auto_devoice = str ? enabled_string(str) : 0; str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING); @@ -3837,6 +3920,8 @@ static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) { struct helpserv_request *req = iter_data(it); if (mn->user != req->user) + continue; + if (GetUserMode(hs->helpchan, mn->user)) //publicchan continue; if (req->text->used) { helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED); @@ -4027,6 +4112,7 @@ static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *u if (!nicknewest || (nicknewest->opened < req->opened)) nicknewest = req; + if (hs->auto_voice && req->helper) { struct mod_chanmode change; @@ -4041,7 +4127,7 @@ static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *u if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) { /* Let the user know. Either the user is forced to be greeted, or the * above has changed which request will get their next message. */ - helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id); + //helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id); } } @@ -4658,7 +4744,9 @@ int helpserv_init() { helpserv_define_option("PRIVMSGONLY", opt_privmsg_only); helpserv_define_option("REQONJOIN", opt_req_on_join); helpserv_define_option("AUTOVOICE", opt_auto_voice); + helpserv_define_option("AUTOJOIN", opt_auto_join); helpserv_define_option("AUTODEVOICE", opt_auto_devoice); + helpserv_define_option("PUBLICCHAN", opt_publicchan); helpserv_usercmd_dict = dict_new(); dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait); diff --git a/src/mod-watchdog.c b/src/mod-watchdog.c new file mode 100644 index 0000000..d9da25d --- /dev/null +++ b/src/mod-watchdog.c @@ -0,0 +1,676 @@ +/* mod-watchdog.c - Watchdog module for srvx + * Copyright 2003-2004 Martijn Smit and srvx Development Team + * + * This file is part of srvx. + * + * srvx 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. + * + * 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 srvx; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +/* Adds new section to srvx.conf: + * "modules" { + * "watchdog" { + * "nick" "Watchdog"; + * "modes" "+iok"; + "ban_duration" "2h"; //only if the channel is registered with chanserv + "gline_duration" "1h"; + "punishment_reason" "Your message contained a forbidden word."; + * }; + * }; + * + */ + +#include "chanserv.h" +#include "opserv.h" +#include "conf.h" +#include "modcmd.h" +#include "saxdb.h" +#include "timeq.h" +#include "gline.h" + +#define KEY_BADWORDS "badwords" +#define KEY_BADWORD_MASK "mask" +#define KEY_BADWORD_TRIGGERED "count" +#define KEY_BADWORD_ACTION "action" +#define KEY_CHANNELS "channel" +#define KEY_BADWORDID "badwordid" + +static const struct message_entry msgtab[] = { + { "WDMSG_REGISTER_SUCCESS", "$b%s$b is now registered with %s." }, + { "WDMSG_NOT_REGISTERED", "$b%s$b is not registered with %s." }, + { "WDMSG_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" }, + { "WDMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." }, + { "WDMSG_BADWORD_NOT_FOUND", "badword with ID %s does not exist." }, + { "WDMSG_BADWORD_REMOVED", "badword ID $b%s$b has been removed (mask: '%s')" }, + { "WDMSG_BADWORD_SET_DONE", "Done." }, + { "WDMSG_BADWORD_SET_INVALID", "Invalid Option for setting %s" }, + { "OSMSG_BADWORD_SETTING_INVALID", "unknown setting $b%s$b." }, + { "WDMSG_BADWORD_SET", "Settings for BadWord entry $b%s$b" }, + { "WDMSG_BADWORD_SET_MASK", "$bMASK$b: %s" }, + { "WDMSG_BADWORD_SET_ACTION", "$bACTION$b: %s" }, + { NULL, NULL } +}; + +struct badword { + char *id; + char *badword_mask; + unsigned int triggered : 29; + unsigned int action : 3; +}; + +struct watchdog_channel { + struct chanNode *channel; + //struct shitList *shitlist; +}; + +/* badword.action fields */ +#define BADACTION_KICK 0 +#define BADACTION_BAN 1 +#define BADACTION_KILL 2 +#define BADACTION_GLINE 3 + +static struct { + const char *nick; + const char *modes; + const char *punishment_reason; + unsigned long ban_duration; + unsigned long gline_duration; +} watchdog_conf; + +const char *watchdog_module_deps[] = { NULL }; +struct userNode *watchdog; +static struct module *watchdog_module; +static struct service *watchdog_service; +static dict_t shitlist; +static dict_t chanlist; +static struct log_type *MS_LOG; +static unsigned int last_badword_id = 0; + +static struct watchdog_channel *add_channel(const char *name); +static struct badword *add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id); +#define watchdog_notice(target, format...) send_message(target , watchdog , ## format) + +static MODCMD_FUNC(cmd_addbad) +{ + dict_iterator_t it; + char *mask = unsplit_string(argv + 1, argc - 1, NULL); + for (it = dict_first(shitlist); it; it = iter_next(it)) { + struct badword *badword = iter_data(it); + if(match_ircglob(mask,badword->badword_mask)) { + reply("WDMSG_ALREADY_ADDED", mask, badword->id); + return 1; + } + } + + struct badword *new_badword = add_badword(mask, 0, BADACTION_KICK, NULL); + for (it = dict_first(shitlist); 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(shitlist, badword->id); + } + } + + reply("WDMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id); + return 1; +} + +static MODCMD_FUNC(cmd_delbad) +{ + unsigned int n; + + for (n=1; nbadword_mask); + dict_remove(shitlist, argv[n]); + } + return 1; +} + +static MODCMD_FUNC(cmd_setbad) +{ + struct badword *badword; + if ((badword = dict_find(shitlist, 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("WDMSG_BADWORD_SET_DONE"); + } + else if(!strcmp("ACTION",setting)) { + if (!strcmp("1",value) || !strcmp("KICK",value)) { + badword->action = BADACTION_KICK; + reply("WDMSG_BADWORD_SET_DONE"); + } else if (!strcmp("2",value) || !strcmp("BAN",value)) { + badword->action = BADACTION_BAN; + reply("WDMSG_BADWORD_SET_DONE"); + } else if (!strcmp("3",value) || !strcmp("KILL",value)) { + badword->action = BADACTION_KILL; + reply("WDMSG_BADWORD_SET_DONE"); + } else if (!strcmp("4",value) || !strcmp("GLINE",value)) { + badword->action = BADACTION_GLINE; + reply("WDMSG_BADWORD_SET_DONE"); + } else { + reply("WDMSG_BADWORD_SET_INVALID", setting); + } + } else { + reply("WDMSG_BADWORD_SETTING_INVALID", setting); + } + + } else { + reply("WDMSG_BADWORD_SET", badword->id); + reply("WDMSG_BADWORD_SET_MASK", badword->badword_mask); + switch(badword->action) { + case BADACTION_KICK: + reply("WDMSG_BADWORD_SET_ACTION", "KICK"); + break; + case BADACTION_BAN: + reply("WDMSG_BADWORD_SET_ACTION", "BAN"); + break; + case BADACTION_KILL: + reply("WDMSG_BADWORD_SET_ACTION", "KILL"); + break; + case BADACTION_GLINE: + reply("WDMSG_BADWORD_SET_ACTION", "GLINE"); + break; + default: + reply("WDMSG_BADWORD_SET_ACTION", "*undef*"); + } + } + } else { + reply("WDMSG_BADWORD_NOT_FOUND", argv[1]); + return 0; + } + return 1; +} + +int +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 MODCMD_FUNC(cmd_listbad) +{ + struct helpfile_table tbl; + unsigned int count = 0, ii = 0; + struct badword **badwords; + + dict_iterator_t it; + for (it = dict_first(shitlist); 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(shitlist); it; it = iter_next(it)) { + struct badword *bw = iter_data(it); + badwords[ii++] = bw; + } + qsort(badwords, count, sizeof(badwords[0]), 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 MODCMD_FUNC(cmd_register) +{ + dict_iterator_t it; + struct modeNode *mn; + + if(channel) + { + + if(channel->bad_channel) + { + reply("CSMSG_ILLEGAL_CHANNEL", channel->name); + return 0; + } + + if(!IsHelping(user) + && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP))) + { + reply("CSMSG_MUST_BE_OPPED", channel->name); + return 0; + } + + } + else + { + + if((argc < 2) || !IsChannelName(argv[1])) + { + reply("MSG_NOT_CHANNEL_NAME"); + return 0; + } + + if(opserv_bad_channel(argv[1])) + { + reply("CSMSG_ILLEGAL_CHANNEL", argv[1]); + return 0; + } + + channel = AddChannel(argv[1], now, NULL, NULL); + } + + for (it = dict_first(chanlist); it; it = iter_next(it)) { + struct watchdog_channel *chan = iter_data(it); + if(chan->channel == channel) { + reply("CSMSG_ALREADY_REGGED", channel->name); + return 0; + } + } + + add_channel(channel->name); + reply("WDMSG_REGISTER_SUCCESS", channel->name, watchdog->nick); + return 1; +} + +static MODCMD_FUNC(cmd_unregister) +{ + struct watchdog_channel *chan = NULL; + dict_iterator_t it; + + for (it = dict_first(chanlist); it; it = iter_next(it)) { + chan = iter_data(it); + if(chan->channel == channel) + break; + } + + if(chan && chan->channel == channel) { + //found, unregister it! + char reason[MAXLEN]; + sprintf(reason, "Unregistered by %s.", user->handle_info->handle); + DelChannelUser(watchdog, channel, reason, 0); + dict_remove(chanlist, channel->name); + reply("CSMSG_UNREG_SUCCESS", channel->name); + return 1; + } else { + reply("WDMSG_NOT_REGISTERED", channel->name, watchdog->nick); + return 0; + } + +} + +static void +watchdog_detected_badword(struct userNode *user, struct chanNode *chan, struct badword *badword) +{ + char *hostmask; + char *reason = watchdog_conf.punishment_reason; + char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' }; + switch(badword->action) { + case BADACTION_BAN: + hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT); + sanitize_ircmask(hostmask); + if(chan->channel_info) { + //registered channel + add_channel_ban(chan->channel_info, hostmask, watchdog->nick, now, now, now + watchdog_conf.ban_duration, reason); + } + struct mod_chanmode change; + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].mode = MODE_BAN; + change.args[0].u.hostmask = hostmask; + mod_chanmode_announce(watchdog, chan, &change); + free(hostmask); + + case BADACTION_KICK: + if(GetUserMode(chan, user)) + KickChannelUser(user, chan, watchdog, reason); + break; + case BADACTION_KILL: + DelUser(user, watchdog, 1, reason); + break; + case BADACTION_GLINE: + irc_ntop(mask + 2, sizeof(mask) - 2, &user->ip); + gline_add(watchdog->nick, mask, watchdog_conf.gline_duration, reason, now, now, 0, 1); + break; + default: + //error? + break; + } +} + +static void +watchdog_channel_message(struct userNode *user, struct chanNode *chan, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice)) +{ + dict_iterator_t it; + + if(!watchdog || !dict_find(chanlist, chan->name, NULL)) + return; + + for (it = dict_first(shitlist); it; it = iter_next(it)) { + struct badword *badword = iter_data(it); + if(match_ircglob(text, badword->badword_mask)) { + watchdog_detected_badword(user, chan, badword); + } + } +} + +static struct badword* +add_badword(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) { + last_badword_id++; + badword->id = strtab(last_badword_id); + } else + badword->id = strdup(id); + badword->badword_mask = strdup(badword_mask); + badword->triggered = triggered; + badword->action = action; + dict_insert(shitlist, badword->id, badword); + return badword; +} + +static void +free_shitlist_entry(void *data) +{ + struct badword *badword = data; + free(badword->id); + free(badword->badword_mask); + free(badword); +} + +static struct watchdog_channel* +add_channel(const char *name) +{ + struct watchdog_channel *wc; + struct mod_chanmode *change; + + if(!watchdog) //module disabled + return NULL; + + wc = calloc(1, sizeof(*wc)); + if (!wc) + return NULL; + + wc->channel = AddChannel(name, now, NULL, NULL); + change = mod_chanmode_alloc(1); + change->argc = 1; + change->args[0].mode = MODE_CHANOP; + change->args[0].u.member = AddChannelUser(watchdog, wc->channel); + mod_chanmode_announce(watchdog, wc->channel, change); + mod_chanmode_free(change); + dict_insert(chanlist, wc->channel->name, wc); + return wc; +} + +static void +free_chanlist_entry(void *data) +{ + struct watchdog_channel *wc = data; + + free(wc); +} + +static void +watchdog_conf_read(void) +{ + dict_t conf_node; + const char *str; + + str = "modules/watchdog"; + if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) { + log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str); + return; + } + + str = database_get_data(conf_node, "nick", RECDB_QSTRING); + if(watchdog_conf.nick && strcmp(watchdog_conf.nick, str)) { + //nick changed + } + watchdog_conf.nick = str; + + str = database_get_data(conf_node, "modes", RECDB_QSTRING); + watchdog_conf.modes = (str ? str : NULL); + + str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING); + watchdog_conf.ban_duration = str ? ParseInterval(str) : ParseInterval("2h"); + + str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING); + watchdog_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h"); + + str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING); + watchdog_conf.punishment_reason = (str ? str : "Your message contained a forbidden word."); + +} + +static int +watchdog_saxdb_read_shitlist(const char *name, void *data, UNUSED_ARG(void *extra)) +{ + struct record_data *rd = data; + char *badword; + char *triggered, *action; + + if (rd->type == RECDB_OBJECT) { + dict_t obj = GET_RECORD_OBJECT(rd); + /* new style structure */ + badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING); + triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING); + action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING); + + add_badword(badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), name); + } + return 0; +} + +static int +watchdog_saxdb_read_chanlist(const char *name, void *data, UNUSED_ARG(void *extra)) +{ + struct record_data *rd = data; + + if (rd->type == RECDB_OBJECT) { + //dict_t obj = GET_RECORD_OBJECT(rd); + /* nothing in here, yet */ + + add_channel(name); + } + return 0; +} + +static int +watchdog_saxdb_read(struct dict *db) +{ + struct dict *object; + char *str; + str = database_get_data(db, KEY_BADWORDID, RECDB_QSTRING); + last_badword_id = str ? strtoul(str, NULL, 0) : 0; + + if ((object = database_get_data(db, KEY_BADWORDS, RECDB_OBJECT))) + dict_foreach(object, watchdog_saxdb_read_shitlist, NULL); + + if ((object = database_get_data(db, KEY_CHANNELS, RECDB_OBJECT))) + dict_foreach(object, watchdog_saxdb_read_chanlist, NULL); + + return 1; +} + +static int +watchdog_saxdb_write(struct saxdb_context *ctx) +{ + dict_iterator_t it; + + saxdb_write_int(ctx, KEY_BADWORDID, last_badword_id); + + if (dict_size(shitlist)) { + saxdb_start_record(ctx, KEY_BADWORDS, 1); + for (it = dict_first(shitlist); it; it = iter_next(it)) { + struct badword *badword = iter_data(it); + if(badword && badword->badword_mask) { + saxdb_start_record(ctx, iter_key(it), 0); + + saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask); + saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered); + saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action); + + saxdb_end_record(ctx); + } + } + saxdb_end_record(ctx); + } + + if (dict_size(chanlist)) { + saxdb_start_record(ctx, KEY_CHANNELS, 1); + for (it = dict_first(chanlist); it; it = iter_next(it)) { + struct watchdog_channel *wc = iter_data(it); + if(wc && wc->channel && wc->channel->name) { + saxdb_start_record(ctx, wc->channel->name, 0); + //anything else? + saxdb_end_record(ctx); + } + } + saxdb_end_record(ctx); + } + + return 0; +} + +static void +watchdog_cleanup(void) +{ + dict_delete(shitlist); + dict_delete(chanlist); +} + +int +watchdog_init(void) +{ + MS_LOG = log_register_type("Watchdog", "file:watchdog.log"); + + /* set up shitlist dict */ + dict_delete(shitlist); + shitlist = dict_new(); + dict_set_free_data(shitlist, free_shitlist_entry); + /* set up chanlist dict */ + dict_delete(chanlist); + chanlist = dict_new(); + dict_set_free_data(chanlist, free_chanlist_entry); + + const char *nick, *modes; + if((nick = conf_get_data("modules/watchdog/nick", RECDB_QSTRING))) { + modes = conf_get_data("modules/watchdog/modes", RECDB_QSTRING); + watchdog = AddLocalUser(nick, nick, NULL, "Watchdog Service", modes); + watchdog_service = service_register(watchdog); + watchdog_service->trigger = ','; + reg_allchanmsg_func(watchdog, watchdog_channel_message); + } + + conf_register_reload(watchdog_conf_read); + reg_exit_func(watchdog_cleanup); + saxdb_register("Watchdog", watchdog_saxdb_read, watchdog_saxdb_write); + + watchdog_module = module_register("Watchdog", MS_LOG, "mod-watchdog.help", NULL); + modcmd_register(watchdog_module, "addbad", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL); + modcmd_register(watchdog_module, "delbad", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL); + modcmd_register(watchdog_module, "setbad", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL); + modcmd_register(watchdog_module, "listbad", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL); + modcmd_register(watchdog_module, "register", cmd_register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL); + modcmd_register(watchdog_module, "unregister", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_CHANNEL, "flags", "+helping", NULL); + message_register_table(msgtab); + + return 1; +} + +int +watchdog_finalize(void) { + dict_t conf_node; + const char *str; + + str = "modules/watchdog"; + if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) { + log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str); + return 0; + } + + str = database_get_data(conf_node, "nick", RECDB_QSTRING); + if (str) watchdog_conf.nick = str; + + str = database_get_data(conf_node, "modes", RECDB_QSTRING); + if (str) watchdog_conf.modes = str; + + str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING); + if (str) watchdog_conf.ban_duration = ParseInterval(str); + + str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING); + if (str) watchdog_conf.gline_duration = ParseInterval(str); + + str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING); + if (str) watchdog_conf.punishment_reason = str; + + return 1; +} diff --git a/src/mod-watchdog.help b/src/mod-watchdog.help new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/mod-watchdog.help @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/modcmd.c b/src/modcmd.c index 9e7d15f..3d57da6 100644 --- a/src/modcmd.c +++ b/src/modcmd.c @@ -1587,7 +1587,7 @@ static MODCMD_FUNC(cmd_stats_services) { service = iter_data(it); tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0])); tbl.contents[ii][0] = service->bot->nick; - tbl.contents[ii][1] = strtab(dict_size(service->commands)); + tbl.contents[ii][1] = (service->commands && dict_size(service->commands) ? strtab(dict_size(service->commands)) : strtab(0)); tbl.contents[ii][2] = service->privileged ? "yes" : "no"; extra[ii*2] = service->trigger; tbl.contents[ii][3] = extra+ii*2; @@ -1919,7 +1919,7 @@ static MODCMD_FUNC(cmd_version) { if (argc > 1) send_message_type(4, user, cmd->parent->bot, "%s", git_version); else - send_message_type(12, user, cmd->parent->bot, "The srvx Development Team includes Paul Chang, Adrian Dewhurst, Miles Peterson, Michael Poole and others.\nThe srvx Development Team can be reached at http://sf.net/projects/srvx/ or in #srvx on irc.gamesurge.net."); + send_message_type(12, user, cmd->parent->bot, "The srvx Development Team includes Paul Chang, Adrian Dewhurst, Miles Peterson, Michael Poole and others.\nThe srvx Development Team can be reached at http://sf.net/projects/srvx/ or in #srvx on irc.gamesurge.net.\nThis version has been modified by pk910 - visit #srvx @ irc.webgamesnet.net."); return 1; } @@ -2418,6 +2418,8 @@ create_default_binds(void) { struct svccmd *svccmd; svccmd = service_make_alias(service, "stats", "*modcmd.joiner", NULL); svccmd->min_opserv_level = 101; + svccmd = service_make_alias(service, "devnull", "*modcmd.joiner", NULL); + svccmd->min_opserv_level = 200; svccmd = service_make_alias(service, "service", "*modcmd.joiner", NULL); svccmd->min_opserv_level = 900; } diff --git a/src/nickserv.c b/src/nickserv.c index f894103..581d9b4 100644 --- a/src/nickserv.c +++ b/src/nickserv.c @@ -75,6 +75,7 @@ #define KEY_OUNREGISTER_INACTIVE "ounregister_inactive" #define KEY_OUNREGISTER_FLAGS "ounregister_flags" #define KEY_HANDLE_TS_MODE "account_timestamp_mode" +#define KEY_MAX_AUTHLOG_LEN "max_authlog_len" #define KEY_ID "id" #define KEY_PASSWD "passwd" @@ -85,6 +86,8 @@ #define KEY_REGISTER_ON "register" #define KEY_LAST_SEEN "lastseen" #define KEY_INFO "info" +#define KEY_DEVNULL "devnull" +#define KEY_WEBSITE "website" #define KEY_USERLIST_STYLE "user_style" #define KEY_SCREEN_WIDTH "screen_width" #define KEY_LAST_AUTHED_HOST "last_authed_host" @@ -109,6 +112,11 @@ #define KEY_NOTE_SETTER "setter" #define KEY_NOTE_NOTE "note" #define KEY_KARMA "karma" +#define KEY_AUTHLOG "authlog" +#define KEY_AUTHLOG_LOGIN_TIME "login_time" +#define KEY_AUTHLOG_LOGOUT_TIME "logout_time" +#define KEY_AUTHLOG_HOSTMASK "hostmask" +#define KEY_AUTHLOG_QUIT_REASON "quit_reason" #define NICKSERV_VALID_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" @@ -212,6 +220,9 @@ static const struct message_entry msgtab[] = { { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", " Cookie: There is currently an allowauth cookie issued for this account" }, { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", " Cookie: There is currently an unknown cookie issued for this account" }, { "NSMSG_HANDLEINFO_INFOLINE", " Infoline: %s" }, + { "NSMSG_HANDLEINFO_DEVNULL", " DevNull Class: %s" }, + { "NSMSG_HANDLEINFO_WEBSITE", " Website: %s" }, + { "NSMSG_HANDLEINFO_ACCESS", " Access: %i" }, { "NSMSG_HANDLEINFO_FLAGS", " Flags: %s" }, { "NSMSG_HANDLEINFO_EPITHET", " Epithet: %s" }, { "NSMSG_HANDLEINFO_FAKEIDENT", " Fake ident: %s" }, @@ -305,6 +316,9 @@ static const struct message_entry msgtab[] = { { "NSMSG_SETTING_LIST", "$b$N account settings:$b" }, { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." }, { "NSMSG_SET_INFO", "$bINFO: $b%s" }, + { "NSMSG_SET_DEVNULL", "$bDEVNULL: $b%s" }, + { "NSMSG_SET_AUTOHIDE", "$bAUTOHIDE: $b%s" }, + { "NSMSG_SET_WEBSITE", "$bWEBSITE: $b%s" }, { "NSMSG_SET_WIDTH", "$bWIDTH: $b%d" }, { "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH: $b%d" }, { "NSMSG_SET_COLOR", "$bCOLOR: $b%s" }, @@ -399,9 +413,18 @@ static struct { unsigned char hard_maxlogins; unsigned long ounregister_inactive; unsigned long ounregister_flags; + unsigned int max_authlog_len; } nickserv_conf; +struct pendingLOCUser { + struct handle_info *handle_info; + unsigned long time; + struct authlogEntry *authlog; + struct pendingLOCUser *next; +}; + const char *titlehost_suffix = NULL; +static struct pendingLOCUser *pendingLOCUsers = NULL; /* We have 2^32 unique account IDs to use. */ unsigned long int highest_id = 0; @@ -463,6 +486,8 @@ register_handle(const char *handle, const char *passwd, unsigned long id) dict_insert(nickserv_handle_dict, hi->handle, hi); hi->id = id; + hi->website = NULL; + hi->devnull = NULL; dict_insert(nickserv_id_dict, strdup(id_base64), hi); return hi; @@ -549,6 +574,8 @@ free_handle_info(void *vhi) free(hi->infoline); free(hi->epithet); free(hi->fakehost); + free(hi->devnull); + free(hi->website); free(hi->fakeident); if (hi->cookie) { timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0); @@ -565,6 +592,26 @@ free_handle_info(void *vhi) if (!hil->used) dict_remove(nickserv_email_dict, hi->email_addr); } + struct authlogEntry *authlog, *next; + for(authlog = hi->authlog; authlog; authlog = next) { + next = authlog->next; + struct pendingLOCUser *pending, *prev_pending = NULL; + for(pending = pendingLOCUsers; pending; pending = pending->next) { + if(pending->authlog == authlog) { + if(prev_pending) + prev_pending->next = pending->next; + else + pendingLOCUsers = pending->next; + free(pending); + break; + } + prev_pending = pending; + } + free((char *) authlog->hostmask); + if(authlog->quit_reason) + free((char *) authlog->quit_reason); + free(authlog); + } free(hi); } @@ -697,9 +744,51 @@ is_registerable_nick(const char *nick) } static int -is_valid_email_addr(const char *email) +is_valid_email_addr(const char *org_email) { - return strchr(email, '@') != NULL; + char email[strlen(org_email)+1]; + strcpy(email, org_email); + //validate email address + //1st check: there need to be one @ + char *p1 = strchr(email, '@'); + if(!p1 || strchr(p1+1, '@')) return 0; + *p1 = '\0'; + //2nd check: username (bevore @) must be at least 1 char long and out of part_chars + char *part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._%+-"; + int i; + if(p1 - email == 0) return 0; + for(i = 0; i < (p1 - email); i++) { + if(!strchr(part_chars, email[i])) return 0; + } + //3rd check: there need to be at least 1 dot in the domain part and all characters out of part_chars + part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"; + char *p2 = NULL; + p1++; + i = 0; + while(*p1) { + if(*p1 == '.') { + if(!i) return 0; + i = 0; + p2 = p1; + } else if(!strchr(part_chars, *p1)) + return 0; + else + i++; + p1++; + } + if(!p2) return 0; + //4th check: TLD must be <= 5 chars, no special chars + i = 0; + p2++; + while(*p2) { + if(!isalpha(*p2)) + return 0; + else + i++; + p2++; + } + if(i > 5) return 0; + return 1; } static const char * @@ -785,7 +874,7 @@ valid_user_for(struct userNode *user, struct handle_info *hi) unsigned int ii; /* If no hostmasks on the account, allow it. */ - if (!hi->masks->used) + if (!hi->masks->used || IsDummy(user)) return 1; /* If any hostmask matches, allow it. */ for (ii=0; iimasks->used; ii++) @@ -891,6 +980,10 @@ generate_fakehost(struct handle_info *handle) /* A leading dot indicates the stored value is actually a title. */ snprintf(buffer, sizeof(buffer), "%s.%s.%s", handle->handle, handle->fakehost+1, titlehost_suffix); return buffer; + } else if (handle->fakehost[0] == '$') { + /* A leading $ indicates the stored value begins with the user handle. */ + snprintf(buffer, sizeof(buffer), "%s%s", handle->handle, handle->fakehost+1); + return buffer; } return handle->fakehost; } @@ -1395,6 +1488,12 @@ static NICKSERV_FUNC(cmd_handleinfo) } reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none)); + if ((oper_has_access(user, cmd->parent->bot, 200, 1)) || IsNetworkHelper(user)) + reply("NSMSG_HANDLEINFO_DEVNULL", (hi->devnull ? hi->devnull : nsmsg_none)); + if (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT)) + reply("NSMSG_HANDLEINFO_WEBSITE", (hi->website ? hi->website : nsmsg_none)); + if(hi->opserv_level > 0 && user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT)) + reply("NSMSG_HANDLEINFO_ACCESS", hi->opserv_level); if (HANDLE_FLAGGED(hi, FROZEN)) reply("NSMSG_HANDLEINFO_VACATION"); @@ -1659,6 +1758,7 @@ static NICKSERV_FUNC(cmd_rename_handle) reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle); global_message(MESSAGE_RECIPIENT_STAFF, msgbuf); free(old_handle); + apply_fakehost(hi, NULL); return 1; } @@ -1680,6 +1780,50 @@ reg_failpw_func(failpw_func_t func) failpw_func_list[failpw_func_used++] = func; } +static struct authlogEntry *authlog_add(struct handle_info *hi, struct userNode *user, const char *mask) { + if(!hi || (!user && !mask)) return NULL; + if(!mask) + mask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST); + struct authlogEntry *authlog, *next, *prev = NULL; + authlog = malloc(sizeof(*authlog)); + authlog->login_time = now; + authlog->logout_time = 0; + authlog->hostmask = mask; + authlog->quit_reason = NULL; + authlog->user = user; + authlog->next = hi->authlog; + hi->authlog = authlog; + unsigned int i = 0; + for(authlog = hi->authlog; authlog; authlog = next) { + i++; + next = authlog->next; + if(i > nickserv_conf.max_authlog_len) { + struct pendingLOCUser *pending, *prev_pending = NULL; + for(pending = pendingLOCUsers; pending; pending = pending->next) { + if(pending->authlog == authlog) { + if(prev_pending) + prev_pending->next = pending->next; + else + pendingLOCUsers = pending->next; + free(pending); + break; + } + prev_pending = pending; + } + free((char *) authlog->hostmask); + if(authlog->quit_reason) + free((char *) authlog->quit_reason); + if(prev) + prev->next = authlog->next; + else + hi->authlog = authlog->next; + free(authlog); + } else + prev = authlog; + } + return hi->authlog; +} + static NICKSERV_FUNC(cmd_auth) { int pw_arg, used, maxlogins; @@ -1779,6 +1923,10 @@ static NICKSERV_FUNC(cmd_auth) return 1; } } + if (HANDLE_FLAGGED(hi, AUTOHIDE)) { + //ok we have a fakehost set... but we need to set mode +x + irc_svsmode(nickserv,user,"+x"); + } set_user_handle_info(user, hi, 1); if (nickserv_conf.email_required && !hi->email_addr) @@ -1793,11 +1941,57 @@ static NICKSERV_FUNC(cmd_auth) if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname)) string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT)); } + authlog_add(hi, user, NULL); argv[pw_arg] = "****"; reply("NSMSG_AUTH_SUCCESS"); return 1; } +struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask) +{ + struct handle_info *hi; + unsigned int match = 0, ii = 0; + hi = dict_find(nickserv_handle_dict, user, NULL); + if(!hi) + return NULL; + /* If no hostmasks on the account, allow it. */ + if (hi->masks->used) { + /* If any hostmask matches, allow it. */ + for (ii=0; iimasks->used; ii++) + if (match_ircglob(hostmask, hi->masks->list[ii]) || match_ircglob(ipmask, hi->masks->list[ii])) { + match = 1; + break; + } + if(!match) + return NULL; + } + if(!checkpass(pass, hi->passwd)) + return NULL; + if (HANDLE_FLAGGED(hi, SUSPENDED)) + return NULL; + char *ptr = malloc(strlen(hostmask)+1); + strcpy(ptr, hostmask); + struct authlogEntry *authlog = authlog_add(hi, NULL, ptr); + struct pendingLOCUser *pending; + if(authlog && (pending = malloc(sizeof(*pending)))) { + pending->handle_info = hi; + pending->time = now; + pending->authlog = authlog; + pending->next = pendingLOCUsers; + pendingLOCUsers = pending; + } + return hi; +} + +char *getfakehost(const char *user) +{ + struct handle_info *hi; + hi = dict_find(nickserv_handle_dict, user, NULL); + if(!hi) + return 0; + return generate_fakehost(hi); +} + static allowauth_func_t *allowauth_func_list; static unsigned int allowauth_func_size = 0, allowauth_func_used = 0; @@ -1816,6 +2010,89 @@ reg_allowauth_func(allowauth_func_t func) allowauth_func_list[allowauth_func_used++] = func; } +static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi); + +static MODCMD_FUNC(cmd_authlog) +{ + return cmd_authlog_func(user, cmd, user->handle_info); +} + +static MODCMD_FUNC(cmd_oauthlog) { + struct handle_info *hi; + + NICKSERV_MIN_PARMS(1); + + if (!(hi = get_victim_oper(user, argv[1]))) + return 0; + + return cmd_authlog_func(user, cmd, hi); +} + +static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi) { + struct helpfile_table tbl; + struct authlogEntry *authlog; + int i = 0; + + for(authlog = hi->authlog; authlog; authlog = authlog->next) { + i++; + } + + tbl.length = i+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] = "Hostmask"; + tbl.contents[0][1] = "Login"; + tbl.contents[0][2] = "Logout"; + tbl.contents[0][3] = "Quit Reason"; + + if(!tbl.length) { + table_send(cmd->parent->bot, user->nick, 0, NULL, tbl); + reply("MSG_NONE"); + free(tbl.contents[0]); + free(tbl.contents); + return 0; + } + + char *str, *ptr; + char intervalBuf[INTERVALLEN]; + i = 0; + for(authlog = hi->authlog; authlog; authlog = authlog->next) { + tbl.contents[++i] = malloc(tbl.width * sizeof(tbl.contents[0][0])); + tbl.contents[i][0] = authlog->hostmask; + str = intervalString(intervalBuf, now - authlog->login_time, hi); + ptr = malloc(strlen(str)+1); + strcpy(ptr, str); + tbl.contents[i][1] = ptr; + if(authlog->logout_time) + str = intervalString(intervalBuf, now - authlog->logout_time, hi); + else if(!authlog->user) + str = "Unknown"; + else { + sprintf(intervalBuf, "Never (%s)", authlog->user->nick); + str = intervalBuf; + } + ptr = malloc(strlen(str)+1); + strcpy(ptr, str); + tbl.contents[i][2] = ptr; + tbl.contents[i][3] = (authlog->quit_reason ? authlog->quit_reason : "-"); + } + + table_send(cmd->parent->bot, user->nick, 0, NULL, tbl); + for(i = 1; i < tbl.length; ++i) + { + free((char *) tbl.contents[i][1]); + free((char *) tbl.contents[i][2]); + free(tbl.contents[i]); + } + free(tbl.contents[0]); + free(tbl.contents); + + return 0; +} + static NICKSERV_FUNC(cmd_allowauth) { struct userNode *target; @@ -2271,7 +2548,7 @@ set_list(struct userNode *user, struct handle_info *hi, int override) unsigned int i; char *set_display[] = { "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE", - "EMAIL", "MAXLOGINS", "LANGUAGE" + "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL" }; send_message(user, nickserv, "NSMSG_SETTING_LIST"); @@ -2346,6 +2623,82 @@ static OPTION_FUNC(opt_info) return 1; } +static OPTION_FUNC(opt_devnull) +{ + const char *devnull; + + if (argc > 1) { + if (!override) { + send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]); + return 0; + } + if ((argv[1][0] == '*') && (argv[1][1] == 0)) { + free(hi->devnull); + hi->devnull = NULL; + } else { + devnull = unsplit_string(argv+1, argc-1, NULL); + if(devnull_check(devnull) == 1) { + if(hi->devnull) + free(hi->devnull); + hi->devnull = strdup(devnull); + } + } + } + + devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE"); + send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull); + return 1; +} + +void nickserv_devnull_delete(char *name) { + dict_iterator_t it; + struct handle_info *hi; + + for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) { + hi = iter_data(it); + if (hi->devnull && !irccasecmp(name, hi->devnull)) { + free(hi->devnull); + hi->devnull = NULL; + } + } +} + +void nickserv_devnull_rename(char *oldname, char *newname) { + dict_iterator_t it; + struct handle_info *hi; + + for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) { + hi = iter_data(it); + if (hi->devnull && !irccasecmp(oldname, hi->devnull)) { + hi->devnull = strdup(newname); + } + } +} + +static OPTION_FUNC(opt_website) +{ + const char *website; + + if (argc > 1) { + if (!HANDLE_FLAGGED(user->handle_info, BOT)) { + send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]); + return 0; + } + if ((argv[1][0] == '*') && (argv[1][1] == 0)) { + free(hi->website); + hi->website = NULL; + } else { + website = unsplit_string(argv+1, argc-1, NULL); + hi->website = strdup(website); + } + } + if (HANDLE_FLAGGED(user->handle_info, BOT)) { + website = hi->website ? hi->website : user_find_message(user, "MSG_NONE"); + send_message(user, nickserv, "NSMSG_SET_WEBSITE", website); + } + return 1; +} + static OPTION_FUNC(opt_width) { if (argc > 1) @@ -2408,6 +2761,23 @@ static OPTION_FUNC(opt_privmsg) return 1; } +static OPTION_FUNC(opt_autohide) +{ + if (argc > 1) { + if (enabled_string(argv[1])) + HANDLE_SET_FLAG(hi, AUTOHIDE); + else if (disabled_string(argv[1])) + HANDLE_CLEAR_FLAG(hi, AUTOHIDE); + else { + send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]); + return 0; + } + } + + send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF")); + return 1; +} + static OPTION_FUNC(opt_style) { char *style; @@ -2443,6 +2813,8 @@ static OPTION_FUNC(opt_password) cryptpass(argv[1], hi->passwd); send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***"); + argv[1] = "****"; + return 1; } @@ -3072,6 +3444,10 @@ nickserv_saxdb_write(struct saxdb_context *ctx) { saxdb_write_int(ctx, KEY_ID, hi->id); if (hi->infoline) saxdb_write_string(ctx, KEY_INFO, hi->infoline); + if (hi->devnull) + saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull); + if (hi->website) + saxdb_write_string(ctx, KEY_WEBSITE, hi->website); if (hi->last_quit_host[0]) saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host); saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen); @@ -3104,6 +3480,21 @@ nickserv_saxdb_write(struct saxdb_context *ctx) { flags[0] = hi->userlist_style; flags[1] = 0; saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags); + if(hi->authlog) { + saxdb_start_record(ctx, KEY_AUTHLOG, 0); + struct authlogEntry *authlog; + int i = 0; + for(authlog = hi->authlog; authlog; authlog = authlog->next) { + saxdb_start_record(ctx, strtab(++i), 0); + saxdb_write_int(ctx, KEY_AUTHLOG_LOGIN_TIME, authlog->login_time); + saxdb_write_int(ctx, KEY_AUTHLOG_LOGOUT_TIME, authlog->logout_time); + saxdb_write_string(ctx, KEY_AUTHLOG_HOSTMASK, authlog->hostmask); + if(authlog->quit_reason) + saxdb_write_string(ctx, KEY_AUTHLOG_QUIT_REASON, authlog->quit_reason); + saxdb_end_record(ctx); + } + saxdb_end_record(ctx); //END KEY_AUTHLOG + } saxdb_end_record(ctx); } return 0; @@ -3249,7 +3640,22 @@ static NICKSERV_FUNC(cmd_merge) return 1; } +#define NICKSERV_DISCRIM_FIELDS_AUTH 0x01 +#define NICKSERV_DISCRIM_FIELDS_EMAIL 0x02 +#define NICKSERV_DISCRIM_FIELDS_SEEN 0x04 +#define NICKSERV_DISCRIM_FIELDS_ACCESS 0x08 +#define NICKSERV_DISCRIM_FIELDS_FAKEHOST 0x10 +#define NICKSERV_DISCRIM_FIELDS_WEBSITE 0x20 +#define NICKSERV_DISCRIM_FIELDS_DEVNULL 0x40 + +#define NICKSERV_DISCRIM_FIELD_COUNT 7 + struct nickserv_discrim { + unsigned int show_fields; + struct helpfile_table *output_table; + int output_table_pos; + unsigned int output_table_free_fields; + unsigned long flags_on, flags_off; unsigned long min_registered, max_registered; unsigned long lastseen; @@ -3261,11 +3667,13 @@ struct nickserv_discrim { const char *hostmask; const char *fakehostmask; const char *fakeidentmask; + const char *website; + const char *devnullclass; const char *handlemask; const char *emailmask; }; -typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi); +typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi, struct nickserv_discrim *discrim); struct discrim_apply_info { struct nickserv_discrim *discrim; @@ -3300,6 +3708,40 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[]) discrim->limit = strtoul(argv[++i], NULL, 0); } else if (!irccasecmp(argv[i], "flags")) { nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off); + } else if (!irccasecmp(argv[i], "fields")) { + char *fields = argv[++i]; + char *delimiter = strstr(fields, ","); + while(1) { + if(delimiter) + *delimiter = '\0'; + if(!irccasecmp(fields, "auth")) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_AUTH; + else if(!irccasecmp(fields, "email")) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_EMAIL; + else if(!irccasecmp(fields, "seen")) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_SEEN; + else if(!irccasecmp(fields, "access")) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_ACCESS; + else if(!irccasecmp(fields, "fakehost")) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_FAKEHOST; + else if(!irccasecmp(fields, "website") && IsBot(user)) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_WEBSITE; + else if(!irccasecmp(fields, "devnull")) + discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_DEVNULL; + else { + send_message(user, nickserv, "MSG_INVALID_FIELD", fields); + goto fail; + } + if(delimiter) { + *delimiter = ','; + fields = delimiter+1; + if(*fields) { + delimiter = strstr(fields, ","); + continue; + } + } + break; + } } else if (!irccasecmp(argv[i], "registered")) { const char *cmp = argv[++i]; if (cmp[0] == '<') { @@ -3366,6 +3808,18 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[]) } else { discrim->fakeidentmask = argv[i]; } + } else if (!irccasecmp(argv[i], "website")) { + if (!irccasecmp(argv[++i], "*")) { + discrim->website = 0; + } else { + discrim->website = argv[i]; + } + } else if (!irccasecmp(argv[i], "devnull")) { + if (!irccasecmp(argv[++i], "*")) { + discrim->devnullclass = 0; + } else { + discrim->devnullclass = argv[i]; + } } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) { if (!irccasecmp(argv[++i], "*")) { discrim->handlemask = 0; @@ -3442,6 +3896,8 @@ nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi) || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask)) || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask))) || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask))) + || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website))) + || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass))) || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask))) || (discrim->min_level > hi->opserv_level) || (discrim->max_level < hi->opserv_level) @@ -3487,7 +3943,7 @@ nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func ds it = next) { next = iter_next(it); if (nickserv_discrim_match(discrim, iter_data(it))) { - dsf(source, iter_data(it)); + dsf(source, iter_data(it), discrim); matched++; } } @@ -3495,18 +3951,51 @@ nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func ds } static void -search_print_func(struct userNode *source, struct handle_info *match) +search_print_func(struct userNode *source, struct handle_info *match, struct nickserv_discrim *discrim) { - send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle); + if(discrim->show_fields) { + //custom fields + if(discrim->output_table) { + discrim->output_table->contents[++discrim->output_table_pos] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0])); + int i = 0; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH) + discrim->output_table->contents[discrim->output_table_pos][i++] = match->handle; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL) + discrim->output_table->contents[discrim->output_table_pos][i++] = (match->email_addr ? match->email_addr : "*"); + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN) { + char *seen; + char seenBuf[INTERVALLEN]; + if(match->users) { + seen = "Here"; + } else if(match->lastseen == 0) { + seen = "Never"; + } else { + seen = intervalString(seenBuf, now - match->lastseen, source->handle_info); + } + discrim->output_table_free_fields |= 1 << i; + discrim->output_table->contents[discrim->output_table_pos][i++] = strdup(seen); + } + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS) + discrim->output_table->contents[discrim->output_table_pos][i++] = strtab(match->opserv_level); + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST) + discrim->output_table->contents[discrim->output_table_pos][i++] = (match->fakehost ? match->fakehost : "*"); + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE) + discrim->output_table->contents[discrim->output_table_pos][i++] = (match->website ? match->website : "*"); + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL) + discrim->output_table->contents[discrim->output_table_pos][i++] = (match->devnull ? match->devnull : "*"); + + } + } else + send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle); } static void -search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match)) +search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match), UNUSED_ARG(struct nickserv_discrim *discrim)) { } static void -search_unregister_func (struct userNode *source, struct handle_info *match) +search_unregister_func (struct userNode *source, struct handle_info *match, UNUSED_ARG(struct nickserv_discrim *discrim)) { if (oper_has_access(source, nickserv, match->opserv_level, 0)) nickserv_unregister_handle(match, source); @@ -3594,12 +4083,58 @@ static NICKSERV_FUNC(cmd_search) discrim->limit = INT_MAX; matches = nickserv_discrim_search(discrim, action, user); - + + if(discrim->show_fields) { + int width = 0; + int ii; + for(ii = 0; ii < NICKSERV_DISCRIM_FIELD_COUNT; ii++) { + if(discrim->show_fields & (1 << ii)) width++; + } + discrim->output_table = malloc(sizeof(discrim->output_table[0])); + discrim->output_table->length = matches+1; + discrim->output_table->width = width; + discrim->output_table->flags = TABLE_NO_FREE; + discrim->output_table->contents = malloc(discrim->output_table->length * sizeof(discrim->output_table->contents[0])); + discrim->output_table->contents[0] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0])); + + ii = 0; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH) + discrim->output_table->contents[0][ii++] = "Auth"; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL) + discrim->output_table->contents[0][ii++] = "EMail"; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN) + discrim->output_table->contents[0][ii++] = "Seen"; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS) + discrim->output_table->contents[0][ii++] = "Access"; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST) + discrim->output_table->contents[0][ii++] = "Fakehost"; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE) + discrim->output_table->contents[0][ii++] = "Website"; + if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL) + discrim->output_table->contents[0][ii++] = "DevNull"; + + nickserv_discrim_search(discrim, action, user); + + table_send(nickserv, user->nick, 0, NULL, *discrim->output_table); + + for(ii = 1; ii < discrim->output_table->length; ++ii) { + int ij; + for(ij = 0; ij < NICKSERV_DISCRIM_FIELD_COUNT; ij++) { + if(discrim->output_table_free_fields & (1 << ij)) + free((char*)discrim->output_table->contents[ii][ij]); + } + free(discrim->output_table->contents[ii]); + } + free(discrim->output_table->contents[0]); + free(discrim->output_table->contents); + free(discrim->output_table); + } if (matches) reply("MSG_MATCH_COUNT", matches); else reply("MSG_NO_MATCHES"); + free(discrim); return 0; } @@ -3638,6 +4173,45 @@ static MODCMD_FUNC(cmd_checkemail) return 1; } +static int +nickserv_db_read_authlog(UNUSED_ARG(const char *key), void *data, void *extra) +{ + struct record_data *rd = data; + struct handle_info *hi = extra; + const char *str; + struct authlogEntry *authlog; + authlog = malloc(sizeof(*authlog)); + + str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGIN_TIME, RECDB_QSTRING); + authlog->login_time = str ? strtoul(str, NULL, 0) : 0; + + str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGOUT_TIME, RECDB_QSTRING); + authlog->logout_time = str ? strtoul(str, NULL, 0) : 0; + + str = database_get_data(rd->d.object, KEY_AUTHLOG_HOSTMASK, RECDB_QSTRING); + authlog->hostmask = str ? strdup(str) : NULL; + + str = database_get_data(rd->d.object, KEY_AUTHLOG_QUIT_REASON, RECDB_QSTRING); + authlog->quit_reason = str ? strdup(str) : NULL; + + authlog->user = NULL; + + authlog->next = NULL; + + //append it to the end of the list... + struct authlogEntry *authlog_entry; + if(!hi->authlog) { + hi->authlog = authlog; + } else { + for(authlog_entry = hi->authlog; authlog_entry; authlog_entry = authlog_entry->next) { + if(!authlog_entry->next) { + authlog_entry->next = authlog; + break; + } + } + } + return 0; +} static void nickserv_db_read_handle(const char *handle, dict_t obj) @@ -3688,6 +4262,12 @@ nickserv_db_read_handle(const char *handle, dict_t obj) str = database_get_data(obj, KEY_INFO, RECDB_QSTRING); if (str) hi->infoline = strdup(str); + str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING); + if (str) + hi->website = strdup(str); + str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING); + if (str) + hi->devnull = strdup(str); str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING); hi->registered = str ? strtoul(str, NULL, 0) : now; str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING); @@ -3813,6 +4393,8 @@ nickserv_db_read_handle(const char *handle, dict_t obj) last_note = note; } } + if ((subdb = database_get_data(obj, KEY_AUTHLOG, RECDB_OBJECT))) + dict_foreach(subdb, nickserv_db_read_authlog, hi); } static int @@ -3991,6 +4573,8 @@ nickserv_conf_read(void) nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2; str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING); nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10; + str = database_get_data(conf_node, KEY_MAX_AUTHLOG_LEN, RECDB_QSTRING); + nickserv_conf.max_authlog_len = str ? strtoul(str, NULL, 0) : 30; str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING); nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28; str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING); @@ -4117,6 +4701,30 @@ nickserv_reclaim_p(void *data) { static void check_user_nick(struct userNode *user) { + //check if this user is a pending LOC user + if(pendingLOCUsers) { + struct pendingLOCUser *pending, *next, *prev = NULL; + for(pending = pendingLOCUsers; pending; pending = next) { + next = pending->next; + if(user->handle_info == pending->handle_info) { + pending->authlog->user = user; + free((char*) pending->authlog->hostmask); + pending->authlog->hostmask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST); + if(prev) + prev->next = next; + else + pendingLOCUsers = next; + free(pending); + } + if(now - pending->time > 10) { + if(prev) + prev->next = next; + else + pendingLOCUsers = next; + free(pending); + } + } + } struct nick_info *ni; user->modes &= ~FLAGS_REGNICK; if (!(ni = get_nick_info(user->nick))) @@ -4177,8 +4785,20 @@ handle_nick_change(struct userNode *user, const char *old_nick) } void -nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why)) +nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), const char *why) { + if(user->handle_info) { + //check if theres an open authlog entry + struct authlogEntry *authlog; + for(authlog = user->handle_info->authlog; authlog; authlog = authlog->next) { + if(authlog->user == user) { + authlog->user = NULL; + authlog->logout_time = now; + authlog->quit_reason = strdup(why); + break; + } + } + } dict_remove(nickserv_allow_auth_dict, user->nick); timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN); set_user_handle_info(user, NULL, 0); @@ -4233,6 +4853,12 @@ nickserv_db_cleanup(void) regfree(&nickserv_conf.valid_handle_regex); if (nickserv_conf.valid_nick_regex_set) regfree(&nickserv_conf.valid_nick_regex); + struct pendingLOCUser *pending, *next; + for(pending = pendingLOCUsers; pending; pending = next) { + next = pending->next; + free(pending); + } + pendingLOCUsers = NULL; } void @@ -4305,6 +4931,8 @@ init_nickserv(const char *nick) nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0); nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0); nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0); + nickserv_define_func("AUTHLOG", cmd_authlog, -1, 1, 0); + nickserv_define_func("OAUTHLOG", cmd_oauthlog, 0, 1, 0); /* other options */ dict_insert(nickserv_opt_dict, "INFO", opt_info); dict_insert(nickserv_opt_dict, "WIDTH", opt_width); @@ -4312,9 +4940,12 @@ init_nickserv(const char *nick) dict_insert(nickserv_opt_dict, "COLOR", opt_color); dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg); dict_insert(nickserv_opt_dict, "STYLE", opt_style); + dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide); dict_insert(nickserv_opt_dict, "PASS", opt_password); dict_insert(nickserv_opt_dict, "PASSWORD", opt_password); dict_insert(nickserv_opt_dict, "FLAGS", opt_flags); + dict_insert(nickserv_opt_dict, "WEBSITE", opt_website); + dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull); dict_insert(nickserv_opt_dict, "ACCESS", opt_level); dict_insert(nickserv_opt_dict, "LEVEL", opt_level); dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet); diff --git a/src/nickserv.h b/src/nickserv.h index 6d6bcd5..ee0db3b 100644 --- a/src/nickserv.h +++ b/src/nickserv.h @@ -38,8 +38,11 @@ struct svccmd; #define HI_FLAG_NODELETE 0x00000080 #define HI_FLAG_NETWORK_HELPER 0x00000100 #define HI_FLAG_BOT 0x00000200 +#define HI_FLAG_AUTOHIDE 0x00000400 +#define HI_FLAG_INVI 0x00000800 + /* Flag characters for the above. First char is LSB, etc. */ -#define HANDLE_FLAGS "SphgscfnHb" +#define HANDLE_FLAGS "SphgscfnHbxI" /* HI_STYLE_* go into handle_info.userlist_style */ #define HI_STYLE_DEF 'd' @@ -59,6 +62,7 @@ struct svccmd; #define IsHelping(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, HELPING)) #define IsStaff(user) (IsOper(user) || IsSupportHelper(user) || IsNetworkHelper(user)) #define IsBot(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT)) +#define IsInvi(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, INVI)) enum cookie_type { ACTIVATION, @@ -84,6 +88,15 @@ struct handle_note { char note[1]; }; +struct authlogEntry { + unsigned long login_time; + unsigned long logout_time; + const char *hostmask; + const char *quit_reason; + struct userNode *user; + struct authlogEntry *next; +}; + struct handle_info { struct nick_info *nicks; struct string_list *masks; @@ -92,6 +105,9 @@ struct handle_info { struct handle_cookie *cookie; struct handle_note *notes; struct language *language; + struct authlogEntry *authlog; + char *website; + char *devnull; char *email_addr; char *epithet; char *infoline; @@ -136,6 +152,10 @@ struct modeNode *find_handle_in_channel(struct chanNode *channel, struct handle_ int nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *add, unsigned long *remove); int oper_has_access(struct userNode *user, struct userNode *bot, unsigned int min_level, unsigned int quiet); void nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd); +struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask); +char *getfakehost(const char *user); +void nickserv_devnull_delete(char *name); +void nickserv_devnull_rename(char *oldname, char *newname); /* auth_funcs are called when a user gets a new handle_info. They are * called *after* user->handle_info has been updated. */ diff --git a/src/nickserv.help b/src/nickserv.help index 81db93d..15a7b96 100644 --- a/src/nickserv.help +++ b/src/nickserv.help @@ -298,6 +298,8 @@ "$bc$b Use mIRC color codes in responses", "$bf$b Account frozen/on vacation (will not be unregistered for inactivity; cleared when account is authenticated against)", "$bn$b No-delete (will never be unregistered for inactivity)", + "$bb$b Bot", + "$bI$b Invisible (user will not be shown in staff list)", "$uSee Also:$u accountinfo, set"); "OADDMASK" ("/msg $N OADDMASK ", "Adds a hostmask to the specified account.", diff --git a/src/opserv.c b/src/opserv.c index 38ad356..b4aa0d9 100644 --- a/src/opserv.c +++ b/src/opserv.c @@ -22,6 +22,7 @@ #include "gline.h" #include "global.h" #include "nickserv.h" +#include "chanserv.h" #include "modcmd.h" #include "opserv.h" #include "timeq.h" @@ -72,10 +73,16 @@ #define KEY_EXPIRES "expires" #define KEY_STAFF_AUTH_CHANNEL "staff_auth_channel" #define KEY_STAFF_AUTH_CHANNEL_MODES "staff_auth_channel_modes" +#define KEY_STAFF_AUTH_FORCE_OPS "staff_auth_force_opers" #define KEY_CLONE_GLINE_DURATION "clone_gline_duration" #define KEY_BLOCK_GLINE_DURATION "block_gline_duration" #define KEY_ISSUER "issuer" #define KEY_ISSUED "issued" +#define KEY_DEVNULL_CLASSES "classes" +#define KEY_DEVNULL_NAME "class" +#define KEY_DEVNULL_MODE "modes" +#define KEY_DEVNULL_MAXCHAN "chanlimit" +#define KEY_DEVNULL_MAXSENDQ "sendq" #define IDENT_FORMAT "%s [%s@%s/%s]" #define IDENT_DATA(user) user->nick, user->ident, user->hostname, irc_ntoa(&user->ip) @@ -257,6 +264,43 @@ static const struct message_entry msgtab[] = { { "OSMSG_CSEARCH_CHANNEL_INFO", "%s [%d users] %s %s" }, { "OSMSG_TRACE_MAX_CHANNELS", "You may not use the 'channel' criterion more than %d times." }, { "OSMSG_FORCEKICK_LOCAL", "You cannot kick $b%s$b forcefully." }, + { "OSMSG_SVSNONICK", "$b%s$b is not a valid nick." }, + { "OSMSG_SVSNICKUSED", "$b%s$b is an already used nickname." }, + { "OSMSG_SVSNICK", "You have renamed $b%s$b to $b%s$b." }, + { "OSMSG_SVSJOIN", "$b%s$b joined $b%s$b." }, + { "OSMSG_SVSMODE", "You have set mode $b%s$b for $b%s$b." }, + { "OSMSG_SIMUL", "You have simuled $b%s$b: %s" }, + { "OSMSG_DEVNULL_USER" , "[%s] %s %s" }, + { "OSMSG_DEVNULL_MATCH" , "%d Users found." }, + { "OSMSG_DEVNULL_CLASS" , "%s is not a valid DevNull class." }, + { "OSMSG_DEVNULL_ADDED", "Added %s to DevNull list (class: %s)" }, + { "OSMSG_DEVNULL_DELETED", "Deleted %s from DevNull list (class: %s)" }, + { "OSMSG_DEVNULL_NOTADDED", "User %s is not listed on DevNull list." }, + { "OSMSG_DEVNULL_ACTION", "Unrecognized trace action $b%s$b" }, + { "OSMSG_DEVNULL_FOUND", "DevNull Class %s is already existing." }, + { "OSMSG_DEVNULL_NOTFOUND", "can't find DevNull class %s." }, + { "OSMSG_DEVNULL_ADDED", "DevNull Class %s added." }, + { "OSMSG_DEVNULL_REMOVED", "DevNull Class %s removed." }, + { "OSMSG_DEVNULL_SET", "Settings for DevNull Class %s" }, + { "OSMSG_DEVNULL_SET_A", "ChanLimit: %s" }, + { "OSMSG_DEVNULL_SET_A_i", "ChanLimit: %i" }, + { "OSMSG_DEVNULL_SET_B", "UnlimitTarget: %s" }, + { "OSMSG_DEVNULL_SET_C", "Flood: %s" }, + { "OSMSG_DEVNULL_SET_E", "ChanHide: %s" }, + { "OSMSG_DEVNULL_SET_F", "IdleHide: %s" }, + { "OSMSG_DEVNULL_SET_G", "ChServMode: %s" }, + { "OSMSG_DEVNULL_SET_H", "XtraOpMode: %s" }, + { "OSMSG_DEVNULL_SET_I", "NetServMode: %s" }, + { "OSMSG_DEVNULL_SET_J", "SeeIdle: %s" }, + { "OSMSG_DEVNULL_SET_K", "ForceIdleHide: %s" }, + { "OSMSG_DEVNULL_SET_L", "OverrideCC: %s" }, + { "OSMSG_DEVNULL_SET_M", "OverrideNoAmsg: %s" }, + { "OSMSG_DEVNULL_SET_N", "MaxSendQ: %s" }, + { "OSMSG_DEVNULL_SET_N_i", "MaxSendQ: %i" }, + { "OSMSG_DEVNULL_SET_OPME", "OpMe: %s" }, + { "OSMSG_DEVNULL_SET_DONE", "Done." }, + { "OSMSG_DEVNULL_RENAMED", "Devnull class %s renamed to %s" }, + { "OSMSG_DEVNULL_SET_INVALID", "Invalid Option for setting %s" }, { NULL, NULL } }; @@ -271,6 +315,7 @@ static dict_t opserv_reserved_nick_dict; /* data is struct userNode* */ static struct string_list *opserv_bad_words; static dict_t opserv_exempt_channels; /* data is not used */ static dict_t opserv_trusted_hosts; /* data is struct trusted_host* */ +static dict_t opserv_devnull_classes; /* data is struct devnull_class* */ static dict_t opserv_hostinfo_dict; /* data is struct opserv_hostinfo* */ static dict_t opserv_user_alerts; /* data is struct opserv_user_alert* */ static dict_t opserv_nick_based_alerts; /* data is struct opserv_user_alert* */ @@ -279,12 +324,15 @@ static dict_t opserv_account_alerts; /* data is struct opserv_user_alert* */ static struct module *opserv_module; static struct log_type *OS_LOG; static unsigned int new_user_flood; +const char *devnull_modes = DEVNULL_MODES; static char *level_strings[1001]; +static char devnull_inverse_modes[256]; static struct { struct chanNode *debug_channel; struct chanNode *alert_channel; struct chanNode *staff_auth_channel; + int staff_auth_force; struct policer_params *join_policer_params; struct policer new_user_policer; unsigned long untrusted_max; @@ -1192,7 +1240,7 @@ static MODCMD_FUNC(cmd_part) static MODCMD_FUNC(cmd_mode) { - if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE)) { + if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE|MCP_OPERMODE)) { reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL)); return 0; } @@ -1291,7 +1339,7 @@ static MODCMD_FUNC(cmd_whois) reply("OSMSG_WHOIS_ACCOUNT", (target->handle_info ? target->handle_info->handle : "Not authenticated")); intervalString(buffer, now - target->timestamp, user->handle_info); reply("OSMSG_WHOIS_NICK_AGE", buffer); - if (target->channels.used <= MAX_CHANNELS_WHOIS) + if (target->channels.used <= MAX_CHANNELS_WHOIS || HANDLE_FLAGGED(user->handle_info, BOT)) opserv_ison(user, target, "OSMSG_WHOIS_CHANNELS"); else reply("OSMSG_WHOIS_HIDECHANS"); @@ -2763,6 +2811,57 @@ trusted_host_read(const char *host, void *data, UNUSED_ARG(void *extra)) return 0; } +static void +opserv_add_devnull_class(const char *name, unsigned long modes, unsigned long maxchan, unsigned long maxsendq) +{ + struct devnull_class *th; + th = calloc(1, sizeof(*th)); + if (!th) + return; + th->name = strdup(name); + th->modes = modes; + th->maxchan = maxchan; + th->maxsendq = maxsendq; + dict_insert(opserv_devnull_classes, th->name, th); +} + +static void +free_devnull_class(void *data) +{ + struct devnull_class *th = data; + free(th->name); + free(th); +} + +static int +devnull_class_read(const char *name, void *data, UNUSED_ARG(void *extra)) +{ + struct record_data *rd = data; + const char *nameb = name, *str; + unsigned long modes, maxchan, maxsendq; + unsigned int ii; + + if (rd->type == RECDB_OBJECT) { + dict_t obj = GET_RECORD_OBJECT(rd); + /* new style structure */ + nameb = database_get_data(obj, KEY_DEVNULL_NAME, RECDB_QSTRING); + str = database_get_data(obj, KEY_DEVNULL_MODE, RECDB_QSTRING); + modes = 0; + if (str) { + for (ii=0; str[ii]; ii++) + modes |= 1 << (devnull_inverse_modes[(unsigned char)str[ii]] - 1); + } + str = database_get_data(obj, KEY_DEVNULL_MAXCHAN, RECDB_QSTRING); + maxchan = str ? ParseInterval(str) : 0; + str = database_get_data(obj, KEY_DEVNULL_MAXSENDQ, RECDB_QSTRING); + maxsendq = str ? ParseInterval(str) : 0; + } else + return 0; + + opserv_add_devnull_class(nameb, modes, maxchan, maxsendq); + return 0; +} + static int opserv_saxdb_read(struct dict *conf_db) { @@ -2807,6 +2906,8 @@ opserv_saxdb_read(struct dict *conf_db) } if ((object = database_get_data(conf_db, KEY_TRUSTED_HOSTS, RECDB_OBJECT))) dict_foreach(object, trusted_host_read, opserv_trusted_hosts); + if ((object = database_get_data(conf_db, KEY_DEVNULL_CLASSES, RECDB_OBJECT))) + dict_foreach(object, devnull_class_read, opserv_devnull_classes); if ((object = database_get_data(conf_db, KEY_GAGS, RECDB_OBJECT))) dict_foreach(object, add_gag_helper, NULL); if ((object = database_get_data(conf_db, KEY_ALERTS, RECDB_OBJECT))) @@ -2864,6 +2965,28 @@ opserv_saxdb_write(struct saxdb_context *ctx) } saxdb_end_record(ctx); } + /* devnull_classes */ + if (dict_size(opserv_devnull_classes)) { + saxdb_start_record(ctx, KEY_DEVNULL_CLASSES, 1); + for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) { + struct devnull_class *th = iter_data(it); + saxdb_start_record(ctx, iter_key(it), 0); + if (th->name) saxdb_write_string(ctx, KEY_DEVNULL_NAME, th->name); + if (th->modes) { + int ii, flen; + char flags[50]; + for (ii=flen=0; devnull_modes[ii]; ++ii) + if (th->modes & (1 << ii)) + flags[flen++] = devnull_modes[ii]; + flags[flen] = 0; + saxdb_write_string(ctx, KEY_DEVNULL_MODE, flags); + } + if (th->maxchan) saxdb_write_int(ctx, KEY_DEVNULL_MAXCHAN, th->maxchan); + if (th->maxsendq) saxdb_write_int(ctx, KEY_DEVNULL_MAXSENDQ, th->maxsendq); + saxdb_end_record(ctx); + } + saxdb_end_record(ctx); + } /* gags */ if (gagList) { struct gag_entry *gag; @@ -3971,6 +4094,8 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han || user->uplink->burst || !user->handle_info) return; + else if (IsBot(user)) + return; else if (user->handle_info->opserv_level) type = "OPER"; else if (IsNetworkHelper(user)) @@ -3981,9 +4106,9 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han return; if (irc_in_addr_is_valid(user->ip)) - send_channel_notice(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle); + send_channel_message(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle); else - send_channel_notice(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle); + send_channel_message(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle); } static void @@ -4135,6 +4260,581 @@ static MODCMD_FUNC(cmd_delalert) return 1; } +static MODCMD_FUNC(cmd_listdevnull) +{ + struct helpfile_table tbl; + unsigned int count = 0, ii = 0; + char *on,*off,*half; + on = "X"; + off = "-"; + half = "1/2"; + + dict_iterator_t it; + for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) { + count++; + } + tbl.length = count+1; + tbl.width = 15; + 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] = "Name"; + tbl.contents[0][1] = "MC"; + tbl.contents[0][2] = "UT"; + tbl.contents[0][3] = "FL"; + tbl.contents[0][4] = "CH"; + tbl.contents[0][5] = "IH"; + tbl.contents[0][6] = "SI"; + tbl.contents[0][7] = "IH²"; + tbl.contents[0][8] = "oC"; + tbl.contents[0][9] = "oM"; + tbl.contents[0][10] = "+k"; + tbl.contents[0][11] = "+S"; + tbl.contents[0][12] = "+X"; + tbl.contents[0][13] = "MaxQ"; + tbl.contents[0][14] = "OpMe"; + if(!count) + { + table_send(cmd->parent->bot, user->nick, 0, NULL, tbl); + reply("MSG_NONE"); + free(tbl.contents[0]); + free(tbl.contents); + return 0; + } + for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) { + struct devnull_class *th = iter_data(it); + tbl.contents[++ii] = malloc(tbl.width * sizeof(tbl.contents[0][0])); + tbl.contents[ii][0] = th->name; + if(DEVNULL_FLAGGED(th, MODE_A)) { + tbl.contents[ii][1] = strtab(th->maxchan); + } else { + tbl.contents[ii][1] = off; + } + if(DEVNULL_FLAGGED(th, MODE_B)) { + tbl.contents[ii][2] = on; + } else { + tbl.contents[ii][2] = off; + } + if(DEVNULL_FLAGGED(th, MODE_D)) { + tbl.contents[ii][3] = on; + } else if(DEVNULL_FLAGGED(th, MODE_C)) { + tbl.contents[ii][3] = half; + } else { + tbl.contents[ii][3] = off; + } + if(DEVNULL_FLAGGED(th, MODE_E)) { + tbl.contents[ii][4] = on; + } else { + tbl.contents[ii][4] = off; + } + if(DEVNULL_FLAGGED(th, MODE_F)) { + tbl.contents[ii][5] = on; + } else { + tbl.contents[ii][5] = off; + } + if(DEVNULL_FLAGGED(th, MODE_J)) { + tbl.contents[ii][6] = on; + } else { + tbl.contents[ii][6] = off; + } + if(DEVNULL_FLAGGED(th, MODE_K)) { + tbl.contents[ii][7] = on; + } else { + tbl.contents[ii][7] = off; + } + if(DEVNULL_FLAGGED(th, MODE_L)) { + tbl.contents[ii][8] = on; + } else { + tbl.contents[ii][8] = off; + } + if(DEVNULL_FLAGGED(th, MODE_M)) { + tbl.contents[ii][9] = on; + } else { + tbl.contents[ii][9] = off; + } + if(DEVNULL_FLAGGED(th, MODE_G)) { + tbl.contents[ii][10] = on; + } else { + tbl.contents[ii][10] = off; + } + if(DEVNULL_FLAGGED(th, MODE_I)) { + tbl.contents[ii][11] = on; + } else { + tbl.contents[ii][11] = off; + } + if(DEVNULL_FLAGGED(th, MODE_H)) { + tbl.contents[ii][12] = on; + } else { + tbl.contents[ii][12] = off; + } + if(DEVNULL_FLAGGED(th, MODE_N)) { + tbl.contents[ii][13] = on; + } else { + tbl.contents[ii][13] = off; + } + if(DEVNULL_FLAGGED(th, MODE_OPME)) { + tbl.contents[ii][14] = on; + } else { + tbl.contents[ii][14] = off; + } + } + 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); + send_message_type(4, user, opserv,"-"); + /* + tbl.contents[0][1] = "MC"; + tbl.contents[0][2] = "UT"; + tbl.contents[0][3] = "FL"; + tbl.contents[0][4] = "CH"; + tbl.contents[0][5] = "IH"; + tbl.contents[0][6] = "SI"; + tbl.contents[0][7] = "IH²"; + tbl.contents[0][8] = "oC"; + tbl.contents[0][9] = "oM"; + tbl.contents[0][10] = "+k"; + tbl.contents[0][11] = "+S"; + tbl.contents[0][12] = "+X"; + tbl.contents[0][13] = "MaxQ"; + */ + send_message_type(4, user, opserv,"MC = MaxChan"); + send_message_type(4, user, opserv,"UT = UnlimitTargets"); + send_message_type(4, user, opserv,"FL = Flood (1 = 1/2 flood, X = unlimited)"); + send_message_type(4, user, opserv,"CH = Channel Hide (mode +n)"); + send_message_type(4, user, opserv,"IH = Idle Hide (mode +I)"); + send_message_type(4, user, opserv,"SI = See Idle"); + send_message_type(4, user, opserv,"IH² = Idle Hide (override SI)"); + send_message_type(4, user, opserv,"oC = Color override (mode +c)"); + send_message_type(4, user, opserv,"oM = AMsg override"); + send_message_type(4, user, opserv,"+k = ChanServ Mode (mode +k)"); + send_message_type(4, user, opserv,"+S = NetServ Mode (mode +S)"); + send_message_type(4, user, opserv,"+X = XtraOp Mode (mode +X)"); + send_message_type(4, user, opserv,"OpMe = ChanServ opme command"); + return 1; +} + +static MODCMD_FUNC(cmd_adddevnull) +{ + + if (dict_find(opserv_devnull_classes, argv[1], NULL)) { + reply("OSMSG_DEVNULL_FOUND", argv[1]); + return 0; + } + + opserv_add_devnull_class(argv[1], 0, 0, 0); + reply("OSMSG_DEVNULL_ADDED",argv[1]); + return 1; +} + + +static MODCMD_FUNC(cmd_deldevnull) +{ + unsigned int n; + + for (n=1; nname); + dict_remove(opserv_devnull_classes, argv[n]); + reply("OSMSG_DEVNULL_REMOVED",argv[n]); + } + return 1; +} + +static MODCMD_FUNC(cmd_renamedevnull) +{ + struct devnull_class *th; + if (dict_find(opserv_devnull_classes, argv[2], NULL)) { + reply("OSMSG_DEVNULL_FOUND", argv[2]); + return 0; + } + if ((th = dict_find(opserv_devnull_classes, argv[1], NULL))) { + opserv_add_devnull_class(argv[2], th->modes, th->maxchan, th->maxsendq); + nickserv_devnull_rename(th->name,argv[2]); + dict_remove(opserv_devnull_classes, argv[1]); + reply("OSMSG_DEVNULL_RENAMED",argv[1],argv[2]); + } else { + reply("OSMSG_DEVNULL_NOTFOUND", argv[1]); + return 0; + } + return 1; +} + +static MODCMD_FUNC(cmd_setdevnull) +{ + struct devnull_class *th; + unsigned int offset=0; + if ((th = dict_find(opserv_devnull_classes, argv[offset+1], NULL))) { + if (argc > 3) { + unsigned int ii; + for( ii = 0; argv[offset+2][ ii ]; ii++) + argv[offset+2][ ii ] = toupper( argv[offset+2][ ii ] ); + for( ii = 0; argv[offset+3][ ii ]; ii++) + argv[offset+3][ ii ] = toupper( argv[offset+3][ ii ] ); + if(!strcmp("CHANLIMIT",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_A); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + DEVNULL_SET_FLAG(th, MODE_A); + th->maxchan = strtoul(argv[offset+3], NULL, 0); + reply("OSMSG_DEVNULL_SET_DONE"); + } + } + else if(!strcmp("UNLIMITTARGET",argv[offset+2]) || !strcmp("UNLIMITEDTARGET",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_B); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_B); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("FLOOD",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_C); + DEVNULL_CLEAR_FLAG(th, MODE_D); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_D); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("HALF",argv[offset+3]) || !strcmp("2",argv[offset+3]) || !strcmp("1/2",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_C); + DEVNULL_CLEAR_FLAG(th, MODE_D); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("CHANHIDE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_E); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_E); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("IDLEHIDE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_F); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_F); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("CHSERVMODE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_G); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_G); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("XTRAOPMODE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_H); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_H); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("NETSERVMODE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_I); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_I); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("SEEIDLE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_J); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_J); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("FORCEIDLEHIDE",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_K); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_K); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("OVERRIDECC",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_L); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_L); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("OVERRIDENOAMSG",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_M); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_M); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } + else if(!strcmp("MAXSENDQ",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_N); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + DEVNULL_SET_FLAG(th, MODE_N); + th->maxsendq = strtoul(argv[offset+3], NULL, 0); + reply("OSMSG_DEVNULL_SET_DONE"); + } + } + else if(!strcmp("OPME",argv[offset+2])) { + if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) { + DEVNULL_CLEAR_FLAG(th, MODE_OPME); + reply("OSMSG_DEVNULL_SET_DONE"); + } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) { + DEVNULL_SET_FLAG(th, MODE_OPME); + reply("OSMSG_DEVNULL_SET_DONE"); + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]); + } + } else { + reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+2]); + } + + } else { + reply("OSMSG_DEVNULL_SET", th->name); + if(DEVNULL_FLAGGED(th, MODE_A)) { + reply("OSMSG_DEVNULL_SET_A_i", th->maxchan); + } else { + reply("OSMSG_DEVNULL_SET_A", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_B)) { + reply("OSMSG_DEVNULL_SET_B", "on"); + } else { + reply("OSMSG_DEVNULL_SET_B", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_D)) { + reply("OSMSG_DEVNULL_SET_C", "on"); + } else if(DEVNULL_FLAGGED(th, MODE_C)) { + reply("OSMSG_DEVNULL_SET_C", "half"); + } else { + reply("OSMSG_DEVNULL_SET_C", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_E)) { + reply("OSMSG_DEVNULL_SET_E", "on"); + } else { + reply("OSMSG_DEVNULL_SET_E", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_F)) { + reply("OSMSG_DEVNULL_SET_F", "on"); + } else { + reply("OSMSG_DEVNULL_SET_F", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_G)) { + reply("OSMSG_DEVNULL_SET_G", "on"); + } else { + reply("OSMSG_DEVNULL_SET_G", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_H)) { + reply("OSMSG_DEVNULL_SET_H", "on"); + } else { + reply("OSMSG_DEVNULL_SET_H", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_I)) { + reply("OSMSG_DEVNULL_SET_I", "on"); + } else { + reply("OSMSG_DEVNULL_SET_I", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_J)) { + reply("OSMSG_DEVNULL_SET_J", "on"); + } else { + reply("OSMSG_DEVNULL_SET_J", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_K)) { + reply("OSMSG_DEVNULL_SET_K", "on"); + } else { + reply("OSMSG_DEVNULL_SET_K", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_L)) { + reply("OSMSG_DEVNULL_SET_L", "on"); + } else { + reply("OSMSG_DEVNULL_SET_L", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_M)) { + reply("OSMSG_DEVNULL_SET_M", "on"); + } else { + reply("OSMSG_DEVNULL_SET_M", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_N)) { + reply("OSMSG_DEVNULL_SET_N_i", th->maxsendq); + } else { + reply("OSMSG_DEVNULL_SET_N", "off"); + } + if(DEVNULL_FLAGGED(th, MODE_OPME)) { + reply("OSMSG_DEVNULL_SET_OPME", "on"); + } else { + reply("OSMSG_DEVNULL_SET_OPME", "off"); + } + } + } else { + reply("OSMSG_DEVNULL_NOTFOUND", argv[offset+1]); + return 0; + } + return 1; +} + +int devnull_check(const char *name) { + if (dict_find(opserv_devnull_classes, name, NULL)) { + return 1; + } + return 0; +} + +struct devnull_class* + devnull_get(const char *name) { + return dict_find(opserv_devnull_classes, name, NULL); +} + +void operpart(struct chanNode *chan, struct userNode *user) +{ + if(opserv_conf.alert_channel && opserv_conf.staff_auth_force > 0 && + !(irccasecmp(chan->name,opserv_conf.alert_channel->name))) { + struct mod_chanmode *change; + change = find_matching_bans(&chan->banlist, user, NULL); //don't join them if they're banned (exceptions from forced join) + if(change) + return; + irc_svsjoin(opserv,user,chan); + } +} + +void operadd(struct userNode *user) +{ + if(opserv_conf.alert_channel && opserv_conf.staff_auth_force > 0) + irc_svsjoin(opserv,user,opserv_conf.alert_channel); +} + +void operdel(struct userNode *user) +{ + if(opserv_conf.alert_channel && opserv_conf.staff_auth_force == 2) + irc_kick(opserv, user, opserv_conf.alert_channel, "mode -o"); +} + +static MODCMD_FUNC(cmd_svsjoin) +{ + struct userNode *target; + if(!(target=GetUserH(argv[1]))) { + reply("OSMSG_SVSNONICK", argv[1]); + return 0; + } + if(!IsChannelName(argv[2])) + { + reply("MSG_NOT_CHANNEL_NAME"); + return 0; + } + irc_svsjoinchan(opserv,target,argv[2]); + reply("OSMSG_SVSJOIN",target->nick,argv[2]); + return 1; +} + +static MODCMD_FUNC(cmd_svsnick) +{ + struct userNode *target; + if(!(target=GetUserH(argv[1]))) { + reply("OSMSG_SVSNONICK", argv[1]); + return 0; + } + if(GetUserH(argv[2])) + { + reply("OSMSG_SVSNICKUSED",argv[2]); + return 0; + } + irc_svsnick(opserv,target,argv[2]); + reply("OSMSG_SVSNICK",target->nick,argv[2]); + return 1; +} + +static MODCMD_FUNC(cmd_svsmode) +{ + struct userNode *target; + char *modestr; + if(!(target=GetUserH(argv[1]))) { + reply("OSMSG_SVSNONICK", argv[1]); + return 0; + } + modestr = unsplit_string(argv + 2, argc - 2, NULL); + irc_svsmode(opserv,target,modestr); + reply("OSMSG_SVSMODE",modestr,target->nick); + return 1; +} + +static MODCMD_FUNC(cmd_simul) +{ + struct userNode *target; + char *line; + if(!(target=GetUserH(argv[1]))) { + reply("OSMSG_SVSNONICK", argv[1]); + return 0; + } + line = unsplit_string(argv + 2, argc - 2, NULL); + irc_simul(target,line); + reply("OSMSG_SIMUL",target->nick,line); + return 1; +} + +static MODCMD_FUNC(cmd_relay) +{ + struct userNode *target; + char *line; + if(!(target=GetUserH(argv[1]))) { + reply("OSMSG_SVSNONICK", argv[1]); + return 0; + } + line = unsplit_string(argv + 2, argc - 2, NULL); + char sendline[512]; + if(channel) + sprintf(sendline, "relay %s %s :%s",user->nick,channel->name,line); + else + sprintf(sendline, "relay %s query :%s",user->nick,line); + irc_privmsg(opserv,target->numeric,sendline); + return 1; +} + static void opserv_conf_read(void) { @@ -4177,8 +4877,11 @@ opserv_conf_read(void) str2 = "+timns"; opserv_conf.staff_auth_channel = AddChannel(str, now, str2, NULL); AddChannelUser(opserv, opserv_conf.staff_auth_channel)->modes |= MODE_CHANOP; + str2 = database_get_data(conf_node, KEY_STAFF_AUTH_FORCE_OPS, RECDB_QSTRING); + opserv_conf.staff_auth_force = str2 ? atoi(str2) : 0; } else { opserv_conf.staff_auth_channel = NULL; + opserv_conf.staff_auth_force = 0; } str = database_get_data(conf_node, KEY_UNTRUSTED_MAX, RECDB_QSTRING); opserv_conf.untrusted_max = str ? strtoul(str, NULL, 0) : 5; @@ -4224,6 +4927,10 @@ opserv_db_init(void) { dict_delete(opserv_trusted_hosts); opserv_trusted_hosts = dict_new(); dict_set_free_data(opserv_trusted_hosts, free_trusted_host); + /* set up opserv_devnull_classes dict */ + dict_delete(opserv_devnull_classes); + opserv_devnull_classes = dict_new(); + dict_set_free_data(opserv_devnull_classes, free_devnull_class); /* set up opserv_chan_warn dict */ dict_delete(opserv_chan_warn); opserv_chan_warn = dict_new(); @@ -4259,6 +4966,7 @@ opserv_db_cleanup(void) free_string_list(opserv_bad_words); dict_delete(opserv_exempt_channels); dict_delete(opserv_trusted_hosts); + dict_delete(opserv_devnull_classes); unreg_del_user_func(opserv_user_cleanup); dict_delete(opserv_hostinfo_dict); dict_delete(opserv_nick_based_alerts); @@ -4283,6 +4991,13 @@ init_opserv(const char *nick) } conf_register_reload(opserv_conf_read); + unsigned int i; + /* set up handle_inverse_flags */ + memset(devnull_inverse_modes, 0, sizeof(devnull_inverse_modes)); + for (i=0; devnull_modes[i]; i++) { + devnull_inverse_modes[(unsigned char)devnull_modes[i]] = i + 1; + } + memset(level_strings, 0, sizeof(level_strings)); opserv_module = module_register("OpServ", OS_LOG, "opserv.help", opserv_help_expand); opserv_define_func("ACCESS", cmd_access, 0, 0, 0); @@ -4361,6 +5076,16 @@ init_opserv(const char *nick) #if defined(WITH_MALLOC_SRVX) || defined(WITH_MALLOC_SLAB) opserv_define_func("STATS MEMORY", cmd_stats_memory, 0, 0, 0); #endif + opserv_define_func("DEVNULL ADD", cmd_adddevnull, 200, 0, 2); + opserv_define_func("DEVNULL DEL", cmd_deldevnull, 200, 0, 2); + opserv_define_func("DEVNULL RENAME", cmd_renamedevnull, 200, 0, 3); + opserv_define_func("DEVNULL SET", cmd_setdevnull, 200, 0, 2); + opserv_define_func("DEVNULL LIST", cmd_listdevnull, 200, 0, 0); + opserv_define_func("SVSJOIN", cmd_svsjoin, 800, 0, 3); + opserv_define_func("SVSMODE", cmd_svsmode, 800, 0, 3); + opserv_define_func("SVSNICK", cmd_svsnick, 800, 0, 3); + opserv_define_func("RELAY", cmd_relay, 800, 0, 0); + opserv_define_func("SIMUL", cmd_simul, 999, 0, 2); opserv_define_func("TRACE", cmd_trace, 100, 0, 3); opserv_define_func("TRACE PRINT", NULL, 0, 0, 0); opserv_define_func("TRACE COUNT", NULL, 0, 0, 0); diff --git a/src/opserv.h b/src/opserv.h index 7daff43..92b35ec 100644 --- a/src/opserv.h +++ b/src/opserv.h @@ -21,8 +21,46 @@ #ifndef _opserv_h #define _opserv_h +/* DEVNULL_MODE_* go into devnull_class.modes */ +#define DEVNULL_MODE_A 0x00000001 +#define DEVNULL_MODE_B 0x00000002 +#define DEVNULL_MODE_C 0x00000004 +#define DEVNULL_MODE_D 0x00000008 +#define DEVNULL_MODE_E 0x00000010 +#define DEVNULL_MODE_F 0x00000020 +#define DEVNULL_MODE_G 0x00000040 +#define DEVNULL_MODE_H 0x00000080 +#define DEVNULL_MODE_I 0x00000100 +#define DEVNULL_MODE_J 0x00000200 +#define DEVNULL_MODE_K 0x00000400 +#define DEVNULL_MODE_L 0x00000800 +#define DEVNULL_MODE_M 0x00001000 +#define DEVNULL_MODE_N 0x00002000 +#define DEVNULL_MODE_OPME 0x00004000 + +#define DEVNULL_MODES "abcdefghijklmno" + +#define DEVNULL_FLAGGED(hi, tok) ((hi)->modes & DEVNULL_##tok) +#define DEVNULL_SET_FLAG(hi, tok) ((hi)->modes |= DEVNULL_##tok) +#define DEVNULL_CLEAR_FLAG(hi, tok) ((hi)->modes &= ~DEVNULL_##tok) + +struct devnull_class { + char *name; + unsigned long modes; + unsigned long maxchan; + unsigned long maxsendq; +}; + void init_opserv(const char *nick); unsigned int gag_create(const char *mask, const char *owner, const char *reason, unsigned long expires); int opserv_bad_channel(const char *name); +void devnull_delete(const char *auth); +void devnull_rename(const char *oldauth, const char *newauth); +int devnull_check(const char *name); +struct devnull_class* devnull_get(const char *name); +struct userNode* GetOpServ(void); +void operpart(struct chanNode *chan, struct userNode *user); +void operadd(struct userNode *user); +void operdel(struct userNode *user); #endif diff --git a/src/proto-bahamut.c b/src/proto-bahamut.c index af7e554..a0c1d98 100644 --- a/src/proto-bahamut.c +++ b/src/proto-bahamut.c @@ -1231,7 +1231,7 @@ void mod_usermode(struct userNode *user, const char *mode_change) { } struct mod_chanmode * -mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel) +mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel) { struct mod_chanmode *change; unsigned int ii, in_arg, ch_arg, add; diff --git a/src/proto-common.c b/src/proto-common.c index 6039d75..bc37f32 100644 --- a/src/proto-common.c +++ b/src/proto-common.c @@ -23,6 +23,9 @@ #include "ioset.h" #include "log.h" #include "nickserv.h" +#include "opserv.h" +#include "chanserv.h" +#include "spamserv.h" #include "timeq.h" #ifdef HAVE_SYS_SOCKET_H #include @@ -447,6 +450,8 @@ privmsg_chan_helper(struct chanNode *cn, void *data) && (offchannel_allowed[(unsigned char)pd->text[0]] || (GetUserMode(cn, cf->service) && !IsDeaf(cf->service)))) cf->func(pd->user, cn, pd->text+1, cf->service, pd->is_notice); + else + spamserv_channel_message(cn, pd->user, pd->text); /* This catches *all* text sent to the channel that the services server sees */ for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) { @@ -478,6 +483,8 @@ part_helper(struct chanNode *cn, void *data) { struct part_desc *desc = data; DelChannelUser(desc->user, cn, desc->text, false); + if (IsOper(desc->user)) + operpart(cn, desc->user); } static CMD_FUNC(cmd_part) @@ -624,6 +631,9 @@ mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra) res->modes_set = orig->modes_set; res->modes_clear = orig->modes_clear; res->new_limit = orig->new_limit; + res->new_access = orig->new_access; + memcpy(res->new_altchan, orig->new_altchan, sizeof(res->new_altchan)); + memcpy(res->new_noflood, orig->new_noflood, sizeof(res->new_noflood)); memcpy(res->new_key, orig->new_key, sizeof(res->new_key)); memcpy(res->new_upass, orig->new_upass, sizeof(res->new_upass)); memcpy(res->new_apass, orig->new_apass, sizeof(res->new_apass)); @@ -643,8 +653,14 @@ mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_ch channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set; if (change->modes_set & MODE_LIMIT) channel->limit = change->new_limit; + if (change->modes_set & MODE_ACCESS) + channel->access = change->new_access; if (change->modes_set & MODE_KEY) strcpy(channel->key, change->new_key); + if (change->modes_set & MODE_ALTCHAN) + strcpy(channel->altchan, change->new_altchan); + if (change->modes_set & MODE_NOFLOOD) + strcpy(channel->noflood, change->new_noflood); if (change->modes_set & MODE_UPASS) strcpy(channel->upass, change->new_upass); if (change->modes_set & MODE_APASS) @@ -721,7 +737,7 @@ mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsig base_oplevel = member->oplevel; else base_oplevel = MAXOPLEVEL; - if (!(change = mod_chanmode_parse(channel, modes, argc, flags, base_oplevel))) + if (!(change = mod_chanmode_parse(channel, who, modes, argc, flags, base_oplevel))) return 0; if (flags & MC_ANNOUNCE) mod_chanmode_announce(who, channel, change); @@ -741,6 +757,9 @@ irc_make_chanmode(struct chanNode *chan, char *out) mod_chanmode_init(&change); change.modes_set = chan->modes; change.new_limit = chan->limit; + change.new_access = chan->access; + safestrncpy(change.new_altchan, chan->altchan, sizeof(change.new_altchan)); + safestrncpy(change.new_noflood, chan->noflood, sizeof(change.new_noflood)); safestrncpy(change.new_key, chan->key, sizeof(change.new_key)); safestrncpy(change.new_upass, chan->upass, sizeof(change.new_upass)); safestrncpy(change.new_apass, chan->apass, sizeof(change.new_apass)); @@ -774,7 +793,12 @@ generate_hostmask(struct userNode *user, int options) } hostname = user->hostname; if (IsFakeHost(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING)) { - hostname = user->fakehost; + if(user->fakehost && user->fakehost[0] == '$') { + hostname = alloca(strlen(user->handle_info->handle) + strlen(user->fakehost)); + sprintf(hostname, "%s%s", user->handle_info->handle, user->fakehost+1); + } else { + hostname = user->fakehost; + } } else if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) { hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2); sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix); diff --git a/src/proto-p10.c b/src/proto-p10.c index 71f6d37..d08fb18 100644 --- a/src/proto-p10.c +++ b/src/proto-p10.c @@ -40,6 +40,7 @@ #define CMD_EOB_ACK "EOB_ACK" #define CMD_ERROR "ERROR" #define CMD_FAKEHOST "FAKE" +#define CMD_FAKEHOST2 "FAKE2" #define CMD_GET "GET" #define CMD_GLINE "GLINE" #define CMD_HASH "HASH" @@ -72,6 +73,7 @@ #define CMD_PROTO "PROTO" #define CMD_QUIT "QUIT" #define CMD_REHASH "REHASH" +#define CMD_RELAY "RELAY" #define CMD_RESET "RESET" #define CMD_RESTART "RESTART" #define CMD_RPING "RPING" @@ -86,6 +88,8 @@ #define CMD_SQUIT "SQUIT" #define CMD_STATS "STATS" #define CMD_SVSNICK "SVSNICK" +#define CMD_SVSMODE "SVSMODE" +#define CMD_SVSJOIN "SVSJOIN" #define CMD_TIME "TIME" #define CMD_TOPIC "TOPIC" #define CMD_TRACE "TRACE" @@ -124,6 +128,7 @@ #define TOK_EOB_ACK "EA" #define TOK_ERROR "Y" #define TOK_FAKEHOST "FA" +#define TOK_FAKEHOST2 "NFH" #define TOK_GET "GET" #define TOK_GLINE "GL" #define TOK_HASH "HASH" @@ -156,6 +161,7 @@ #define TOK_PROTO "PROTO" #define TOK_QUIT "Q" #define TOK_REHASH "REHASH" +#define TOK_RELAY "RL" #define TOK_RESET "RESET" #define TOK_RESTART "RESTART" #define TOK_RPING "RI" @@ -170,6 +176,8 @@ #define TOK_SQUIT "SQ" #define TOK_STATS "R" #define TOK_SVSNICK "SN" +#define TOK_SVSMODE "SM" +#define TOK_SVSJOIN "SJ" #define TOK_TIME "TI" #define TOK_TOPIC "T" #define TOK_TRACE "TR" @@ -219,6 +227,7 @@ #define P10_EOB_ACK TYPE(EOB_ACK) #define P10_ERROR TYPE(ERROR) #define P10_FAKEHOST TYPE(FAKEHOST) +#define P10_FAKEHOST2 TYPE(FAKEHOST2) #define P10_GET TYPE(GET) #define P10_GLINE TYPE(GLINE) #define P10_HASH TYPE(HASH) @@ -251,6 +260,7 @@ #define P10_PROTO TYPE(PROTO) #define P10_QUIT TYPE(QUIT) #define P10_REHASH TYPE(REHASH) +#define P10_RELAY TYPE(RELAY) #define P10_RESET TYPE(RESET) #define P10_RESTART TYPE(RESTART) #define P10_RPING TYPE(RPING) @@ -265,6 +275,8 @@ #define P10_SQUIT TYPE(SQUIT) #define P10_STATS TYPE(STATS) #define P10_SVSNICK TYPE(SVSNICK) +#define P10_SVSMODE TYPE(SVSMODE) +#define P10_SVSJOIN TYPE(SVSJOIN) #define P10_TIME TYPE(TIME) #define P10_TOPIC TYPE(TOPIC) #define P10_TRACE TYPE(TRACE) @@ -299,8 +311,8 @@ static const char *his_servername; static const char *his_servercomment; static struct channelList dead_channels; -/* These correspond to 1 << X: 012345678901234567 */ -const char irc_user_mode_chars[] = "o iw dkgn x I"; +/* These correspond to 1 << X: 012345678901234567890123 */ +const char irc_user_mode_chars[] = "o iw dkgn x ISDXHst"; static struct userNode *AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip); @@ -499,7 +511,25 @@ irc_account(struct userNode *user, const char *stamp, unsigned long timestamp, u void irc_fakehost(struct userNode *user, const char *host, const char *ident, int force) { - putsock("%s " P10_FAKEHOST " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : ""); + /* SRVX added the possibility for FAKE IDENTS + * but this is currently *NOT* supported by our IRCu + * + * edit 24.11.11: + * NFH (P10_FAKEHOST2) is now supported by our IRCu (git-65-21592a4) + */ + putsock("%s " P10_FAKEHOST2 " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : ""); +} + +void +irc_relay(char *message) +{ + putsock("%s " P10_RELAY " %s", self->numeric, message); +} + +void +irc_simul(struct userNode *target, char *command) +{ + putsock("%s " P10_RELAY " %s SI %s :%s", self->numeric, target->numeric, target->numeric, command); } void @@ -600,6 +630,24 @@ irc_privmsg(struct userNode *from, const char *to, const char *message) putsock("%s " P10_PRIVMSG " %s :%s", from->numeric, to, message); } +void +irc_svsmode(struct userNode *from, struct userNode *user, const char *modes) +{ +putsock("%s " P10_SVSMODE " %s %s", from->numeric, user->numeric, modes); +} + +void +irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan) +{ +putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan->name); +} + +void +irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan) +{ +putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan); +} + void irc_eob(void) { @@ -660,12 +708,16 @@ irc_introduce(const char *passwd) void irc_gline(struct server *srv, struct gline *gline) { + // GL [!][+|-|>|<] [] [] [] [:] + //expiration = relative time (seconds) + //lastmod = timestamp + //livetime = timestamp if (gline->lastmod) - putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s", - self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->lastmod, gline->lifetime, gline->reason); + putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s", self->numeric, (srv ? srv->numeric : "*"), + gline->target, gline->expires-now, gline->lastmod, gline->lifetime, gline->reason); else - putsock("%s " P10_GLINE " %s +%s %lu :%s", - self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->reason); + putsock("%s " P10_GLINE " %s +%s %lu :%s", self->numeric, (srv ? srv->numeric : "*"), + gline->target, gline->expires-now, gline->reason); } void @@ -681,6 +733,7 @@ void irc_ungline(const char *mask) { putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now); + //putsock("%s " P10_GLINE " * %s * %lu", self->numeric, mask, now); } /* Return negative if *(struct modeNode**)pa is "less than" pb, @@ -1168,6 +1221,8 @@ create_helper(char *name, void *data) return; } + handle_new_channel_created(name, cd->user); + AddChannelUser(cd->user, AddChannel(name, cd->when, NULL, NULL)); } @@ -1301,7 +1356,7 @@ static CMD_FUNC(cmd_burst) int n_modes; for (pos=argv[next], n_modes = 1; *pos; pos++) if ((*pos == 'k') || (*pos == 'l') || (*pos == 'A') - || (*pos == 'U')) + || (*pos == 'U') || (*pos == 'a') || (*pos == 'F')) n_modes++; if (next + n_modes > argc) n_modes = argc - next; @@ -1556,6 +1611,8 @@ static CMD_FUNC(cmd_kick) { if (argc < 3) return 0; + if (GetUserN(argv[2]) && IsOper(GetUserN(argv[2]))) + operpart(GetChannel(argv[1]), GetUserN(argv[2])); ChannelUserKicked(GetUserH(origin), GetUserN(argv[2]), GetChannel(argv[1])); return 1; } @@ -1678,6 +1735,105 @@ static CMD_FUNC(cmd_time) return 1; } +static CMD_FUNC(cmd_relay) +{ + struct server *sNode; + unsigned int len; + char buf[3]; + // RELAY + len = strlen(argv[1]); + buf[2] = 0; + switch(len) { + case 2: + sNode = GetServerN(argv[1]); + break; + case 5: + buf[0] = argv[1][0]; + buf[1] = argv[1][1]; + sNode = GetServerN(buf); + break; + case 6: + buf[0] = argv[1][1]; + buf[1] = argv[1][2]; + sNode = GetServerN(buf); + break; + default: + /* Invalid destination. Ignore. */ + return 0; + } + if(sNode->numeric == self->numeric) { + //ok someone relayed something to us! + if(strcmp("LQ", argv[2]) == 0) { + //oooh thats exciting - we've got a LOC Query! :D + //LQ !ABADE pk910 80.153.5.212 server.zoelle1.de ~watchcat :test + //ok let's check the login datas + struct handle_info *hi; + char tmp[MAXLEN], tmp2[MAXLEN]; + sprintf(tmp, "%s@%s",argv[7],argv[6]); + sprintf(tmp2, "%s@%s",argv[7],argv[5]); + if((hi = checklogin(argv[4],argv[argc-1],&argv[3][1],tmp,tmp2))) { + //login ok + struct devnull_class *th; + char devnull[512]; + if(hi->devnull && (th = devnull_get(hi->devnull))) { + const char *devnull_modes = DEVNULL_MODES; + int ii, flen; + char flags[50]; + for (ii=flen=0; devnull_modes[ii]; ++ii) + if (th->modes & (1 << ii)) + flags[flen++] = devnull_modes[ii]; + flags[flen] = 0; + sprintf(devnull, "+%s %s %lu %lu",flags,th->name,th->maxchan,th->maxsendq); + } else { + devnull[0] = 0; + } + if(!HANDLE_FLAGGED(hi, AUTOHIDE)) { + sprintf(tmp,"%s LA %s 0 %s\n",argv[3],hi->handle,devnull); + } else if(getfakehost(argv[4])) { + sprintf(tmp,"%s LA %s %s %s\n",argv[3],hi->handle,getfakehost(argv[4]),devnull); + } else { + extern const char *hidden_host_suffix; + sprintf(tmp,"%s LA %s %s.%s %s\n",argv[3],hi->handle,hi->handle,hidden_host_suffix,devnull); + } + irc_relay(tmp); + } else { + //login rejected + sprintf(tmp,"%s LR\n",argv[3]); + irc_relay(tmp); + } + } else if(strcmp("UC", argv[2]) == 0) { + char tmp[MAXLEN]; + sprintf(tmp,"%s UC %s %s",argv[3],argv[3],argv[4]); + irc_relay(tmp); + } else if(strcmp("JA", argv[2]) == 0) { + struct userData *uData; + struct chanNode *cn; + struct userNode *user; + char tmp[MAXLEN]; + cn = GetChannel(argv[4]); + if (!cn) return 0; + if (!(user = GetUserN(argv[3]))) return 0; + if(!cn->channel_info) { + //channel not registered + sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]); + } else if((uData = GetChannelUser(cn->channel_info, user->handle_info))) { + if(uData->access >= atoi(argv[5])) { + //we can join + sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]); + } else { + //access too low + sprintf(tmp,"%s JAR %s %i %i\n",argv[3],cn->name,uData->access,uData->access); + } + } else { + //0 access + sprintf(tmp,"%s JAR %s %s %s\n",argv[3],cn->name,"0","0"); + } + irc_relay(tmp); + } + } + return 1; +} + static CMD_FUNC(cmd_xquery) { struct server *source; @@ -1815,6 +1971,8 @@ init_parse(void) dict_insert(irc_func_dict, TOK_STATS, cmd_stats); dict_insert(irc_func_dict, CMD_SVSNICK, cmd_svsnick); dict_insert(irc_func_dict, TOK_SVSNICK, cmd_svsnick); + dict_insert(irc_func_dict, CMD_RELAY, cmd_relay); + dict_insert(irc_func_dict, TOK_RELAY, cmd_relay); dict_insert(irc_func_dict, CMD_WHOIS, cmd_whois); dict_insert(irc_func_dict, TOK_WHOIS, cmd_whois); dict_insert(irc_func_dict, CMD_GLINE, cmd_gline); @@ -2322,8 +2480,10 @@ void mod_usermode(struct userNode *user, const char *mode_change) { case 'o': do_user_mode(FLAGS_OPER); if (!add) { + operdel(user); userList_remove(&curr_opers, user); } else if (!userList_contains(&curr_opers, user)) { + operadd(user); userList_append(&curr_opers, user); call_oper_funcs(user); } @@ -2340,6 +2500,12 @@ void mod_usermode(struct userNode *user, const char *mode_change) { case 'g': do_user_mode(FLAGS_GLOBAL); break; case 'n': do_user_mode(FLAGS_NOCHAN); break; case 'I': do_user_mode(FLAGS_NOIDLE); break; + case 'S': do_user_mode(FLAGS_NETSERV); break; + case 'D': do_user_mode(FLAGS_SECURITYSERV); break; + case 'X': do_user_mode(FLAGS_XTRAOP); break; + case 's': do_user_mode(FLAGS_SERVERNOTICE); break; + case 'H': do_user_mode(FLAGS_HIDDENOPER); break; + case 't': do_user_mode(FLAGS_SEENOIDLE); break; case 'x': do_user_mode(FLAGS_HIDDEN_HOST); break; case 'r': if (*word) { @@ -2415,7 +2581,7 @@ keyncpy(char output[], char input[], size_t output_size) } struct mod_chanmode * -mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel) +mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel) { struct mod_chanmode *change; unsigned int ii, in_arg, ch_arg, add; @@ -2439,6 +2605,10 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un case 'C': do_chan_mode(MODE_NOCTCPS); break; case 'D': do_chan_mode(MODE_DELAYJOINS); break; case 'c': do_chan_mode(MODE_NOCOLORS); break; + case 'M': do_chan_mode(MODE_NOAMSGS); break; + case 'N': do_chan_mode(MODE_NONOTICES); break; + case 'u': do_chan_mode(MODE_AUDITORIUM); break; + case 'S': do_chan_mode(MODE_SSLCHAN); break; case 'i': do_chan_mode(MODE_INVITEONLY); break; case 'm': do_chan_mode(MODE_MODERATED); break; case 'n': do_chan_mode(MODE_NOPRIVMSGS); break; @@ -2447,8 +2617,14 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un case 's': do_chan_mode(MODE_SECRET); break; case 't': do_chan_mode(MODE_TOPICLIMIT); break; case 'z': - if (!(flags & MCP_REGISTERED)) - do_chan_mode(MODE_REGISTERED); + if (!(flags & MCP_REGISTERED) && (!(flags & MCP_IGN_REGISTERED) || add)) { + do_chan_mode(MODE_REGISTERED); + } else if (flags & MCP_IGN_REGISTERED) { + /* ignore the modechange but continue parsing */ + } else { + mod_chanmode_free(change); + return NULL; + } break; #undef do_chan_mode case 'l': @@ -2462,6 +2638,17 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un change->modes_clear |= MODE_LIMIT; } break; + case 'a': + if (add) { + if (in_arg >= argc) + goto error; + change->modes_set |= MODE_ACCESS; + change->new_access = atoi(modes[in_arg++]); + } else { + change->modes_set &= ~MODE_ACCESS; + change->modes_clear |= MODE_ACCESS; + } + break; case 'k': if (add) { if ((in_arg >= argc) @@ -2477,6 +2664,70 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un } } break; + case 'f': + if (add) { + if (in_arg >= argc) + goto error; + char *mode = modes[in_arg++]; + if(mode[0] == '!' && !(flags & MCP_OPERMODE)) //noflood flag also for overriders + goto error;//only allow opers + else if(mode[0] == '!') + mode++; + + if(mode[0] == '+' || mode[0] == '@') { + mode++; + } + char *p; + int count = 0, time = 0; + for(p = mode; p[0]; p++) { + if(p[0] == ':') { + char tmpchar = p[0]; + p[0] = '\0'; + count = strtoul(mode,0,10); + p[0] = tmpchar; + p++; + time = strtoul(p,0,10); + break; + } + } + if(count <= 0 || time <= 0 || count > 100 || time > 600) + goto error; + change->modes_set |= MODE_NOFLOOD; + safestrncpy(change->new_noflood, modes[in_arg - 1], sizeof(change->new_noflood)); + } else { + change->modes_clear |= MODE_NOFLOOD; + } + break; + case 'F': + if (add) { + if (in_arg >= argc) + goto error; + char *altchan = modes[in_arg++]; + struct chanNode *target; + if(!IsChannelName(altchan) || !(target = GetChannel(altchan))) + goto error; + if(!(flags & MCP_OPERMODE)) { + //check if the user has the permissions to use this channel as target + struct modeNode *mn; + struct userData *uData; + struct chanData *cData; + if(user && (mn = GetUserMode(target, user)) && (mn->modes & MODE_CHANOP)) { + //allow - user is opped on target channel + } else if(user && user->handle_info && + (uData = GetChannelUser(channel->channel_info, user->handle_info)) && + (cData = uData->channel) && + uData->access >= cData->lvlOpts[lvlGiveOps] + ) { + //allow - user has access to get op on the channel + } else + goto error; + } + change->modes_set |= MODE_ALTCHAN; + safestrncpy(change->new_altchan, altchan, sizeof(change->new_altchan)); + } else { + change->modes_clear |= MODE_ALTCHAN; + } + break; case 'U': if (flags & MCP_NO_APASS) goto error; @@ -2657,10 +2908,17 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod DO_MODE_CHAR(INVITEONLY, 'i'); DO_MODE_CHAR(NOPRIVMSGS, 'n'); DO_MODE_CHAR(LIMIT, 'l'); + DO_MODE_CHAR(ACCESS, 'a'); + DO_MODE_CHAR(ALTCHAN, 'F'); + DO_MODE_CHAR(NOFLOOD, 'f'); DO_MODE_CHAR(DELAYJOINS, 'D'); DO_MODE_CHAR(REGONLY, 'r'); DO_MODE_CHAR(NOCOLORS, 'c'); DO_MODE_CHAR(NOCTCPS, 'C'); + DO_MODE_CHAR(NONOTICES, 'N'); + DO_MODE_CHAR(NOAMSGS, 'M'); + DO_MODE_CHAR(AUDITORIUM, 'u'); + DO_MODE_CHAR(SSLCHAN, 'S'); DO_MODE_CHAR(REGISTERED, 'z'); #undef DO_MODE_CHAR if (change->modes_clear & channel->modes & MODE_KEY) @@ -2704,6 +2962,10 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod DO_MODE_CHAR(REGONLY, 'r'); DO_MODE_CHAR(NOCOLORS, 'c'); DO_MODE_CHAR(NOCTCPS, 'C'); + DO_MODE_CHAR(NONOTICES, 'N'); + DO_MODE_CHAR(NOAMSGS, 'M'); + DO_MODE_CHAR(AUDITORIUM, 'u'); + DO_MODE_CHAR(SSLCHAN, 'S'); DO_MODE_CHAR(REGISTERED, 'z'); #undef DO_MODE_CHAR if(change->modes_set & MODE_KEY) @@ -2716,6 +2978,14 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod sprintf(int_buff, "%d", change->new_limit); mod_chanmode_append(&chbuf, 'l', int_buff); } + if(change->modes_set & MODE_ACCESS) { + sprintf(int_buff, "%d", change->new_access); + mod_chanmode_append(&chbuf, 'a', int_buff); + } + if (change->modes_set & MODE_ALTCHAN) + mod_chanmode_append(&chbuf, 'F', change->new_altchan); + if (change->modes_set & MODE_NOFLOOD) + mod_chanmode_append(&chbuf, 'f', change->new_noflood); } for (arg = 0; arg < change->argc; ++arg) { if (change->args[arg].mode & MODE_REMOVE) @@ -2762,6 +3032,9 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff) DO_MODE_CHAR(INVITEONLY, 'i'); DO_MODE_CHAR(NOPRIVMSGS, 'n'); DO_MODE_CHAR(LIMIT, 'l'); + DO_MODE_CHAR(ACCESS, 'a'); + DO_MODE_CHAR(ALTCHAN, 'F'); + DO_MODE_CHAR(NOFLOOD, 'f'); DO_MODE_CHAR(KEY, 'k'); DO_MODE_CHAR(UPASS, 'U'); DO_MODE_CHAR(APASS, 'A'); @@ -2769,6 +3042,10 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff) DO_MODE_CHAR(REGONLY, 'r'); DO_MODE_CHAR(NOCOLORS, 'c'); DO_MODE_CHAR(NOCTCPS, 'C'); + DO_MODE_CHAR(NONOTICES, 'N'); + DO_MODE_CHAR(NOAMSGS, 'M'); + DO_MODE_CHAR(AUDITORIUM, 'u'); + DO_MODE_CHAR(SSLCHAN, 'S'); DO_MODE_CHAR(REGISTERED, 'z'); #undef DO_MODE_CHAR } @@ -2785,9 +3062,16 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff) DO_MODE_CHAR(REGONLY, 'r'); DO_MODE_CHAR(NOCOLORS, 'c'); DO_MODE_CHAR(NOCTCPS, 'C'); + DO_MODE_CHAR(NONOTICES, 'N'); + DO_MODE_CHAR(NOAMSGS, 'M'); + DO_MODE_CHAR(AUDITORIUM, 'u'); + DO_MODE_CHAR(SSLCHAN, 'S'); DO_MODE_CHAR(REGISTERED, 'z'); DO_MODE_CHAR(LIMIT, 'l'), args_used += sprintf(args + args_used, " %d", change->new_limit); DO_MODE_CHAR(KEY, 'k'), args_used += sprintf(args + args_used, " %s", change->new_key); + DO_MODE_CHAR(ACCESS, 'a'), args_used += sprintf(args + args_used, " %d", change->new_access); + DO_MODE_CHAR(ALTCHAN, 'F'), args_used += sprintf(args + args_used, " %s", change->new_altchan); + DO_MODE_CHAR(NOFLOOD, 'f'), args_used += sprintf(args + args_used, " %s", change->new_noflood); DO_MODE_CHAR(UPASS, 'U'), args_used += sprintf(args + args_used, " %s", change->new_upass); DO_MODE_CHAR(APASS, 'A'), args_used += sprintf(args + args_used, " %s", change->new_apass); #undef DO_MODE_CHAR @@ -2812,6 +3096,14 @@ clear_chanmode(struct chanNode *channel, const char *modes) case 't': cleared |= MODE_TOPICLIMIT; break; case 'i': cleared |= MODE_INVITEONLY; break; case 'n': cleared |= MODE_NOPRIVMSGS; break; + case 'F': + cleared |= MODE_ALTCHAN; + channel->altchan[0] = '\0'; + break; + case 'f': + cleared |= MODE_NOFLOOD; + channel->noflood[0] = '\0'; + break; case 'k': cleared |= MODE_KEY; channel->key[0] = '\0'; @@ -2828,11 +3120,19 @@ clear_chanmode(struct chanNode *channel, const char *modes) cleared |= MODE_LIMIT; channel->limit = 0; break; + case 'a': + cleared |= MODE_ACCESS; + channel->access = 0; + break; case 'b': cleared |= MODE_BAN; break; case 'D': cleared |= MODE_DELAYJOINS; break; case 'r': cleared |= MODE_REGONLY; break; case 'c': cleared |= MODE_NOCOLORS; break; case 'C': cleared |= MODE_NOCTCPS; break; + case 'M': cleared |= MODE_NOAMSGS; break; + case 'u': cleared |= MODE_AUDITORIUM; break; + case 'S': cleared |= MODE_SSLCHAN; break; + case 'N': cleared |= MODE_NONOTICES; break; case 'z': cleared |= MODE_REGISTERED; break; } } diff --git a/src/proto.h b/src/proto.h index 6ded4df..2bd70b4 100644 --- a/src/proto.h +++ b/src/proto.h @@ -152,6 +152,13 @@ void irc_account(struct userNode *user, const char *stamp, unsigned long timesta void irc_regnick(struct userNode *user); void irc_fakehost(struct userNode *user, const char *host, const char *ident, int force); +/* svs maintenance */ +void irc_svsmode(struct userNode *from, struct userNode *user, const char *modes); +void irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan); +void irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan); +void irc_relay(char *message); +void irc_simul(struct userNode *target, char *command); + /* numeric messages */ void irc_numeric(struct userNode *user, unsigned int num, const char *format, ...); /* RFC1459-compliant numeric responses */ @@ -185,6 +192,8 @@ unsigned int irc_user_modes(const struct userNode *user, char modes[], size_t le /* Channel mode manipulation */ #define KEYLEN 23 +#define NOFLOODLEN 15 +#define CHANNELLEN 200 typedef unsigned long chan_mode_t; /* Rules for struct mod_chanmode: * For a membership mode change, args[n].mode can contain more than @@ -193,11 +202,13 @@ typedef unsigned long chan_mode_t; */ struct mod_chanmode { chan_mode_t modes_set, modes_clear; - unsigned int new_limit, argc; + unsigned int new_limit, new_access, argc; #ifndef NDEBUG unsigned int alloc_argc; #endif char new_key[KEYLEN + 1]; + char new_altchan[CHANNELLEN + 1]; + char new_noflood[NOFLOODLEN + 1]; char new_upass[KEYLEN + 1]; char new_apass[KEYLEN + 1]; struct { @@ -208,15 +219,17 @@ struct mod_chanmode { } u; } args[1]; }; -#define MCP_ALLOW_OVB 0x0001 /* allow op, voice, ban manipulation */ -#define MCP_FROM_SERVER 0x0002 /* parse as from a server */ -#define MCP_KEY_FREE 0x0004 /* -k without a key argument */ -#define MCP_REGISTERED 0x0008 /* chan is already registered; do not allow changes to MODE_REGISTERED */ -#define MCP_UPASS_FREE 0x0010 /* -U without a key argument */ -#define MCP_APASS_FREE 0x0020 /* -A without a key argument */ -#define MCP_NO_APASS 0x0040 /* Do not allow +/-A or +/-U */ -#define MC_ANNOUNCE 0x0100 /* send a mod_chanmode() change out */ -#define MC_NOTIFY 0x0200 /* make local callbacks to announce */ +#define MCP_ALLOW_OVB 0x0001 /* allow op, voice, ban manipulation */ +#define MCP_FROM_SERVER 0x0002 /* parse as from a server */ +#define MCP_KEY_FREE 0x0004 /* -k without a key argument */ +#define MCP_REGISTERED 0x0008 /* chan is already registered; do not allow changes to MODE_REGISTERED */ +#define MCP_UPASS_FREE 0x0010 /* -U without a key argument */ +#define MCP_APASS_FREE 0x0020 /* -A without a key argument */ +#define MCP_NO_APASS 0x0040 /* Do not allow +/-A or +/-U */ +#define MCP_IGN_REGISTERED 0x0080 /* chan is already registered; ignore changes to MODE_REGISTERED */ +#define MC_ANNOUNCE 0x0100 /* send a mod_chanmode() change out */ +#define MC_NOTIFY 0x0200 /* make local callbacks to announce */ +#define MCP_OPERMODE 0x0400 #ifdef NDEBUG #define mod_chanmode_init(CHANMODE) do { memset((CHANMODE), 0, sizeof(*CHANMODE)); } while (0) #else @@ -225,7 +238,7 @@ struct mod_chanmode { struct mod_chanmode *mod_chanmode_alloc(unsigned int argc); struct mod_chanmode *mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra); -struct mod_chanmode *mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel); +struct mod_chanmode *mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel); void mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change); void mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change); char *mod_chanmode_format(struct mod_chanmode *desc, char *buffer); diff --git a/src/spamserv.c b/src/spamserv.c new file mode 100644 index 0000000..35cf836 --- /dev/null +++ b/src/spamserv.c @@ -0,0 +1,2055 @@ +/* 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; + time_t 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(); +} diff --git a/src/spamserv.h b/src/spamserv.h new file mode 100644 index 0000000..a97ff6e --- /dev/null +++ b/src/spamserv.h @@ -0,0 +1,172 @@ +/* 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]; + time_t 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 diff --git a/src/spamserv.help b/src/spamserv.help new file mode 100644 index 0000000..23ee5ce --- /dev/null +++ b/src/spamserv.help @@ -0,0 +1,91 @@ +"" ("$b$X Help$b", + "The $b$X$b service checks the channel for spam, flood, joinflood and disallowed advertisements.", + "$bUser Commands:$b", + " ADDEXCEPTION Adds a word to the exception list.", + " DELEXCEPTION Deletes a word from the exception list.", + " SET Changes various channel settings.", + " STATUS Shows general information about $X.", + " VERSION Prints the srvx and $X version information.", + "$bStaff Commands:$b", + " REGISTER Registers a new channel.", + " UNREGISTER Removes $X from a registered channel."); +"ADDEXCEPTION" ("/msg $X ADDEXCEPTION [word]", + "Without an argument, it will show all existing exceptions.", + "With an argument, it will add the given word to the exception list.", + "$X checks, if one of the words in the sentence of a user is in the exception list; if so, $X will not punish the user, doesn't matter, if it's a bad advertisement.", + "This means, you have to make sure, all exceptions are adequate.", + "$bFirst example$b: You added the word \"gamesurge.net\" to the exception list and someone posts \"www.gamesurge.net/aup\", he won't get punished.", + "$bSecond example$b: You added the word \"support\" to the list and someone tells another person to join #support, he won't get punished.", + "$bThird example$b: You added \"GameSurge\" to the list and someone posts \"JOIN #channelxyz on GameSurge\", he will NOT get punished, because the word \"GameSurge\" is in the sentence.", + "If he would say \"JOIN #channelxyz\", $X would punish him.", + "$uSee Also:$u delexception"); +"DELEXCEPTION" ("/msg $X DELEXCEPTION", + "Without an argument, it will show all existing exceptions.", + "With an argument, it will delete the given word from the exception list.", + "$uSee Also:$u addexception"); +"SET" ("/msg $X SET <#channel> [ [setting]]", + "This command will set various channel options. With no arguments, it will show the current values of all channel options.", + "Only channel owners and coowners may change settings.", + "SPAMLIMIT: Number of equal lines, a user may send.", + "ADVREACTION: What happens when someone advertises after warning.", + "WARNREACTION: What happens when someone continues spamming/flooding after warning.", + "ADVSCAN: Enables/Disables scanning for advertisements.", + "SPAMSCAN: Enables/Disables scanning for spam.", + "FLOODSCAN: Enables/Disables scanning for flood.", + "JOINFLOODSCAN: Enables/Disables scanning for joinflood.", + "SCANCHANOPS: Indicates whether $X has to scan messages from channel ops.", + "SCANVOICED: Indicates whether $X has to scan messages from voiced users.", + "$uSee Also:$u set spamlimit, set advreaction, set warnreaction, set advscan, set spamscan, set floodscan, set joinfloodscan, set scanchanops, set scanvoiced"); +"SET SPAMLIMIT" ("/msg $X SET <#channel> SPAMLIMIT ", + "You can specify the number of equal messages, a user may send. Valid settings are:", + "$b0$b Users may send the same message $b2$b times.", + "$b1$b Users may send the same message $b3$b times.", + "$b2$b Users may send the same message $b4$b times.", + "$b3$b Users may send the same message $b5$b times.", + "$b4$b Users may send the same message $b6$b times.", + "$uSee Also:$u set spamscan"); +"SET ADVREACTION" ("/msg $X SET <#channel> ADVREACTION ", + "This setting controls what happens to those who send disallowed advertisements to the channel after a warning:", + "$b0$b Kick on disallowed advertising.", + "$b1$b Kickban on disallowed advertising.", + "$b2$b Short timed ban (default: 15 minutes) on disallowed advertising.", + "$b3$b Long timed ban (default: 1 hour) on disallowed advertising.", + "$b4$b Kill on disallowed advertising. Only settable by irc operators.", + "$uSee Also:$u set advscan"); +"SET WARNREACTION" ("/msg $X SET <#channel> WARNREACTION ", + "This setting controls what happens to those who spam or flood the channel after a warning:", + "$b0$b Kick after warning.", + "$b1$b Kickban after warning.", + "$b2$b Short timed ban (default: 15 minutes) after warning.", + "$b3$b Long timed ban (default: 1 hour) after warning.", + "$b4$b Kill after warning. Only settable by irc operators.", + "$uSee Also:$u set spamscan, set floodscan"); +"SET ADVSCAN" ("/msg $X SET <#channel> ADVSCAN <1/0>", + "If this setting is enabled, $X checks all messages for advertisements.", + "Advertisements are: www.*, http:*, ftp.*, ftp:* and #*; e.g. #srvx, http://www.srvx.net etc .."); +"SET SPAMSCAN" ("/msg $X SET <#channel> SPAMSCAN <1/0>", + "If this setting is enabled, $X checks all incoming channel messages for spam.", + "Posting the same message multiple times is considered as spam, which means, if someone posts the same message more than the number of times, which is allowed (/msg $X set SPAMLIMIT), $X will punish him."); +"SET FLOODSCAN" ("/msg $X SET <#channel> FLOODSCAN <1/0>", + "If this setting is enabled, $X checks, if a person tries to flood the channel.", + "Posting messages in a small amount of time is considered as flood, so if someone tries to flood the channel, $X will punish him."); +"SET JOINFLOODSCAN" ("/msg $X SET <#channel> JOINFLOODSCAN <1/0>", + "If this setting is enabled, $X checks, if a person joins the channel more than one time.", + "Normally users join a channel and stay in the channel or part and do not rejoin after a few seconds.", + "If they want to cause trouble, they join/part the channel very often. $X will punish every user, who does that."); +"SET SCANCHANOPS" ("/msg $X SET <#channel> SCANCHANOPS <1/0>", + "If this setting is disabled, $X doesn't check messages from oped users for spam, flood and advertisements."); +"SET SCANVOICED" ("/msg $X SET <#channel> SCANVOICED <1/0>", + "If this setting is disabled, $X doesn't check messages from voiced users for spam, flood and advertisements."); +"REGISTER" ("/msg $X REGISTER <#channel>", + "Registers a channel with $X.", + "The Channel must be registered with $C and may not be suspended.", + "$uSee Also:$u unregister"); +"STATUS" ("/msg $X STATUS [MEMORY|CHANNELS]", + "$bSTATUS$b shows you general information about $X. An irc operator can get information about the memory usage and a list of all registered channels."); +"UNREGISTER" ("/msg $X UNREGISTER <#channel> [CONFIRM]", + "Removes $X from the given channel.", + "If you are not network staff, you must add $bCONFIRM$b to the end of your line to confirm unregistration.", + "$bSee Also:$b register"); +"VERSION" ("/msg $X VERSION", + "$bVERSION$b causes $X to send you the srvx version and some additional version information about $X."); \ No newline at end of file diff --git a/srvx.conf.example b/srvx.conf.example index 5c1556b..3319751 100644 --- a/srvx.conf.example +++ b/srvx.conf.example @@ -102,7 +102,8 @@ "lc_h" "800"; // specifically lower case h "uc_H" "800"; // .. and upper case H "S" "999"; - "b" "1"; + "b" "800"; + "I" "800"; }; // and for who can change epithets for staff "set_epithet_level" "800"; @@ -118,7 +119,7 @@ // how long must an account be inactive so it can be ounregistered without force? "ounregister_inactive" "1M"; // which flags on an account require the ounregister to be used with force? - "ounregister_flags" "ShgsfnHbu"; + "ounregister_flags" "ShgsfnHbuI"; /* "require_qualified" has been removed. It is now * integrated into the modcmd command authorization * and dispatch mechanism. "/msg OpServ help modcmd" @@ -161,6 +162,11 @@ // who to tell about staff auths? "staff_auth_channel" "#opserv"; "staff_auth_channel_modes" "+tinms"; + // Force Opers to be in staff_auth_channel + // 0 = don't force opers to be in the channel + // 1 = force opers to be in the channel + // 2 = force opers to be in the channel but kick them if they get mode -o set (deoper) + "staff_auth_force_opers" "2"; // how many clones to allow from an untrusted host? "untrusted_max" "4"; // how long of a g-line should be issued if the max hosts is exceeded? @@ -185,6 +191,7 @@ "size" "200"; "drain-rate" "3"; }; + }; "chanserv" { @@ -228,7 +235,7 @@ // How often to look for dnrs that have expired? "dnr_expire_freq" "1h"; // what !set options should we show when user calls "!set" with no arguments? - "set_shows" ("DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", "PubCmd", "InviteMe", "StrictOp", "AutoOp", "EnfModes", "EnfTopic", "TopicSnarf", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", "Setters", "CtcpUser", "CtcpReaction", "Protect", "Toys", "DynLimit", "NoDelete"); + "set_shows" ("DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", "PubCmd", "InviteMe", "EnfModes", "EnfTopic", "TopicSnarf", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", "Setters", "CtcpUsers", "CtcpReaction", "Protect", "Toys", "DynLimit", "NoDelete"); // A list of !8ball responses "8ball" ("Not a chance.", @@ -242,6 +249,8 @@ "max_owned" "5"; // how long between automatic topic refreshes with TopicRefresh 0 "refresh_period" "3h"; + // how long between two invites of an user + "invite_timeout" "10s"; // what should !access say for various staff? "irc_operator_epithet" "a megalomaniacal power hungry tyrant"; "network_helper_epithet" "a wannabe tyrant"; @@ -252,6 +261,11 @@ "nodelete_level" "1"; // how long before a new channel owner can give ownership away? "giveownership_timeout" "1w"; + // message sent to new channels + "new_channel_unauthed_join" ""; //only sent if the user is unauthed + "new_channel_authed_join" ""; //only sent if the user is authed + "new_channel_message" ""; //always after the message above + }; "global" { @@ -263,6 +277,42 @@ // should users get community announcements by default or not? "announcements_default" "on"; }; + + "spamserv" { + // You may disable a service by removing its "nick" config item. + // That means: SERVICES WILL ONLY WORK IF YOU DEFINE THEIR NICK. + // (This is changed relative srvx-1.0.x, which would use default + // unless you specified ".disabled".) + "nick" "SpamServ"; + "debug_channel" "#spamserv"; + // url of the network rules. if you don't have network rules, remove this key. + "network_rules" "http://www.gamesurge.net/aup"; + // trigger for spamserv; remove this key to disable the trigger + "trigger" "-"; + // ban duration of a short timedban. + "short_ban_duration" "15m"; + // ban duration of a long timedban. + "long_ban_duration" "1h"; + // duration of a gline. SpamServ will issue it after several violations and a kill. + "gline_duration" "1h"; + // users may add "exception_max" exceptions to the list. IRCOps can override "exception_max". + "exception_max" "10"; + // minimum & maximum length of an exception. + "exception_min_len" "4"; + "exception_max_len" "12"; + // if someone advertises a channel, which doesn't exist (channel is empty, no users), + // SpamServ doesn't punish the user. + // enable this setting, if SpamServ has to ignore advertisements of channels, which do not exist. + // disable this setting, if SpamServ has to punish the users whenever they advertise. + "adv_chan_must_exist" "0"; + // remove all mirc codes from messages before checking for advertisements. + // if this setting is disabled and someone spams a url which + // contains a bold char, SpamServ doesn't punish him. + "strip_mirc_codes" "1"; + // enable this, if SpamServ has to "follow" ChanServ, when a channel moves or merges. + // disable it, if it shouldn't be possible to move or merge SpamServ with /msg chanserv move|merge. + "allow_move_merge" "1"; + }; }; // The modules section gives configuration information to optional modules. @@ -350,6 +400,13 @@ // "search" ("example.org", "example.net"); // "nameservers" ("127.0.0.1"); }; + "watchdog" { + "nick" "Watchdog"; + "modes" "+iok"; + "ban_duration" "2h"; //only if the channel is registered with chanserv + "gline_duration" "1h"; + "punishment_reason" "Your message contained a forbidden word."; + }; }; "policers" { @@ -418,6 +475,8 @@ "NickServ" { "mondo_section" "NickServ"; }; "OpServ" { "mondo_section" "OpServ"; }; "sendmail" { "mondo_section" "sendmail"; }; + "SpamServ" { "mondo_section" "SpamServ"; }; + "Watchdog" { "mondo_section" "Watchdog"; }; // These are the options if you want a database to be in its own file. "mondo" {