X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/5aa400d2612f104275d7f6b466a71f3139b4375b..1fe3e66c35da96898c54d5689d4ecb1456d0c22b:/src/opserv.c diff --git a/src/opserv.c b/src/opserv.c index dce0fd6..b3f3b97 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,7 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +#include "config.h" #include "chanserv.h" #include "conf.h" #include "common.h" @@ -44,7 +45,6 @@ #include #endif - #define OPSERV_CONF_NAME "services/opserv" #define KEY_ALERT_CHANNEL "alert_channel" @@ -149,6 +149,12 @@ static const struct message_entry msgtab[] = { { "OSMSG_SHUN_FORCE_REMOVED", "Unknown/expired Shun removed for $b%s$b." }, { "OSMSG_SHUN_ONE_REFRESHED", "All Shuns resent to $b%s$b." }, { "OSMSG_SHUN_REFRESHED", "All Shuns refreshed." }, + { "OSMSG_NO_GLINE_CMD", "The GLINE command is not bound so you can only block with the default duration." }, + { "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_NO_SHUN_CMD", "The SHUN command is not bound so you can only block with the default duration." }, + { "OSMSG_SHUN_TRUSTED", "$b%s$b is on a trusted ip. If you really want to Shun him, use the SHUN command." }, + { "OSMSG_SHUN_OPER" , "Shunning $b%s$b (*@%s) would also hit the IRC operator $b%s$b." }, { "OSMSG_GLINE_ISSUED", "G-line issued for $b%s$b." }, { "OSMSG_GLINE_REMOVED", "G-line removed for $b%s$b." }, { "OSMSG_GLINE_FORCE_REMOVED", "Unknown/expired G-line removed for $b%s$b." }, @@ -206,6 +212,7 @@ static const struct message_entry msgtab[] = { { "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." }, @@ -233,6 +240,8 @@ static const struct message_entry msgtab[] = { { "OSMSG_BADWORD_LIST", "Bad words: %s" }, { "OSMSG_EXEMPTED_LIST", "Exempted channels: %s" }, { "OSMSG_GLINE_COUNT", "There are %d glines 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_SHUN_COUNT", "There are %d shuns active on the network." }, { "OSMSG_LINKS_SERVER", "%s%s (%u clients; %s)" }, { "OSMSG_MAX_CLIENTS", "Max clients: %d at %s" }, @@ -292,8 +301,8 @@ static const struct message_entry msgtab[] = { { "OSMSG_LOG_SEARCH_RESULTS", "The following log entries were found:" }, { "OSMSG_GSYNC_RUNNING", "Synchronizing glines from %s." }, { "OSMSG_SSYNC_RUNNING", "Synchronizing shuns from %s." }, - { "OSMSG_GTRACE_FORMAT", "%s (issued %s by %s, expires %s): %s" }, - { "OSMSG_STRACE_FORMAT", "%s (issued %s by %s, expires %s): %s" }, + { "OSMSG_STRACE_FORMAT", "%s (issued %s by %s, lastmod %s, expires %s): %s" }, + { "OSMSG_STRACE_FORMAT", "%s (issued %s by %s, lastmod %s, expires %s): %s" }, { "OSMSG_GAG_APPLIED", "Gagged $b%s$b, affecting %d users." }, { "OSMSG_GAG_ADDED", "Gagged $b%s$b." }, { "OSMSG_REDUNDANT_GAG", "Gag $b%s$b is redundant." }, @@ -402,6 +411,8 @@ static const struct message_entry msgtab[] = { { "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_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_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" }, @@ -457,6 +468,7 @@ 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_channel_alerts; /* data is struct opserv_user_alert* */ +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; @@ -522,9 +534,12 @@ 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, *mask_version, *server, *reason, *accountmask, *chantarget, *mark; + 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, *notice_target; irc_in_addr_t ip_mask; unsigned long limit; time_t min_ts, max_ts; @@ -532,8 +547,8 @@ typedef struct opservDiscrim { 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; @@ -578,8 +593,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); @@ -600,6 +616,8 @@ opserv_free_user_alert(void *data) #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) +#define opserv_custom_alert(chan, format...) do { if (chan) send_target_message(4 , chan , opserv , ## format); else if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel , opserv , ## format); } while (0) + char *defconReverseModes(const char *modes) @@ -1273,7 +1291,7 @@ opserv_block(struct userNode *target, char *src_handle, char *reason, unsigned l "G-line requested by %s.", src_handle); if (!duration) duration = opserv_conf.block_gline_duration; - return gline_add(src_handle, mask, duration, reason, now, 1, silent ? 1 : 0); + return gline_add(src_handle, mask, duration, reason, now, now, 1, silent ? 1 : 0); } static MODCMD_FUNC(cmd_block) @@ -1281,6 +1299,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) { @@ -1291,8 +1313,34 @@ 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; } @@ -1317,7 +1365,7 @@ static MODCMD_FUNC(cmd_gline) reply("MSG_INVALID_DURATION", argv[2]); return 0; } - gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, 1, 0); + gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, now, 1, 0); reply("OSMSG_GLINE_ISSUED", gline->target); return 1; } @@ -1398,8 +1446,10 @@ opserv_svsjoin(struct userNode *target, UNUSED_ARG(char *src_handle), UNUSED_ARG return; /* channel is invite only */ } - if (channel->members.used >= channel->limit) { - return; /* channel is invite on */ + if (channel->limit > 0) { + if (channel->members.used >= channel->limit) { + return; /* channel is invite on */ + } } if (*channel->key) { @@ -1445,7 +1495,7 @@ opserv_shun(struct userNode *target, char *src_handle, char *reason, unsigned lo snprintf(reason, MAXLEN, "Shun requested by %s.", src_handle); } if (!duration) duration = opserv_conf.block_shun_duration; - return shun_add(src_handle, mask, duration, reason, now, 1); + return shun_add(src_handle, mask, duration, reason, now, now, 1); } static MODCMD_FUNC(cmd_sblock) @@ -1489,7 +1539,7 @@ static MODCMD_FUNC(cmd_shun) reply("MSG_INVALID_DURATION", argv[2]); return 0; } - shun = shun_add(user->handle_info->handle, argv[1], duration, reason, now, 1); + shun = shun_add(user->handle_info->handle, argv[1], duration, reason, now, now, 1); reply("OSMSG_SHUN_ISSUED", shun->target); return 1; } @@ -1631,10 +1681,13 @@ static MODCMD_FUNC(cmd_join) { struct userNode *bot = cmd->parent->bot; - if (!IsChannelName(argv[1])) { + if (!channel) { + if((argc < 2) || !IsChannelName(argv[1])) + { reply("MSG_NOT_CHANNEL_NAME"); return 0; - } else if (!(channel = GetChannel(argv[1]))) { + } + channel = AddChannel(argv[1], now, NULL, NULL, NULL); AddChannelUser(bot, channel)->modes |= MODE_CHANOP; } else if (GetUserMode(channel, bot)) { @@ -1648,11 +1701,40 @@ static MODCMD_FUNC(cmd_join) change.args[0].u.member = AddChannelUser(bot, channel); modcmd_chanmode_announce(&change); } + irc_fetchtopic(bot, channel->name); reply("OSMSG_JOIN_DONE", channel->name); 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_kick) { struct userNode *target; @@ -2035,6 +2117,9 @@ static MODCMD_FUNC(cmd_whois) 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) { @@ -2045,15 +2130,13 @@ static MODCMD_FUNC(cmd_whois) 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 (IsNoChan(target)) buffer[bpos++] = 'n'; + if (IsNoIdle(target)) buffer[bpos++] = 'I'; if (IsGagged(target)) buffer_cat(" (gagged)"); if (IsRegistering(target)) buffer_cat(" (registered account)"); buffer[bpos] = 0; @@ -2173,16 +2256,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; @@ -2363,11 +2436,15 @@ static MODCMD_FUNC(cmd_stats_uplink) { } static MODCMD_FUNC(cmd_stats_uptime) { + extern int lines_processed; + extern time_t boot_time; + double kernel_time; + double user_time; char uptime[INTERVALLEN]; + +#if defined(HAVE_TIMES) + static double clocks_per_sec; struct tms buf; - extern time_t boot_time; - extern int lines_processed; - static long clocks_per_sec; if (!clocks_per_sec) { #if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK) @@ -2379,12 +2456,27 @@ static MODCMD_FUNC(cmd_stats_uptime) { clocks_per_sec = CLOCKS_PER_SEC; } } - intervalString(uptime, time(NULL)-boot_time, user->handle_info); times(&buf); - reply("OSMSG_UPTIME_STATS", - uptime, lines_processed, - buf.tms_utime/(double)clocks_per_sec, - buf.tms_stime/(double)clocks_per_sec); + user_time = buf.tms_utime / clocks_per_sec; + kernel_time = buf.tms_stime / clocks_per_sec; +#elif defined(HAVE_GETPROCESSTIMES) + FILETIME times[4]; + LARGE_INTEGER li[2]; + + GetProcessTimes(GetCurrentProcess(), ×[0], ×[1], ×[2], ×[3]); + li[0].LowPart = times[2].dwLowDateTime; + li[0].HighPart = times[2].dwHighDateTime; + kernel_time = li[0].QuadPart * 1e-7; + li[1].LowPart = times[3].dwLowDateTime; + li[1].HighPart = times[3].dwHighDateTime; + user_time = li[1].QuadPart * 1e-7; +#else + user_time = NAN; + system_time = NAN; +#endif + + intervalString(uptime, time(NULL)-boot_time, user->handle_info); + reply("OSMSG_UPTIME_STATS", uptime, lines_processed, user_time, kernel_time); return 1; } @@ -2402,7 +2494,7 @@ static MODCMD_FUNC(cmd_stats_alerts) { 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)) ) + 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; @@ -2548,7 +2640,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; @@ -2673,9 +2765,9 @@ opserv_new_user_check(struct userNode *user) strcpy(target + 2, user->hostname); if (checkDefCon(DEFCON_GLINE_NEW_CLIENTS)) - gline_add(opserv->nick, target, DefConGlineExpire, DefConGlineReason, now, 1, 0); + gline_add(opserv->nick, target, DefConGlineExpire, DefConGlineReason, now, now, 1, 0); else if (checkDefCon(DEFCON_SHUN_NEW_CLIENTS)) - shun_add(opserv->nick, target, DefConGlineExpire, DefConGlineReason, now, 1); + shun_add(opserv->nick, target, DefConGlineExpire, DefConGlineReason, now, now, 1); return 0; } @@ -2699,7 +2791,7 @@ opserv_new_user_check(struct userNode *user) } else if (ohi->clients.used > limit) { char target[IRC_NTOP_MAX_SIZE + 3] = { '*', '@', '\0' }; strcpy(target + 2, addr); - gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "Excessive connections from a single host.", now, 1, 1); + gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "Excessive connections from a single host.", now, now, 1, 1); } } @@ -2796,16 +2888,20 @@ opserv_channel_delete(struct chanNode *chan) } static void -opserv_notice_handler(struct userNode *user, struct userNode *bot, char *text, UNUSED_ARG(int server_qualified)) +opserv_notice_handler(struct userNode *user, struct userNode *bot, const char *text, UNUSED_ARG(int server_qualified)) { char *cmd; + char *tmpline; + + tmpline = strdup(text); + /* if its a version reply, do an alert check (only alerts with version=something) */ if(bot == opserv) { if(text[0] == '\001') { text++; - cmd = mysep(&text, " "); - if(!irccasecmp(cmd, "VERSION")) { - char *version = mysep(&text, "\n"); + cmd = mysep(&tmpline, " "); + if(cmd && !irccasecmp(cmd, "VERSION")) { + const char *version = mysep(&tmpline, "\n"); if(!version) version = ""; /* opserv_debug("Opserv got CTCP VERSION Notice from %s: %s", user->nick, version); */ @@ -2854,7 +2950,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; @@ -3177,6 +3273,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), @@ -4304,7 +4402,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; } @@ -4505,7 +4603,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); } @@ -4658,10 +4756,12 @@ 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 || alert->discrim->authed != -1) + dict_insert(opserv_account_alerts, name_dup, alert); return alert; } @@ -5193,6 +5293,8 @@ 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"); @@ -5266,25 +5368,31 @@ 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) { + 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 |= MODE_CHANOP | MODE_HALFOP | MODE_VOICE; + discrim->chan_no_modes[discrim->channel_count] |= MODE_CHANOP | MODE_VOICE; break; case '+': - discrim->chan_req_modes |= MODE_VOICE; - discrim->chan_no_modes |= MODE_CHANOP; - discrim->chan_no_modes |= MODE_HALFOP; + 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 |= MODE_HALFOP; - discrim->chan_no_modes |= MODE_CHANOP; - discrim->chan_no_modes |= MODE_VOICE; + 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 |= MODE_CHANOP; + discrim->chan_req_modes[discrim->channel_count] |= MODE_CHANOP; break; case '\0': send_message(user, bot, "MSG_NOT_CHANNEL_NAME"); @@ -5292,18 +5400,19 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int } } find_channel: - discrim->chan_no_modes &= ~discrim->chan_req_modes; - if (!(discrim->channel = GetChannel(argv[i]+j))) { + 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, bot, "MSG_CHANNEL_UNKNOWN", argv[i]); goto fail; } else { - discrim->channel = AddChannel(argv[i]+j, now, NULL, NULL, NULL); + discrim->channels[discrim->channel_count] = AddChannel(argv[i]+j, now, NULL, NULL, NULL); } } - LockChannel(discrim->channel); + 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) { @@ -5311,6 +5420,12 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int } 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) @@ -5329,7 +5444,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]; @@ -5349,11 +5464,15 @@ 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) { @@ -5476,7 +5595,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 access, i; char *scmp=NULL, *dcmp=NULL; if ((user->timestamp < discrim->min_ts) @@ -5488,13 +5607,15 @@ 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) { @@ -5558,22 +5679,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) + if (discrim->channel_count) + { + for (nn=0; (nn < discrim->channels[0]->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)) { + 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)) { + + 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); } } @@ -5643,12 +5780,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) + 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 @@ -5656,7 +5813,7 @@ 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); } @@ -5668,7 +5825,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); } @@ -5680,7 +5837,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) && is_trust_victim(match, das->discrim->match_trusted)) { char *reason; if (das->discrim->reason) { reason = das->discrim->reason; @@ -5725,20 +5882,22 @@ trace_svsjoin_func(struct userNode *match, void *extra) } if (checkrestrictions) { - if (trace_check_bans(target, channel) == 1) { - return; /* found on lamer list */ + if (trace_check_bans(match, channel) == 1) { + return 1; /* found on lamer list */ } if (channel->modes & MODE_INVITEONLY) { - return; /* channel is invite only */ + return 1; /* channel is invite only */ } - if (channel->members.used >= channel->limit) { - return; /* channel is invite on */ + if (channel->limit > 0) { + if (channel->members.used >= channel->limit) { + return 1; /* channel is invite on */ + } } if (*channel->key) { - return; /* channel is password protected */ + return 1; /* channel is password protected */ } } @@ -5794,7 +5953,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) { @@ -5884,7 +6043,7 @@ 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; @@ -5985,8 +6144,8 @@ static MODCMD_FUNC(cmd_trace) 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) @@ -6033,6 +6192,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. */ @@ -6060,7 +6222,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]; @@ -6103,10 +6265,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; @@ -6230,11 +6392,38 @@ 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 lastmod[INTERVALLEN]; + char expires[INTERVALLEN]; + + intervalString(issued, now - gline->issued, xtra->user->handle_info); + if (gline->lastmod) + intervalString(lastmod, now - gline->lastmod, xtra->user->handle_info); + else + strcpy(lastmod, ""); + 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, lastmod, 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 @@ -6312,11 +6501,38 @@ 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 lastmod[INTERVALLEN]; + char expires[INTERVALLEN]; + + intervalString(issued, now - shun->issued, xtra->user->handle_info); + if (shun->lastmod) + intervalString(lastmod, now - shun->lastmod, xtra->user->handle_info); + else + strcpy(lastmod, ""); + 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, lastmod, 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 *sh; + + extra.user = user; + sh = shun_find(argv[1]); + if (!sh) + reply("OSMSG_NO_SHUN", argv[1]); + else + strace_print_func(sh, &extra); + return 1; + } else return 0; } static void @@ -6399,6 +6615,11 @@ 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); @@ -6437,11 +6658,11 @@ alert_check_user(const char *key, void *data, void *extra) 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 - opserv_alert("Alert $b%s$b triggered by user $b%s$b!%s@%s (%s) (Tracking).", 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). (Tracking)", key, user->nick, user->ident, user->hostname, alert->discrim->reason); add_track_user(user); #endif break; @@ -6486,6 +6707,8 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han send_channel_notice(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); + + dict_foreach(opserv_account_alerts, alert_check_user, user); } static MODCMD_FUNC(cmd_log) @@ -6640,6 +6863,7 @@ static MODCMD_FUNC(cmd_delalert) for (i=1; i