X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/21f6caee9936b429da5fcd482b4907315fcb109a..e9b008053cd96b7893d720265d1afef0d6405945:/src/opserv.c diff --git a/src/opserv.c b/src/opserv.c index a1d489f..e4c210a 100644 --- a/src/opserv.c +++ b/src/opserv.c @@ -5,7 +5,7 @@ * * x3 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 + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -18,6 +18,8 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +#include "config.h" +#include "chanserv.h" #include "conf.h" #include "common.h" #include "gline.h" @@ -25,6 +27,7 @@ #include "nickserv.h" #include "modcmd.h" #include "modules.h" +#include "proto.h" #include "opserv.h" #include "timeq.h" #include "saxdb.h" @@ -42,7 +45,6 @@ #include #endif - #define OPSERV_CONF_NAME "services/opserv" #define KEY_ALERT_CHANNEL "alert_channel" @@ -74,6 +76,8 @@ #define KEY_WARN "chanwarn" #define KEY_MAX "max" #define KEY_TIME "time" +#define KEY_LAST "last" +#define KEY_EXPIRE "expire" #define KEY_MAX_CLIENTS "max_clients" #define KEY_LIMIT "limit" #define KEY_EXPIRES "expires" @@ -142,6 +146,11 @@ static const struct message_entry msgtab[] = { { "OSMSG_NEED_CHANNEL", "You must specify a channel for $b%s$b." }, { "OSMSG_INVALID_IRCMASK", "$b%s$b is an invalid IRC hostmask." }, { "OSMSG_ADDED_BAN", "I have banned $b%s$b from $b%s$b." }, + { "OSMSG_BLOCK_TRUSTED", "$b%s$b is on a trusted ip. If you really want to G-line him, use the GLINE command." }, + { "OSMSG_BLOCK_OPER" , "G-lining $b%s$b (*@%s) would also hit the IRC operator $b%s$b." }, + { "OSMSG_SBLOCK_OPER" , "Shuning $b%s$b (*@%s) would also hit the IRC operator $b%s$b." }, + { "OSMSG_NO_GLINE_CMD", "The GLINE command is not bound so you can only block with the default duration." }, + { "OSMSG_NO_SHUN_CMD", "The SHUN command is not bound so you can only block with the default duration." }, { "OSMSG_SHUN_ISSUED", "Shun issued for $b%s$b." }, { "OSMSG_SHUN_REMOVED", "Shun removed for $b%s$b." }, { "OSMSG_SHUN_FORCE_REMOVED", "Unknown/expired Shun removed for $b%s$b." }, @@ -162,16 +171,24 @@ static const struct message_entry msgtab[] = { { "OSMSG_NO_DEBUG_CHANNEL", "No debug channel has been configured." }, { "OSMSG_INVITE_DONE", "Invited $b%s$b to $b%s$b." }, { "OSMSG_ALREADY_THERE", "You are already in $b%s$b." }, + { "OSMSG_USER_ALREADY_THERE", "%s is already in $b%s$b." }, + { "OSMSG_NOT_THERE", "You not in $b%s$b." }, { "OSMSG_JOIN_DONE", "I have joined $b%s$b." }, + { "OSMSG_MARK_SET", "Set the MARK." }, + { "OSMSG_SVSJOIN_SENT", "Sent the SVSJOIN." }, + { "OSMSG_SVSPART_SENT", "Sent the SVSPART." }, { "OSMSG_ALREADY_JOINED", "I am already in $b%s$b." }, { "OSMSG_NOT_ON_CHANNEL", "$b%s$b does not seem to be on $b%s$b." }, { "OSMSG_KICKALL_DONE", "I have cleared out %s." }, { "OSMSG_LEAVING", "Leaving $b%s$b." }, + { "OSMSG_MARK_INVALID", "Sorry, marks must contain only letters, numbers, and dashes ('-')." }, { "OSMSG_MODE_SET", "I have set the modes for $b%s$b." }, { "OSMSG_OP_DONE", "Opped the requested lusers." }, { "OSMSG_OPALL_DONE", "Opped everyone on $b%s$b." }, { "OSMSG_HOP_DONE", "Halfopped the requested lusers." }, { "OSMSG_HOPALL_DONE", "Halfopped everyone on $b%s$b." }, + { "OMSG_BAD_SVSNICK", "$b%s$b is an invalid nickname." }, + { "OSMSG_WHOIS_IDENT", "%s (%s@%s) from %d.%d.%d.%d" }, { "OSMSG_WHOIS_NICK", "Nick : %s" }, { "OSMSG_WHOIS_HOST", "Host : %s@%s" }, @@ -186,6 +203,7 @@ static const struct message_entry msgtab[] = { { "OSMSG_WHOIS_POSTAL_CODE","Postal Code : %s" }, { "OSMSG_WHOIS_LATITUDE", "Latitude : %f" }, { "OSMSG_WHOIS_LONGITUDE", "Longitude : %f" }, + { "OSMSG_WHOIS_MAP", "Map : %s" }, { "OSMSG_WHOIS_DMA_CODE", "DMA Code : %d" }, { "OSMSG_WHOIS_AREA_CODE", "Area Code : %d" }, { "OSMSG_WHOIS_MODES", "Modes : +%s " }, @@ -194,8 +212,12 @@ static const struct message_entry msgtab[] = { { "OSMSG_WHOIS_SERVER", "Server : %s" }, { "OSMSG_WHOIS_NICK_AGE", "Nick Age : %s" }, { "OSMSG_WHOIS_ACCOUNT", "Account : %s" }, + { "OSMSG_WHOIS_PRIVS", "IRCd Privs : %s" }, { "OSMSG_WHOIS_CHANNELS", "Channels : %s" }, { "OSMSG_WHOIS_HIDECHANS", "Channel list omitted for your sanity." }, + { "OSMSG_WHOIS_VERSION", "Version : %s" }, + { "OSMSG_WHOIS_MARK", "Mark : %s" }, + { "OSMSG_WHOIS_NO_NOTICE", "No_notices : %s" }, { "OSMSG_UNBAN_DONE", "Ban(s) removed from channel %s." }, { "OSMSG_CHANNEL_VOICED", "All users on %s voiced." }, { "OSMSG_CHANNEL_DEVOICED", "All voiced users on %s de-voiced." }, @@ -223,6 +245,8 @@ static const struct message_entry msgtab[] = { { "OSMSG_EXEMPTED_LIST", "Exempted channels: %s" }, { "OSMSG_GLINE_COUNT", "There are %d glines active on the network." }, { "OSMSG_SHUN_COUNT", "There are %d shuns active on the network." }, + { "OSMSG_NO_GLINE", "$b%s$b is not a known G-line." }, + { "OSMSG_NO_SHUN", "$b%s$b is not a known Shun" }, { "OSMSG_LINKS_SERVER", "%s%s (%u clients; %s)" }, { "OSMSG_MAX_CLIENTS", "Max clients: %d at %s" }, { "OSMSG_NETWORK_INFO", "Total users: %d (%d invisible, %d opers)" }, @@ -272,6 +296,9 @@ static const struct message_entry msgtab[] = { { "OSMSG_USER_SEARCH_BAR", "-------------------------------------------" }, { "OSMSG_USER_SEARCH_COUNT", "There were %4u matches" }, { "OSMSG_USER_SEARCH_COUNT_BAR", "------------ Found %4u matches -----------" }, + { "OSMSG_MARK_NO_MARK", "MARK action requires mark criteria (what do you want to mark them as?)" }, + { "OSMSG_SVSJOIN_NO_TARGET", "SVSJOIN action requires chantarget criteria (where should they join?)" }, + { "OSMSG_SVSPART_NO_TARGET", "SVSPART action requires chantarget criteria (where should they join?)" }, { "OSMSG_CHANNEL_SEARCH_RESULTS", "The following channels were found:" }, { "OSMSG_GLINE_SEARCH_RESULTS", "The following glines were found:" }, { "OSMSG_SHUN_SEARCH_RESULTS", "The following shun were found:" }, @@ -291,24 +318,29 @@ static const struct message_entry msgtab[] = { { "OSMSG_ALERT_EXISTS", "An alert named $b%s$b already exists." }, { "OSMSG_UNKNOWN_REACTION", "Unknown alert reaction $b%s$b." }, { "OSMSG_ADDED_ALERT", "Added alert named $b%s$b." }, + { "OSMSG_ALERT_ADD_FAILED", "Unable to add alert. Check syntax, required parts, and access" }, { "OSMSG_REMOVED_ALERT", "Removed alert named $b%s$b." }, { "OSMSG_NO_SUCH_ALERT", "No alert named $b%s$b could be found." }, - { "OSMSG_ALERTS_LIST", "$bCurrent $O alerts$b" }, + { "OSMSG_ALERTS_LIST", "$bCurrent $O alerts matching '$b%s$b'$b" }, { "OSMSG_ALERTS_BAR", "----------------------------------------------" }, { "OSMSG_ALERTS_HEADER", "Name Action (by Oper)" }, - { "OSMSG_ALERTS_DESC", " Criteria: %s" }, + { "OSMSG_ALERTS_DESC", " $uCriteria$u: %s" }, + { "OSMSG_ALERTS_LAST", " $uTriggered$u: %s" }, { "OSMSG_ALERT_IS", "$b%-20s$b %-6s (by %s)" }, + { "OSMSG_ALERT_EXPIRE", " $uExpires:$u: %s" }, { "OSMSG_ALERT_END", "----------------End of Alerts-----------------" }, /* routing messages */ - { "OSMSG_ROUTINGPLAN_LIST", "$bRouting Plans$b" }, + { "OSMSG_ROUTINGPLAN", "$bRouting Plan(s)$b" }, + { "OSMSG_ROUTINGPLAN_LIST_HEAD", "$bRouting Plans$b" }, { "OSMSG_ROUTINGPLAN_BAR", "----------------------------------------------" }, - { "OSMSG_ROUTINGPLAN_END", "-------------End of Routing Plans-------------" }, + { "OSMSG_ROUTINGPLAN_END", "------------End of Routing Plan(s)------------" }, { "OSMSG_ROUTINGPLAN_OPTION", "%s is set to %s" }, { "OSMSG_ROUTINGPLAN_ACTIVE", "Auto routing is active, using plan '%s'." }, { "OSMSG_ROUTING_ACTIVATION_ERROR", "There was an error activating the routing plan. Check for loops, and make sure the map includes my own uplink." }, { "OSMSG_ROUTINGPLAN_OPTION_NOT_FOUND", "There is no routing plan option '%s'." }, { "OSMSG_ROUTINGPLAN_OPTION_NOT_SET", "Option '%s' is not currently set." }, { "OSMSG_ROUTINGPLAN_NAME", "$b%s:$b" }, + { "OSMSG_ROUTINGPLAN_LIST", "$b%s$b" }, { "OSMSG_ROUTINGPLAN_SERVER"," %s:%d <-- %s[%d/%s] (%s)" }, { "OSMSG_ADDPLAN_SUCCESS", "Added new routing plan '%s'." }, { "OSMSG_ADDPLAN_FAILED", "Could not add new plan '%s' (does it already exist?)." }, @@ -382,9 +414,11 @@ static const struct message_entry msgtab[] = { { "OSMSG_CHANINFO_MANY_USERS", "%d users (\"/msg $S %s %s users\" for the list)" }, { "OSMSG_CHANINFO_USER_COUNT", "Users (%d):" }, { "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_INVALID_REGEX", "Invalid regex: %s: %s (%d)" }, { "OSMSG_TRACK_DISABLED", "Tracking is not currently compiled into X3" }, { "OSMSG_MAXUSERS_RESET", "Max clients has been reset to $b%d$b" }, + { "OSMSG_FORCEKICK_LOCAL", "You cannot kick $b%s$b forcefully." }, { "OSMSG_DEFCON_INVALID", "DefCon level %d is invalid, please choose a value between 1 and 5" }, { "OSMSG_DEFCON_ALLOWING_ALL", "DefCon is at level 5 and allowing everything" }, @@ -401,6 +435,9 @@ static const struct message_entry msgtab[] = { { "OSMSG_DEFCON_SHUN_NEW_CLIENTS", "Shunning New Clients" }, { "OSMSG_DEFCON_NO_NEW_MEMOS", "Disallowing New Memos" }, + { "OSMSG_PRIV_UNKNOWN", "Unknown privilege flag %s, see /msg $O HELP PRIVFLAGS for a flag list" }, + { "OSMSG_PRIV_SET", "Privilege flag %s has been %sset" }, + { NULL, NULL } }; @@ -436,6 +473,7 @@ static dict_t opserv_waiting_connections; /* data is struct waitingConnection */ 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* */ +static dict_t opserv_account_based_alerts; /* data is struct opserv_user_alert* */ static dict_t opserv_channel_alerts; /* data is struct opserv_user_alert* */ static struct module *opserv_module; static struct log_type *OS_LOG; @@ -502,22 +540,26 @@ opserv_free_waiting_connection(void *data) free(wc); } +#define DISCRIM_MAX_CHANS 20 + typedef struct opservDiscrim { - struct chanNode *channel; - char *mask_nick, *mask_ident, *mask_host, *mask_info, *server, *reason, *accountmask; + struct chanNode *channels[DISCRIM_MAX_CHANS]; + unsigned int channel_count; + char *mask_nick, *mask_ident, *mask_host, *mask_info, *mask_version, *server, *reason, *accountmask, *chantarget, *mark, *mask_mark, *modes, *notice_target; irc_in_addr_t ip_mask; unsigned long limit; time_t min_ts, max_ts; - regex_t regex_nick, regex_ident, regex_host, regex_info; - unsigned int has_regex_nick : 1, has_regex_ident : 1, has_regex_host : 1, has_regex_info : 1; + regex_t regex_nick, regex_ident, regex_host, regex_info, regex_version; + unsigned int has_regex_nick : 1, has_regex_ident : 1, has_regex_host : 1, has_regex_info : 1, has_regex_version : 1; unsigned int min_level, max_level, domain_depth, duration, min_clones, min_channels, max_channels; unsigned char ip_mask_bits; - unsigned int match_opers : 1, option_log : 1; - unsigned int chan_req_modes : 2, chan_no_modes : 2; + unsigned int match_opers : 1, match_trusted : 1, option_log : 1; + unsigned int chan_req_modes[DISCRIM_MAX_CHANS], chan_no_modes[DISCRIM_MAX_CHANS]; int authed : 2, info_space : 2; unsigned int intra_scmp : 2, intra_dcmp : 2; unsigned int use_regex : 1; unsigned int silent : 1; + unsigned int checkrestrictions : 2; } *discrim_t; struct discrim_and_source { @@ -532,14 +574,18 @@ static discrim_t opserv_discrim_create(struct userNode *user, struct userNode *b static unsigned int opserv_discrim_search(discrim_t discrim, discrim_search_func dsf, void *data); static int gag_helper_func(struct userNode *match, void *extra); static int ungag_helper_func(struct userNode *match, void *extra); +static void alert_expire(void* name); typedef enum { REACT_NOTICE, REACT_KILL, -// REACT_SILENT, REACT_GLINE, REACT_TRACK, - REACT_SHUN + REACT_SHUN, + REACT_SVSJOIN, + REACT_SVSPART, + REACT_VERSION, + REACT_MARK } opserv_alert_reaction; struct opserv_user_alert { @@ -547,6 +593,8 @@ struct opserv_user_alert { char *text_discrim, *split_discrim; discrim_t discrim; opserv_alert_reaction reaction; + int last; + time_t expire; }; /* funny type to make it acceptible to dict_set_free_data, far below */ @@ -554,8 +602,9 @@ static void opserv_free_user_alert(void *data) { struct opserv_user_alert *alert = data; - if (alert->discrim->channel) - UnlockChannel(alert->discrim->channel); + unsigned int i; + for(i = 0; i < alert->discrim->channel_count; i++) + UnlockChannel(alert->discrim->channels[i]); free(alert->owner); free(alert->text_discrim); free(alert->split_discrim); @@ -567,14 +616,22 @@ opserv_free_user_alert(void *data) regfree(&alert->discrim->regex_host); if(alert->discrim->has_regex_info) regfree(&alert->discrim->regex_info); + if(alert->discrim->has_regex_version) + regfree(&alert->discrim->regex_version); free(alert->discrim->reason); free(alert->discrim); free(alert); } -#define opserv_debug(format...) do { if (opserv_conf.debug_channel) send_channel_notice(opserv_conf.debug_channel , opserv , ## format); } while (0) -#define opserv_alert(format...) do { if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel , opserv , ## format); } while (0) - +#if defined(GCC_VARMACROS) +# define opserv_debug(ARGS...) do { if (opserv_conf.debug_channel) send_channel_notice(opserv_conf.debug_channel, opserv, ARGS); } while (0) +# define opserv_alert(ARGS...) do { if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel, opserv, ARGS); } while (0) +# define opserv_custom_alert(CHAN, ARGS...) do { if (CHAN) send_target_message(4, (CHAN), opserv, ARGS); else if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel, opserv, ARGS); } while (0) +#elif defined(C99_VARMACROS) +# define opserv_debug(...) do { if (opserv_conf.debug_channel) send_channel_notice(opserv_conf.debug_channel, opserv, __VA_ARGS__); } while (0) +# define opserv_alert(...) do { if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel, opserv, __VA_ARGS__); } while (0) +# define opserv_custom_alert(chan, ...) do { if (chan) send_target_message(4, chan, opserv, __VA_ARGS__); else if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel, opserv, __VA_ARGS__); } while (0) +#endif char *defconReverseModes(const char *modes) { @@ -725,7 +782,7 @@ static MODCMD_FUNC(cmd_defcon) showDefConSettings(user, cmd); if (DefConTimeOut > 0) { - timeq_del(0, defcon_timeout, NULL, TIMEQ_IGNORE_DATA & TIMEQ_IGNORE_WHEN); + timeq_del(0, defcon_timeout, NULL, TIMEQ_IGNORE_DATA | TIMEQ_IGNORE_WHEN); timeq_add(now + DefConTimeOut, defcon_timeout, NULL); } @@ -733,6 +790,55 @@ static MODCMD_FUNC(cmd_defcon) return 1; } +/* TODO +static MODCMD_FUNC(cmd_privallow) +{ +//privallow servername/username +/-flag (global is set in conf) +} + +static MODCMD_FUNC(cmd_privdissallow) +{ +//privdisallow servername/username +/-flag (global is set in conf) +} + +static MODCMD_FUNC(cmd_privlist) +{ +//privlist servername/user (global with none) +} +*/ + +static MODCMD_FUNC(cmd_privset) +{ + struct userNode *target; + char *flag; + int add = PRIV_ADD; + + flag = argv[2]; + if (*flag == '-') { + add = PRIV_DEL; + flag++; + } else if (*flag == '+') { + add = PRIV_ADD; + flag++; + } + + target = GetUserH(argv[1]); + if (!target) { + reply("MSG_NICK_UNKNOWN", argv[1]); + return 0; + } + + if (check_priv(flag)) { + irc_privs(target, flag, add); + reply("OSMSG_PRIV_SET", argv[2], (add == 1) ? "" : "un"); + } else { + reply("OSMSG_PRIV_UNKNOWN", argv[2]); + return 0; + } + + return 1; +} + /* A lot of these commands are very similar to what ChanServ can do, * but OpServ can do them even on channels that aren't registered. */ @@ -1206,6 +1312,10 @@ static MODCMD_FUNC(cmd_block) struct userNode *target; struct gline *gline; char *reason; + unsigned long duration = 0; + unsigned int offset = 2; + unsigned int nn; + struct svccmd *gline_cmd; target = GetUserH(argv[1]); if (!target) { @@ -1216,8 +1326,32 @@ static MODCMD_FUNC(cmd_block) reply("MSG_SERVICE_IMMUNE", target->nick); return 0; } - reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : NULL; - gline = opserv_block(target, user->handle_info->handle, reason, 0, 0); + if (dict_find(opserv_trusted_hosts, irc_ntoa(&target->ip), NULL)) { + reply("OSMSG_BLOCK_TRUSTED", target->nick); + return 0; + } + for(nn = 0; nn < curr_opers.used; nn++) { + if(memcmp(&curr_opers.list[nn]->ip, &target->ip, sizeof(irc_in_addr_t)) == 0) { + reply("OSMSG_BLOCK_OPER", target->nick, irc_ntoa(&target->ip), curr_opers.list[nn]->nick); + return 0; + } + } + if(argc > 2 && (duration = ParseInterval(argv[2]))) { + offset = 3; + } + if(duration && duration != opserv_conf.block_gline_duration) { + /* We require more access when the duration is not the default block duration. */ + gline_cmd = dict_find(cmd->parent->commands, "gline", NULL); + if(!gline_cmd) + { + reply("OSMSG_NO_GLINE_CMD"); + return 0; + } + if(!svccmd_can_invoke(user, cmd->parent->bot, gline_cmd, channel, SVCCMD_NOISY)) + return 0; + } + reason = (argc > offset) ? unsplit_string(argv+offset, argc-offset, NULL) : NULL; + gline = opserv_block(target, user->handle_info->handle, reason, duration, 0); reply("OSMSG_GLINE_ISSUED", gline->target); return 1; } @@ -1282,6 +1416,85 @@ static MODCMD_FUNC(cmd_refreshg) return 1; } +static void +opserv_version(struct userNode *target) +{ + irc_version_user(opserv, target); +} + +static void +opserv_mark(struct userNode *target, UNUSED_ARG(char *src_handle), UNUSED_ARG(char *reason), char *mark) +{ + if(!mark) + return; + irc_mark(target, mark); +} + +static void +opserv_svsjoin(struct userNode *target, UNUSED_ARG(char *src_handle), UNUSED_ARG(char *reason), char *channame, unsigned int checkrestrictions) +{ + struct chanNode *channel; + + if(!channame || !IsChannelName(channame)) { + /* Not a valid channel name. We shouldnt ever get this if we check properly in addalert */ + return; + } + + if (!(channel = GetChannel(channame))) { + channel = AddChannel(channame, now, NULL, NULL, NULL); + } + if (GetUserMode(channel, target)) { + /* already in it */ + return; + } + + if (checkrestrictions) { + if (trace_check_bans(target, channel) == 1) { + return; /* found on lamer list */ + } + + if (channel->modes & MODE_INVITEONLY) { + return; /* channel is invite only */ + } + + if (channel->limit > 0) { + if (channel->members.used >= channel->limit) { + return; /* channel is invite on */ + } + } + + if (*channel->key) { + return; /* channel is password protected */ + } + } + + irc_svsjoin(opserv, target, channel); + /* Should we tell the user they got joined? -Rubin*/ +} + +static void +opserv_svspart(struct userNode *target, UNUSED_ARG(char *src_handle), UNUSED_ARG(char *reason), char *channame) +{ + struct chanNode *channel; + + if(!channame || !IsChannelName(channame)) { + /* Not a valid channel name. We shouldnt ever get this if we check properly in addalert */ + return; + } + + if (!(channel = GetChannel(channame))) { + /* channel doesnt exist */ + return; + } + + if (!GetUserMode(channel, target)) { + /* not in it */ + return; + } + + irc_svspart(opserv, target, channel); +} + static struct shun * opserv_shun(struct userNode *target, char *src_handle, char *reason, unsigned long duration) { @@ -1301,6 +1514,10 @@ static MODCMD_FUNC(cmd_sblock) struct userNode *target; struct shun *shun; char *reason; + unsigned long duration = 0; + unsigned int offset = 2; + unsigned int nn; + struct svccmd *shun_cmd; target = GetUserH(argv[1]); if (!target) { @@ -1311,8 +1528,32 @@ static MODCMD_FUNC(cmd_sblock) reply("MSG_SERVICE_IMMUNE", target->nick); return 0; } + if (dict_find(opserv_trusted_hosts, irc_ntoa(&target->ip), NULL)) { + reply("OSMSG_BLOCK_TRUSTED", target->nick); + return 0; + } + for(nn = 0; nn < curr_opers.used; nn++) { + if(memcmp(&curr_opers.list[nn]->ip, &target->ip, sizeof(irc_in_addr_t)) == 0) { + reply("OSMSG_SBLOCK_OPER", target->nick, irc_ntoa(&target->ip), curr_opers.list[nn]->nick); + return 0; + } + } + if(argc > 2 && (duration = ParseInterval(argv[2]))) { + offset = 3; + } + if(duration && duration != opserv_conf.block_shun_duration) { + // We require more access when the duration is not the default block duration. + shun_cmd = dict_find(cmd->parent->commands, "shun", NULL); + if(!shun_cmd) + { + reply("OSMSG_NO_SHUN_CMD"); + return 0; + } + if(!svccmd_can_invoke(user, cmd->parent->bot, shun_cmd, channel, SVCCMD_NOISY)) + return 0; + } reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : NULL; - shun = opserv_shun(target, user->handle_info->handle, reason, 0); + shun = opserv_shun(target, user->handle_info->handle, reason, duration); reply("OSMSG_SHUN_ISSUED", shun->target); return 1; } @@ -1378,7 +1619,7 @@ static MODCMD_FUNC(cmd_refreshs) } static void -opserv_ison(struct userNode *tell, struct userNode *target, const char *message) +opserv_ison(struct userNode *bot, struct userNode *tell, struct userNode *target, const char *message) { struct modeNode *mn; unsigned int count, here_len, n, maxlen; @@ -1392,7 +1633,7 @@ opserv_ison(struct userNode *tell, struct userNode *target, const char *message) here_len = strlen(mn->channel->name); if ((count + here_len + 4) > maxlen) { buff[count] = 0; - send_message(tell, opserv, message, buff); + send_message(tell, bot, message, buff); count = 0; } if (mn->modes & MODE_CHANOP) @@ -1407,7 +1648,7 @@ opserv_ison(struct userNode *tell, struct userNode *target, const char *message) } if (count) { buff[count] = 0; - send_message(tell, opserv, message, buff); + send_message(tell, bot, message, buff); } } @@ -1448,14 +1689,61 @@ static MODCMD_FUNC(cmd_invite) return 1; } -static MODCMD_FUNC(cmd_join) +static MODCMD_FUNC(cmd_svsjoin) { - struct userNode *bot = cmd->parent->bot; + struct userNode *target; - if (!IsChannelName(argv[1])) { + + if(!IsChannelName(argv[2])) { reply("MSG_NOT_CHANNEL_NAME"); return 0; - } else if (!(channel = GetChannel(argv[1]))) { + } + target = GetUserH(argv[1]); + if (!target) { + reply("MSG_NICK_UNKNOWN", argv[1]); + return 0; + } + + if (!(channel = GetChannel(argv[2]))) { + channel = AddChannel(argv[2], now, NULL, NULL, NULL); + } + if (GetUserMode(channel, target)) { + reply("OSMSG_USER_ALREADY_THERE", target->nick, channel->name); + return 0; + } + irc_svsjoin(opserv, target, channel); + reply("OSMSG_SVSJOIN_SENT"); + return 1; +} + +static MODCMD_FUNC(cmd_svsnick) +{ + struct userNode *target; + + target = GetUserH(argv[1]); + if (!target) { + reply("MSG_NICK_UNKNOWN", argv[1]); + return 0; + } + if(!is_valid_nick(argv[2])) { + reply("OMSG_BAD_SVSNICK", argv[2]); + return 0; + } + irc_svsnick(opserv, target, argv[2]); + return 1; +} + +static MODCMD_FUNC(cmd_join) +{ + struct userNode *bot = cmd->parent->bot; + + if (!channel) { + if((argc < 2) || !IsChannelName(argv[1])) + { + reply("MSG_NOT_CHANNEL_NAME"); + return 0; + } + channel = AddChannel(argv[1], now, NULL, NULL, NULL); AddChannelUser(bot, channel)->modes |= MODE_CHANOP; } else if (GetUserMode(channel, bot)) { @@ -1498,6 +1786,34 @@ static MODCMD_FUNC(cmd_kick) return 1; } +static MODCMD_FUNC(cmd_forcekick) +{ + struct userNode *target; + char *reason; + + if (argc < 3) { + reason = alloca(strlen(OSMSG_KICK_REQUESTED)+strlen(user->nick)+1); + sprintf(reason, OSMSG_KICK_REQUESTED, user->nick); + } else { + reason = unsplit_string(argv+2, argc-2, NULL); + } + target = GetUserH(argv[1]); + if (!target) { + reply("MSG_NICK_UNKNOWN", argv[1]); + return 0; + } + if (!GetUserMode(channel, target)) { + reply("OSMSG_NOT_ON_CHANNEL", target->nick, channel->name); + return 0; + } + if (IsLocal(target)) { + reply("OSMSG_FORCEKICK_LOCAL", target->nick); + return 0; + } + irc_kick(cmd->parent->bot, target, channel, reason); + return 1; +} + static MODCMD_FUNC(cmd_kickall) { unsigned int limit, n, inchan; @@ -1614,23 +1930,47 @@ static MODCMD_FUNC(cmd_kickbanall) return 1; } -static MODCMD_FUNC(cmd_part) +static MODCMD_FUNC(cmd_svspart) { - char *reason; + struct userNode *target; + struct chanNode *target_channel; - if (!IsChannelName(argv[1])) { + if(!IsChannelName(argv[2])) { reply("MSG_NOT_CHANNEL_NAME"); return 0; } - if ((channel = GetChannel(argv[1]))) { - if (!GetUserMode(channel, cmd->parent->bot)) { - reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name); - return 0; - } - reason = (argc < 3) ? "Leaving." : unsplit_string(argv+2, argc-2, NULL); - reply("OSMSG_LEAVING", channel->name); - DelChannelUser(cmd->parent->bot, channel, reason, 0); + if(!(target_channel = GetChannel(argv[2]))) + { + reply("MSG_INVALID_CHANNEL"); + return 0; + } + target = GetUserH(argv[1]); + if (!target) { + reply("MSG_NICK_UNKNOWN", argv[1]); + return 0; + } + + if (!GetUserMode(target_channel, target)) { + reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, target_channel->name); + return 0; } + + irc_svspart(opserv, target, target_channel); + reply("OSMSG_SVSPART_SENT"); + return 1; +} + +static MODCMD_FUNC(cmd_part) +{ + char *reason; + + if (!GetUserMode(channel, cmd->parent->bot)) { + reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name); + return 0; + } + reason = (argc < 2) ? "Leaving." : unsplit_string(argv+1, argc-1, NULL); + reply("OSMSG_LEAVING", channel->name); + DelChannelUser(cmd->parent->bot, channel, reason, 0); return 1; } @@ -1644,6 +1984,40 @@ static MODCMD_FUNC(cmd_mode) return 1; } +int is_valid_mark(char *mark) +{ + char *ptr; + + if(!mark || !*mark) + return 0; + if(strlen(mark) > MARKLEN) + return 0; + + for(ptr = mark; *ptr; ptr++) { + if(! (isalnum(*ptr) || *ptr == '-')) + return 0; + } + + return 1; +} + +static MODCMD_FUNC(cmd_mark) +{ + char *mark = argv[2]; + struct userNode *victim = GetUserH(argv[1]); + + if(!victim) + reply("MSG_NICK_UNKNOWN", argv[1]); + else if(!is_valid_mark(mark)) + reply("OSMSG_MARK_INVALID"); + else { + irc_mark(victim, mark); + reply("OSMSG_MARK_SET"); + return 1; + } + return 0; +} + static MODCMD_FUNC(cmd_op) { struct mod_chanmode *change; @@ -1775,29 +2149,32 @@ static MODCMD_FUNC(cmd_whois) reply("OSMSG_WHOIS_POSTAL_CODE", target->postal_code); reply("OSMSG_WHOIS_LATITUDE", target->latitude); reply("OSMSG_WHOIS_LONGITUDE", target->longitude); + /* Only show a map url if we have a city, latitude and longitude. + * Theres not much point of latitude and longitude coordinates are + * returned but no city, the coordinates are useless. + */ + if (target->latitude && target->longitude && target->city) { + char map_url[MAXLEN]; + snprintf(map_url, sizeof(map_url), "http://www.mapquest.com/maps/map.adp?searchtype=address&formtype=address&latlongtype=decimal&latitude=%f&longitude=%f", + target->latitude, target->longitude); + reply("OSMSG_WHOIS_MAP", map_url); + } reply("OSMSG_WHOIS_DMA_CODE", target->dma_code); reply("OSMSG_WHOIS_AREA_CODE", target->area_code); } else if (target->country_name) { reply("OSMSG_WHOIS_COUNTRY", target->country_name); } + if(target->version_reply) { + reply("OSMSG_WHOIS_VERSION", target->version_reply); + } + if(target->mark) { + reply("OSMSG_WHOIS_MARK", target->mark); + } + reply("OSMSG_WHOIS_NO_NOTICE", target->no_notice ? "YES":"NO"); if (target->modes) { - bpos = 0; + bpos = irc_user_modes(target, buffer, sizeof(buffer)); #define buffer_cat(str) (herelen = strlen(str), memcpy(buffer+bpos, str, herelen), bpos += herelen) - if (IsInvisible(target)) buffer[bpos++] = 'i'; - if (IsWallOp(target)) buffer[bpos++] = 'w'; - if (IsOper(target)) buffer[bpos++] = 'o'; - if (IsGlobal(target)) buffer[bpos++] = 'g'; - if (IsServNotice(target)) buffer[bpos++] = 's'; - - // sethost - reed/apples - // if (IsHelperIrcu(target)) buffer[bpos++] = 'h'; - if (IsSetHost(target)) buffer[bpos++] = 'h'; - - if (IsService(target)) buffer[bpos++] = 'k'; - if (IsDeaf(target)) buffer[bpos++] = 'd'; - if (target->handle_info) buffer[bpos++] = 'r'; - if (IsHiddenHost(target)) buffer[bpos++] = 'x'; if (IsGagged(target)) buffer_cat(" (gagged)"); if (IsRegistering(target)) buffer_cat(" (registered account)"); buffer[bpos] = 0; @@ -1810,10 +2187,13 @@ static MODCMD_FUNC(cmd_whois) #endif reply("OSMSG_WHOIS_SERVER", target->uplink->name); reply("OSMSG_WHOIS_ACCOUNT", (target->handle_info ? target->handle_info->handle : "Not authenticated")); + + reply("OSMSG_WHOIS_PRIVS", client_report_privs(target)); + intervalString(buffer, now - target->timestamp, user->handle_info); reply("OSMSG_WHOIS_NICK_AGE", buffer); if (target->channels.used <= MAX_CHANNELS_WHOIS) - opserv_ison(user, target, "OSMSG_WHOIS_CHANNELS"); + opserv_ison(cmd->parent->bot, user, target, "OSMSG_WHOIS_CHANNELS"); else reply("OSMSG_WHOIS_HIDECHANS"); return 1; @@ -1914,16 +2294,6 @@ static MODCMD_FUNC(cmd_stats_bad) { return 1; } -static MODCMD_FUNC(cmd_stats_glines) { - reply("OSMSG_GLINE_COUNT", gline_count()); - return 1; -} - -static MODCMD_FUNC(cmd_stats_shuns) { - reply("OSMSG_SHUN_COUNT", shun_count()); - return 1; -} - static void trace_links(struct userNode *bot, struct userNode *user, struct server *server, unsigned int depth) { unsigned int nn, pos; @@ -2019,7 +2389,7 @@ static MODCMD_FUNC(cmd_stats_network2) { #endif tbl.contents[nn][1] = buffer; ofs = strlen(buffer) + 1; - intervalString(buffer + ofs, now - server->link, user->handle_info); + intervalString(buffer + ofs, now - server->link_time, user->handle_info); if (server->self_burst) strcat(buffer + ofs, " Bursting"); tbl.contents[nn][2] = buffer + ofs; @@ -2133,13 +2503,20 @@ static MODCMD_FUNC(cmd_stats_alerts) { dict_iterator_t it; struct opserv_user_alert *alert; const char *reaction; + char t_buffer[INTERVALLEN]; + char expire_buffer[30]; + char *m = NULL; - reply("OSMSG_ALERTS_LIST"); + if(argc > 1) + m = unsplit_string(argv + 1, argc - 1, NULL); + reply("OSMSG_ALERTS_LIST", m ? m : "*"); reply("OSMSG_ALERTS_BAR"); reply("OSMSG_ALERTS_HEADER"); reply("OSMSG_ALERTS_BAR"); for (it = dict_first(opserv_user_alerts); it; it = iter_next(it)) { alert = iter_data(it); + if(m && (!match_ircglob(alert->text_discrim, m) && strcasecmp(alert->owner, m) && strcasecmp(iter_key(it), m))) + continue; /* not a match to requested filter */ switch (alert->reaction) { case REACT_NOTICE: reaction = "notice"; break; case REACT_KILL: reaction = "kill"; break; @@ -2147,10 +2524,22 @@ static MODCMD_FUNC(cmd_stats_alerts) { case REACT_GLINE: reaction = "gline"; break; case REACT_TRACK: reaction = "track"; break; case REACT_SHUN: reaction = "shun"; break; + case REACT_SVSJOIN: reaction = "svsjoin"; break; + case REACT_SVSPART: reaction = "svspart"; break; + case REACT_VERSION: reaction = "version"; break; + case REACT_MARK: reaction = "mark"; break; default: reaction = ""; break; } reply("OSMSG_ALERT_IS", iter_key(it), reaction, alert->owner); + if (alert->expire) { + strftime(expire_buffer, sizeof(expire_buffer), "%Y-%m-%d %H:%M:%S %z", localtime(&alert->expire)); + reply("OSMSG_ALERT_EXPIRE", expire_buffer); + } reply("OSMSG_ALERTS_DESC", alert->text_discrim); + if (alert->last > 0) + reply("OSMSG_ALERTS_LAST", intervalString(t_buffer, now - alert->last, user->handle_info)); + else + reply("OSMSG_ALERTS_LAST", "Never"); } reply("OSMSG_ALERT_END"); return 1; @@ -2280,7 +2669,7 @@ opserv_add_reserve(struct svccmd *cmd, struct userNode *user, const char *nick, return NULL; } } - if ((resv = AddClone(nick, ident, host, desc))) { + if ((resv = AddLocalUser(nick, ident, host, desc, "+i"))) { dict_insert(opserv_reserved_nick_dict, resv->nick, resv); } return resv; @@ -2352,7 +2741,7 @@ opserv_part_channel(void *data) static int alert_check_user(const char *key, void *data, void *extra); static int -opserv_new_user_check(struct userNode *user) +opserv_new_user_check(struct userNode *user, UNUSED_ARG(void *extra)) { struct opserv_hostinfo *ohi; struct gag_entry *gag; @@ -2364,11 +2753,11 @@ opserv_new_user_check(struct userNode *user) /* Check for alerts, and stop if we find one that kills them. */ if (dict_foreach(opserv_user_alerts, alert_check_user, user)) - return 1; + return 0; /* Gag them if appropriate. */ for (gag = gagList; gag; gag = gag->next) { - if (user_matches_glob(user, gag->mask, MATCH_USENICK)) { + if (user_matches_glob(user, gag->mask, MATCH_USENICK, 0)) { gag_helper_func(user, NULL); break; } @@ -2396,7 +2785,7 @@ opserv_new_user_check(struct userNode *user) } if (checkDefCon(DEFCON_NO_NEW_CLIENTS)) { - irc_kill(opserv, user, DefConGlineReason); + DelUser(user, opserv, 1, DefConGlineReason); return 0; } @@ -2439,7 +2828,7 @@ opserv_new_user_check(struct userNode *user) } static void -opserv_user_cleanup(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why)) +opserv_user_cleanup(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why), UNUSED_ARG(void *extra)) { struct opserv_hostinfo *ohi; char addr[IRC_NTOP_MAX_SIZE]; @@ -2527,6 +2916,31 @@ opserv_channel_delete(struct chanNode *chan) timeq_del(0, opserv_part_channel, chan, TIMEQ_IGNORE_WHEN); } +static void +opserv_notice_handler(struct userNode *user, struct userNode *bot, const char *text, UNUSED_ARG(int server_qualified)) +{ + char *cmd; + char *textb; + + textb = strdup(text); + + /* if its a version reply, do an alert check (only alerts with version=something) */ + if(bot == opserv) { + if(text[0] == '\001') { + textb++; + cmd = mysep(&textb, " "); + if(cmd && !irccasecmp(cmd, "VERSION")) { + char *version = mysep(&textb, "\n"); + if(!version) + version = ""; + /* opserv_debug("Opserv got CTCP VERSION Notice from %s: %s", user->nick, version); */ + /* user->version_reply = strdup(version); done in parse-p10.c now */ + dict_foreach(opserv_user_alerts, alert_check_user, user); + } + } + } +} + static int opserv_join_check(struct modeNode *mNode) { @@ -2539,7 +2953,7 @@ opserv_join_check(struct modeNode *mNode) dict_foreach(opserv_channel_alerts, alert_check_user, user); - if (channel->bad_channel) { + if (opserv && channel->bad_channel) { opserv_debug("Found $b%s$b in bad-word channel $b%s$b; removing the user.", user->nick, channel->name); if (channel->name[0] != '#') DelUser(user, opserv, 1, "OSMSG_ILLEGAL_KILL_REASON"); @@ -2565,7 +2979,7 @@ opserv_join_check(struct modeNode *mNode) struct mod_chanmode change; mod_chanmode_init(&change); channel->join_flooded = 1; - if (opserv_conf.join_flood_moderate && (channel->members.used > opserv_conf.join_flood_moderate_threshold)) { + if (opserv && opserv_conf.join_flood_moderate && (channel->members.used > opserv_conf.join_flood_moderate_threshold)) { if (!GetUserMode(channel, opserv)) { /* If we aren't in the channel, join it. */ change.args[0].mode = MODE_CHANOP; @@ -2609,7 +3023,6 @@ opserv_add_bad_word(struct svccmd *cmd, struct userNode *user, const char *new_b reply("OSMSG_BAD_NUKING", orig_bad); string_list_delete(opserv_bad_words, bad_idx); bad_idx--; - free(orig_bad); } return 1; } @@ -2743,7 +3156,7 @@ rank_outside_rec(struct route *route, char *server, int count) return(max + 1); } else { - log_module(MAIN_LOG, LOG_ERROR, "routing struct rank_outsideness() couldnt find %s", server); + log_module(MAIN_LOG, LOG_WARNING, "routing struct rank_outsideness() couldnt find %s", server); return 0; } } @@ -2816,7 +3229,7 @@ change_route_uplinks(struct route *route) strcpy(lastserver, servicename); rptr = find_routeList_server(route, self->uplink->name); if(!rptr) { - log_module(MAIN_LOG, LOG_ERROR, "Cannot convert routing map to center: My uplink is not on the map! Marking map as uncentered."); + log_module(MAIN_LOG, LOG_WARNING, "Cannot convert routing map to center: My uplink is not on the map! Marking map as uncentered."); route->centered = false; return false; } @@ -2863,7 +3276,7 @@ activate_routing(struct svccmd *cmd, struct userNode *user, char *plan_name) /* since it doesnt exist, remove the active setting */ dict_remove(opserv_routing_plan_options, plan_name); } - log_module(MAIN_LOG, LOG_ERROR, "activate_routing() couldnt find active routing plan!"); + log_module(MAIN_LOG, LOG_WARNING, "activate_routing() couldnt find active routing plan!"); return 0; } } @@ -2888,6 +3301,8 @@ activate_routing(struct svccmd *cmd, struct userNode *user, char *plan_name) /* Delete the existing active route */ wipe_route_list(opserv_route); + if(!rp || !rp->servers) + return 1; for(it = dict_first(rp->servers); it; it = iter_next(it)) { const char* servername = iter_key(it); struct routingPlanServer *rps = iter_data(it), @@ -2922,15 +3337,27 @@ activate_routing(struct svccmd *cmd, struct userNode *user, char *plan_name) add_routestruct_server(opserv_route, servername, rps->port, uplink, NULL); } if(change_route_uplinks(opserv_route)) + { return 1; + } else if(user) { reply("OSMSG_ROUTING_ACTIVATION_ERROR"); activate_routing(cmd, user, "*"); return 0; } + /* routing activation failed but we dont do anything? */ return 1; } + +void routing_init() +{ + activate_routing(NULL, NULL, NULL); + + /* start auto-routing system */ + reroute_timer_reset(0); +} + /******************************************************* * Functions to handle online route configuration via opserv */ @@ -2997,6 +3424,10 @@ routing_connect_server(char *server, int port, struct server *to) wc->server = strdup(server); wc->target = strdup(to->name); + /* Just to make sure there isn't one left hanging + * if 2 connections are attempted at once.. + * */ + routing_delete_connect_timer(server); dict_insert(opserv_waiting_connections, strdup(server), wc); timeq_add(now + ROUTING_CONNECT_TIMEOUT, routing_connect_timeout, wc); @@ -3156,6 +3587,9 @@ static MODCMD_FUNC(cmd_reroute) { * and setup timer. */ void reroute_timer(void *data) { + /* Delete any other timers such as this one.. */ + timeq_del(0, reroute_timer, NULL, TIMEQ_IGNORE_DATA | TIMEQ_IGNORE_WHEN); + if(!opserv_route || !opserv_route->servers) return; /* no active route */ char *retry_period = dict_find(opserv_routing_plan_options, "RETRY_PERIOD", NULL); @@ -3165,6 +3599,9 @@ void reroute_timer(void *data) { if(freq < 1) return; /* retry_period set to 0, disable */ + /* opserv_debug("Reroute timer checking reroute"); */ + log_module(MAIN_LOG, LOG_DEBUG, "Reroute timer checking reroute()"); + /* Do the reroute C attempt */ if(data) reroute(opserv_route, NULL, NULL, "C"); @@ -3216,7 +3653,7 @@ void routing_karma_timer(void *data) { routing_change_karma(rps, iter_key(it), KARMA_ENTROPE); /* give an additonal +KARMA_RELIABLE to servers that * have been linked at least KARMA_TIMER seconds. */ - if(server && (server->link < (now - KARMA_TIMER) ) ) { + if(server && (server->link_time < (now - KARMA_TIMER) ) ) { routing_change_karma(rps, iter_key(it), KARMA_RELIABLE); } } @@ -3338,7 +3775,7 @@ routing_handle_connect_failure(struct server *source, char *server, char *messag char *active; struct routingPlan *rp; struct routingPlanServer *rps; - log_module(MAIN_LOG, LOG_ERROR, "Failed to connect %s to %s: %s", server, source->name, message); + log_module(MAIN_LOG, LOG_WARNING, "Failed to connect %s to %s: %s", server, source->name, message); /* remove the waiting connection n timeq */ routing_delete_connect_timer(server); /* check if routing is active.. */ @@ -3370,10 +3807,23 @@ routing_handle_connect_failure(struct server *source, char *server, char *messag /* Delete any existing timers, and start the timer again * using the passed time for the first run. * - this is called during a retry_period change - * before it has saved the new value. */ + * before it has saved the new value. + * + * If time is 0, lookup the interval. */ void reroute_timer_reset(unsigned int time) { - timeq_del(0, reroute_timer, NULL, TIMEQ_IGNORE_DATA & TIMEQ_IGNORE_WHEN); + timeq_del(0, reroute_timer, NULL, TIMEQ_IGNORE_DATA | TIMEQ_IGNORE_WHEN); + if(time == 0) { + if(!opserv_route || !opserv_route->servers) + return; /* no active route */ + char *retry_period = dict_find(opserv_routing_plan_options, "RETRY_PERIOD", NULL); + if(!retry_period) + return; /* retry_period invalid */ + time = atoi(retry_period); + if(time < 1) + return; /* retry_period set to 0, disable */ + + } timeq_add(now + time, reroute_timer, "run"); } @@ -3446,21 +3896,34 @@ static MODCMD_FUNC(cmd_stats_routing_plans) { dict_iterator_t rpit; dict_iterator_t it; struct routingPlan *rp; - reply("OSMSG_ROUTINGPLAN_LIST"); - reply("OSMSG_ROUTINGPLAN_BAR"); - for(rpit = dict_first(opserv_routing_plans); rpit; rpit = iter_next(rpit)) { - const char* name = iter_key(rpit); - rp = iter_data(rpit); - reply("OSMSG_ROUTINGPLAN_NAME", name); - for(it = dict_first(rp->servers); it; it = iter_next(it)) { - const char* servername = iter_key(it); - struct routingPlanServer *rps = iter_data(it); - reply("OSMSG_ROUTINGPLAN_SERVER", servername, rps->port, rps->uplink, rps->karma, rps->offline? "offline" : "online", rps->secondaryuplink ? rps->secondaryuplink : "None"); - } + if(argc > 1) { + reply("OSMSG_ROUTINGPLAN"); + reply("OSMSG_ROUTINGPLAN_BAR"); + for(rpit = dict_first(opserv_routing_plans); rpit; rpit = iter_next(rpit)) { + const char* name = iter_key(rpit); + rp = iter_data(rpit); + if(match_ircglob(name, argv[1])) { + reply("OSMSG_ROUTINGPLAN_NAME", name); + for(it = dict_first(rp->servers); it; it = iter_next(it)) { + const char* servername = iter_key(it); + struct routingPlanServer *rps = iter_data(it); + reply("OSMSG_ROUTINGPLAN_SERVER", servername, rps->port, rps->uplink, rps->karma, rps->offline? "offline" : "online", rps->secondaryuplink ? rps->secondaryuplink : "None"); + } + } + } + reply("OSMSG_ROUTINGPLAN_END"); + } + else { + reply("OSMSG_ROUTINGPLAN_LIST_HEAD"); + reply("OSMSG_ROUTINGPLAN_BAR"); + for(rpit = dict_first(opserv_routing_plans); rpit; rpit = iter_next(rpit)) { + const char* name = iter_key(rpit); + reply("OSMSG_ROUTINGPLAN_LIST", name); + } + reply("OSMSG_ROUTINGPLAN_END"); + route_show_options(cmd, user); } - reply("OSMSG_ROUTINGPLAN_END"); - route_show_options(cmd, user); return 1; } @@ -3717,17 +4180,17 @@ static MODCMD_FUNC(cmd_addbad) /* Scan for existing channels that match the new bad word. */ if (!bad_found) { for (it = dict_first(channels); it; it = iter_next(it)) { - struct chanNode *channel = iter_data(it); + struct chanNode *chan = iter_data(it); - if (!opserv_bad_channel(channel->name)) + if (!opserv_bad_channel(chan->name)) continue; - channel->bad_channel = 1; - if (channel->name[0] == '#') - opserv_shutdown_channel(channel, "OSMSG_ILLEGAL_REASON"); + chan->bad_channel = 1; + if (chan->name[0] == '#') + opserv_shutdown_channel(chan, "OSMSG_ILLEGAL_REASON"); else { unsigned int nn; - for (nn=0; nnmembers.used; nn++) { - struct userNode *user = channel->members.list[nn]->user; + for (nn=0; nnmembers.used; nn++) { + struct userNode *user = chan->members.list[nn]->user; DelUser(user, cmd->parent->bot, 1, "OSMSG_ILLEGAL_KILL_REASON"); } } @@ -3888,7 +4351,7 @@ static MODCMD_FUNC(cmd_edittrust) return 0; } count = strtoul(argv[2], &tmp, 10); - if (!count || *tmp) { + if (*tmp != '\0') { reply("OSMSG_BAD_NUMBER", argv[2]); return 0; } @@ -3967,7 +4430,7 @@ static MODCMD_FUNC(cmd_clone) reply("OSMSG_NOT_A_HOSTMASK"); return 0; } - if (!(clone = AddClone(argv[2], ident, argv[3]+i, userinfo))) { + if (!(clone = AddLocalUser(argv[2], ident, argv[3]+i, userinfo, "+i"))) { reply("OSMSG_CLONE_FAILED", argv[2]); return 0; } @@ -4168,7 +4631,7 @@ int add_reserved(const char *key, void *data, void *extra) log_module(OS_LOG, LOG_ERROR, "Missing description for reserve of %s", key); return 0; } - if ((reserve = AddClone(key, ident, hostname, desc))) { + if ((reserve = AddLocalUser(key, ident, hostname, desc, "+i"))) { reserve->modes |= FLAGS_PERSISTENT; dict_insert(extra, reserve->nick, reserve); } @@ -4284,7 +4747,7 @@ add_gag_helper(const char *key, void *data, UNUSED_ARG(void *extra)) } static struct opserv_user_alert * -opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_reaction reaction, const char *text_discrim) +opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_reaction reaction, const char *text_discrim, int last, int expire) { unsigned int wordc; char *wordv[MAXNUMPARAMS], *discrim_copy; @@ -4298,10 +4761,15 @@ opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_react alert = malloc(sizeof(*alert)); alert->owner = strdup(req->handle_info ? req->handle_info->handle : req->nick); alert->text_discrim = strdup(text_discrim); + alert->last = last; discrim_copy = strdup(text_discrim); /* save a copy of the discrim */ wordc = split_line(discrim_copy, false, ArrayLength(wordv), wordv); alert->discrim = opserv_discrim_create(req, opserv, wordc, wordv, 0); - if (!alert->discrim) { + alert->expire = expire; + /* Check for missing required criteria or broken records */ + if (!alert->discrim || (reaction==REACT_SVSJOIN && !alert->discrim->chantarget) || + (reaction==REACT_SVSPART && !alert->discrim->chantarget) || + (reaction==REACT_MARK && !alert->discrim->mark)) { free(alert->text_discrim); free(discrim_copy); free(alert); @@ -4318,10 +4786,15 @@ opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_react * max_channels would have to be checked on /part, which we do not * yet do, and which seems of questionable value. */ - if (alert->discrim->channel || alert->discrim->min_channels) + if (alert->discrim->channel_count || alert->discrim->min_channels) dict_insert(opserv_channel_alerts, name_dup, alert); if (alert->discrim->mask_nick) dict_insert(opserv_nick_based_alerts, name_dup, alert); + if (alert->discrim->accountmask) + dict_insert(opserv_account_based_alerts, name_dup, alert); + + if (alert->expire) + timeq_add(alert->expire, alert_expire, (void*)name_dup); return alert; } @@ -4346,6 +4819,8 @@ static int add_user_alert(const char *key, void *data, UNUSED_ARG(void *extra)) { dict_t alert_dict; + char *str; + int last = 0, expire = 0; const char *discrim, *react, *owner; opserv_alert_reaction reaction; struct opserv_user_alert *alert; @@ -4356,6 +4831,13 @@ add_user_alert(const char *key, void *data, UNUSED_ARG(void *extra)) } discrim = database_get_data(alert_dict, KEY_DISCRIM, RECDB_QSTRING); react = database_get_data(alert_dict, KEY_REACTION, RECDB_QSTRING); + str = database_get_data(alert_dict, KEY_LAST, RECDB_QSTRING); + if (str) + last = atoi(str); + str = database_get_data(alert_dict, KEY_EXPIRE, RECDB_QSTRING); + if (str) + expire = atoi(str); + if (!react || !irccasecmp(react, "notice")) reaction = REACT_NOTICE; else if (!irccasecmp(react, "kill")) @@ -4370,11 +4852,19 @@ add_user_alert(const char *key, void *data, UNUSED_ARG(void *extra)) reaction = REACT_TRACK; else if (!irccasecmp(react, "shun")) reaction = REACT_SHUN; + else if (!irccasecmp(react, "svsjoin")) + reaction = REACT_SVSJOIN; + else if (!irccasecmp(react, "svspart")) + reaction = REACT_SVSPART; + else if (!irccasecmp(react, "version")) + reaction = REACT_VERSION; + else if (!irccasecmp(react, "mark")) + reaction = REACT_MARK; else { log_module(OS_LOG, LOG_ERROR, "Invalid reaction %s for alert %s.", react, key); return 0; } - alert = opserv_add_user_alert(opserv, key, reaction, discrim); + alert = opserv_add_user_alert(opserv, key, reaction, discrim, last, expire); if (!alert) { log_module(OS_LOG, LOG_ERROR, "Unable to create alert %s from database.", key); return 0; @@ -4644,6 +5134,8 @@ opserv_saxdb_write(struct saxdb_context *ctx) saxdb_start_record(ctx, iter_key(it), 0); saxdb_write_string(ctx, KEY_DISCRIM, alert->text_discrim); saxdb_write_string(ctx, KEY_OWNER, alert->owner); + saxdb_write_int(ctx, KEY_LAST, alert->last); + saxdb_write_int(ctx, KEY_EXPIRE, alert->expire); switch (alert->reaction) { case REACT_NOTICE: reaction = "notice"; break; case REACT_KILL: reaction = "kill"; break; @@ -4651,6 +5143,10 @@ opserv_saxdb_write(struct saxdb_context *ctx) case REACT_GLINE: reaction = "gline"; break; case REACT_TRACK: reaction = "track"; break; case REACT_SHUN: reaction = "shun"; break; + case REACT_SVSJOIN: reaction = "svsjoin"; break; + case REACT_SVSPART: reaction = "svspart"; break; + case REACT_VERSION: reaction = "version"; break; + case REACT_MARK: reaction = "mark"; break; default: reaction = NULL; log_module(OS_LOG, LOG_ERROR, "Invalid reaction type %d for alert %s (while writing database).", alert->reaction, iter_key(it)); @@ -4824,6 +5320,8 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int discrim->intra_scmp = 3; else discrim->mask_info = argv[i]; + } else if (irccasecmp(argv[i], "version") == 0) { + discrim->mask_version = argv[++i]; } else if (irccasecmp(argv[i], "server") == 0) { discrim->server = argv[++i]; } else if (irccasecmp(argv[i], "ip") == 0) { @@ -4839,6 +5337,30 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int } discrim->accountmask = argv[++i]; discrim->authed = 1; + } else if (irccasecmp(argv[i], "marked") == 0) { + discrim->mask_mark = argv[++i]; + } else if (irccasecmp(argv[i], "chantarget") == 0) { + if(!IsChannelName(argv[i+1])) { + send_message(user, bot, "MSG_NOT_CHANNEL_NAME"); + goto fail; + } + discrim->chantarget = argv[++i]; + } else if (irccasecmp(argv[i], "checkrestrictions") == 0) { + i++; + if (true_string(argv[i])) { + discrim->checkrestrictions = 1; + } else if (false_string(argv[i])) { + discrim->checkrestrictions = 0; + } else { + send_message(user, bot, "MSG_INVALID_BINARY", argv[i]); + goto fail; + } + } else if (irccasecmp(argv[i], "mark") == 0) { + if(!is_valid_mark(argv[i+1])) { + send_message(user, bot, "OSMSG_MARK_INVALID"); + goto fail; + } + discrim->mark = argv[++i]; } else if (irccasecmp(argv[i], "authed") == 0) { i++; /* true_string and false_string are macros! */ if (true_string(argv[i])) { @@ -4889,52 +5411,65 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int } } else if (irccasecmp(argv[i], "duration") == 0) { discrim->duration = ParseInterval(argv[++i]); - } else if (irccasecmp(argv[i], "channel") == 0) { - for (j=0, i++; ; j++) { - switch (argv[i][j]) { - case '#': - goto find_channel; - case '-': - discrim->chan_no_modes |= MODE_CHANOP | MODE_HALFOP | MODE_VOICE; - break; - case '+': - discrim->chan_req_modes |= MODE_VOICE; - discrim->chan_no_modes |= MODE_CHANOP; - discrim->chan_no_modes |= MODE_HALFOP; - break; - case '%': - discrim->chan_req_modes |= MODE_HALFOP; - discrim->chan_no_modes |= MODE_CHANOP; - discrim->chan_no_modes |= MODE_VOICE; - break; - case '@': - discrim->chan_req_modes |= MODE_CHANOP; - break; - case '\0': - send_message(user, bot, "MSG_NOT_CHANNEL_NAME"); - goto fail; - } + } else if (irccasecmp(argv[i], "channel") == 0) { + if(discrim->channel_count == DISCRIM_MAX_CHANS) + { + send_message(user, opserv, "OSMSG_TRACE_MAX_CHANNELS", DISCRIM_MAX_CHANS); + goto fail; + } + + for (j=0, i++; ; j++) { + switch (argv[i][j]) { + case '#': + goto find_channel; + case '-': + discrim->chan_no_modes[discrim->channel_count] |= MODE_CHANOP | MODE_VOICE; + break; + case '+': + discrim->chan_req_modes[discrim->channel_count] |= MODE_VOICE; + discrim->chan_no_modes[discrim->channel_count] |= MODE_CHANOP; + discrim->chan_no_modes[discrim->channel_count] |= MODE_HALFOP; + break; + case '%': + discrim->chan_req_modes[discrim->channel_count] |= MODE_HALFOP; + discrim->chan_no_modes[discrim->channel_count] |= MODE_CHANOP; + discrim->chan_no_modes[discrim->channel_count] |= MODE_VOICE; + break; + case '@': + discrim->chan_req_modes[discrim->channel_count] |= MODE_CHANOP; + break; + case '\0': + send_message(user, opserv, "MSG_NOT_CHANNEL_NAME"); + goto fail; } - find_channel: - discrim->chan_no_modes &= ~discrim->chan_req_modes; - if (!(discrim->channel = GetChannel(argv[i]+j))) { - /* secretly "allow_channel" now means "if a channel name is - * specified, require that it currently exist" */ - if (allow_channel) { - send_message(user, bot, "MSG_CHANNEL_UNKNOWN", argv[i]); - goto fail; - } else { - discrim->channel = AddChannel(argv[i]+j, now, NULL, NULL, NULL); - } + } + find_channel: + discrim->chan_no_modes[discrim->channel_count] &= ~discrim->chan_req_modes[discrim->channel_count]; + if (!(discrim->channels[discrim->channel_count] = GetChannel(argv[i]+j))) { + /* secretly "allow_channel" now means "if a channel name is + * specified, require that it currently exist" */ + if (allow_channel) { + send_message(user, opserv, "MSG_CHANNEL_UNKNOWN", argv[i]); + goto fail; + } else { + discrim->channels[discrim->channel_count] = AddChannel(argv[i]+j, now, NULL, NULL, NULL); } - LockChannel(discrim->channel); - } else if (irccasecmp(argv[i], "numchannels") == 0) { - discrim->min_channels = discrim->max_channels = strtoul(argv[++i], NULL, 10); - } else if (irccasecmp(argv[i], "limit") == 0) { - discrim->limit = strtoul(argv[++i], NULL, 10); + } + LockChannel(discrim->channels[discrim->channel_count]); + discrim->channel_count++; + } else if (irccasecmp(argv[i], "numchannels") == 0) { + discrim->min_channels = discrim->max_channels = strtoul(argv[++i], NULL, 10); + } else if (irccasecmp(argv[i], "limit") == 0) { + discrim->limit = strtoul(argv[++i], NULL, 10); } else if (irccasecmp(argv[i], "reason") == 0) { discrim->reason = strdup(unsplit_string(argv+i+1, argc-i-1, NULL)); i = argc; + } else if (irccasecmp(argv[i], "notice_target") == 0 || irccasecmp(argv[i], "target") == 0) { + if (!IsChannelName(argv[i + 1])) { + send_message(user, opserv, "MSG_NOT_CHANNEL_NAME"); + goto fail; + } + discrim->notice_target = argv[++i]; } else if (irccasecmp(argv[i], "last") == 0) { discrim->min_ts = now - ParseInterval(argv[++i]); } else if ((irccasecmp(argv[i], "linked") == 0) @@ -4953,7 +5488,7 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int discrim->max_ts = now - (ParseInterval(cmp+1) - 1); } } else { - discrim->min_ts = now - ParseInterval(cmp+2); + discrim->min_ts = now - ParseInterval(cmp); } } else if (irccasecmp(argv[i], "access") == 0) { const char *cmp = argv[++i]; @@ -4973,15 +5508,21 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int discrim->min_level = strtoul(cmp+1, NULL, 0) + 1; } } else { - discrim->min_level = strtoul(cmp+2, NULL, 0); + discrim->min_level = strtoul(cmp, NULL, 0); + } + } else if (irccasecmp(argv[i], "abuse") == 0) { + const char *abuse_what = argv[++i]; + if (irccasecmp(abuse_what, "opers") == 0) { + discrim->match_opers = 1; + } else if (irccasecmp(abuse_what, "trusted") == 0) { + discrim->match_trusted = 1; } - } else if ((irccasecmp(argv[i], "abuse") == 0) - && (irccasecmp(argv[++i], "opers") == 0)) { - discrim->match_opers = 1; } else if (irccasecmp(argv[i], "depth") == 0) { discrim->domain_depth = strtoul(argv[++i], NULL, 0); } else if (irccasecmp(argv[i], "clones") == 0) { discrim->min_clones = strtoul(argv[++i], NULL, 0); + } else if (irccasecmp(argv[i], "modes") == 0) { + discrim->modes = argv[++i]; } else { send_message(user, bot, "MSG_INVALID_CRITERIA", argv[i]); goto fail; @@ -4997,6 +5538,9 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int if (discrim->mask_info && !strcmp(discrim->mask_info, "*")) { discrim->mask_info = 0; } + if (discrim->mask_version && !strcmp(discrim->mask_version, "*")) { + discrim->mask_version = 0; + } if (discrim->mask_host && !discrim->mask_host[strspn(discrim->mask_host, "*.")]) { discrim->mask_host = 0; } @@ -5005,7 +5549,7 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int { if(discrim->mask_nick) { - int err = regcomp(&discrim->regex_nick, discrim->mask_nick, REG_EXTENDED|REG_ICASE|REG_NOSUB); + int err = regcomp(&discrim->regex_nick, discrim->mask_nick, REG_EXTENDED|REG_NOSUB); discrim->has_regex_nick = !err; if(err) { @@ -5019,7 +5563,7 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int if(discrim->mask_ident) { - int err = regcomp(&discrim->regex_ident, discrim->mask_ident, REG_EXTENDED|REG_ICASE|REG_NOSUB); + int err = regcomp(&discrim->regex_ident, discrim->mask_ident, REG_EXTENDED|REG_NOSUB); discrim->has_regex_ident = !err; if(err) { @@ -5033,7 +5577,7 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int if(discrim->mask_host) { - int err = regcomp(&discrim->regex_host, discrim->mask_host, REG_EXTENDED|REG_ICASE|REG_NOSUB); + int err = regcomp(&discrim->regex_host, discrim->mask_host, REG_EXTENDED|REG_NOSUB); discrim->has_regex_host = !err; if(err) { @@ -5047,7 +5591,7 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int if(discrim->mask_info) { - int err = regcomp(&discrim->regex_info, discrim->mask_info, REG_EXTENDED|REG_ICASE|REG_NOSUB); + int err = regcomp(&discrim->regex_info, discrim->mask_info, REG_EXTENDED|REG_NOSUB); discrim->has_regex_info = !err; if(err) { @@ -5058,6 +5602,20 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int goto regfail; } } + + if(discrim->mask_version) + { + int err = regcomp(&discrim->regex_version, discrim->mask_version, REG_EXTENDED|REG_NOSUB); + discrim->has_regex_version = !err; + if(err) + { + char buff[256]; + buff[regerror(err, &discrim->regex_version, buff, sizeof(buff))] = 0; + + send_message(user, bot, "OSMSG_INVALID_REGEX", discrim->mask_version, buff, err); + goto regfail; + } + } } return discrim; @@ -5083,7 +5641,7 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int static int discrim_match(discrim_t discrim, struct userNode *user) { - unsigned int access; + unsigned int level, i; char *scmp=NULL, *dcmp=NULL; if ((user->timestamp < discrim->min_ts) @@ -5095,20 +5653,23 @@ discrim_match(discrim_t discrim, struct userNode *user) || (discrim->info_space == 0 && user->info[0] == ' ') || (discrim->info_space == 1 && user->info[0] != ' ') || (discrim->server && !match_ircglob(user->uplink->name, discrim->server)) + || (discrim->mask_mark && (!user->mark || !match_ircglob(user->mark, discrim->mask_mark))) || (discrim->accountmask && (!user->handle_info || !match_ircglob(user->handle_info->handle, discrim->accountmask))) || (discrim->ip_mask_bits && !irc_check_mask(&user->ip, &discrim->ip_mask, discrim->ip_mask_bits)) ) return 0; - if (discrim->channel && !GetUserMode(discrim->channel, user)) - return 0; + for(i = 0; i < discrim->channel_count; i++) + if (!GetUserMode(discrim->channels[i], user)) + return 0; if(discrim->use_regex) { if((discrim->has_regex_nick && regexec(&discrim->regex_nick, user->nick, 0, 0, 0)) || (discrim->has_regex_ident && regexec(&discrim->regex_ident, user->ident, 0, 0, 0)) || (discrim->has_regex_host && regexec(&discrim->regex_host, user->hostname, 0, 0, 0)) - || (discrim->has_regex_info && regexec(&discrim->regex_info, user->info, 0, 0, 0))) { + || (discrim->has_regex_info && regexec(&discrim->regex_info, user->info, 0, 0, 0)) + || (discrim->has_regex_version && (!user->version_reply || regexec(&discrim->regex_version, user->version_reply, 0, 0, 0)))) { return 0; } } @@ -5117,7 +5678,8 @@ discrim_match(discrim_t discrim, struct userNode *user) if ((discrim->mask_nick && !match_ircglob(user->nick, discrim->mask_nick)) || (discrim->mask_ident && !match_ircglob(user->ident, discrim->mask_ident)) || (discrim->mask_host && !match_ircglob(user->hostname, discrim->mask_host)) - || (discrim->mask_info && !match_ircglob(user->info, discrim->mask_info))) { + || (discrim->mask_info && !match_ircglob(user->info, discrim->mask_info)) + || (discrim->mask_version && (!user->version_reply || !match_ircglob(user->version_reply, discrim->mask_version))) ) { return 0; } } @@ -5147,9 +5709,57 @@ discrim_match(discrim_t discrim, struct userNode *user) return 0; } - access = user->handle_info ? user->handle_info->opserv_level : 0; - if ((access < discrim->min_level) - || (access > discrim->max_level)) { + if (discrim->modes) { + unsigned int ii, matches = 0; + for (ii = 0; ii < strlen(discrim->modes); ii++) { + switch(discrim->modes[ii]) { + case 'O': + if(IsOper(user)) matches++; + break; + case 'o': + if(IsOper(user)) matches++; + break; + case 'i': + if(IsInvisible(user)) matches++; + break; + case 'w': + if(IsWallOp(user)) matches++; + break; + case 'd': + if(IsDeaf(user)) matches++; + break; + case 'k': + if(IsService(user)) matches++; + break; + case 'g': + if(IsGlobal(user)) matches++; + break; + case 'h': + if(IsSetHost(user)) matches++; + break; + case 'B': + if(IsBotM(user)) matches++; + break; + case 'n': + if(IsHideChans(user)) matches++; + break; + case 'I': + if(IsHideIdle(user)) matches++; + break; + case 'X': + if(IsXtraOp(user)) matches++; + break; + case 'x': + if(IsHiddenHost(user)) matches++; + break; + } + } + if (matches != strlen(discrim->modes)) return 0; + } + + level = user->handle_info ? user->handle_info->opserv_level : 0; + if ((level < discrim->min_level) + || (level > discrim->max_level)) { return 0; } if (discrim->min_clones > 1) { @@ -5163,23 +5773,38 @@ discrim_match(discrim_t discrim, struct userNode *user) static unsigned int opserv_discrim_search(discrim_t discrim, discrim_search_func dsf, void *data) { - unsigned int nn, count; + unsigned int nn, count, match; struct userList matched; userList_init(&matched); /* Try most optimized search methods first */ - if (discrim->channel) { - for (nn=0; - (nn < discrim->channel->members.used) - && (matched.used < discrim->limit); - nn++) { - struct modeNode *mn = discrim->channel->members.list[nn]; - if (((mn->modes & discrim->chan_req_modes) != discrim->chan_req_modes) - || ((mn->modes & discrim->chan_no_modes) != 0)) { + if (discrim->channel_count) + { + for (nn=0; (nn < discrim->channels[0]->members.used) + && (matched.used < discrim->limit); + nn++) { + struct modeNode *mn = discrim->channels[0]->members.list[nn]; + + if (((mn->modes & discrim->chan_req_modes[0]) != discrim->chan_req_modes[0]) + || ((mn->modes & discrim->chan_no_modes[0]) != 0)) { continue; } - if (discrim_match(discrim, mn->user)) { - userList_append(&matched, mn->user); + if ((match = discrim_match(discrim, mn->user))) + { + unsigned int i; + + for (i = 1; i < discrim->channel_count; i++) { + struct modeNode *mn2 = GetUserMode(discrim->channels[i], mn->user); + + if (((mn2->modes & discrim->chan_req_modes[i]) != discrim->chan_req_modes[i]) + || ((mn2->modes & discrim->chan_no_modes[i]) != 0)) { + match = 0; + break; + } + } + + if (match) + userList_append(&matched, mn->user); } } } else if (discrim->ip_mask_bits == 128) { @@ -5241,6 +5866,30 @@ trace_print_func(struct userNode *match, void *extra) return 0; } +static int +trace_privmsg_func(struct userNode *match, void *extra) +{ + struct discrim_and_source *das = extra; + char *reason; + if (das->discrim->reason) { + reason = das->discrim->reason; + irc_privmsg_user(opserv, match, reason); + } + return 0; +} + +static int +trace_notice_func(struct userNode *match, void *extra) +{ + struct discrim_and_source *das = extra; + char *reason; + if (das->discrim->reason) { + reason = das->discrim->reason; + irc_notice_user(opserv, match, reason); + } + return 0; +} + static int trace_count_func(UNUSED_ARG(struct userNode *match), UNUSED_ARG(void *extra)) { @@ -5248,12 +5897,32 @@ trace_count_func(UNUSED_ARG(struct userNode *match), UNUSED_ARG(void *extra)) } static int -is_oper_victim(struct userNode *user, struct userNode *target, int match_opers) +is_oper_victim(struct userNode *user, struct userNode *target, int match_opers, int check_ip) { - return !(IsService(target) - || (!match_opers && IsOper(target)) - || (target->handle_info - && target->handle_info->opserv_level > user->handle_info->opserv_level)); + unsigned char is_victim; + unsigned int nn; + + is_victim = !(IsService(target) + || (!match_opers && IsOper(target)) + || (target->handle_info + && target->handle_info->opserv_level > user->handle_info->opserv_level)); + + /* If we don't need an ip check or want to hit opers or the the "cheap" check already disqualified the target, we are done. */ + if (!check_ip || match_opers || !is_victim) + return is_victim; + + for(nn = 0; nn < curr_opers.used; nn++) { + if(memcmp(&curr_opers.list[nn]->ip, &target->ip, sizeof(irc_in_addr_t)) == 0) + return 0; + } + + return 1; +} + +static int +is_trust_victim(struct userNode *target, int match_trusted) +{ + return (match_trusted || !dict_find(opserv_trusted_hosts, irc_ntoa(&target->ip), NULL)); } static int @@ -5261,7 +5930,8 @@ trace_gline_func(struct userNode *match, void *extra) { struct discrim_and_source *das = extra; - if (is_oper_victim(das->source, match, das->discrim->match_opers)) { + + if (is_oper_victim(das->source, match, das->discrim->match_opers, 1) && is_trust_victim(match, das->discrim->match_trusted)) { opserv_block(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration, das->discrim->silent); } @@ -5273,7 +5943,7 @@ trace_shun_func(struct userNode *match, void *extra) { struct discrim_and_source *das = extra; - if (is_oper_victim(das->source, match, das->discrim->match_opers)) { + if (is_oper_victim(das->source, match, das->discrim->match_opers, 1) && is_trust_victim(match, das->discrim->match_trusted)) { opserv_shun(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration); } @@ -5285,7 +5955,7 @@ trace_kill_func(struct userNode *match, void *extra) { struct discrim_and_source *das = extra; - if (is_oper_victim(das->source, match, das->discrim->match_opers)) { + if (is_oper_victim(das->source, match, das->discrim->match_opers, 1)) { char *reason; if (das->discrim->reason) { reason = das->discrim->reason; @@ -5299,6 +5969,92 @@ trace_kill_func(struct userNode *match, void *extra) return 0; } +static int +trace_mark_func(struct userNode *match, void *extra) +{ + struct discrim_and_source *das = extra; + char *mark = das->discrim->mark; + + if(!mark) + return 1; + irc_mark(match, mark); + return 0; +} + +static int +trace_svsjoin_func(struct userNode *match, void *extra) +{ + struct discrim_and_source *das = extra; + + char *channame = das->discrim->chantarget; + int checkrestrictions = das->discrim->checkrestrictions; + struct chanNode *channel; + + if(!channame || !IsChannelName(channame)) { + //reply("MSG_NOT_CHANNEL_NAME"); + return 1; + } + + if (!(channel = GetChannel(channame))) { + channel = AddChannel(channame, now, NULL, NULL, NULL); + } + + if (checkrestrictions) { + if (trace_check_bans(match, channel) == 1) { + return 1; /* found on lamer list */ + } + + if (channel->modes & MODE_INVITEONLY) { + return 1; /* channel is invite only */ + } + + if (channel->limit > 0) { + if (channel->members.used >= channel->limit) { + return 1; /* channel is invite on */ + } + } + + if (*channel->key) { + return 1; /* channel is password protected */ + } + } + + if (GetUserMode(channel, match)) { +// reply("OSMSG_ALREADY_THERE", channel->name); + return 1; + } + irc_svsjoin(opserv, match, channel); + // reply("OSMSG_SVSJOIN_SENT"); + return 0; +} + +static int +trace_svspart_func(struct userNode *match, void *extra) +{ + struct discrim_and_source *das = extra; + char *channame = das->discrim->chantarget; + struct chanNode *channel; + + if(!channame || !IsChannelName(channame)) + return 1; + + if (!(channel = GetChannel(channame))) + return 1; + + if (!GetUserMode(channel, match)) + return 1; + + irc_svspart(opserv, match, channel); + return 0; +} + +static int +trace_version_func(struct userNode *match, UNUSED_ARG(void *extra)) +{ + irc_version_user(opserv, match); + return 0; +} + static int is_gagged(char *mask) { @@ -5315,7 +6071,7 @@ trace_gag_func(struct userNode *match, void *extra) { struct discrim_and_source *das = extra; - if (is_oper_victim(das->source, match, das->discrim->match_opers)) { + if (is_oper_victim(das->source, match, das->discrim->match_opers, 1) && is_trust_victim(match, das->discrim->match_trusted)) { char *reason, *mask; int masksize; if (das->discrim->reason) { @@ -5405,9 +6161,10 @@ static MODCMD_FUNC(cmd_trace) { struct discrim_and_source das; discrim_search_func action; - unsigned int matches; + unsigned int matches, i; struct svccmd *subcmd; char buf[MAXLEN]; + int ret = 1; sprintf(buf, "trace %s", argv[1]); if (!(subcmd = dict_find(opserv_service->commands, buf, NULL))) { @@ -5420,6 +6177,10 @@ static MODCMD_FUNC(cmd_trace) action = trace_print_func; else if (!irccasecmp(argv[1], "count")) action = trace_count_func; + else if (!irccasecmp(argv[1], "privmsg")) + action = trace_privmsg_func; + else if (!irccasecmp(argv[1], "notice")) + action = trace_notice_func; else if (!irccasecmp(argv[1], "domains")) action = trace_domains_func; else if (!irccasecmp(argv[1], "gline")) @@ -5430,6 +6191,14 @@ static MODCMD_FUNC(cmd_trace) action = trace_kill_func; else if (!irccasecmp(argv[1], "gag")) action = trace_gag_func; + else if (!irccasecmp(argv[1], "svsjoin")) + action = trace_svsjoin_func; + else if (!irccasecmp(argv[1], "svspart")) + action = trace_svspart_func; + else if (!irccasecmp(argv[1], "version")) + action = trace_version_func; + else if (!irccasecmp(argv[1], "mark")) + action = trace_mark_func; else { reply("OSMSG_BAD_ACTION", argv[1]); return 0; @@ -5467,23 +6236,38 @@ static MODCMD_FUNC(cmd_trace) das.disp_limit = das.discrim->limit; das.discrim->limit = INT_MAX; } - matches = opserv_discrim_search(das.discrim, action, &das); - if (action == trace_domains_func) - dict_foreach(das.dict, opserv_show_hostinfo, &das); + if (action == trace_svsjoin_func && !das.discrim->chantarget) { + reply("OSMSG_SVSJOIN_NO_TARGET"); + ret = 0; + } + else if (action == trace_svspart_func && !das.discrim->chantarget) { + reply("OSMSG_SVSPART_NO_TARGET"); + ret = 0; + } + else if (action == trace_mark_func && !das.discrim->mark) { + reply("OSMSG_MARK_NO_MARK"); + ret = 0; + } + else { + matches = opserv_discrim_search(das.discrim, action, &das); - if (matches) - { - if(action == trace_print_func) - reply("OSMSG_USER_SEARCH_COUNT_BAR", matches); + if (action == trace_domains_func) + dict_foreach(das.dict, opserv_show_hostinfo, &das); + + if (matches) + { + if(action == trace_print_func) + reply("OSMSG_USER_SEARCH_COUNT_BAR", matches); + else + reply("OSMSG_USER_SEARCH_COUNT", matches); + } else - reply("OSMSG_USER_SEARCH_COUNT", matches); + reply("MSG_NO_MATCHES"); } - else - reply("MSG_NO_MATCHES"); - if (das.discrim->channel) - UnlockChannel(das.discrim->channel); + for (i = 0; i < das.discrim->channel_count; i++) + UnlockChannel(das.discrim->channels[i]); free(das.discrim->reason); if(das.discrim->has_regex_nick) @@ -5494,10 +6278,12 @@ static MODCMD_FUNC(cmd_trace) regfree(&das.discrim->regex_host); if(das.discrim->has_regex_info) regfree(&das.discrim->regex_info); + if(das.discrim->has_regex_version) + regfree(&das.discrim->regex_version); free(das.discrim); dict_delete(das.dict); - return 1; + return ret; } typedef void (*cdiscrim_search_func)(struct chanNode *match, void *data, struct userNode *bot); @@ -5528,6 +6314,9 @@ opserv_cdiscrim_create(struct userNode *user, struct userNode *bot, unsigned int discrim = calloc(1, sizeof(*discrim)); discrim->limit = 25; + discrim->max_users = ~0; + /* So, time_t is frequently signed. Fun. */ + discrim->max_ts = (1ul << (CHAR_BIT * sizeof(time_t) - 1)) - 1; for (i = 0; i < argc; i++) { /* Assume all criteria require arguments. */ @@ -5555,7 +6344,7 @@ opserv_cdiscrim_create(struct userNode *user, struct userNode *bot, unsigned int else discrim->min_users = strtoul(cmp+1, NULL, 0) + 1; } else { - discrim->min_users = strtoul(cmp+2, NULL, 0); + discrim->min_users = strtoul(cmp, NULL, 0); } } else if (!irccasecmp(argv[i], "timestamp")) { const char *cmp = argv[++i]; @@ -5598,10 +6387,10 @@ cdiscrim_match(cdiscrim_t discrim, struct chanNode *chan) { if ((discrim->name && !match_ircglob(chan->name, discrim->name)) || (discrim->topic && !match_ircglob(chan->topic, discrim->topic)) || - (discrim->min_users && chan->members.used < discrim->min_users) || - (discrim->max_users && chan->members.used > discrim->max_users) || - (discrim->min_ts && chan->timestamp < discrim->min_ts) || - (discrim->max_ts && chan->timestamp > discrim->max_ts)) { + (chan->members.used < discrim->min_users) || + (chan->members.used > discrim->max_users) || + (chan->timestamp < discrim->min_ts) || + (chan->timestamp > discrim->max_ts)) { return 0; } return 1; @@ -5725,11 +6514,33 @@ static void gtrace_print_func(struct gline *gline, void *extra) { struct gline_extra *xtra = extra; - char *when_text, set_text[20]; - strftime(set_text, sizeof(set_text), "%Y-%m-%d", localtime(&gline->issued)); - when_text = asctime(localtime(&gline->expires)); - when_text[strlen(when_text)-1] = 0; /* strip lame \n */ - send_message(xtra->user, xtra->bot, "OSMSG_GTRACE_FORMAT", gline->target, set_text, gline->issuer, when_text, gline->reason); + char issued[INTERVALLEN]; + char expires[INTERVALLEN]; + + intervalString(issued, now - gline->issued, xtra->user->handle_info); + if (gline->expires) + intervalString(expires, gline->expires - now, xtra->user->handle_info); + else + strcpy(expires, "never"); + send_message(xtra->user, opserv, "OSMSG_GTRACE_FORMAT", gline->target, issued, gline->issuer, expires, gline->reason); +} + +static MODCMD_FUNC(cmd_stats_glines) { + if (argc < 2) { + reply("OSMSG_GLINE_COUNT", gline_count()); + return 1; + } else if (argc < 3) { + struct gline_extra extra; + struct gline *gl; + + extra.user = user; + gl = gline_find(argv[1]); + if (!gl) + reply("OSMSG_NO_GLINE", argv[1]); + else + gtrace_print_func(gl, &extra); + return 1; + } else return 0; } static void @@ -5807,11 +6618,33 @@ static void strace_print_func(struct shun *shun, void *extra) { struct shun_extra *xtra = extra; - char *when_text, set_text[20]; - strftime(set_text, sizeof(set_text), "%Y-%m-%d", localtime(&shun->issued)); - when_text = asctime(localtime(&shun->expires)); - when_text[strlen(when_text)-1] = 0; /* strip lame \n */ - send_message(xtra->user, xtra->bot, "OSMSG_STRACE_FORMAT", shun->target, set_text, shun->issuer, when_text, shun->reason); + char issued[INTERVALLEN]; + char expires[INTERVALLEN]; + + intervalString(issued, now - shun->issued, xtra->user->handle_info); + if (shun->expires) + intervalString(expires, shun->expires - now, xtra->user->handle_info); + else + strcpy(expires, "never"); + send_message(xtra->user, opserv, "OSMSG_STRACE_FORMAT", shun->target, issued, shun->issuer, expires, shun->reason); +} + +static MODCMD_FUNC(cmd_stats_shuns) { + if (argc < 2) { + reply("OSMSG_SHUN_COUNT", shun_count()); + return 1; + } else if (argc < 3) { + struct shun_extra extra; + struct shun *gl; + + extra.user = user; + gl = shun_find(argv[1]); + if (!gl) + reply("OSMSG_NO_SHUN", argv[1]); + else + strace_print_func(gl, &extra); + return 1; + } else return 0; } static void @@ -5894,32 +6727,52 @@ alert_check_user(const char *key, void *data, void *extra) return 0; } + if ((alert->reaction != REACT_NOTICE) + && !is_trust_victim(user, alert->discrim->match_trusted)) { + return 0; + } + /* The user matches the alert criteria, so trigger the reaction. */ if (alert->discrim->option_log) log_module(OS_LOG, LOG_INFO, "Alert %s triggered by user %s!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason); + alert->last = now; + /* Return 1 to halt alert matching, such as when killing the user that triggered the alert. */ switch (alert->reaction) { case REACT_KILL: DelUser(user, opserv, 1, alert->discrim->reason); return 1; -/* - case REACT_SILENT: - opserv_block(user, alert->owner, alert->discrim->reason, alert->discrim->duration, 1); - return 1; -*/ case REACT_GLINE: opserv_block(user, alert->owner, alert->discrim->reason, alert->discrim->duration, alert->discrim->silent); return 1; case REACT_SHUN: opserv_shun(user, alert->owner, alert->discrim->reason, alert->discrim->duration); return 1; + case REACT_SVSJOIN: + opserv_svsjoin(user, alert->owner, alert->discrim->reason, alert->discrim->chantarget, alert->discrim->checkrestrictions); + break; + case REACT_SVSPART: + opserv_svspart(user, alert->owner, alert->discrim->reason, alert->discrim->chantarget); + break; + case REACT_VERSION: + /* Don't auto-version a user who we already have a version on, because the version reply itself + * re-triggers this check... + * TODO: maybe safer if we didn't even check react_version type alerts for the 2nd check? + * sort of like we only look at channel alerts on join. -Rubin + */ + if(!user->version_reply) + opserv_version(user); + break; + case REACT_MARK: + opserv_mark(user, alert->owner, alert->discrim->reason, alert->discrim->mark); + break; default: log_module(OS_LOG, LOG_ERROR, "Invalid reaction type %d for alert %s.", alert->reaction, key); /* fall through to REACT_NOTICE case */ case REACT_NOTICE: - opserv_alert("Alert $b%s$b triggered by user $b%s$b!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason); + opserv_custom_alert(alert->discrim->notice_target, "Alert $b%s$b triggered by user $b%s$b!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason); break; case REACT_TRACK: #ifdef HAVE_TRACK @@ -5931,15 +6784,23 @@ alert_check_user(const char *key, void *data, void *extra) return 0; } +static void +opserv_alert_check_account(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) +{ + dict_foreach(opserv_account_based_alerts, alert_check_user, user); +} + static void opserv_alert_check_nick(struct userNode *user, UNUSED_ARG(const char *old_nick)) { struct gag_entry *gag; - dict_foreach(opserv_nick_based_alerts, alert_check_user, user); + + dict_foreach(opserv_nick_based_alerts, alert_check_user, user); + /* Gag them if appropriate (and only if). */ user->modes &= ~FLAGS_GAGGED; for (gag = gagList; gag; gag = gag->next) { - if (user_matches_glob(user, gag->mask, MATCH_USENICK)) { + if (user_matches_glob(user, gag->mask, MATCH_USENICK, 0)) { gag_helper_func(user, NULL); break; } @@ -6073,6 +6934,7 @@ static MODCMD_FUNC(cmd_addalert) struct svccmd *subcmd; const char *name; char buf[MAXLEN]; + int expire = 0; name = argv[1]; sprintf(buf, "addalert %s", argv[2]); @@ -6084,10 +6946,6 @@ static MODCMD_FUNC(cmd_addalert) reaction = REACT_NOTICE; else if (!irccasecmp(argv[2], "kill")) reaction = REACT_KILL; -/* - else if (!irccasecmp(argv[2], "silent")) - reaction = REACT_SILENT; -*/ else if (!irccasecmp(argv[2], "gline")) reaction = REACT_GLINE; else if (!irccasecmp(argv[2], "track")) { @@ -6099,24 +6957,55 @@ static MODCMD_FUNC(cmd_addalert) #endif } else if (!irccasecmp(argv[2], "shun")) reaction = REACT_SHUN; + else if(!irccasecmp(argv[2], "svsjoin")) + reaction = REACT_SVSJOIN; + else if(!irccasecmp(argv[2], "svspart")) + reaction = REACT_SVSPART; + else if(!irccasecmp(argv[2], "version")) + reaction = REACT_VERSION; + else if(!irccasecmp(argv[2], "mark")) + reaction = REACT_MARK; else { reply("OSMSG_UNKNOWN_REACTION", argv[2]); return 0; } + + if (argc >= 4 && !irccasecmp(argv[3], "expire")) + expire = now + ParseInterval(argv[4]); + if (!svccmd_can_invoke(user, opserv_service->bot, subcmd, channel, SVCCMD_NOISY) - || !opserv_add_user_alert(user, name, reaction, unsplit_string(argv + 3, argc - 3, NULL))) + || !opserv_add_user_alert(user, name, reaction, unsplit_string(argv + (expire ? 5 : 3), argc - (expire ? 5 : 3), NULL), 0, expire)) { + reply("OSMSG_ALERT_ADD_FAILED"); return 0; + } reply("OSMSG_ADDED_ALERT", name); return 1; } +static int delete_alert(char const* name) +{ + dict_remove(opserv_nick_based_alerts, (char const*)name); + dict_remove(opserv_channel_alerts, (char const*)name); + dict_remove(opserv_account_based_alerts, (char const*)name); + return dict_remove(opserv_user_alerts, (char const*)name); +} + +static void alert_expire(void* name) +{ + int present = 0; + struct opserv_user_alert* alert = NULL; + + alert = dict_find(opserv_user_alerts, (char const*)name, &present); + + if (present && alert && alert->expire > 0 && alert->expire <= now) + delete_alert(name); +} + static MODCMD_FUNC(cmd_delalert) { unsigned int i; for (i=1; i