X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/7a27854095fb80ce76e2ef5a59e87f42fbbe45fe..928e5a35af3dc82bdfbdd041dd3160e0a59aa01d:/src/chanserv.c diff --git a/src/chanserv.c b/src/chanserv.c index d9784e7..74f9898 100644 --- a/src/chanserv.c +++ b/src/chanserv.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, @@ -39,6 +39,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" @@ -116,6 +117,9 @@ #define KEY_LEVEL "level" #define KEY_INFO "info" #define KEY_SEEN "seen" +#define KEY_ACCESSEXPIRY "accessexpiry" +#define KEY_CLVLEXPIRY "clvlexpiry" +#define KEY_LASTLEVEL "lastlevel" /* Ban data */ #define KEY_OWNER "owner" @@ -127,7 +131,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 */ @@ -149,6 +154,7 @@ static const struct message_entry msgtab[] = { { "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_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." }, @@ -156,6 +162,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." }, @@ -199,6 +207,8 @@ static const struct message_entry msgtab[] = { { "CSMSG_DELETED_YOU", "Your $b%d$b access has been deleted from $b%s$b." }, /* User management */ + { "CSMSG_AUTO_DELETED", "Your %s access has expired in %s." }, + { "CSMSG_CLVL_EXPIRED", "Your CLVL access has expired in %s." }, { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %s (%d)." }, { "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." }, { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." }, @@ -217,9 +227,11 @@ static const struct message_entry msgtab[] = { { "CSMSG_NO_SELF_CLVL", "You cannot change your own access." }, { "CSMSG_NO_BUMP_ACCESS", "You cannot give users access greater than or equal to your own." }, + { "CSMSG_NO_BUMP_EXPIRY", "You cannot give users timed $bCLVL$b's when they already have timed access." }, { "CSMSG_MULTIPLE_OWNERS", "There is more than one owner in %s; please use $bCLVL$b, $bDELOWNER$b and/or $bADDOWNER$b instead." }, { "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_CONFIRM_GIVEOWNERSHIP", "To really give ownership to $b%1$s$b, you must use 'giveownership *%1$s %2$s'." }, { "CSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." }, { "CSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." }, @@ -230,7 +242,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_BAN_DONE", "Banned $b%s$b from %s." }, { "CSMSG_REASON_CHANGE", "Reason for LAMER $b%s$b changed." }, { "CSMSG_LAMER_EXTENDED", "Extended LAMER for $b%s$b, now expires in %s." }, - { "CSMSG_BAN_REMOVED", "Matching ban(s) and LAMER(s) in $b%s$b were removed." }, + { "CSMSG_BAN_REMOVED", "Ban(s) and LAMER(s) matching $b%s$b were removed." }, { "CSMSG_TRIMMED_LAMERS", "Trimmed $b%d LAMERs$b from the %s LAMER list that were inactive for at least %s." }, { "CSMSG_REDUNDANT_BAN", "$b%s$b is already banned in %s." }, { "CSMSG_REDUNDANT_LAMER", "$b%s$b is already LAMER'd in %s." }, @@ -291,6 +303,7 @@ static const struct message_entry msgtab[] = { { "CSMSG_SET_TOYS", "$bToys $b %d - %s" }, { "CSMSG_SET_CTCPREACTION", "$bCTCPReaction$b %d - %s" }, { "CSMSG_SET_TOPICREFRESH", "$bTopicRefresh$b %d - %s" }, + { "CSMSG_SET_UNREVIEWED", "$bUnreviewed $b %s" }, { "CSMSG_SET_RESYNC", "$bResync $b %d - %s" }, { "CSMSG_SET_BANTIMEOUT", "$bBanTimeout $b %d - %s" }, @@ -419,6 +432,9 @@ static const struct message_entry msgtab[] = { { "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_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 */ @@ -493,6 +509,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." }, @@ -565,6 +582,7 @@ static struct unsigned long db_backup_frequency; unsigned long channel_expire_frequency; + unsigned long dnr_expire_frequency; unsigned long ban_timeout_frequency; long info_delay; @@ -912,7 +930,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)) { unsigned int eflags, argc; char *argv[4]; @@ -935,7 +953,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)) @@ -1238,7 +1256,7 @@ 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) +add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned short access, time_t seen, const char *info, time_t accessexpiry) { struct userData *ud; @@ -1251,6 +1269,9 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned ud->seen = seen; ud->access = access; ud->info = info ? strdup(info) : NULL; + ud->accessexpiry = accessexpiry ? accessexpiry : 0; + ud->clvlexpiry = 0; + ud->lastaccess = 0; ud->prev = NULL; ud->next = channel->users; @@ -1273,6 +1294,110 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned static void unregister_channel(struct chanData *channel, const char *reason); +static void +chanserv_expire_tempuser(void *data) +{ + struct userData *uData = data; + char *handle; + + if (data) { + handle = strdup(uData->handle->handle); + if (uData->accessexpiry > 0) { + if (uData->present) { + struct userNode *user, *next_un = NULL; + struct handle_info *hi; + + hi = get_handle_info(handle); + for (user = hi->users; user; user = next_un) { + struct mod_chanmode *change; + struct modeNode *mn; + unsigned int count = 0; + + send_message(user, chanserv, "CSMSG_AUTO_DELETED", chanserv->nick, uData->channel->channel->name); + if (!(mn = GetUserMode(uData->channel->channel, user)) || !(mn->modes & MODE_CHANOP)) { + next_un = user->next_authed; + continue; + } + + change = mod_chanmode_alloc(2); + change->args[count].mode = MODE_REMOVE | MODE_CHANOP; + change->args[count++].u.member = mn; + + if (count) { + change->argc = count; + mod_chanmode_announce(chanserv, uData->channel->channel, change); + } + mod_chanmode_free(change); + next_un = user->next_authed; + } + } + del_channel_user(uData, 1); + } + } +} + +static void +chanserv_expire_tempclvl(void *data) +{ + struct userData *uData = data; + char *handle; + + if (data) { + handle = strdup(uData->handle->handle); + if (uData->clvlexpiry > 0) { + int changemodes = 0; + unsigned int mode = 0; + + if (((uData->lastaccess == UL_PEON) || (uData->lastaccess == UL_HALFOP)) && (uData->access >= UL_OP)) { + changemodes = 1; + mode = MODE_REMOVE | MODE_CHANOP; + } else if ((uData->lastaccess == UL_PEON) && (uData->access == UL_HALFOP)) { + changemodes = 1; + mode = MODE_REMOVE | MODE_HALFOP; + } else + changemodes = 0; + + if (uData->present) { + struct userNode *user, *next_un = NULL; + struct handle_info *hi; + + hi = get_handle_info(handle); + for (user = hi->users; user; user = next_un) { + struct mod_chanmode *change; + struct modeNode *mn; + unsigned int count = 0; + + send_message(user, chanserv, "CSMSG_CLVL_EXPIRED", uData->channel->channel->name); + if (!(mn = GetUserMode(uData->channel->channel, user))) { + next_un = user->next_authed; + continue; + } + + if (changemodes == 0) { + next_un = user->next_authed; + continue; + } + + change = mod_chanmode_alloc(2); + change->args[count].mode = mode; + change->args[count++].u.member = mn; + + if (count) { + change->argc = count; + mod_chanmode_announce(chanserv, uData->channel->channel, change); + } + mod_chanmode_free(change); + next_un = user->next_authed; + } + } + + uData->access = uData->lastaccess; + uData->lastaccess = 0; + uData->clvlexpiry = 0; + } + } +} + void del_channel_user(struct userData *user, int do_gc) { @@ -1281,6 +1406,9 @@ del_channel_user(struct userData *user, int do_gc) channel->userCount--; userCount--; + timeq_del(0, chanserv_expire_tempuser, user, TIMEQ_IGNORE_WHEN); + timeq_del(0, chanserv_expire_tempclvl, user, TIMEQ_IGNORE_WHEN); + if(user->prev) user->prev->next = user->next; else @@ -1391,7 +1519,7 @@ process_adduser_pending(struct userNode *user) } else { - actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL); + actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL, 0); scan_user_presence(actee, NULL); } del_adduser_pending(ap); @@ -1637,6 +1765,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; + struct do_not_register *dnr; + + for(it = dict_first(handle_dnrs); it; it = iter_next(it)) + { + dnr = iter_data(it); + if(!dnr->expires || dnr->expires > now) + continue; + dict_remove(handle_dnrs, dnr->chan_name + 1); + } + for(it = dict_first(plain_dnrs); it; it = iter_next(it)) + { + dnr = iter_data(it); + if(!dnr->expires || dnr->expires > now) + continue; + dict_remove(plain_dnrs, dnr->chan_name); + } + for(it = dict_first(mask_dnrs); it; it = iter_next(it)) + { + dnr = iter_data(it); + if(!dnr->expires || dnr->expires > now) + continue; + dict_remove(mask_dnrs, dnr->chan_name); + } + + 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) { @@ -1774,13 +1934,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, "*?")) @@ -1791,44 +1952,81 @@ 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))) + { + 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))) + { + 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); @@ -1838,65 +2036,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]; + 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 @@ -1914,20 +2097,34 @@ 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; + } + + const char *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; } reply("CSMSG_DNR_SEARCH_RESULTS"); - if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) - reply("CSMSG_BAR"); if(*target == '*') matches = chanserv_show_dnrs(user, cmd, NULL, target + 1); else @@ -1941,26 +2138,262 @@ 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 && ((dnr->expires < search->min_expires) + || (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->max_expires = 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; + } } - else if(dict_find(mask_dnrs, chan_name, NULL)) + 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) { - dict_remove(mask_dnrs, chan_name); - reply("CSMSG_DNR_REMOVED", chan_name); + 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(target_fixed) + { + /* 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 + { + /* 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_NO_SUCH_DNR", chan_name); + 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; } @@ -2103,7 +2536,7 @@ static CHANSERV_FUNC(cmd_register) channel = AddChannel(argv[1], now, NULL, NULL, NULL); cData = register_channel(channel, user->handle_info->handle); - scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL), NULL); + scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL, 0), NULL); cData->modes = chanserv_conf.default_modes; if(off_channel > 0) cData->modes.modes_set |= MODE_REGISTERED; @@ -2592,9 +3025,9 @@ static CHANSERV_FUNC(cmd_opchan) static CHANSERV_FUNC(cmd_adduser) { struct userData *actee; - struct userData *actor; + struct userData *actor, *real_actor;; struct handle_info *handle; - unsigned short access; + unsigned short access, override = 0; REQUIRE_PARAMS(3); @@ -2612,12 +3045,18 @@ static CHANSERV_FUNC(cmd_adduser) } actor = GetChannelUser(channel->channel_info, user->handle_info); + real_actor = GetChannelAccess(channel->channel_info, user->handle_info); + if(actor->access <= access) { reply("CSMSG_NO_BUMP_ACCESS"); return 0; } + /* Trying to add someone with equal/more access? */ + if (!real_actor || real_actor->access <= access) + override = CMD_LOG_OVERRIDE; + if(!(handle = modcmd_get_handle_info(user, argv[1]))) { // 'kevin must first authenticate with AuthServ.' is sent to user @@ -2650,23 +3089,35 @@ static CHANSERV_FUNC(cmd_adduser) return 0; } - actee = add_channel_user(channel->channel_info, handle, access, 0, NULL); + time_t accessexpiry = 0; + unsigned int duration = 0; + if (argc > 3) { + if ((duration = ParseInterval(argv[3]))) + accessexpiry = now + duration; + } + + actee = add_channel_user(channel->channel_info, handle, access, 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; + 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; @@ -2703,22 +3154,52 @@ static CHANSERV_FUNC(cmd_clvl) return 0; } + time_t clvlexpiry = 0; + unsigned int duration = 0; + if (argc > 3) { + if ((duration = ParseInterval(argv[3]))) + clvlexpiry = now + duration; + } + + if (duration > 0) { + if (victim->accessexpiry > 0) { + reply("CSMSG_NO_BUMP_EXPIRY"); + return 0; + } + + victim->clvlexpiry = clvlexpiry; + victim->lastaccess = victim->access; + 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, override = 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; @@ -2732,12 +3213,13 @@ static CHANSERV_FUNC(cmd_deluser) if(argc > 2) { access = user_level_from_name(argv[1], UL_OWNER); + char *useraccess = user_level_name_from_level(victim->access); if(!access) { reply("CSMSG_INVALID_ACCESS", argv[1]); return 0; } - if(access != victim->access) + if(strcasecmp(argv[1], useraccess)) { reply("CSMSG_INCORRECT_ACCESS", handle->handle, user_level_name_from_level(victim->access), argv[1]); return 0; @@ -2754,19 +3236,28 @@ 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); 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) { @@ -2780,6 +3271,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; @@ -2791,7 +3285,8 @@ 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) @@ -2927,13 +3422,15 @@ cmd_trim_users(struct svccmd *cmd, struct userNode *user, struct chanNode *chann 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; @@ -3273,6 +3770,35 @@ 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); + + if(bad_channel_ban(channel, user, banmask, &victimCount, victims)) + { + reply("CSMSG_MASK_PROTECTED", 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])) @@ -3676,13 +4202,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])) - { - reply("MSG_NICK_UNKNOWN", argv[1]); - return 0; - } + if(!is_ircmask(argv[1]) && *argv[1] == '*') + { + char banmask[NICKLEN + USERLEN + HOSTLEN + 3]; + const char *accountname = argv[1] + 1; - mask = strdup(argv[1]); + snprintf(banmask, sizeof(banmask), "*!*@%s.*", accountname); + mask = strdup(banmask); + } + else if(!is_ircmask(argv[1])) + { + reply("MSG_NICK_UNKNOWN", argv[1]); + return 0; + } + else + { + mask = strdup(argv[1]); + } } /* We don't sanitize the mask here because ircu @@ -3927,17 +4463,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) { @@ -4184,9 +4720,9 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg lData.table.contents = malloc(lData.table.length*sizeof(*lData.table.contents)); if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED) - lData.table.width = 5; /* with level = 5 */ + lData.table.width = 6; /* with level = 6 */ else - lData.table.width = 4; /* without = 4 */ + lData.table.width = 5; /* without = 5 */ ary = malloc(lData.table.width*sizeof(**lData.table.contents)); lData.table.contents[0] = ary; if(user->handle_info) { @@ -4214,6 +4750,7 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg ary[i] = "Last Seen"; seen_index = i++; ary[i++] = "Status"; + ary[i++] = "Expiry"; for(matches = 1; matches < lData.table.length; ++matches) { struct userData *uData = lData.users[matches-1]; @@ -4258,7 +4795,23 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg ary[i++] = "Vacation"; else ary[i++] = "Normal"; + + if ((uData->accessexpiry > 0) || (uData->clvlexpiry > 0)) { + char delay[INTERVALLEN]; + time_t diff; + + if (uData->accessexpiry > 0) { + diff = uData->accessexpiry - now; + intervalString(delay, diff, user->handle_info); + } else { + diff = uData->clvlexpiry - now; + intervalString(delay, diff, user->handle_info); + } + ary[i++] = delay; + } else + ary[i++] = "Never"; } + send_list(&lData); for(matches = 1; matches < lData.table.length; ++matches) { @@ -4322,16 +4875,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 */ @@ -4339,8 +4898,16 @@ static CHANSERV_FUNC(cmd_lamers) /* lamers */ for(ban = channel->channel_info->bans; ban; ban = ban->next) { - if(search && !match_ircglobs(search, ban->mask)) + if(search_u) + { + if(!user_matches_glob(search_u, ban->mask, MATCH_USENICK | MATCH_VISIBLE)) 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; @@ -4942,10 +5509,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))) @@ -4958,11 +5527,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 @@ -5599,6 +6170,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]); @@ -5741,6 +6314,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) @@ -6028,7 +6655,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; @@ -6283,6 +6911,9 @@ static CHANSERV_FUNC(cmd_set) return 0; } + argv[0] = ""; + argv[1] = buf; + return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd); } @@ -6449,9 +7080,12 @@ 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; + const char *confirm; struct giveownership *giveownership; unsigned int force, override; unsigned short co_access, new_owner_old_access; @@ -6486,7 +7120,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); @@ -6509,7 +7143,7 @@ static CHANSERV_FUNC(cmd_giveownership) { if(force) { - new_owner = add_channel_user(cData, new_owner_hi, UL_COOWNER, 0, NULL); + new_owner = add_channel_user(cData, new_owner_hi, UL_OWNER - 1, 0, NULL, 0); } else { @@ -6530,6 +7164,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; @@ -6542,8 +7187,8 @@ static CHANSERV_FUNC(cmd_giveownership) giveownership = calloc(1, sizeof(*giveownership)); giveownership->issued = now; - giveownership->old_owner = curr_user->handle->handle; - giveownership->target = new_owner_hi->handle; + giveownership->old_owner = strdup(curr_user->handle->handle); + giveownership->target = strdup(new_owner_hi->handle); giveownership->target_access = new_owner_old_access; if(override) { @@ -6576,12 +7221,14 @@ chanserv_expire_user_suspension(void *data) static CHANSERV_FUNC(cmd_suspend) { struct handle_info *hi; - struct userData *self, *target; + struct userData *self, *real_self, *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); + real_self = GetChannelAccess(channel->channel_info, user->handle_info); if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) { reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); @@ -6620,19 +7267,23 @@ static CHANSERV_FUNC(cmd_suspend) if(target->expires) timeq_add(target->expires, chanserv_expire_user_suspension, target); + if(!real_self || target->access >= real_self->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 *self, *real_self, *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); + real_self = GetChannelAccess(channel->channel_info, user->handle_info); if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) { reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name); @@ -6648,11 +7299,13 @@ static CHANSERV_FUNC(cmd_unsuspend) reply("CSMSG_NOT_SUSPENDED", hi->handle); return 0; } + if(!real_self || target->access >= real_self->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) @@ -7098,6 +7751,101 @@ handle_new_channel(struct chanNode *channel) SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1); } +int +trace_check_bans(struct userNode *user, struct chanNode *chan) +{ + struct banData *bData; + struct mod_chanmode *change; + + change = find_matching_bans(&chan->banlist, user, NULL); + if (change) + return 1; + + /* lamer list */ + if (chan->channel_info) { + for(bData = chan->channel_info->bans; bData; bData = bData->next) { + + if(!user_matches_glob(user, bData->mask, MATCH_USENICK)) + continue; + + if(bData) + return 1; + } + } + + return 0; +} + +int +check_bans(struct userNode *user, const char *channel) +{ + struct chanNode *chan; + struct mod_chanmode change; + struct chanData *cData; + struct banData *bData; + + if (!(chan = GetChannel(channel))) + return 0; + + if(!(cData = chan->channel_info)) + return 0; + + mod_chanmode_init(&change); + change.argc = 1; + + if(chan->banlist.used < MAXBANS) + { + /* Not joining through a ban. */ + for(bData = cData->bans; + bData && !user_matches_glob(user, bData->mask, MATCH_USENICK); + bData = bData->next); + + if(bData) + { + char kick_reason[MAXLEN]; + sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason); + + bData->triggered = now; + if(bData != cData->bans) + { + /* Shuffle the ban to the head of the list. */ + if(bData->next) + bData->next->prev = bData->prev; + if(bData->prev) + bData->prev->next = bData->next; + + bData->prev = NULL; + bData->next = cData->bans; + + if(cData->bans) + cData->bans->prev = bData; + + cData->bans = bData; + } + + change.args[0].mode = MODE_BAN; + change.args[0].u.hostmask = bData->mask; + mod_chanmode_announce(chanserv, chan, &change); + KickChannelUser(user, chan, chanserv, kick_reason); + return 1; + } + } + return 0; +} + +int +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)) + return true; + } + return false; +} + + /* Welcome to my worst nightmare. Warning: Read (or modify) the code below at your own risk. */ static int @@ -7113,7 +7861,7 @@ handle_join(struct modeNode *mNode) unsigned int modes = 0, info = 0; char *greeting; - if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info)) + if(IsLocal(user) || !channel || !channel->channel_info || IsSuspended(channel->channel_info)) return 0; cData = channel->channel_info; @@ -7156,13 +7904,12 @@ handle_join(struct modeNode *mNode) } - mod_chanmode_init(&change); change.argc = 1; /* TODO: maybe only people above inviteme level? -Rubin */ /* We don't kick people with access */ - if(!uData) + if(!uData && !channel_user_is_exempt(user, channel)) { if(channel->banlist.used < MAXBANS) { @@ -7472,6 +8219,8 @@ 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)) @@ -7780,6 +8529,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); @@ -7936,9 +8687,9 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) { struct handle_info *handle; struct userData *uData; - char *seen, *inf, *flags, *expires; + char *seen, *inf, *flags, *expires, *accessexpiry, *clvlexpiry, *lstacc; time_t last_seen; - unsigned short access; + unsigned short access, lastaccess = 0; if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) { @@ -7958,6 +8709,11 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) last_seen = seen ? (signed)strtoul(seen, NULL, 0) : now; flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING); expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING); + accessexpiry = database_get_data(rd->d.object, KEY_ACCESSEXPIRY, RECDB_QSTRING); + clvlexpiry = database_get_data(rd->d.object, KEY_CLVLEXPIRY, RECDB_QSTRING); + lstacc = database_get_data(rd->d.object, KEY_LASTLEVEL, RECDB_QSTRING); + lastaccess = lstacc ? atoi(lstacc) : 0; + handle = get_handle_info(key); if(!handle) { @@ -7965,10 +8721,20 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) return; } - uData = add_channel_user(chan, handle, access, last_seen, inf); + uData = add_channel_user(chan, handle, access, last_seen, inf, 0); uData->flags = flags ? strtoul(flags, NULL, 0) : 0; uData->expires = expires ? strtoul(expires, NULL, 0) : 0; + uData->accessexpiry = accessexpiry ? strtoul(accessexpiry, NULL, 0) : 0; + if (uData->accessexpiry > 0) + timeq_add(uData->accessexpiry, chanserv_expire_tempuser, uData); + + uData->clvlexpiry = clvlexpiry ? strtoul(clvlexpiry, NULL, 0) : 0; + if (uData->clvlexpiry > 0) + timeq_add(uData->clvlexpiry, chanserv_expire_tempclvl, uData); + + uData->lastaccess = lastaccess; + if((uData->flags & USER_SUSPENDED) && uData->expires) { if(uData->expires > now) @@ -8297,6 +9063,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) @@ -8310,7 +9077,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); @@ -8367,6 +9138,9 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData) saxdb_start_record(ctx, uData->handle->handle, 0); saxdb_write_int(ctx, KEY_LEVEL, uData->access); saxdb_write_int(ctx, KEY_SEEN, uData->seen); + saxdb_write_int(ctx, KEY_ACCESSEXPIRY, uData->accessexpiry); + saxdb_write_int(ctx, KEY_CLVLEXPIRY, uData->clvlexpiry); + saxdb_write_int(ctx, KEY_LASTLEVEL, uData->lastaccess); if(uData->flags) saxdb_write_int(ctx, KEY_FLAGS, uData->flags); if(uData->expires) @@ -8544,14 +9318,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) + { + dict_remove(dnrs, iter_key(it)); + continue; + } saxdb_start_record(ctx, dnr->chan_name, 0); if(dnr->set) saxdb_write_int(ctx, KEY_DNR_SET, dnr->set); + if(dnr->expires) + 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); @@ -8629,8 +9411,9 @@ init_chanserv(const char *nick) CS_LOG = log_register_type("ChanServ", "file:chanserv.log"); conf_register_reload(chanserv_conf_read); + if(nick) + { reg_server_link_func(handle_server_link); - reg_new_channel_func(handle_new_channel); reg_join_func(handle_join); reg_part_func(handle_part); @@ -8638,8 +9421,10 @@ init_chanserv(const char *nick) reg_topic_func(handle_topic); reg_mode_change_func(handle_mode); reg_nick_change_func(handle_nick_change); - reg_auth_func(handle_auth); + } + + reg_handle_rename_func(handle_rename); reg_unreg_func(handle_unreg); @@ -8655,6 +9440,10 @@ init_chanserv(const char *nick) DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", 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); @@ -8707,6 +9496,9 @@ init_chanserv(const char *nick) DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "hop", "flags", "+never_csuspend", NULL); DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL); DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL); + DEFINE_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_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "manager", NULL); DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL); DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); @@ -8807,7 +9599,7 @@ 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); } @@ -8817,6 +9609,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); @@ -8838,3 +9633,4 @@ init_chanserv(const char *nick) reg_exit_func(chanserv_db_cleanup); message_register_table(msgtab); } +