X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/19c83de8069e0014e8ed4b42ace074d469806e63..9895149b20f675f169119ee9271b1aba01fed896:/src/chanserv.c diff --git a/src/chanserv.c b/src/chanserv.c index 400b143..06a67fd 100644 --- a/src/chanserv.c +++ b/src/chanserv.c @@ -25,6 +25,7 @@ #include "ioset.h" #include "modcmd.h" #include "opserv.h" /* for opserv_bad_channel() */ +#include "nickserv.h" /* for oper_outranks() */ #include "saxdb.h" #include "shun.h" #include "spamserv.h" @@ -42,6 +43,7 @@ #define KEY_ADJUST_DELAY "adjust_delay" #define KEY_CHAN_EXPIRE_FREQ "chan_expire_freq" #define KEY_CHAN_EXPIRE_DELAY "chan_expire_delay" +#define KEY_DNR_EXPIRE_FREQ "dnr_expire_freq" #define KEY_BAN_TIMEOUT_FREQ "ban_timeout_freq" #define KEY_MAX_CHAN_USERS "max_chan_users" #define KEY_MAX_CHAN_BANS "max_chan_bans" @@ -59,6 +61,7 @@ #define KEY_NODELETE_LEVEL "nodelete_level" #define KEY_MAX_USERINFO_LENGTH "max_userinfo_length" #define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout" +#define KEY_VALID_CHANNEL_REGEX "valid_channel_regex" /* ChanServ database */ #define KEY_VERSION_CONTROL "version_control" @@ -134,7 +137,8 @@ #define KEY_GOD_TIMEOUT "god_timeout" -#define CHANNEL_DEFAULT_FLAGS (CHANNEL_OFFCHANNEL) +#define CHANNEL_DEFAULT_FLAGS (CHANNEL_OFFCHANNEL | CHANNEL_UNREVIEWED) +#define CHANNEL_PRESERVED_FLAGS (CHANNEL_UNREVIEWED) #define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat" /* Administrative messages */ @@ -154,8 +158,9 @@ static const struct message_entry msgtab[] = { /* Do-not-register channels */ { "CSMSG_NOT_DNR", "$b%s$b is not a valid channel name or *account." }, { "CSMSG_DNR_SEARCH_RESULTS", "$bDo-Not-Register Channels$b" }, - { "CSMSG_DNR_INFO", "$b%s$b (set by $b%s$b): %s" }, - { "CSMSG_DNR_INFO_SET", "$b%s$b (set %s ago by $b%s$b): %s" }, + { "CSMSG_DNR_INFO", "$b%s$b is do-not-register (by $b%s$b): %s" }, + { "CSMSG_DNR_INFO_SET", "$b%s$b is do-not-register (set %s by $b%s$b): %s" }, + { "CSMSG_DNR_INFO_SET_EXPIRES", "$b%s$b is do-not-register (set %s by $b%s$b; expires %s): %s" }, { "CSMSG_MORE_DNRS", "%d more do-not-register entries skipped." }, { "CSMSG_DNR_CHANNEL", "Only network staff may register $b%s$b." }, { "CSMSG_DNR_CHANNEL_MOVE", "Only network staff may move $b%s$b." }, @@ -163,6 +168,8 @@ static const struct message_entry msgtab[] = { { "CSMSG_NOREGISTER_CHANNEL", "$b%s$b has been added to the do-not-register list." }, { "CSMSG_NO_SUCH_DNR", "$b%s$b is not in the do-not-register list." }, { "CSMSG_DNR_REMOVED", "$b%s$b has been removed from the do-not-register list." }, + { "CSMSG_DNR_BAD_ACTION", "$b%s$b is not a recognized do-not-register action." }, + { "CSMSG_DNR_SEARCH_RESULTS", "The following do-not-registers were found:" }, /* Channel unregistration */ { "CSMSG_UNREG_SUCCESS", "$b%s$b has been unregistered." }, @@ -198,7 +205,9 @@ static const struct message_entry msgtab[] = { { "CSMSG_ALREADY_VOICED", "You are already voiced in $b%s$b." }, { "CSMSG_ALREADY_DOWN", "You are not opped, halfopped, or voiced in $b%s$b." }, { "CSMSG_ALREADY_OPCHANNED", "There has been no net.join since the last opchan in $b%s$b." }, + { "CSMSG_OUT_OF_CHANNEL", "For some reason I don't seem to be in $b%s$b." }, { "CSMSG_OPCHAN_DONE", "I have (re-)opped myself in $b%s$b." }, + { "CSMSG_NOT_IN_CHANNEL", "I am not in %s." }, /* Removing yourself from a channel. */ { "CSMSG_NO_OWNER_DELETEME", "You cannot delete your owner access in $b%s$b." }, @@ -231,6 +240,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_NO_OWNER", "There is no owner for %s; please use $bCLVL$b and/or $bADDOWNER$b instead." }, { "CSMSG_TRANSFER_WAIT", "You must wait %s before you can give ownership of $b%s$b to someone else." }, { "CSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." }, + { "CSMSG_CONFIRM_GIVEOWNERSHIP", "To really give ownership to $b%1$s$b, you must use 'giveownership *%1$s %2$s'." }, { "CSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." }, /* Ban management */ @@ -247,10 +257,12 @@ static const struct message_entry msgtab[] = { { "CSMSG_DURATION_TOO_LOW", "Timed bans must last for at least 15 seconds." }, { "CSMSG_DURATION_TOO_HIGH", "Timed bans must last for less than 2 years." }, { "CSMSG_LAME_MASK", "$b%s$b is a little too general. Try making it more specific." }, + { "CSMSG_NO_EXTBANS", "$b%s$b is an extended ban, which are not allowed." }, { "CSMSG_MASK_PROTECTED", "Sorry, ban for $b%s$b conflicts with a protected user's hostmask." }, { "CSMSG_NO_MATCHING_USERS", "No one in $b%s$b has a hostmask matching $b%s$b." }, { "CSMSG_BAN_NOT_FOUND", "Sorry, no ban or LAMER found: $b%s$b." }, { "CSMSG_BANLIST_FULL", "The $b%s$b channel ban list is $bfull$b." }, + { "CSMSG_BAD_BAN", "The given ban $b%s$b is invalid." }, { "CSMSG_INVALID_TRIM", "$b%s$b isn't a valid trim target." }, @@ -285,6 +297,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_SET_USERGREETING", "$bUserGreeting$b %s" }, { "CSMSG_SET_MODES", "$bModes $b %s" }, { "CSMSG_SET_NODELETE", "$bNoDelete $b %s" }, + { "CSMSG_SET_UNREVIEWED", "$bUnreviewed $b %s" }, { "CSMSG_SET_DYNLIMIT", "$bDynLimit $b %s - +l joinflood protection." }, { "CSMSG_SET_OFFCHANNEL", "$bOffChannel $b %s" }, { "CSMSG_SET_USERINFO", "$bUserInfo $b %d - and above userinfos are shown." }, @@ -324,7 +337,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_DEVOICED_USERS", "Devoiced users in $b%s$b." }, { "CSMSG_AUTOMODE_NONE", "Noone will be automatically oped, half-oped, or voiced." }, - { "CSMSG_AUTOMODE_NORMAL", "Give voice to peons, half-op to halfops, and op to ops." }, + { "CSMSG_AUTOMODE_NORMAL", "Give voice to pals, half-op to halfops, and op to ops." }, { "CSMSG_AUTOMODE_VOICE", "#1 plus give voice to everyone." }, { "CSMSG_AUTOMODE_HOP", "#1 plus give halfops to everyone." }, { "CSMSG_AUTOMODE_OP", "#1 plus give ops to everyone (not advised)" }, @@ -370,7 +383,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_CANNOT_INVITE", "You cannot invite %s to %s." }, { "CSMSG_ALREADY_PRESENT", "%s is already in $b%s$b." }, { "CSMSG_YOU_ALREADY_PRESENT", "You are already in $b%s$b." }, - { "CSMSG_LOW_CHANNEL_ACCESS", "You lack sufficient access in %s to use this command." }, + { "CSMSG_LOW_CHANNEL_ACCESS", "You lack sufficient access in %s for $S to invite you." }, { "CSMSG_INFOLINE_TOO_LONG", "Your infoline may not exceed %u characters." }, { "CSMSG_BAD_INFOLINE", "You may not use the character \\%03o in your infoline." }, @@ -429,7 +442,10 @@ static const struct message_entry msgtab[] = { { "CSMSG_HELPER_NO_ACCESS", "%s lacks access to %s but has $bsecurity override$b enabled." }, { "CSMSG_HELPER_HAS_ACCESS", "%s has $b%s$b access (%d) in %s and has $bsecurity override$b enabled." }, { "CSMSG_LAZY_SMURF_TARGET", "%s is %s ($bIRCOp$b; not logged in)." }, - { "CSMSG_SMURF_TARGET", "%s is %s ($b%s$b)." }, + { "CSMSG_SMURF_TARGET", "%s %s ($b%s$b)." }, + { "CSMSG_OPERATOR_TITLE", "IRC operator" }, + { "CSMSG_UC_H_TITLE", "network helper" }, + { "CSMSG_LC_H_TITLE", "support helper" }, { "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." }, /* Seen information */ @@ -504,6 +520,7 @@ static const struct message_entry msgtab[] = { /* Channel configuration */ { "CSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." }, + { "CSMSG_INVALID_CFLAG", "$b%s$b is not a recognized channel flag." }, { "CSMSG_CHANNEL_OPTIONS", "$bChannel Options for %s$b" }, { "CSMSG_CHANNEL_OPTIONS_END", "-------------End of Options-------------" }, { "CSMSG_GREETING_TOO_LONG", "Your greeting ($b%d$b characters) must be shorter than $b%d$b characters." }, @@ -524,7 +541,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" }, { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" }, { "CSMSG_ROULETTE_LOADS", "\001ACTION loads the gun and sets it on the table\001" }, - { "CSMSG_ROULETTE_NEW", "Please type .roulette to start a new round" } , + { "CSMSG_ROULETTE_NEW", "Please type %croulette to start a new round" } , { "CSMSG_ROULETTE_BETTER_LUCK", "Better luck next time, %s" }, { "CSMSG_ROULETTE_BANG", "Bang!!!" } , { "CSMSG_ROULETTE_CLICK", "Click" } , @@ -579,7 +596,7 @@ static const struct message_entry msgtab[] = { return 0; } DECLARE_LIST(dnrList, struct do_not_register *); -DEFINE_LIST(dnrList, struct do_not_register *); +DEFINE_LIST(dnrList, struct do_not_register *) static int eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action); @@ -601,6 +618,7 @@ static struct unsigned long db_backup_frequency; unsigned long channel_expire_frequency; unsigned long ban_timeout_frequency; + unsigned long dnr_expire_frequency; long info_delay; unsigned int adjust_delay; @@ -618,6 +636,9 @@ static struct unsigned int max_chan_users; unsigned int max_chan_bans; /* lamers */ unsigned int max_userinfo_length; + unsigned int valid_channel_regex_set : 1; + + regex_t valid_channel_regex; struct string_list *set_shows; struct string_list *eightball; @@ -690,6 +711,7 @@ static const struct { unsigned short level; char ch; } accessLevels[] = { /* MUST be orderd less to most! */ + { "pal", "Pal", UL_PEON, '+' }, { "peon", "Peon", UL_PEON, '+' }, { "halfop", "HalfOp", UL_HALFOP, '%' }, { "op", "Op", UL_OP, '@' }, @@ -839,7 +861,7 @@ user_level_name_from_level(int level) highest = "None"; if(level >= 1) - highest = "Peon"; + highest = "Pal"; for(ii = 0; (ii < ArrayLength(accessLevels)); ii++) if(level >= accessLevels[ii].level) highest = accessLevels[ii].title; @@ -973,7 +995,7 @@ scan_user_presence(struct userData *uData, struct userNode *user) } static void -chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, char *text, UNUSED_ARG(struct userNode *bot)) +chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice), UNUSED_ARG(void *extra)) { unsigned int eflags, argc; char *argv[4]; @@ -996,7 +1018,7 @@ chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, char *text, return; /* We need to enforce against them; do so. */ eflags = 0; - argv[0] = text; + argv[0] = (char*)text; argv[1] = user->nick; argc = 2; if(GetUserMode(channel, user)) @@ -1299,18 +1321,18 @@ register_channel(struct chanNode *cNode, char *registrar) } static struct userData* -add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned short access, time_t seen, const char *info, time_t accessexpiry) +add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned short access_level, time_t seen, const char *info, time_t accessexpiry) { struct userData *ud; - if(access > UL_OWNER) + if(access_level > UL_OWNER) return NULL; ud = calloc(1, sizeof(*ud)); ud->channel = channel; ud->handle = handle; ud->seen = seen; - ud->access = access; + ud->access = access_level; ud->info = info ? strdup(info) : NULL; ud->accessexpiry = accessexpiry ? accessexpiry : 0; ud->clvlexpiry = 0; @@ -1808,6 +1830,38 @@ expire_channels(UNUSED_ARG(void *data)) timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL); } +static void +expire_dnrs(UNUSED_ARG(void *data)) +{ + dict_iterator_t it, next; + struct do_not_register *dnr; + + for(it = dict_first(handle_dnrs); it; it = next) + { + dnr = iter_data(it); + next = iter_next(it); + if(dnr->expires && dnr->expires <= now) + dict_remove(handle_dnrs, dnr->chan_name + 1); + } + for(it = dict_first(plain_dnrs); it; it = next) + { + dnr = iter_data(it); + next = iter_next(it); + if(dnr->expires && dnr->expires <= now) + dict_remove(plain_dnrs, dnr->chan_name + 1); + } + for(it = dict_first(mask_dnrs); it; it = next) + { + dnr = iter_data(it); + next = iter_next(it); + if(dnr->expires && dnr->expires <= now) + dict_remove(mask_dnrs, dnr->chan_name + 1); + } + + if(chanserv_conf.dnr_expire_frequency) + timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL); +} + static int protect_user(const struct userNode *victim, const struct userNode *aggressor, struct chanData *channel, int protect_invitables) { @@ -1945,13 +1999,14 @@ validate_dehop(struct svccmd *cmd, struct userNode *user, struct chanNode *chann } static struct do_not_register * -chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason) +chanserv_add_dnr(const char *chan_name, const char *setter, time_t expires, const char *reason) { struct do_not_register *dnr = calloc(1, sizeof(*dnr)+strlen(reason)); safestrncpy(dnr->chan_name, chan_name, sizeof(dnr->chan_name)); safestrncpy(dnr->setter, setter, sizeof(dnr->setter)); strcpy(dnr->reason, reason); dnr->set = now; + dnr->expires = expires; if(dnr->chan_name[0] == '*') dict_insert(handle_dnrs, dnr->chan_name+1, dnr); else if(strpbrk(dnr->chan_name, "*?")) @@ -1962,44 +2017,78 @@ chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason) } static struct dnrList -chanserv_find_dnrs(const char *chan_name, const char *handle) +chanserv_find_dnrs(const char *chan_name, const char *handle, unsigned int max) { struct dnrList list; - dict_iterator_t it; + dict_iterator_t it, next; struct do_not_register *dnr; dnrList_init(&list); + if(handle && (dnr = dict_find(handle_dnrs, handle, NULL))) - dnrList_append(&list, dnr); + { + if(dnr->expires && dnr->expires <= now) + dict_remove(handle_dnrs, handle); + else if (list.used < max) + dnrList_append(&list, dnr); + } + if(chan_name && (dnr = dict_find(plain_dnrs, chan_name, NULL))) - dnrList_append(&list, dnr); + { + if(dnr->expires && dnr->expires <= now) + dict_remove(plain_dnrs, chan_name); + else if (list.used < max) + dnrList_append(&list, dnr); + } if(chan_name) - for(it = dict_first(mask_dnrs); it; it = iter_next(it)) - if(match_ircglob(chan_name, iter_key(it))) - dnrList_append(&list, iter_data(it)); + { + for(it = dict_first(mask_dnrs); it && list.used < max; it = next) + { + next = iter_next(it); + if(!match_ircglob(chan_name, iter_key(it))) + continue; + dnr = iter_data(it); + if(dnr->expires && dnr->expires <= now) + dict_remove(mask_dnrs, iter_key(it)); + else + dnrList_append(&list, dnr); + } + } return list; } +static int dnr_print_func(struct do_not_register *dnr, void *extra) +{ + struct userNode *user; + char buf1[INTERVALLEN]; + char buf2[INTERVALLEN]; + + user = extra; + if(dnr->set) + strftime(buf1, sizeof(buf1), "%d %b %Y", localtime(&dnr->set)); + if(dnr->expires) + { + strftime(buf2, sizeof(buf2), "%d %b %Y", localtime(&dnr->expires)); + send_message(user, chanserv, "CSMSG_DNR_INFO_SET_EXPIRES", dnr->chan_name, buf1, dnr->setter, buf2, dnr->reason); + } + else if(dnr->set) + { + send_message(user, chanserv, "CSMSG_DNR_INFO_SET", dnr->chan_name, buf1, dnr->setter, dnr->reason); + } + else + send_message(user, chanserv, "CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason); + return 0; +} + static unsigned int chanserv_show_dnrs(struct userNode *user, struct svccmd *cmd, const char *chan_name, const char *handle) { struct dnrList list; - struct do_not_register *dnr; unsigned int ii; - char buf[INTERVALLEN]; - list = chanserv_find_dnrs(chan_name, handle); + list = chanserv_find_dnrs(chan_name, handle, UINT_MAX); for(ii = 0; (ii < list.used) && (ii < 10); ++ii) - { - dnr = list.list[ii]; - if(dnr->set) - { - strftime(buf, sizeof(buf), "%Y %b %d", localtime(&dnr->set)); - reply("CSMSG_DNR_INFO_SET", dnr->chan_name, buf, dnr->setter, dnr->reason); - } - else - reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason); - } + dnr_print_func(list.list[ii], user); if(ii < list.used) reply("CSMSG_MORE_DNRS", list.used - ii); free(list.list); @@ -2009,65 +2098,50 @@ chanserv_show_dnrs(struct userNode *user, struct svccmd *cmd, const char *chan_n struct do_not_register * chanserv_is_dnr(const char *chan_name, struct handle_info *handle) { + struct dnrList list; struct do_not_register *dnr; - dict_iterator_t it; - if(handle && (dnr = dict_find(handle_dnrs, handle->handle, NULL))) - return dnr; - if(chan_name) + list = chanserv_find_dnrs(chan_name, handle ? handle->handle : NULL, 1); + dnr = list.used ? list.list[0] : NULL; + free(list.list); + return dnr; +} + +static unsigned int send_dnrs(struct userNode *user, dict_t dict) +{ + struct do_not_register *dnr; + dict_iterator_t it, next; + unsigned int matches = 0; + + for(it = dict_first(dict); it; it = next) { - if((dnr = dict_find(plain_dnrs, chan_name, NULL))) - return dnr; - for(it = dict_first(mask_dnrs); it; it = iter_next(it)) - if(match_ircglob(chan_name, iter_key(it))) - return iter_data(it); + dnr = iter_data(it); + next = iter_next(it); + if(dnr->expires && dnr->expires <= now) + { + dict_remove(dict, iter_key(it)); + continue; + } + dnr_print_func(dnr, user); + matches++; } - return NULL; + + return matches; } static CHANSERV_FUNC(cmd_noregister) { const char *target; - struct do_not_register *dnr; - char buf[INTERVALLEN]; + const char *reason; + time_t expiry, duration; unsigned int matches; if(argc < 2) { - dict_iterator_t it; - reply("CSMSG_DNR_SEARCH_RESULTS"); - if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) - reply("CSMSG_BAR"); - matches = 0; - for(it = dict_first(handle_dnrs); it; it = iter_next(it)) - { - dnr = iter_data(it); - if(dnr->set) - reply("CSMSG_DNR_INFO_SET", dnr->chan_name, intervalString(buf, now - dnr->set, user->handle_info), dnr->setter, dnr->reason); - else - reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason); - matches++; - } - for(it = dict_first(plain_dnrs); it; it = iter_next(it)) - { - dnr = iter_data(it); - if(dnr->set) - reply("CSMSG_DNR_INFO_SET", dnr->chan_name, intervalString(buf, now - dnr->set, user->handle_info), dnr->setter, dnr->reason); - else - reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason); - matches++; - } - for(it = dict_first(mask_dnrs); it; it = iter_next(it)) - { - dnr = iter_data(it); - if(dnr->set) - reply("CSMSG_DNR_INFO_SET", dnr->chan_name, intervalString(buf, now - dnr->set, user->handle_info), dnr->setter, dnr->reason); - else - reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason); - matches++; - } - + matches = send_dnrs(user, handle_dnrs); + matches += send_dnrs(user, plain_dnrs); + matches += send_dnrs(user, mask_dnrs); if(matches) reply("MSG_MATCH_COUNT", matches); else @@ -2085,13 +2159,30 @@ static CHANSERV_FUNC(cmd_noregister) if(argc > 2) { - const char *reason = unsplit_string(argv + 2, argc - 2, NULL); + if(argc == 3) + { + reply("MSG_INVALID_DURATION", argv[2]); + return 0; + } + + if(!strcmp(argv[2], "0")) + expiry = 0; + else if((duration = ParseInterval(argv[2]))) + expiry = now + duration; + else + { + reply("MSG_INVALID_DURATION", argv[2]); + return 0; + } + + reason = unsplit_string(argv + 3, argc - 3, NULL); + if((*target == '*') && !get_handle_info(target + 1)) { reply("MSG_HANDLE_UNKNOWN", target + 1); return 0; } - chanserv_add_dnr(target, user->handle_info->handle, reason); + chanserv_add_dnr(target, user->handle_info->handle, expiry, reason); reply("CSMSG_NOREGISTER_CHANNEL", target); return 1; } @@ -2112,26 +2203,264 @@ static CHANSERV_FUNC(cmd_allowregister) { const char *chan_name = argv[1]; - if((chan_name[0] == '*') && dict_find(handle_dnrs, chan_name+1, NULL)) + if(((chan_name[0] == '*') && dict_remove(handle_dnrs, chan_name+1)) + || dict_remove(plain_dnrs, chan_name) + || dict_remove(mask_dnrs, chan_name)) { - dict_remove(handle_dnrs, chan_name+1); reply("CSMSG_DNR_REMOVED", chan_name); + return 1; } - else if(dict_find(plain_dnrs, chan_name, NULL)) + reply("CSMSG_NO_SUCH_DNR", chan_name); + return 0; +} + +struct dnr_search { + struct userNode *source; + char *chan_mask; + char *setter_mask; + char *reason_mask; + time_t min_set, max_set; + time_t min_expires, max_expires; + unsigned int limit; +}; + +static int +dnr_search_matches(const struct do_not_register *dnr, const struct dnr_search *search) +{ + return !((dnr->set < search->min_set) + || (dnr->set > search->max_set) + || (dnr->expires < search->min_expires) + || (search->max_expires + && ((dnr->expires == 0) + || (dnr->expires > search->max_expires))) + || (search->chan_mask + && !match_ircglob(dnr->chan_name, search->chan_mask)) + || (search->setter_mask + && !match_ircglob(dnr->setter, search->setter_mask)) + || (search->reason_mask + && !match_ircglob(dnr->reason, search->reason_mask))); +} + +static struct dnr_search * +dnr_search_create(struct userNode *user, struct svccmd *cmd, unsigned int argc, char *argv[]) +{ + struct dnr_search *discrim; + unsigned int ii; + + discrim = calloc(1, sizeof(*discrim)); + discrim->source = user; + discrim->chan_mask = NULL; + discrim->setter_mask = NULL; + discrim->reason_mask = NULL; + discrim->max_set = INT_MAX; + discrim->limit = 50; + + for(ii=0; iichan_mask = argv[++ii]; + } + else if(0 == irccasecmp(argv[ii], "setter")) + { + discrim->setter_mask = argv[++ii]; + } + else if(0 == irccasecmp(argv[ii], "reason")) + { + discrim->reason_mask = argv[++ii]; + } + else if(0 == irccasecmp(argv[ii], "limit")) + { + discrim->limit = strtoul(argv[++ii], NULL, 0); + } + else if(0 == irccasecmp(argv[ii], "set")) + { + const char *cmp = argv[++ii]; + if(cmp[0] == '<') { + if(cmp[1] == '=') + discrim->min_set = now - ParseInterval(cmp + 2); + else + discrim->min_set = now - (ParseInterval(cmp + 1) - 1); + } else if(cmp[0] == '=') { + discrim->min_set = discrim->max_set = now - ParseInterval(cmp + 1); + } else if(cmp[0] == '>') { + if(cmp[1] == '=') + discrim->max_set = now - ParseInterval(cmp + 2); + else + discrim->max_set = now - (ParseInterval(cmp + 1) - 1); + } else { + discrim->max_set = now - (ParseInterval(cmp) - 1); + } + } + else if(0 == irccasecmp(argv[ii], "expires")) + { + const char *cmp = argv[++ii]; + if(cmp[0] == '<') { + if(cmp[1] == '=') + discrim->max_expires = now + ParseInterval(cmp + 2); + else + discrim->max_expires = now + (ParseInterval(cmp + 1) - 1); + } else if(cmp[0] == '=') { + discrim->min_expires = discrim->max_expires = now + ParseInterval(cmp + 1); + } else if(cmp[0] == '>') { + if(cmp[1] == '=') + discrim->min_expires = now + ParseInterval(cmp + 2); + else + discrim->min_expires = now + (ParseInterval(cmp + 1) - 1); + } else { + discrim->min_expires = now + (ParseInterval(cmp) - 1); + } + } + else + { + reply("MSG_INVALID_CRITERIA", argv[ii]); + goto fail; + } + } + return discrim; + + fail: + free(discrim); + return NULL; +} + +typedef int (*dnr_search_func)(struct do_not_register *match, void *extra); + +static unsigned int +dnr_search(struct dnr_search *discrim, dnr_search_func dsf, void *data) +{ + struct do_not_register *dnr; + dict_iterator_t next; + dict_iterator_t it; + unsigned int count; + int target_fixed; + + /* Initialize local variables. */ + count = 0; + target_fixed = 0; + if(discrim->chan_mask) + { + int shift = (discrim->chan_mask[0] == '\\' && discrim->chan_mask[1] == '*') ? 2 : 0; + if('\0' == discrim->chan_mask[shift + strcspn(discrim->chan_mask+shift, "*?")]) + target_fixed = 1; + } + + if(target_fixed && discrim->chan_mask[0] == '\\' && discrim->chan_mask[1] == '*') + { + /* Check against account-based DNRs. */ + dnr = dict_find(handle_dnrs, discrim->chan_mask + 2, NULL); + if(dnr && dnr_search_matches(dnr, discrim) && (count++ < discrim->limit)) + dsf(dnr, data); } - else if(dict_find(mask_dnrs, chan_name, NULL)) + else if(target_fixed) { - dict_remove(mask_dnrs, chan_name); - reply("CSMSG_DNR_REMOVED", chan_name); + /* Check against channel-based DNRs. */ + dnr = dict_find(plain_dnrs, discrim->chan_mask, NULL); + if(dnr && dnr_search_matches(dnr, discrim) && (count++ < discrim->limit)) + dsf(dnr, data); } else { - reply("CSMSG_NO_SUCH_DNR", chan_name); + /* Exhaustively search account DNRs. */ + for(it = dict_first(handle_dnrs); it; it = next) + { + next = iter_next(it); + dnr = iter_data(it); + if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data)) + break; + } + + /* Do the same for channel DNRs. */ + for(it = dict_first(plain_dnrs); it; it = next) + { + next = iter_next(it); + dnr = iter_data(it); + if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data)) + break; + } + + /* Do the same for wildcarded channel DNRs. */ + for(it = dict_first(mask_dnrs); it; it = next) + { + next = iter_next(it); + dnr = iter_data(it); + if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data)) + break; + } + } + return count; +} + +static int +dnr_remove_func(struct do_not_register *match, void *extra) +{ + struct userNode *user; + char *chan_name; + + chan_name = alloca(strlen(match->chan_name) + 1); + strcpy(chan_name, match->chan_name); + user = extra; + if(((chan_name[0] == '*') && dict_remove(handle_dnrs, chan_name+1)) + || dict_remove(plain_dnrs, chan_name) + || dict_remove(mask_dnrs, chan_name)) + { + send_message(user, chanserv, "CSMSG_DNR_REMOVED", chan_name); + } + return 0; +} + +static int +dnr_count_func(struct do_not_register *match, void *extra) +{ + return 0; (void)match; (void)extra; +} + +static MODCMD_FUNC(cmd_dnrsearch) +{ + struct dnr_search *discrim; + dnr_search_func action; + struct svccmd *subcmd; + unsigned int matches; + char buf[MAXLEN]; + + sprintf(buf, "dnrsearch %s", argv[1]); + subcmd = dict_find(cmd->parent->commands, buf, NULL); + if(!subcmd) + { + reply("CSMSG_DNR_BAD_ACTION", argv[1]); return 0; } + if(!svccmd_can_invoke(user, cmd->parent->bot, subcmd, channel, SVCCMD_NOISY)) + return 0; + if(!irccasecmp(argv[1], "print")) + action = dnr_print_func; + else if(!irccasecmp(argv[1], "remove")) + action = dnr_remove_func; + else if(!irccasecmp(argv[1], "count")) + action = dnr_count_func; + else + { + reply("CSMSG_DNR_BAD_ACTION", argv[1]); + return 0; + } + + discrim = dnr_search_create(user, cmd, argc-2, argv+2); + if(!discrim) + return 0; + + if(action == dnr_print_func) + reply("CSMSG_DNR_SEARCH_RESULTS"); + matches = dnr_search(discrim, action, user); + if(matches) + reply("MSG_MATCH_COUNT", matches); + else + reply("MSG_NO_MATCHES"); + free(discrim); return 1; } @@ -2270,8 +2599,21 @@ static CHANSERV_FUNC(cmd_register) } */ + if (chanserv_conf.valid_channel_regex_set) { + int err = regexec(&chanserv_conf.valid_channel_regex, chan_name, 0, 0, 0); + if (err) { + char buff[256]; + buff[regerror(err, &chanserv_conf.valid_channel_regex, buff, sizeof(buff))] = 0; + log_module(CS_LOG, LOG_INFO, "regexec error: %s (%d)", buff, err); + } + if(err == REG_NOMATCH) { + reply("CSMSG_ILLEGAL_CHANNEL", chan_name); + return 0; + } + } + if(new_channel) - channel = AddChannel(argv[1], now, NULL, NULL, NULL); + channel = AddChannel(chan_name, now, NULL, NULL, NULL); cData = register_channel(channel, user->handle_info->handle); scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL, 0), NULL); @@ -2692,6 +3034,7 @@ static CHANSERV_FUNC(cmd_merge) struct userData *target_user; struct chanNode *target; char reason[MAXLEN]; + int nodelete = 0; REQUIRE_PARAMS(2); @@ -2703,6 +3046,11 @@ static CHANSERV_FUNC(cmd_merge) return 0; } + if (argc > 2) { + if (!irccasecmp("nodelete", argv[2])) + nodelete = 1; + } + if(!target->channel_info) { reply("CSMSG_NOT_REGISTERED", target->name); @@ -2738,7 +3086,8 @@ static CHANSERV_FUNC(cmd_merge) merge_channel(channel->channel_info, target->channel_info); spamserv_cs_move_merge(user, channel, target, 0); sprintf(reason, "merged into %s by %s.", target->name, user->handle_info->handle); - unregister_channel(channel->channel_info, reason); + if (!nodelete) + unregister_channel(channel->channel_info, reason); reply("CSMSG_MERGE_SUCCESS", target->name); return 1; } @@ -2751,11 +3100,20 @@ static CHANSERV_FUNC(cmd_opchan) reply("CSMSG_ALREADY_OPCHANNED", channel->name); return 0; } + if(!IsInChannel(channel,chanserv)) { + reply("CSMSG_NOT_IN_CHANNEL", channel->name); + return 0; + } channel->channel_info->may_opchan = 0; mod_chanmode_init(&change); change.argc = 1; change.args[0].mode = MODE_CHANOP; change.args[0].u.member = GetUserMode(channel, chanserv); + if(!change.args[0].u.member) + { + reply("CSMSG_OUT_OF_CHANNEL", channel->name); + return 0; + } mod_chanmode_announce(chanserv, channel, &change); reply("CSMSG_OPCHAN_DONE", channel->name); return 1; @@ -2764,9 +3122,10 @@ static CHANSERV_FUNC(cmd_opchan) static CHANSERV_FUNC(cmd_adduser) { struct userData *actee; - struct userData *actor; - struct handle_info *handle; - unsigned short access; + struct userData *actor, *real_actor; + struct handle_info *handle = NULL; + struct adduserPending *tmp; + unsigned short access_level, override = 0; REQUIRE_PARAMS(3); @@ -2776,23 +3135,29 @@ static CHANSERV_FUNC(cmd_adduser) return 0; } - access = user_level_from_name(argv[2], UL_OWNER); - if(!access) + access_level = user_level_from_name(argv[2], UL_OWNER); + if(!access_level) { reply("CSMSG_INVALID_ACCESS", argv[2]); return 0; } actor = GetChannelUser(channel->channel_info, user->handle_info); - if(actor->access <= access) + real_actor = GetTrueChannelAccess(channel->channel_info, user->handle_info); + + if(actor->access <= access_level) { reply("CSMSG_NO_BUMP_ACCESS"); return 0; } + /* Trying to add someone with equal/more access */ + if (!real_actor || real_actor->access <= access_level) + override = CMD_LOG_OVERRIDE; + if(!(handle = modcmd_get_handle_info(user, argv[1]))) { - // 'kevin must first authenticate with AuthServ.' is sent to user + /* 'kevin must first authenticate with AuthServ'. is sent to user */ struct userNode *unode; unode = GetUserH(argv[1]); /* find user struct by nick */ if(unode) @@ -2803,7 +3168,7 @@ static CHANSERV_FUNC(cmd_adduser) else { if(IsInChannel(channel, unode)) { reply("CSMSG_ADDUSER_PENDING"); - add_adduser_pending(channel, unode, access); + tmp = add_adduser_pending(channel, unode, access_level); send_message_type(1,unode, chanserv, "CSMSG_ADDUSER_PENDING_TARGET", user->nick, channel->name); } /* this results in user must auth AND not in chan errors. too confusing.. @@ -2829,27 +3194,28 @@ static CHANSERV_FUNC(cmd_adduser) accessexpiry = now + duration; } - actee = add_channel_user(channel->channel_info, handle, access, 0, NULL, accessexpiry); + actee = add_channel_user(channel->channel_info, handle, access_level, 0, NULL, accessexpiry); scan_user_presence(actee, NULL); if (duration > 0) timeq_add(accessexpiry, chanserv_expire_tempuser, actee); - reply("CSMSG_ADDED_USER", handle->handle, channel->name, user_level_name_from_level(access), access); - return 1; + reply("CSMSG_ADDED_USER", handle->handle, channel->name, user_level_name_from_level(access_level), access_level); + return 1 | override; } static CHANSERV_FUNC(cmd_clvl) { struct handle_info *handle; struct userData *victim; - struct userData *actor; - unsigned short new_access; + struct userData *actor, *real_actor; + unsigned short new_access, override = 0; int privileged = IsHelping(user) && ((user->handle_info->opserv_level >= chanserv_conf.nodelete_level) || !IsProtected(channel->channel_info)); REQUIRE_PARAMS(3); actor = GetChannelUser(channel->channel_info, user->handle_info); + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); if(!(handle = modcmd_get_handle_info(user, argv[1]))) return 0; @@ -2904,23 +3270,34 @@ static CHANSERV_FUNC(cmd_clvl) timeq_add(clvlexpiry, chanserv_expire_tempclvl, victim); } + /* Trying to clvl a equal/higher user */ + if(!real_actor || (real_actor->access <= victim->access && handle != user->handle_info)) + override = CMD_LOG_OVERRIDE; + /* Trying to clvl someone to equal/higher access */ + if(!real_actor || new_access >= real_actor->access) + override = CMD_LOG_OVERRIDE; + /* Helpers clvling themselves get caught by the "clvl someone to equal/higher access" check. + * If they lower their own access it's not a big problem. + */ victim->access = new_access; reply("CSMSG_CHANGED_ACCESS", handle->handle, user_level_name_from_level(new_access), new_access, channel->name); - return 1; + return 1 | override; } static CHANSERV_FUNC(cmd_deluser) { struct handle_info *handle; struct userData *victim; - struct userData *actor; - unsigned short access; + struct userData *actor, *real_actor; + unsigned short access_level, override = 0; + unsigned short access_level_user = 0; char *chan_name; REQUIRE_PARAMS(2); actor = GetChannelUser(channel->channel_info, user->handle_info); - + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); + if(!(handle = modcmd_get_handle_info(user, argv[argc-1]))) return 0; @@ -2932,22 +3309,23 @@ static CHANSERV_FUNC(cmd_deluser) if(argc > 2) { - access = user_level_from_name(argv[1], UL_OWNER); + access_level = user_level_from_name(argv[1], UL_OWNER); char *useraccess = user_level_name_from_level(victim->access); - if(!access) + access_level_user = user_level_from_name(useraccess, UL_OWNER); + if(!access_level) { reply("CSMSG_INVALID_ACCESS", argv[1]); return 0; } - if(strcasecmp(argv[1], useraccess)) + if(access_level != access_level_user) { - reply("CSMSG_INCORRECT_ACCESS", handle->handle, user_level_name_from_level(victim->access), argv[1]); + reply("CSMSG_INCORRECT_ACCESS", handle->handle, useraccess, argv[1]); return 0; } } else { - access = victim->access; + access_level = victim->access; } if((actor->access <= victim->access) && !IsHelping(user)) @@ -2956,20 +3334,26 @@ static CHANSERV_FUNC(cmd_deluser) return 0; } + /* If people delete themselves it is an override, but they could've used deleteme so we don't log it as an override */ + if(!real_actor || (real_actor->access <= victim->access && real_actor != victim)) + override = CMD_LOG_OVERRIDE; + chan_name = strdup(channel->name); del_channel_user(victim, 1); - reply("CSMSG_DELETED_USER", handle->handle, access, chan_name); + reply("CSMSG_DELETED_USER", handle->handle, access_level, chan_name); free(chan_name); - return 1; + return 1 | override; } static int cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, char *mask, struct svccmd *cmd) { - struct userData *actor, *uData, *next; + struct userData *actor, *real_actor, *uData, *next; + unsigned int override = 0; actor = GetChannelUser(channel->channel_info, user->handle_info); - + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); + if(min_access > max_access) { reply("CSMSG_BAD_RANGE", min_access, max_access); @@ -2982,6 +3366,9 @@ cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short mi return 0; } + if(!real_actor || real_actor->access <= max_access) + override = CMD_LOG_OVERRIDE; + for(uData = channel->channel_info->users; uData; uData = next) { next = uData->next; @@ -2993,7 +3380,7 @@ cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short mi } reply("CSMSG_DELETED_USERS", mask, min_access, max_access, channel->name); - return 1; + return 1 | override; } static CHANSERV_FUNC(cmd_mdelowner) @@ -3026,6 +3413,10 @@ static CHANSERV_FUNC(cmd_mdelpeon) return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd); } +static CHANSERV_FUNC(cmd_mdelpal) +{ + return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd); +} static CHANSERV_FUNC(cmd_levels) { @@ -3128,14 +3519,14 @@ cmd_trim_users(struct svccmd *cmd, struct userNode *user, struct chanNode *chann unsigned int count; time_t limit; - actor = GetChannelUser(channel->channel_info, user->handle_info); + actor = GetChannelAccess(channel->channel_info, user->handle_info); if(min_access > max_access) { reply("CSMSG_BAD_RANGE", min_access, max_access); return 0; } - if((actor->access <= max_access) && !IsHelping(user)) + if(!actor || actor->access <= max_access) { reply("CSMSG_NO_ACCESS"); return 0; @@ -3393,6 +3784,7 @@ static int bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims) { unsigned int ii; + int b = 0; if(victimCount) *victimCount = 0; @@ -3403,7 +3795,10 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban if(IsService(mn->user)) continue; - if(!user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE)) + b = user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE, 0); + if (b == -1) + return -1; + else if (b == 0) continue; if(protect_user(mn->user, user, channel->channel_info, false)) @@ -3415,12 +3810,20 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban return 0; } +int is_extban(char *b) { + if(*b == '~') { + return 1; + } + return 0; +} + static int eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action) { struct userNode *victim; struct modeNode **victims; unsigned int offset, n, victimCount, duration = 0; + int b = 0; char *reason = "Bye.", *ban, *name; char interval[INTERVALLEN]; @@ -3475,6 +3878,40 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT); name = victim->nick; } + else if(!is_ircmask(argv[1]) && (*argv[1] == '*')) + { + struct handle_info *hi; + char banmask[NICKLEN + USERLEN + HOSTLEN + 3]; + const char *accountname = argv[1] + 1; + + if(!(hi = get_handle_info(accountname))) + { + reply("MSG_HANDLE_UNKNOWN", accountname); + return 0; + } + + snprintf(banmask, sizeof(banmask), "*!*@%s.*", hi->handle); + victims = alloca(sizeof(victims[0]) * channel->members.used); + + b = bad_channel_ban(channel, user, banmask, &victimCount, victims); + if(b == 1) + { + reply("CSMSG_MASK_PROTECTED", banmask); + return 0; + }else if(b == -1) + { + reply("CSMSG_BAD_BAN", banmask); + return 0; + } + + if((action == ACTION_KICK) && (victimCount == 0)) + { + reply("CSMSG_NO_MATCHING_USERS", channel->name, banmask); + return 0; + } + + name = ban = strdup(banmask); + } else { if(!is_ircmask(argv[1])) @@ -3486,10 +3923,13 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c victims = alloca(sizeof(victims[0]) * channel->members.used); - if(bad_channel_ban(channel, user, argv[1], &victimCount, victims)) - { - if(cmd) - reply("CSMSG_MASK_PROTECTED", argv[1]); + b = bad_channel_ban(channel, user, argv[1], &victimCount, victims); + if(cmd && (b == 1)) { + reply("CSMSG_MASK_PROTECTED", argv[1]); + return 0; + } + else if(cmd && (b == -1)) { + reply("CSMSG_BAD_BAN", argv[1]); return 0; } /* If i want to ban *.nl and theres 5 of them, what is it to the bot?!? @@ -3504,6 +3944,13 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c reply("CSMSG_LAME_MASK", argv[1]); return 0; } + //TODO: We have no support to do protection etc etc so for now we dont let you use x3 to set extended bans. + if(is_extban(argv[1])) + { + if(cmd) + reply("CSMSG_NO_EXTBANS", argv[1]); + return 0; + } if((action == ACTION_KICK) && (victimCount == 0)) { @@ -3517,7 +3964,8 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c /* Truncate the ban in place if necessary; we must ensure that 'ban' is a valid ban mask before sanitizing it. */ - sanitize_ircmask(ban); + if (*ban != '~') + sanitize_ircmask(ban); if(action & ACTION_ADD_LAMER) { @@ -3776,7 +4224,7 @@ find_matching_bans(struct banList *bans, struct userNode *actee, const char *mas for(ii = count = 0; ii < bans->used; ++ii) { match[ii] = user_matches_glob(actee, bans->list[ii]->ban, - MATCH_USENICK | MATCH_VISIBLE); + MATCH_USENICK | MATCH_VISIBLE, 0); if(match[ii]) count++; } @@ -3827,7 +4275,8 @@ void expire_bans(UNUSED_ARG(void* data)) /* Real bans, not lamers */ count = 0; /* First find out how many bans were going to unset */ for (jj=0; jj < channel->channel->banlist.used; ++jj) { - if(channel->channel->banlist.list[jj]->set < bantimeout) + //TODO: for now, were just not removing extended bans, but ultimately some types we should, some shouldn't...see below + if(channel->channel->banlist.list[jj]->set < bantimeout && !is_extban(channel->channel->banlist.list[jj]->ban)) count++; } if(count > 0) { @@ -3837,7 +4286,8 @@ void expire_bans(UNUSED_ARG(void* data)) /* Real bans, not lamers */ /* Walk over every ban in this channel.. */ for (jj=0; jj < channel->channel->banlist.used; ++jj) { bn = channel->channel->banlist.list[jj]; - if (bn->set < bantimeout) { + //TODO: for now, were just not removing extended bans, but ultimately some types we should, some shouldn't...see above + if (bn->set < bantimeout && !is_extban(bn->ban)) { log_module(CS_LOG, LOG_DEBUG, "Removing ban %s from %s", bn->ban, channel->channel->name); /* Add this ban to the mode change */ @@ -3878,13 +4328,23 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c /* may want to allow a comma delimited list of users... */ if(!(actee = GetUserH(argv[1]))) { - if(!is_ircmask(argv[1])) + if(!is_ircmask(argv[1]) && *argv[1] == '*') + { + char banmask[NICKLEN + USERLEN + HOSTLEN + 3]; + const char *accountname = argv[1] + 1; + + snprintf(banmask, sizeof(banmask), "*!*@%s.*", accountname); + mask = strdup(banmask); + } + else if(!is_ircmask(argv[1])) { reply("MSG_NICK_UNKNOWN", argv[1]); return 0; } - - mask = strdup(argv[1]); + else + { + mask = strdup(argv[1]); + } } /* We don't sanitize the mask here because ircu @@ -3913,7 +4373,7 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c while(ban) { if(actee) - for( ; ban && !user_matches_glob(actee, ban->mask, MATCH_USENICK | MATCH_VISIBLE); + for( ; ban && !user_matches_glob(actee, ban->mask, MATCH_USENICK | MATCH_VISIBLE, 0); ban = ban->next); else for( ; ban && !match_ircglobs(mask, ban->mask); @@ -3972,6 +4432,7 @@ static CHANSERV_FUNC(cmd_unbanall) return 0; } + // TODO: dont remove some kinds of extended bans such as ~c change = mod_chanmode_alloc(channel->banlist.used); for(ii=0; iibanlist.used; ii++) { @@ -4014,13 +4475,17 @@ static CHANSERV_FUNC(cmd_myaccess) if(argc < 2) target_handle = user->handle_info; - else if(!(target_handle = modcmd_get_handle_info(user, argv[1]))) - return 0; - else if(!IsHelping(user) && target_handle != user->handle_info) + else if(!IsStaff(user)) { reply("CSMSG_MYACCESS_SELF_ONLY", argv[0]); return 0; } + else if(!(target_handle = modcmd_get_handle_info(user, argv[1]))) + return 0; + + if(!oper_outranks(user, target_handle)) + return 0; + if(!target_handle->channels) { reply("CSMSG_SQUAT_ACCESS", target_handle->handle); @@ -4040,7 +4505,7 @@ static CHANSERV_FUNC(cmd_myaccess) continue; sbuf.used = 0; string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access); - if(uData->flags == USER_AUTO_OP) + if(uData->flags != 0) string_buffer_append(&sbuf, ','); if(IsUserSuspended(uData)) string_buffer_append(&sbuf, 's'); @@ -4129,17 +4594,17 @@ static CHANSERV_FUNC(cmd_access) if(IsOper(target)) { epithet = chanserv_conf.irc_operator_epithet; - type = "IRCOp"; + type = user_find_message(user, "CSMSG_OPERATOR_TITLE"); } else if(IsNetworkHelper(target)) { epithet = chanserv_conf.network_helper_epithet; - type = "network helper"; + type = user_find_message(user, "CSMSG_UC_H_TITLE"); } else if(IsSupportHelper(target)) { epithet = chanserv_conf.support_helper_epithet; - type = "support helper"; + type = user_find_message(user, "CSMSG_LC_H_TITLE"); } if(epithet) { @@ -4419,9 +4884,10 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg ary[i++] = "Expiry"; for(matches = 1; matches < lData.table.length; ++matches) { - struct userData *uData = lData.users[matches-1]; char seen[INTERVALLEN]; + + uData = lData.users[matches-1]; i = 0; ary = malloc(lData.table.width*sizeof(**lData.table.contents)); lData.table.contents[matches] = ary; @@ -4541,16 +5007,22 @@ static CHANSERV_FUNC(cmd_plist) static CHANSERV_FUNC(cmd_lamers) { + struct userNode *search_u = NULL; struct helpfile_table tbl; - unsigned int matches = 0, timed = 0, ii; + unsigned int matches = 0, timed = 0, search_wilds = 0, ii; char t_buffer[INTERVALLEN], e_buffer[INTERVALLEN], *search; const char *msg_never, *triggered, *expires; struct banData *ban, **bans; /* lamers */ - if(argc > 1) - search = argv[1]; - else + if(argc < 2) search = NULL; + else if(strchr(search = argv[1], '!')) + { + search = argv[1]; + search_wilds = search[strcspn(search, "?*")]; + } + else if(!(search_u = GetUserH(search))) + reply("MSG_NICK_UNKNOWN", search); reply("CSMSG_LAMERS_HEADER", channel->name); bans = alloca(channel->channel_info->banCount * sizeof(struct banData *)); /* lamers */ @@ -4558,8 +5030,16 @@ static CHANSERV_FUNC(cmd_lamers) /* lamers */ for(ban = channel->channel_info->bans; ban; ban = ban->next) { - if(search && !match_ircglobs(search, ban->mask)) - continue; + if(search_u) + { + if(!user_matches_glob(search_u, ban->mask, MATCH_USENICK | MATCH_VISIBLE, 0)) + continue; + } + else if(search) + { + if(search_wilds ? !match_ircglobs(search, ban->mask) : !match_ircglob(search, ban->mask)) + continue; + } bans[matches++] = ban; if(ban->expires) timed = 1; @@ -4856,7 +5336,7 @@ static CHANSERV_FUNC(cmd_invite) if (invite->handle_info && invite->handle_info->ignores->used && (argc > 1)) { unsigned int i; for (i=0; i < invite->handle_info->ignores->used; i++) { - if (user_matches_glob(user, invite->handle_info->ignores->list[i], MATCH_USENICK)) { + if (user_matches_glob(user, invite->handle_info->ignores->list[i], MATCH_USENICK, 0)) { reply("CSMSG_CANNOT_INVITE", argv[1], channel->name); return 0; } @@ -5080,7 +5560,7 @@ send_staff_list(struct userNode *to, struct userList *list, int skip_flags) user = list->list[nn]; if(user->modes & skip_flags) continue; - if(IsBot(user)) + if(IsBot(user) || IsHideOper(user)) continue; table.contents[table.length] = alloca(table.width*sizeof(**table.contents)); if(IsAway(user)) @@ -5161,10 +5641,12 @@ static CHANSERV_FUNC(cmd_peek) static MODCMD_FUNC(cmd_wipeinfo) { struct handle_info *victim; - struct userData *ud, *actor; + struct userData *ud, *actor, *real_actor; + unsigned int override = 0; REQUIRE_PARAMS(2); actor = GetChannelUser(channel->channel_info, user->handle_info); + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); if(!(victim = modcmd_get_handle_info(user, argv[1]))) return 0; if(!(ud = GetTrueChannelAccess(channel->channel_info, victim))) @@ -5177,11 +5659,13 @@ static MODCMD_FUNC(cmd_wipeinfo) reply("MSG_USER_OUTRANKED", victim->handle); return 0; } + if((ud != real_actor) && (!real_actor || (ud->access >= real_actor->access))) + override = CMD_LOG_OVERRIDE; if(ud->info) free(ud->info); ud->info = NULL; reply("CSMSG_WIPED_INFO_LINE", argv[1], channel->name); - return 1; + return 1 | override; } static void @@ -5215,6 +5699,9 @@ resync_channel(struct chanNode *channel) } else /* Give various userlevels their modes.. */ { + /* If the user has autoop/autovoice disabled then ignore them */ + if(uData && !IsUserAutoOp(uData)) + continue; if(uData && uData->access >= UL_OP ) { if(!(mn->modes & MODE_CHANOP)) @@ -5599,6 +6086,23 @@ static CHANSERV_FUNC(cmd_say) msg = unsplit_string(argv + 1, argc - 1, NULL); send_channel_message(channel, cmd->parent->bot, "%s", msg); } + else if(*argv[1] == '*' && argv[1][1] != '\0') + { + struct handle_info *hi; + struct userNode *authed; + + REQUIRE_PARAMS(3); + msg = unsplit_string(argv + 2, argc - 2, NULL); + + if (!(hi = get_handle_info(argv[1] + 1))) + { + reply("MSG_HANDLE_UNKNOWN", argv[1] + 1); + return 0; + } + + for (authed = hi->users; authed; authed = authed->next_authed) + send_target_message(5, authed->nick, cmd->parent->bot, "%s", msg); + } else if(GetUserH(argv[1])) { REQUIRE_PARAMS(3); @@ -5623,6 +6127,23 @@ static CHANSERV_FUNC(cmd_emote) msg = unsplit_string(argv + 1, argc - 1, NULL); send_channel_message(channel, cmd->parent->bot, "\001ACTION %s\001", msg); } + else if(*argv[1] == '*' && argv[1][1] != '\0') + { + struct handle_info *hi; + struct userNode *authed; + + REQUIRE_PARAMS(3); + msg = unsplit_string(argv + 2, argc - 2, NULL); + + if (!(hi = get_handle_info(argv[1] + 1))) + { + reply("MSG_HANDLE_UNKNOWN", argv[1] + 1); + return 0; + } + + for (authed = hi->users; authed; authed = authed->next_authed) + send_target_message(5, authed->nick, cmd->parent->bot, "\001ACTION %s\001", msg); + } else if(GetUserH(argv[1])) { msg = unsplit_string(argv + 2, argc - 2, NULL); @@ -5655,17 +6176,32 @@ chanserv_expire_suspension(void *data) { struct suspended *suspended = data; struct chanNode *channel; + unsigned int ii; + /* Update the channel registration data structure. */ if(!suspended->expires || (now < suspended->expires)) suspended->revoked = now; channel = suspended->cData->channel; suspended->cData->channel = channel; suspended->cData->flags &= ~CHANNEL_SUSPENDED; + + /* If appropriate, re-join ChanServ to the channel. */ if(!IsOffChannel(suspended->cData)) { spamserv_cs_suspend(channel, 0, 0, NULL); ss_cs_join_channel(channel, 1); } + + /* Mark everyone currently in the channel as present. */ + for(ii = 0; ii < channel->members.used; ++ii) + { + struct userData *uData = GetChannelAccess(suspended->cData, channel->members.list[ii]->user->handle_info); + if(uData) + { + uData->present = 1; + uData->seen = now; + } + } } static CHANSERV_FUNC(cmd_csuspend) @@ -5818,6 +6354,8 @@ chanserv_search_create(struct svccmd *cmd, struct userNode *user, unsigned int a search->flags |= CHANNEL_NODELETE; else if(!irccasecmp(argv[i], "suspended")) search->flags |= CHANNEL_SUSPENDED; + else if(!irccasecmp(argv[i], "unreviewed")) + search->flags |= CHANNEL_UNREVIEWED; else { reply("CSMSG_INVALID_CFLAG", argv[i]); @@ -5960,6 +6498,60 @@ static CHANSERV_FUNC(cmd_unvisited) return 1; } +static MODCMD_FUNC(chan_opt_unreviewed) +{ + struct chanData *cData = channel->channel_info; + int value = (cData->flags & CHANNEL_UNREVIEWED) ? 1 : 0; + + if(argc > 1) + { + int new_value; + + /* The two directions can have different ACLs. */ + if(enabled_string(argv[1])) + new_value = 1; + else if(disabled_string(argv[1])) + new_value = 0; + else + { + reply("MSG_INVALID_BINARY", argv[1]); + return 0; + } + + if (new_value != value) + { + struct svccmd *subcmd; + char subcmd_name[32]; + + snprintf(subcmd_name, sizeof(subcmd_name), "%s %s", argv[0], (new_value ? "on" : "off")); + subcmd = dict_find(cmd->parent->commands, subcmd_name, NULL); + if(!subcmd) + { + reply("MSG_COMMAND_DISABLED", subcmd_name); + return 0; + } + else if(!svccmd_can_invoke(user, cmd->parent->bot, subcmd, channel, SVCCMD_NOISY)) + return 0; + + if (new_value) + cData->flags |= CHANNEL_UNREVIEWED; + else + { + free(cData->registrar); + cData->registrar = strdup(user->handle_info->handle); + cData->flags &= ~CHANNEL_UNREVIEWED; + } + value = new_value; + } + } + + if(value) + reply("CSMSG_SET_UNREVIEWED", user_find_message(user, "MSG_ON")); + else + reply("CSMSG_SET_UNREVIEWED", user_find_message(user, "MSG_OFF")); + return 1; +} + static MODCMD_FUNC(chan_opt_defaulttopic) { if(argc > 1) @@ -6081,7 +6673,7 @@ static MODCMD_FUNC(chan_opt_maxsetinfo) if(argc > 1) { charmax = atoi(argv[1]); - if ((charmax > 0) && (charmax < chanserv_conf.max_userinfo_length)) + if ((charmax > 0) && (charmax <= chanserv_conf.max_userinfo_length)) channel->channel_info->maxsetinfo = charmax; } @@ -6189,6 +6781,16 @@ static MODCMD_FUNC(chan_opt_nodelete) static MODCMD_FUNC(chan_opt_dynlimit) { + struct mod_chanmode change; + + if (argc > 1) { + if (disabled_string(argv[1])) { + mod_chanmode_init(&change); + change.modes_clear |= MODE_LIMIT; + mod_chanmode_announce(chanserv, channel, &change); + } + } + CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT); } @@ -6261,7 +6863,8 @@ static MODCMD_FUNC(chan_opt_defaults) reply("CSMSG_CONFIRM_DEFAULTS", channel->name, confirm); return 0; } - cData->flags = CHANNEL_DEFAULT_FLAGS; + cData->flags = (CHANNEL_DEFAULT_FLAGS & ~CHANNEL_PRESERVED_FLAGS) + | (cData->flags & CHANNEL_PRESERVED_FLAGS); cData->modes = chanserv_conf.default_modes; for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt) cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value; @@ -6380,31 +6983,31 @@ static int channel_multiple_option(enum charOption option, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd) { struct chanData *cData = channel->channel_info; - int count = charOptions[option].count, index; + int count = charOptions[option].count, idx; if(argc > 1) { - index = atoi(argv[1]); + idx = atoi(argv[1]); - if(!isdigit(argv[1][0]) || (index < 0) || (index >= count)) + if(!isdigit(argv[1][0]) || (idx < 0) || (idx >= count)) { - reply("CSMSG_INVALID_NUMERIC", index); + reply("CSMSG_INVALID_NUMERIC", idx); /* Show possible values. */ - for(index = 0; index < count; index++) - reply(charOptions[option].format_name, index, user_find_message(user, charOptions[option].values[index].format_name)); + for(idx = 0; idx < count; idx++) + reply(charOptions[option].format_name, idx, user_find_message(user, charOptions[option].values[idx].format_name)); return 0; } - cData->chOpts[option] = charOptions[option].values[index].value; + cData->chOpts[option] = charOptions[option].values[idx].value; } else { /* Find current option value. */ find_value: - for(index = 0; - (index < count) && (cData->chOpts[option] != charOptions[option].values[index].value); - index++); - if(index == count) + for(idx = 0; + (idx < count) && (cData->chOpts[option] != charOptions[option].values[idx].value); + idx++); + if(idx == count) { /* Somehow, the option value is corrupt; reset it to the default. */ cData->chOpts[option] = charOptions[option].default_value; @@ -6412,7 +7015,7 @@ channel_multiple_option(enum charOption option, struct userNode *user, struct ch } } - reply(charOptions[option].format_name, index, user_find_message(user, charOptions[option].values[index].format_name)); + reply(charOptions[option].format_name, idx, user_find_message(user, charOptions[option].values[idx].format_name)); return 1; } @@ -6454,7 +7057,7 @@ static MODCMD_FUNC(chan_opt_resync) static struct svccmd_list set_shows_list; static void -handle_svccmd_unbind(struct svccmd *target) { +handle_svccmd_unbind(struct svccmd *target, UNUSED_ARG(void *extra)) { unsigned int ii; for(ii=0; iicommand->func(user, channel, argc - 1, argv + 1, subcmd); } @@ -6571,6 +7176,10 @@ static MODCMD_FUNC(user_opt_autoop) static MODCMD_FUNC(user_opt_autoinvite) { + if((argc > 1) && !check_user_level(channel, user, lvlInviteMe, 1, 0)) + { + reply("CSMSG_LOW_CHANNEL_ACCESS", channel->name); + } return user_binary_option("CSMSG_USET_AUTOINVITE", USER_AUTO_INVITE, CSFUNC_ARGS); } @@ -6682,10 +7291,13 @@ static CHANSERV_FUNC(cmd_uset) static CHANSERV_FUNC(cmd_giveownership) { struct handle_info *new_owner_hi; - struct userData *new_owner, *curr_user; + struct userData *new_owner; + struct userData *curr_user; + struct userData *invoker; struct chanData *cData = channel->channel_info; struct do_not_register *dnr; struct giveownership *giveownership; + const char *confirm; unsigned int force, override; unsigned short co_access, new_owner_old_access; char transfer_reason[MAXLEN]; @@ -6719,7 +7331,7 @@ static CHANSERV_FUNC(cmd_giveownership) } curr_user = owner; } - else if (!force && (now < (time_t)(cData->ownerTransfer + chanserv_conf.giveownership_period))) + else if(!force && (now < (time_t)(cData->ownerTransfer + chanserv_conf.giveownership_period))) { char delay[INTERVALLEN]; intervalString(delay, cData->ownerTransfer + chanserv_conf.giveownership_period - now, user->handle_info); @@ -6742,7 +7354,7 @@ static CHANSERV_FUNC(cmd_giveownership) { if(force) { - new_owner = add_channel_user(cData, new_owner_hi, UL_COOWNER, 0, NULL, 0); + new_owner = add_channel_user(cData, new_owner_hi, UL_OWNER - 1, 0, NULL, 0); } else { @@ -6763,6 +7375,17 @@ static CHANSERV_FUNC(cmd_giveownership) return 0; } + invoker = GetChannelUser(cData, user->handle_info); + if(invoker->access <= UL_OWNER) + { + confirm = make_confirmation_string(curr_user); + if((argc < 3) || strcmp(argv[2], confirm)) + { + reply("CSMSG_CONFIRM_GIVEOWNERSHIP", new_owner_hi->handle, confirm); + return 0; + } + } + new_owner_old_access = new_owner->access; if(new_owner->access >= UL_COOWNER) co_access = new_owner->access; @@ -6809,18 +7432,20 @@ chanserv_expire_user_suspension(void *data) static CHANSERV_FUNC(cmd_suspend) { struct handle_info *hi; - struct userData *self, *target; + struct userData *actor, *real_actor, *target; + unsigned int override = 0; time_t expiry; REQUIRE_PARAMS(3); if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0; - self = GetChannelUser(channel->channel_info, user->handle_info); + actor = GetChannelUser(channel->channel_info, user->handle_info); + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) { reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); return 0; } - if(target->access >= self->access) + if(target->access >= actor->access) { reply("MSG_USER_OUTRANKED", hi->handle); return 0; @@ -6853,25 +7478,29 @@ static CHANSERV_FUNC(cmd_suspend) if(target->expires) timeq_add(target->expires, chanserv_expire_user_suspension, target); + if(!real_actor || target->access >= real_actor->access) + override = CMD_LOG_OVERRIDE; target->flags |= USER_SUSPENDED; reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name); - return 1; + return 1 | override; } static CHANSERV_FUNC(cmd_unsuspend) { struct handle_info *hi; - struct userData *self, *target; + struct userData *actor = NULL, *real_actor = NULL, *target; + unsigned int override = 0; REQUIRE_PARAMS(2); if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0; - self = GetChannelUser(channel->channel_info, user->handle_info); + actor = GetChannelUser(channel->channel_info, user->handle_info); + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) { reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); return 0; } - if(target->access >= self->access) + if(target->access >= actor->access) { reply("MSG_USER_OUTRANKED", hi->handle); return 0; @@ -6881,11 +7510,13 @@ static CHANSERV_FUNC(cmd_unsuspend) reply("CSMSG_NOT_SUSPENDED", hi->handle); return 0; } + if(!real_actor || target->access >= real_actor->access) + override = CMD_LOG_OVERRIDE; target->flags &= ~USER_SUSPENDED; scan_user_presence(target, NULL); timeq_del(target->expires, chanserv_expire_user_suspension, target, 0); reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name); - return 1; + return 1 | override; } static MODCMD_FUNC(cmd_deleteme) @@ -6924,7 +7555,7 @@ static MODCMD_FUNC(cmd_deleteme) static void chanserv_refresh_topics(UNUSED_ARG(void *data)) { - unsigned int refresh_num = (now - self->link) / chanserv_conf.refresh_period; + unsigned int refresh_num = (now - self->link_time) / chanserv_conf.refresh_period; struct chanData *cData; char opt; @@ -6947,7 +7578,7 @@ chanserv_refresh_topics(UNUSED_ARG(void *data)) static void chanserv_auto_resync(UNUSED_ARG(void *data)) { - unsigned int refresh_num = (now - self->link) / chanserv_conf.refresh_period; + unsigned int refresh_num = (now - self->link_time) / chanserv_conf.refresh_period; struct chanData *cData; char opt; @@ -7029,7 +7660,10 @@ static CHANSERV_FUNC(cmd_shoot) struct chanData *cData = channel->channel_info; if (cData->roulette_chamber <= 0) { - reply("CSMSG_ROULETTE_NEW"); + struct service *service; + if ((service = service_find(chanserv->nick))) { + reply("CSMSG_ROULETTE_NEW", service->trigger); + } return 1; } @@ -7075,7 +7709,7 @@ static CHANSERV_FUNC(cmd_spin) if(!channel) return 1; - int type, lamep = 1; + int type = 0, lamep = 1; char *tstr; tstr = conf_get_data("server/type", RECDB_QSTRING); @@ -7124,7 +7758,7 @@ static CHANSERV_FUNC(cmd_spin) } /* random time gline */ else if (!strcasecmp(wheel, "gline")) { - char target[IRC_NTOP_MAX_SIZE + 3]; + char target[HOSTLEN + 3]; int wtime = 120 + rand() % 600; strcpy(target, "*@"); @@ -7136,7 +7770,7 @@ static CHANSERV_FUNC(cmd_spin) } /* random shun */ else if (!strcasecmp(wheel, "shun")) { - char target[IRC_NTOP_MAX_SIZE + 3]; + char target[HOSTLEN + 3]; int wtime = 120 + rand() % 600; strcpy(target, "*@"); @@ -7160,7 +7794,7 @@ static CHANSERV_FUNC(cmd_spin) while(complete != 1) { if (rndchans != 15) { chango = 120 + rand() % 600; - sputsock("%s SJ %s #%d %ld", self->numeric, user->numeric, chango, now); + sputsock("%s SJ %s #%d "FMT_TIME_T, self->numeric, user->numeric, chango, now); rndchans++; } else { if (roundz0r != 1) { @@ -7203,14 +7837,12 @@ static CHANSERV_FUNC(cmd_spin) char *oldnick = NULL; char *oldident = NULL; - char *oldhost = NULL; char abusednick[NICKLEN] = ""; int abusednum = 1 + (int) (10000.0 * (rand() / (RAND_MAX + 1.0))); struct userNode *clone; oldnick = strdup(user->nick); oldident = strdup(user->ident); - oldhost = strdup(user->hostname); //snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120)); while (1) { @@ -7222,8 +7854,7 @@ static CHANSERV_FUNC(cmd_spin) SVSNickChange(user, abusednick); irc_svsnick(chanserv, user, abusednick); - - clone = AddClone(oldnick, oldident, oldhost, "I got abused by the wheel of misfortune :D"); + clone = AddLocalUser(oldnick, oldident, "abused.by.wheel.of.misfortune", "I got abused by the wheel of misfortune :D", "+i"); timeq_add(now + 300, chanserv_remove_abuse, clone->nick); } /* kill */ @@ -7236,7 +7867,7 @@ static CHANSERV_FUNC(cmd_spin) /* service ignore */ else if (!strcasecmp(wheel, "svsignore")) { int gagged, ignoretime = 0; - char target[IRC_NTOP_MAX_SIZE + 13]; + char target[HOSTLEN + 13]; if(IsOper(user)) { /* we cant gag opers, so just verbally abuse them */ @@ -7255,16 +7886,16 @@ static CHANSERV_FUNC(cmd_spin) else if (!strcasecmp(wheel, "kickbanall")) { unsigned int count, n; struct modeNode *mn; - //char ban[IRC_NTOP_MAX_SIZE + 1]; + //char ban[HOSTLEN + 1]; send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKBANALL"); //snprintf(ban, sizeof(ban), "*!*@%s", user->hostname); for (n=count=0; nchannels.used; n++) { struct mod_chanmode *change; - struct banData *bData; +/* struct banData *bData; */ unsigned int exists; - int duration = 300; +/* int duration = 300; */ char *ban; ban = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT|GENMASK_USENICK); @@ -7445,11 +8076,11 @@ static CHANSERV_FUNC(cmd_8ball) word3 = argc>3?argv[3]:""; /*** COLOR *****/ - if((word2) && strcasecmp(word1, "what") == 0 && strcasecmp(word2, "color") == 0) + if((word2) && strcasecmp(word1, "what") == 0 && ((strcasecmp(word2, "color") == 0) || (strcasecmp(word2, "colour") == 0))) eightball(eb, 1, accum); - else if((word3) && strcasecmp(word1, "what's") == 0 && strcasecmp(word2, "the") == 0 && strcasecmp(word3, "color") == 0) + else if((word3) && strcasecmp(word1, "what's") == 0 && strcasecmp(word2, "the") == 0 && ((strcasecmp(word2, "color") == 0) || (strcasecmp(word2, "colour") == 0))) eightball(eb, 1, accum); - else if((word3) && strcasecmp(word1, "whats") == 0 && strcasecmp(word2, "the") == 0 && strcasecmp(word3, "color") == 0) + else if((word3) && strcasecmp(word1, "whats") == 0 && strcasecmp(word2, "the") == 0 && ((strcasecmp(word2, "color") == 0) || (strcasecmp(word2, "colour") == 0))) eightball(eb, 1, accum); /*** LOCATION *****/ else if( @@ -7624,7 +8255,7 @@ chanserv_adjust_limit(void *data) } static void -handle_new_channel(struct chanNode *channel) +handle_new_channel(struct chanNode *channel, UNUSED_ARG(void *extra)) { struct chanData *cData; @@ -7652,7 +8283,7 @@ trace_check_bans(struct userNode *user, struct chanNode *chan) if (chan->channel_info) { for(bData = chan->channel_info->bans; bData; bData = bData->next) { - if(!user_matches_glob(user, bData->mask, MATCH_USENICK)) + if(!user_matches_glob(user, bData->mask, MATCH_USENICK, 0)) continue; if(bData) @@ -7684,7 +8315,7 @@ check_bans(struct userNode *user, const char *channel) { /* Not joining through a ban. */ for(bData = cData->bans; - bData && !user_matches_glob(user, bData->mask, MATCH_USENICK); + bData && !user_matches_glob(user, bData->mask, MATCH_USENICK, 0); bData = bData->next); if(bData) @@ -7726,7 +8357,7 @@ channel_user_is_exempt(struct userNode *user, struct chanNode *channel) unsigned int ii; for(ii = 0; ii < channel->exemptlist.used; ii++) { - if(user_matches_glob(user, channel->exemptlist.list[ii]->exempt, MATCH_USENICK)) + if(user_matches_glob(user, channel->exemptlist.list[ii]->exempt, MATCH_USENICK, 0)) return true; } return false; @@ -7736,7 +8367,7 @@ channel_user_is_exempt(struct userNode *user, struct chanNode *channel) /* Welcome to my worst nightmare. Warning: Read (or modify) the code below at your own risk. */ static int -handle_join(struct modeNode *mNode) +handle_join(struct modeNode *mNode, UNUSED_ARG(void *extra)) { struct mod_chanmode change; struct userNode *user = mNode->user; @@ -7771,7 +8402,7 @@ handle_join(struct modeNode *mNode) unsigned int ii; for(ii = 0; ii < channel->banlist.used; ii++) { - if(user_matches_glob(user, channel->banlist.list[ii]->ban, MATCH_USENICK)) + if(user_matches_glob(user, channel->banlist.list[ii]->ban, MATCH_USENICK, 0)) { /* Riding a netburst. Naughty. */ KickChannelUser(user, channel, chanserv, "User from far side of netsplit should have been banned - bye."); @@ -7802,7 +8433,7 @@ handle_join(struct modeNode *mNode) { /* Not joining through a ban. */ for(bData = cData->bans; - bData && !user_matches_glob(user, bData->mask, MATCH_USENICK); + bData && !user_matches_glob(user, bData->mask, MATCH_USENICK, 0); bData = bData->next); if(bData) @@ -7967,7 +8598,7 @@ chanserv_autojoin_channels(struct userNode *user) } static void -handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) +handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle), UNUSED_ARG(void *extra)) { struct mod_chanmode change; struct userData *channel; @@ -8025,30 +8656,30 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) for(ii = 0; ii < user->channels.used; ++ii) { - struct chanNode *channel = user->channels.list[ii]->channel; + struct chanNode *chan = user->channels.list[ii]->channel; struct banData *ban; if((user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) - || !channel->channel_info - || IsSuspended(channel->channel_info)) + || !chan->channel_info + || IsSuspended(chan->channel_info)) continue; - if(protect_user(user, chanserv, channel->channel_info, true)) + if(protect_user(user, chanserv, chan->channel_info, true)) continue; - for(jj = 0; jj < channel->banlist.used; ++jj) - if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK)) + for(jj = 0; jj < chan->banlist.used; ++jj) + if(user_matches_glob(user, chan->banlist.list[jj]->ban, MATCH_USENICK, 0)) break; - if(jj < channel->banlist.used) + if(jj < chan->banlist.used) continue; - for(ban = channel->channel_info->bans; ban; ban = ban->next) + for(ban = chan->channel_info->bans; ban; ban = ban->next) { char kick_reason[MAXLEN]; - if(!user_matches_glob(user, ban->mask,MATCH_USENICK | MATCH_VISIBLE)) + if(!user_matches_glob(user, ban->mask,MATCH_USENICK | MATCH_VISIBLE, 0)) continue; change.args[0].mode = MODE_BAN; change.args[0].u.hostmask = ban->mask; - mod_chanmode_announce(chanserv, channel, &change); + mod_chanmode_announce(chanserv, chan, &change); sprintf(kick_reason, "(%s) %s", ban->owner, ban->reason); - KickChannelUser(user, channel, chanserv, kick_reason); + KickChannelUser(user, chan, chanserv, kick_reason); ban->triggered = now; break; } @@ -8082,7 +8713,7 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) } static void -handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) +handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason), UNUSED_ARG(void *extra)) { struct chanData *cData; struct userData *uData; @@ -8106,17 +8737,23 @@ handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) { scan_user_presence(uData, mn->user); uData->seen = now; - } + if (uData->access >= UL_PRESENT) + cData->visited = now; + } if(IsHelping(mn->user) && IsSupportHelper(mn->user)) { - unsigned int ii, jj; - for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii) - { - for(jj = 0; jj < mn->user->channels.used; ++jj) - if(mn->user->channels.list[jj]->channel == chanserv_conf.support_channels.list[ii]) - break; - if(jj < mn->user->channels.used) + unsigned int ii; + for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii) { + struct chanNode *channel; + struct userNode *exclude; + /* When looking at the channel that is being /part'ed, we + * have to skip over the client that is leaving. For + * other channels, we must not do that. + */ + channel = chanserv_conf.support_channels.list[ii]; + exclude = (channel == mn->channel) ? mn->user : NULL; + if(find_handle_in_channel(channel, mn->user->handle_info, exclude)) break; } if(ii == chanserv_conf.support_channels.used) @@ -8125,7 +8762,7 @@ handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) } static void -handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *channel) +handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *channel, UNUSED_ARG(void *extra)) { struct userData *uData; @@ -8145,7 +8782,7 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c } static int -handle_topic(struct userNode *user, struct chanNode *channel, const char *old_topic) +handle_topic(struct userNode *user, struct chanNode *channel, const char *old_topic, UNUSED_ARG(void *extra)) { struct chanData *cData; @@ -8188,7 +8825,7 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to } static void -handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_chanmode *change) +handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_chanmode *change, UNUSED_ARG(void *extra)) { struct mod_chanmode *bounce = NULL; unsigned int bnc, ii; @@ -8263,7 +8900,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch } static void -handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick)) +handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick), UNUSED_ARG(void *extra)) { struct chanNode *channel; struct banData *bData; @@ -8287,7 +8924,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick)) continue; /* Look for a matching ban already on the channel. */ for(jj = 0; jj < channel->banlist.used; ++jj) - if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK)) + if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK, 0)) break; /* Need not act if we found one. */ if(jj < channel->banlist.used) @@ -8298,7 +8935,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick)) /* Look for a matching ban in this channel. */ for(bData = channel->channel_info->bans; bData; bData = bData->next) { - if(!user_matches_glob(user, bData->mask, MATCH_USENICK | MATCH_VISIBLE)) + if(!user_matches_glob(user, bData->mask, MATCH_USENICK | MATCH_VISIBLE, 0)) continue; change.args[0].u.hostmask = bData->mask; mod_chanmode_announce(chanserv, channel, &change); @@ -8310,7 +8947,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick)) } } -static void handle_rename(struct handle_info *handle, const char *old_handle) +static void handle_rename(struct handle_info *handle, const char *old_handle, UNUSED_ARG(void *extra)) { struct do_not_register *dnr = dict_find(handle_dnrs, old_handle, NULL); @@ -8323,7 +8960,7 @@ static void handle_rename(struct handle_info *handle, const char *old_handle) } static void -handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle) +handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle, UNUSED_ARG(void *extra)) { struct userNode *h_user; @@ -8337,8 +8974,8 @@ handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle) } } -static void -handle_server_link(UNUSED_ARG(struct server *server)) +static int +handle_server_link(UNUSED_ARG(struct server *server), UNUSED_ARG(void *extra)) { struct chanData *cData; @@ -8355,6 +8992,7 @@ handle_server_link(UNUSED_ARG(struct server *server)) timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData); } } + return 0; } static void @@ -8414,6 +9052,8 @@ chanserv_conf_read(void) chanserv_conf.ban_timeout_frequency = str ? ParseInterval(str) : 600; str = database_get_data(conf_node, KEY_CHAN_EXPIRE_DELAY, RECDB_QSTRING); chanserv_conf.channel_expire_delay = str ? ParseInterval(str) : 86400*30; + str = database_get_data(conf_node, KEY_DNR_EXPIRE_FREQ, RECDB_QSTRING); + chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600; str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING); chanserv_conf.nodelete_level = str ? atoi(str) : 1; str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING); @@ -8454,6 +9094,16 @@ chanserv_conf_read(void) chanserv_conf.default_modes = *change; mod_chanmode_free(change); } + str = database_get_data(conf_node, KEY_VALID_CHANNEL_REGEX, RECDB_QSTRING); + if (chanserv_conf.valid_channel_regex_set) + regfree(&chanserv_conf.valid_channel_regex); + if (str) { + int err = regcomp(&chanserv_conf.valid_channel_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB); + chanserv_conf.valid_channel_regex_set = !err; + if (err) log_module(CS_LOG, LOG_ERROR, "Bad valid_channel_regex (error %d)", err); + } else { + chanserv_conf.valid_channel_regex_set = 0; + } free_string_list(chanserv_conf.wheel); strlist = database_get_data(conf_node, "wheel", RECDB_STRING_LIST); if(strlist) @@ -8491,7 +9141,6 @@ chanserv_conf_read(void) /* delimiter */ NULL }; - unsigned int ii; strlist = alloc_string_list(ArrayLength(list)-1); for(ii=0; list[ii]; ii++) string_list_append(strlist, strdup(list[ii])); @@ -8592,7 +9241,7 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) struct userData *uData; char *seen, *inf, *flags, *expires, *accessexpiry, *clvlexpiry, *lstacc; time_t last_seen; - unsigned short access, lastaccess = 0; + unsigned short access_level, lastaccess = 0; if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) { @@ -8600,8 +9249,8 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) return; } - access = atoi(database_get_data(rd->d.object, KEY_LEVEL, RECDB_QSTRING)); - if(access > UL_OWNER) + access_level = atoi(database_get_data(rd->d.object, KEY_LEVEL, RECDB_QSTRING)); + if(access_level > UL_OWNER) { log_module(CS_LOG, LOG_ERROR, "Invalid access level for %s in %s.", key, chan->channel->name); return; @@ -8624,7 +9273,7 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) return; } - uData = add_channel_user(chan, handle, access, last_seen, inf, 0); + uData = add_channel_user(chan, handle, access_level, last_seen, inf, 0); uData->flags = flags ? strtoul(flags, NULL, 0) : 0; uData->expires = expires ? strtoul(expires, NULL, 0) : 0; @@ -8969,6 +9618,7 @@ chanserv_dnr_read(const char *key, struct record_data *hir) { const char *setter, *reason, *str; struct do_not_register *dnr; + time_t expiry; setter = database_get_data(hir->d.object, KEY_DNR_SETTER, RECDB_QSTRING); if(!setter) @@ -8982,7 +9632,11 @@ chanserv_dnr_read(const char *key, struct record_data *hir) log_module(CS_LOG, LOG_ERROR, "Missing reason for DNR %s.", key); return; } - dnr = chanserv_add_dnr(key, setter, reason); + str = database_get_data(hir->d.object, KEY_EXPIRES, RECDB_QSTRING); + expiry = str ? (time_t)strtoul(str, NULL, 0) : 0; + if(expiry && expiry <= now) + return; + dnr = chanserv_add_dnr(key, setter, expiry, reason); if(!dnr) return; str = database_get_data(hir->d.object, KEY_DNR_SET, RECDB_QSTRING); @@ -9222,14 +9876,22 @@ static void write_dnrs_helper(struct saxdb_context *ctx, struct dict *dnrs) { struct do_not_register *dnr; - dict_iterator_t it; + dict_iterator_t it, next; - for(it = dict_first(dnrs); it; it = iter_next(it)) + for(it = dict_first(dnrs); it; it = next) { + next = iter_next(it); dnr = iter_data(it); + if(dnr->expires && dnr->expires <= now) + continue; saxdb_start_record(ctx, dnr->chan_name, 0); if(dnr->set) saxdb_write_int(ctx, KEY_DNR_SET, dnr->set); + if(dnr->expires) + { + dict_remove(dnrs, iter_key(it)); + saxdb_write_int(ctx, KEY_EXPIRES, dnr->expires); + } saxdb_write_string(ctx, KEY_DNR_SETTER, dnr->setter); saxdb_write_string(ctx, KEY_DNR_REASON, dnr->reason); saxdb_end_record(ctx); @@ -9270,9 +9932,9 @@ chanserv_saxdb_write(struct saxdb_context *ctx) } static void -chanserv_db_cleanup(void) { +chanserv_db_cleanup(UNUSED_ARG(void *extra)) { unsigned int ii; - unreg_part_func(handle_part); + unreg_part_func(handle_part, NULL); while(channelList) unregister_channel(channelList, "terminating."); for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii) @@ -9296,7 +9958,11 @@ chanserv_db_cleanup(void) { } } -#define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, OPTIONS...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, ## OPTIONS) +#if defined(GCC_VARMACROS) +# define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, ARGS...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, ARGS) +#elif defined(C99_VARMACROS) +# define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, ...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, __VA_ARGS__) +#endif #define DEFINE_CHANNEL_OPTION(NAME) modcmd_register(chanserv_module, "set "#NAME, chan_opt_##NAME, 1, 0, NULL) #define DEFINE_USER_OPTION(NAME) modcmd_register(chanserv_module, "uset "#NAME, user_opt_##NAME, 1, MODCMD_REQUIRE_REGCHAN, NULL) @@ -9309,19 +9975,20 @@ init_chanserv(const char *nick) CS_LOG = log_register_type("ChanServ", "file:chanserv.log"); conf_register_reload(chanserv_conf_read); - reg_server_link_func(handle_server_link); - - reg_new_channel_func(handle_new_channel); - reg_join_func(handle_join); - reg_part_func(handle_part); - reg_kick_func(handle_kick); - reg_topic_func(handle_topic); - reg_mode_change_func(handle_mode); - reg_nick_change_func(handle_nick_change); + if (nick) { + reg_server_link_func(handle_server_link, NULL); + reg_new_channel_func(handle_new_channel, NULL); + reg_join_func(handle_join, NULL); + reg_part_func(handle_part, NULL); + reg_kick_func(handle_kick, NULL); + reg_topic_func(handle_topic, NULL); + reg_mode_change_func(handle_mode, NULL); + reg_nick_change_func(handle_nick_change, NULL); + reg_auth_func(handle_auth, NULL); + } - reg_auth_func(handle_auth); - reg_handle_rename_func(handle_rename); - reg_unreg_func(handle_unreg); + reg_handle_rename_func(handle_rename, NULL); + reg_unreg_func(handle_unreg, NULL); handle_dnrs = dict_new(); dict_set_free_data(handle_dnrs, free); @@ -9330,14 +9997,18 @@ init_chanserv(const char *nick) mask_dnrs = dict_new(); dict_set_free_data(mask_dnrs, free); - reg_svccmd_unbind_func(handle_svccmd_unbind); + reg_svccmd_unbind_func(handle_svccmd_unbind, NULL); chanserv_module = module_register("ChanServ", CS_LOG, "chanserv.help", chanserv_expand_variable); - DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL); + DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+channel", NULL); DEFINE_COMMAND(noregister, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL); DEFINE_COMMAND(allowregister, 2, 0, "template", "noregister", NULL); + DEFINE_COMMAND(dnrsearch, 3, 0, "template", "noregister", NULL); + modcmd_register(chanserv_module, "dnrsearch print", NULL, 0, 0, NULL); + modcmd_register(chanserv_module, "dnrsearch remove", NULL, 0, 0, NULL); + modcmd_register(chanserv_module, "dnrsearch count", NULL, 0, 0, NULL); DEFINE_COMMAND(move, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "template", "register", NULL); - DEFINE_COMMAND(csuspend, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+helping", NULL); - DEFINE_COMMAND(cunsuspend, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+helping", NULL); + DEFINE_COMMAND(csuspend, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN|MODCMD_IGNORE_CSUSPEND, "flags", "+helping", NULL); + DEFINE_COMMAND(cunsuspend, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN|MODCMD_IGNORE_CSUSPEND, "flags", "+helping", NULL); DEFINE_COMMAND(createnote, 5, 0, "level", "800", NULL); DEFINE_COMMAND(removenote, 2, 0, "level", "800", NULL); @@ -9357,6 +10028,7 @@ init_chanserv(const char *nick) DEFINE_COMMAND(mdelmanager, 2, MODCMD_REQUIRE_CHANUSER, "access", "coowner", NULL); DEFINE_COMMAND(mdelop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); + DEFINE_COMMAND(mdelpal, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); DEFINE_COMMAND(mdelhalfop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); DEFINE_COMMAND(levels, 1, 0, NULL); @@ -9469,6 +10141,9 @@ init_chanserv(const char *nick) DEFINE_CHANNEL_OPTION(ctcpreaction); DEFINE_CHANNEL_OPTION(bantimeout); DEFINE_CHANNEL_OPTION(inviteme); + DEFINE_CHANNEL_OPTION(unreviewed); + modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL); + modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, "flags", "+oper", NULL); DEFINE_CHANNEL_OPTION(maxsetinfo); if(off_channel > 1) DEFINE_CHANNEL_OPTION(offchannel); @@ -9491,9 +10166,9 @@ init_chanserv(const char *nick) if(nick) { const char *modes = conf_get_data("services/chanserv/modes", RECDB_QSTRING); - chanserv = AddService(nick, modes ? modes : NULL, "Channel Services", NULL); + chanserv = AddLocalUser(nick, nick, NULL, "Channel Services", modes); service_register(chanserv)->trigger = '!'; - reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check); + reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check, NULL); } saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write); @@ -9501,6 +10176,9 @@ init_chanserv(const char *nick) if(chanserv_conf.channel_expire_frequency) timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL); + if(chanserv_conf.dnr_expire_frequency) + timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL); + if(chanserv_conf.ban_timeout_frequency) timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL); @@ -9519,7 +10197,7 @@ init_chanserv(const char *nick) } } - reg_exit_func(chanserv_db_cleanup); + reg_exit_func(chanserv_db_cleanup, NULL); message_register_table(msgtab); }