X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/4b6129c055ce20d1f0b614c36fe7f6072aec69da..d0f04f713ca0b689f745842fcc9e61d24610f11a:/src/chanserv.c diff --git a/src/chanserv.c b/src/chanserv.c index a44f19f..7eb1973 100644 --- a/src/chanserv.c +++ b/src/chanserv.c @@ -3,7 +3,7 @@ * * This file is part of x3. * - * srvx is free software; you can redistribute it and/or modify + * 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 * (at your option) any later version. @@ -92,6 +92,11 @@ #define KEY_REVOKED "revoked" #define KEY_SUSPEND_EXPIRES "suspend_expires" #define KEY_SUSPEND_REASON "suspend_reason" +#define KEY_GIVEOWNERSHIP "giveownership" +#define KEY_STAFF_ISSUER "staff_issuer" +#define KEY_OLD_OWNER "old_owner" +#define KEY_TARGET "target" +#define KEY_TARGET_ACCESS "target_access" #define KEY_VISITED "visited" #define KEY_TOPIC "topic" #define KEY_GREETING "greeting" @@ -303,10 +308,10 @@ static const struct message_entry msgtab[] = { { "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_VOICE", "Give voice to strangers." }, - { "CSMSG_AUTOMODE_HOP", "Give halfops to strangers." }, - { "CSMSG_AUTOMODE_OP", "Give ops to strangers (not advised)" }, - { "CSMSG_AUTOMODE_MUTE", "Give half-op to halfops, and op to ops (no voice to peons)." }, + { "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)" }, + { "CSMSG_AUTOMODE_MUTE", "Give half-op to halfops, and op to ops only." }, { "CSMSG_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." }, { "CSMSG_PROTECT_EQUAL", "Users will be protected from those of equal or lower access." }, @@ -349,8 +354,14 @@ static const struct message_entry msgtab[] = { { "CSMSG_BANS_REMOVED", "Removed all channel bans from $b%s$b." }, /* Channel userlist */ - { "CSMSG_ACCESS_ALL_HEADER", "$b%s Users From Level %s To %s$b" }, - { "CSMSG_ACCESS_SEARCH_HEADER", "$b%s Users From Level %s To %s Matching %s$b" }, + { "CSMSG_ACCESS_ALL_HEADER_NORMAL", "$b%s Users From Level %s To %s$b" }, + { "CSMSG_ACCESS_SEARCH_HEADER_NORMAL", "$b%s Users From Level %s To %s Matching %s$b" }, + /* uncomment if needed to adujust styles (and change code below) + { "CSMSG_ACCESS_ALL_HEADER_CLEAN", "$b%s Users From Level %s To %s$b" }, + { "CSMSG_ACCESS_SEARCH_HEADER_CLEAN", "$b%s Users From Level %s To %s Matching %s$b" }, + { "CSMSG_ACCESS_ALL_HEADER_ADVANCED", "$b%s Users From Level %s To %s$b" }, + { "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", "$b%s Users From Level %s To %s Matching %s$b" }, + */ { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." }, { "CSMSG_CHANGED_ACCESS", "%s now has access $b%s$b (%u) in %s." }, { "CSMSG_LAMERS_HEADER", "$bLamers in %s$b" }, @@ -427,7 +438,12 @@ static const struct message_entry msgtab[] = { { "CSMSG_CHANNEL_SUSPENDED_7", " %s ago by %s; revoked %s ago: %s" }, { "CSMSG_CHANNEL_REGISTERED", "$bRegistered: $b%s ago." }, { "CSMSG_CHANNEL_VISITED", "$bVisited: $b%s ago." }, + { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for $b%s$b" }, + { "CSMSG_CHANNEL_OWNERSHIP_NORMAL", "from %s to %s (%d access) on %s" }, + { "CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", "from %s to %s (%d access) by %s on %s reason: %s" }, + { "CSMSG_CHANNEL_OWNERSHIP_STAFF", "from %s to %s (%d access) by %s on %s" }, { "CSMSG_CHANNEL_END", "---------------End of Info--------------"}, + { "CSMSG_CHANNEL_END_CLEAN", "End of Info"}, { "CSMSG_PEEK_INFO", "$bStatus of %s$b" }, { "CSMSG_PEEK_TOPIC", "$bTopic: $b%s" }, @@ -633,14 +649,14 @@ static const struct { unsigned int old_flag; unsigned short flag_value; } levelOptions[] = { - { "CSMSG_SET_ENFOPS", "enfops", 300, 1, 0, 0 }, - { "CSMSG_SET_ENFHALFOPS", "enfhalfops", 300, 1, 0, 0 }, - { "CSMSG_SET_ENFMODES", "enfmodes", 200, 3, 0, 0 }, - { "CSMSG_SET_ENFTOPIC", "enftopic", 200, 4, 0, 0 }, - { "CSMSG_SET_PUBCMD", "pubcmd", 0, 5, 0, 0 }, - { "CSMSG_SET_SETTERS", "setters", 400, 7, 0, 0 }, - { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 }, - { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 }, + { "CSMSG_SET_ENFOPS", "enfops", 300, 1, 0, 0 }, + { "CSMSG_SET_ENFHALFOPS", "enfhalfops", 300, 1, 0, 0 }, + { "CSMSG_SET_ENFMODES", "enfmodes", 200, 3, 0, 0 }, + { "CSMSG_SET_ENFTOPIC", "enftopic", 200, 4, 0, 0 }, + { "CSMSG_SET_PUBCMD", "pubcmd", 0, 5, 0, 0 }, + { "CSMSG_SET_SETTERS", "setters", 400, 7, 0, 0 }, + { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 }, + { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 }, { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 } }; @@ -1798,7 +1814,8 @@ static CHANSERV_FUNC(cmd_noregister) dict_iterator_t it; reply("CSMSG_DNR_SEARCH_RESULTS"); - reply("CSMSG_BAR"); + 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)) { @@ -1857,7 +1874,8 @@ static CHANSERV_FUNC(cmd_noregister) } reply("CSMSG_DNR_SEARCH_RESULTS"); - reply("CSMSG_BAR"); + 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 @@ -2388,8 +2406,20 @@ merge_bans(struct chanData *source, struct chanData *target) static void merge_data(struct chanData *source, struct chanData *target) { + /* Use more recent visited and owner-transfer time; use older + * registered time. Bitwise or may_opchan. Use higher max. + * Do not touch last_refresh, ban count or user counts. + */ if(source->visited > target->visited) target->visited = source->visited; + if(source->registered < target->registered) + target->registered = source->registered; + if(source->ownerTransfer > target->ownerTransfer) + target->ownerTransfer = source->ownerTransfer; + if(source->may_opchan) + target->may_opchan = 1; + if(source->max > target->max) + target->max = source->max; } static void @@ -2737,7 +2767,7 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur } static int -cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration) +cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration, int vacation) { struct userData *actor, *uData, *next; char interval[INTERVALLEN]; @@ -2763,7 +2793,9 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m { next = uData->next; - if((uData->seen > limit) || uData->present) + if((uData->seen > limit) + || uData->present + || (HANDLE_FLAGGED(uData->handle, FROZEN) && !vacation)) continue; if(((uData->access >= min_access) && (uData->access <= max_access)) @@ -2787,9 +2819,11 @@ static CHANSERV_FUNC(cmd_trim) { unsigned long duration; unsigned short min_level, max_level; + int vacation; REQUIRE_PARAMS(3); + vacation = argc > 3 && !strcmp(argv[3], "vacation"); duration = ParseInterval(argv[2]); if(duration < 60) { @@ -2804,17 +2838,17 @@ static CHANSERV_FUNC(cmd_trim) } else if(!irccasecmp(argv[1], "users")) { - cmd_trim_users(user, channel, 0, 0, duration); + cmd_trim_users(user, channel, 0, 0, duration, vacation); return 1; } else if(parse_level_range(&min_level, &max_level, argv[1])) { - cmd_trim_users(user, channel, min_level, max_level, duration); + cmd_trim_users(user, channel, min_level, max_level, duration, vacation); return 1; } else if((min_level = user_level_from_name(argv[1], UL_OWNER))) { - cmd_trim_users(user, channel, min_level, min_level, duration); + cmd_trim_users(user, channel, min_level, min_level, duration, vacation); return 1; } else @@ -3002,7 +3036,7 @@ static CHANSERV_FUNC(cmd_devoice) } static int -bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, int *victimCount, struct modeNode **victims) +bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims) { unsigned int ii; @@ -3015,7 +3049,7 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban if(IsService(mn->user)) continue; - if(!user_matches_glob(mn->user, ban, 1)) + if(!user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE)) continue; if(protect_user(mn->user, user, channel->channel_info)) @@ -3104,18 +3138,18 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c reply("CSMSG_MASK_PROTECTED", argv[1]); return 0; } - /* We dont actually want a victem count if were banning a mask manually, IMO -Rubin*/ - if(cmd) - victimCount = 0; /* Dont deop etc ppl who match this */ - -#ifdef entropy_lameness - if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user)) +/* If i want to ban *.nl and theres 5 of them, what is it to the bot?!? +// if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user)) + And, ^^^^^^^^^ BAH! + We use x2 style over-mask detection instead because it doesnt stop channel owners from doing + reasonable bans, but does stop *@*, *@*a* *@*b* etc type masks. Yes, you can defeat it with + some creativity, but its not x3's job to be the ban censor anyway. */ + if(is_overmask(argv[1])) { if(cmd) reply("CSMSG_LAME_MASK", argv[1]); return 0; } -#endif if((action == ACTION_KICK) && (victimCount == 0)) { @@ -3375,7 +3409,8 @@ 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, 1); + match[ii] = user_matches_glob(actee, bans->list[ii]->ban, + MATCH_USENICK | MATCH_VISIBLE); if(match[ii]) count++; } @@ -3512,7 +3547,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, 1); + for( ; ban && !user_matches_glob(actee, ban->mask, MATCH_USENICK | MATCH_VISIBLE); ban = ban->next); else for( ; ban && !match_ircglobs(mask, ban->mask); @@ -3841,13 +3876,31 @@ zoot_list(struct listData *list) */ static void -def_list(struct listData *list) +normal_list(struct listData *list) +{ + const char *msg; + if(list->search) + send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search); + else + send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest)); + if(list->table.length == 1) + { + msg = user_find_message(list->user, "MSG_NONE"); + send_message_type(4, list->user, list->bot, " %s", msg); + } + else + table_send(list->bot, list->user->nick, 0, NULL, list->table); +} + +/* if these need changed, uncomment and customize +static void +clean_list(struct listData *list) { const char *msg; if(list->search) - send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search); + send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search); else - send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest)); + send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest)); if(list->table.length == 1) { msg = user_find_message(list->user, "MSG_NONE"); @@ -3857,6 +3910,25 @@ def_list(struct listData *list) table_send(list->bot, list->user->nick, 0, NULL, list->table); } +static void +advanced_list(struct listData *list) +{ + const char *msg; + if(list->search) + send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search); + else + send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest)); + if(list->table.length == 1) + { + msg = user_find_message(list->user, "MSG_NONE"); + send_message_type(4, list->user, list->bot, " %s", msg); + } + else + table_send(list->bot, list->user->nick, 0, NULL, list->table); +} + +*/ + static int userData_access_comp(const void *arg_a, const void *arg_b) { @@ -3878,6 +3950,8 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg struct listData lData; unsigned int matches; const char **ary; + int i = 0; + int seen_index; lData.user = user; lData.bot = cmd->parent->bot; @@ -3885,20 +3959,29 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg lData.lowest = lowest; lData.highest = highest; lData.search = (argc > 1) ? argv[1] : NULL; - send_list = def_list; + send_list = normal_list; /* What does the following line do exactly?? */ /*(void)zoot_list; ** since it doesn't show user levels */ - /* this does nothing!! -rubin + /* if(user->handle_info) { - switch(user->handle_info->userlist_style) - { - case HI_STYLE_DEF: send_list = def_list; break; - case HI_STYLE_ZOOT: send_list = def_list; break; - } + switch(user->handle_info->userlist_style) + { + case HI_STYLE_CLEAN: + send_list = clean_list; + break; + case HI_STYLE_ADVANCED: + send_list = advanced_list; + break; + case HI_STYLE_NORMAL: + default: + send_list = normal_list; + break; + } } */ + send_list = normal_list; lData.users = alloca(channel->channel_info->userCount * sizeof(struct userData *)); matches = 0; @@ -3913,45 +3996,54 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg qsort(lData.users, matches, sizeof(lData.users[0]), userData_access_comp); lData.table.length = matches+1; - lData.table.width = 5; lData.table.flags = TABLE_NO_FREE; 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 */ + else + lData.table.width = 4; /* without = 4 */ ary = malloc(lData.table.width*sizeof(**lData.table.contents)); lData.table.contents[0] = ary; - ary[0] = "Access"; - ary[1] = "Level"; - ary[2] = "Account"; - ary[3] = "Last Seen"; - ary[4] = "Status"; + ary[i++] = "Access"; + if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED) + ary[i++] = "Level"; /* Only on advanced view */ + ary[i++] = "Account"; + ary[i] = "Last Seen"; + seen_index = i++; + ary[i++] = "Status"; for(matches = 1; matches < lData.table.length; ++matches) { struct userData *uData = lData.users[matches-1]; char seen[INTERVALLEN]; + i = 0; ary = malloc(lData.table.width*sizeof(**lData.table.contents)); lData.table.contents[matches] = ary; - /* ary[0] = strtab(uData->access);*/ - ary[0] = user_level_name_from_level(uData->access); - ary[1] = strtab(uData->access); - ary[2] = uData->handle->handle; + ary[i++] = user_level_name_from_level(uData->access); + if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED) + ary[i++] = strtab(uData->access); + ary[i++] = uData->handle->handle; if(uData->present) - ary[3] = "Here"; + ary[i] = "Here"; else if(!uData->seen) - ary[3] = "Never"; + ary[i] = "Never"; else - ary[3] = intervalString(seen, now - uData->seen, user->handle_info); - ary[3] = strdup(ary[3]); + ary[i] = intervalString(seen, now - uData->seen, user->handle_info); + ary[i] = strdup(ary[i]); + i++; if(IsUserSuspended(uData)) - ary[4] = "Suspended"; + ary[i++] = "Suspended"; else if(HANDLE_FLAGGED(uData->handle, FROZEN)) - ary[4] = "Vacation"; + ary[i++] = "Vacation"; else - ary[4] = "Normal"; + ary[i++] = "Normal"; } send_list(&lData); for(matches = 1; matches < lData.table.length; ++matches) { - free((char*)lData.table.contents[matches][3]); + /* Free strdup above */ + free((char*)lData.table.contents[matches][seen_index]); free(lData.table.contents[matches]); } free(lData.table.contents[0]); @@ -3965,7 +4057,8 @@ static CHANSERV_FUNC(cmd_pending) { struct adduserPending *ap; reply("CSMSG_ADDUSER_PENDING_HEADER"); - reply("CSMSG_BAR"); + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_BAR"); for(ap = adduser_pendings;ap;ap = ap->next) reply("CSMSG_ADDUSER_PENDING_LIST", ap->channel->name, ap->user->nick); reply("CSMSG_ADDUSER_PENDING_FOOTER"); @@ -4170,13 +4263,18 @@ static CHANSERV_FUNC(cmd_topic) { struct chanData *cData; char *topic; + int p10 = 0; + +#ifdef WITH_PROTOCOL_P10 + p10 = 1; +#endif cData = channel->channel_info; if(argc < 2) { if(cData->topic) { - SetChannelTopic(channel, chanserv, cData->topic, 1); + SetChannelTopic(channel, chanserv, p10 ? user : chanserv, cData->topic, 1); reply("CSMSG_TOPIC_SET", cData->topic); return 1; } @@ -4212,10 +4310,10 @@ static CHANSERV_FUNC(cmd_topic) reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN); return 0; } - SetChannelTopic(channel, chanserv, new_topic, 1); + SetChannelTopic(channel, chanserv, p10 ? user : chanserv, new_topic, 1); } else /* No mask set, just set the topic */ - SetChannelTopic(channel, chanserv, topic, 1); + SetChannelTopic(channel, chanserv, p10 ? user : chanserv, topic, 1); } if(check_user_level(channel, user, lvlTopicSnarf, 1, 0)) @@ -4230,7 +4328,9 @@ static CHANSERV_FUNC(cmd_topic) static CHANSERV_FUNC(cmd_mode) { + struct userData *uData; struct mod_chanmode *change; + short base_oplevel; if(argc < 2) { @@ -4243,7 +4343,15 @@ static CHANSERV_FUNC(cmd_mode) return 1; } - change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED); + uData = GetChannelUser(channel->channel_info, user->handle_info); + if (!uData) + base_oplevel = MAXOPLEVEL; + else if (uData->access >= UL_OWNER) + base_oplevel = 1; + else + base_oplevel = 1 + UL_OWNER - uData->access; + change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED, base_oplevel); + if(!change) { reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL)); @@ -4378,6 +4486,31 @@ show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended } } +static void +show_giveownership_info(struct svccmd *cmd, struct userNode *user, struct giveownership *giveownership) +{ + char buf[MAXLEN]; + const char *fmt = "%a %b %d %H:%M %Y"; + strftime(buf, sizeof(buf), fmt, localtime(&giveownership->issued)); + + if(giveownership->staff_issuer) + { + if(giveownership->reason) + reply("CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", giveownership->old_owner, + giveownership->target, giveownership->target_access, + giveownership->staff_issuer, buf, giveownership->reason); + else + reply("CSMSG_CHANNEL_OWNERSHIP_STAFF", giveownership->old_owner, + giveownership->target, giveownership->target_access, + giveownership->staff_issuer, buf); + } + else + { + reply("CSMSG_CHANNEL_OWNERSHIP_NORMAL", giveownership->old_owner, giveownership->target, giveownership->target_access, buf); + } +} + + static CHANSERV_FUNC(cmd_info) { char modes[MAXLEN], buffer[INTERVALLEN]; @@ -4390,7 +4523,8 @@ static CHANSERV_FUNC(cmd_info) cData = channel->channel_info; reply("CSMSG_CHANNEL_INFO", channel->name); - reply("CSMSG_BAR"); + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_BAR"); uData = GetChannelUser(cData, user->handle_info); if(uData && (uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/)) @@ -4419,9 +4553,10 @@ static CHANSERV_FUNC(cmd_info) reply("CSMSG_CHANNEL_USERS", cData->userCount); reply("CSMSG_CHANNEL_LAMERS", cData->banCount); reply("CSMSG_CHANNEL_VISITED", intervalString(buffer, now - cData->visited, user->handle_info)); - reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info)); privileged = IsStaff(user); + if(privileged) + reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info)); if(((uData && uData->access >= UL_COOWNER) || privileged) && cData->registrar) reply("CSMSG_CHANNEL_REGISTRAR", cData->registrar); @@ -4440,7 +4575,17 @@ static CHANSERV_FUNC(cmd_info) reply("CSMSG_CHANNEL_SUSPENDED", channel->name); show_suspension_info(cmd, user, cData->suspended); } - reply("CSMSG_CHANNEL_END"); + if(cData->giveownership && ((uData && (uData->access >= UL_COOWNER)) || IsStaff(user))) + { + struct giveownership *giveownership; + reply("CSMSG_CHANNEL_OWNERSHIP_HISTORY", channel->name); + for(giveownership = cData->giveownership; giveownership; giveownership = giveownership->previous) + show_giveownership_info(cmd, user, giveownership); + } + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_CHANNEL_END"); + else + reply("CSMSG_CHANNEL_END_CLEAN"); return 1; } @@ -4527,7 +4672,8 @@ static CHANSERV_FUNC(cmd_peek) irc_make_chanmode(channel, modes); reply("CSMSG_PEEK_INFO", channel->name); - reply("CSMSG_BAR"); + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_BAR"); reply("CSMSG_PEEK_TOPIC", channel->topic); reply("CSMSG_PEEK_MODES", modes); reply("CSMSG_PEEK_USERS", channel->members.used); @@ -4588,7 +4734,8 @@ static CHANSERV_FUNC(cmd_resync) struct chanData *cData = channel->channel_info; unsigned int ii, used; - changes = mod_chanmode_alloc(channel->members.used * 2); + /* 6 = worst case -ovh+ovh on everyone */ + changes = mod_chanmode_alloc(channel->members.used * 6); for(ii = used = 0; ii < channel->members.used; ++ii) { struct modeNode *mn = channel->members.list[ii]; @@ -4597,53 +4744,128 @@ static CHANSERV_FUNC(cmd_resync) if(IsService(mn->user)) continue; + uData = GetChannelAccess(cData, mn->user->handle_info); - if(uData && uData->access >= UL_OP ) + + /* If the channel is in no-mode mode, de-mode EVERYONE */ + if(cData->chOpts[chAutomode] == 'n') { - if(!(mn->modes & MODE_CHANOP)) - { - changes->args[used].mode = MODE_CHANOP; - changes->args[used++].u.member = mn; - } - } - else if(uData && uData->access >= UL_HALFOP) - { - if(mn->modes & MODE_CHANOP) - { - changes->args[used].mode = MODE_REMOVE | MODE_CHANOP; - changes->args[used++].u.member = mn; - } - if(!(mn->modes & MODE_HALFOP)) - { - changes->args[used].mode = MODE_HALFOP; - changes->args[used++].u.member = mn; - } + if(mn->modes) + { + changes->args[used].mode = MODE_REMOVE | mn->modes; + changes->args[used++].u.member = mn; + } } - else if(uData && uData->access >= UL_PEON ) + else /* Give various userlevels their modes.. */ { - if(mn->modes & MODE_CHANOP) + if(uData && uData->access >= UL_OP ) { - changes->args[used].mode = MODE_REMOVE | MODE_CHANOP; - changes->args[used++].u.member = mn; + if(!(mn->modes & MODE_CHANOP)) + { + changes->args[used].mode = MODE_CHANOP; + changes->args[used++].u.member = mn; + } } - if(mn->modes & MODE_HALFOP) + else if(uData && uData->access >= UL_HALFOP) { - changes->args[used].mode = MODE_REMOVE | MODE_HALFOP; - changes->args[used++].u.member = mn; + if(mn->modes & MODE_CHANOP) + { + changes->args[used].mode = MODE_REMOVE | MODE_CHANOP; + changes->args[used++].u.member = mn; + } + if(!(mn->modes & MODE_HALFOP)) + { + changes->args[used].mode = MODE_HALFOP; + changes->args[used++].u.member = mn; + } } - /* Don't voice peons if were in mode m */ - if(!(mn->modes & MODE_VOICE) && cData->chOpts[chAutomode] != 'm') + else if(uData && uData->access >= UL_PEON ) { - changes->args[used].mode = MODE_VOICE; - changes->args[used++].u.member = mn; + if(mn->modes & MODE_CHANOP) + { + changes->args[used].mode = MODE_REMOVE | MODE_CHANOP; + changes->args[used++].u.member = mn; + } + if(mn->modes & MODE_HALFOP) + { + changes->args[used].mode = MODE_REMOVE | MODE_HALFOP; + changes->args[used++].u.member = mn; + } + /* Don't voice peons if were in mode m */ + if( cData->chOpts[chAutomode] == 'm') + { + if(mn->modes & MODE_VOICE) + { + changes->args[used].mode = MODE_REMOVE | MODE_VOICE; + changes->args[used++].u.member = mn; + } + } + /* otherwise, make user they do have voice */ + else if(!(mn->modes & MODE_VOICE)) + { + changes->args[used].mode = MODE_VOICE; + changes->args[used++].u.member = mn; + } } - } - else - { - if(mn->modes) + else /* They arnt on the userlist.. */ { - changes->args[used].mode = MODE_REMOVE | mn->modes; - changes->args[used++].u.member = mn; + /* If we voice everyone, but they dont.. */ + if(cData->chOpts[chAutomode] == 'v') + { + /* Remove anything except v */ + if(mn->modes & ~MODE_VOICE) + { + changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE); + changes->args[used++].u.member = mn; + } + /* Add v */ + if(!(mn->modes & MODE_VOICE)) + { + changes->args[used].mode = MODE_VOICE; + changes->args[used++].u.member = mn; + } + } + /* If we hop everyone, but they dont.. */ + else if(cData->chOpts[chAutomode] == 'h') + { + /* Remove anything except h */ + if(mn->modes & ~MODE_HALFOP) + { + changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_HALFOP); + changes->args[used++].u.member = mn; + } + /* Add h */ + if(!(mn->modes & MODE_HALFOP)) + { + changes->args[used].mode = MODE_HALFOP; + changes->args[used++].u.member = mn; + } + } + /* If we op everyone, but they dont.. */ + else if(cData->chOpts[chAutomode] == 'o') + { + /* Remove anything except h */ + if(mn->modes & ~MODE_CHANOP) + { + changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_CHANOP); + changes->args[used++].u.member = mn; + } + /* Add h */ + if(!(mn->modes & MODE_CHANOP)) + { + changes->args[used].mode = MODE_CHANOP; + changes->args[used++].u.member = mn; + } + } + /* they have no excuse for having modes, de-everything them */ + else + { + if(mn->modes) + { + changes->args[used].mode = MODE_REMOVE | mn->modes; + changes->args[used++].u.member = mn; + } + } } } } @@ -4741,16 +4963,16 @@ note_type_settable_by_user(struct chanNode *channel, struct note_type *ntype, st switch(ntype->set_access_type) { - case NOTE_SET_CHANNEL_ACCESS: - if(!user->handle_info) - return 0; - if(!(uData = GetChannelUser(channel->channel_info, user->handle_info))) - return 0; - return uData->access >= ntype->set_access.min_ulevel; - case NOTE_SET_CHANNEL_SETTER: - return check_user_level(channel, user, lvlSetters, 1, 0); - case NOTE_SET_PRIVILEGED: default: - return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv); + case NOTE_SET_CHANNEL_ACCESS: + if(!user->handle_info) + return 0; + if(!(uData = GetChannelUser(channel->channel_info, user->handle_info))) + return 0; + return uData->access >= ntype->set_access.min_ulevel; + case NOTE_SET_CHANNEL_SETTER: + return check_user_level(channel, user, lvlSetters, 1, 0); + case NOTE_SET_PRIVILEGED: default: + return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv); } } @@ -4896,7 +5118,8 @@ static CHANSERV_FUNC(cmd_events) report.reporter = chanserv; report.user = user; reply("CSMSG_EVENT_SEARCH_RESULTS", channel->name); - reply("CSMSG_BAR"); + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_BAR"); matches = log_entry_search(&discrim, log_report_entry, &report); if(matches) reply("MSG_MATCH_COUNT", matches); @@ -5232,8 +5455,9 @@ static CHANSERV_FUNC(cmd_search) if(action == search_print) { - reply("CSMSG_CHANNEL_SEARCH_RESULTS"); - reply("CSMSG_BAR"); + reply("CSMSG_CHANNEL_SEARCH_RESULTS"); + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_BAR"); } matches = chanserv_channel_search(search, action, user); @@ -5303,7 +5527,7 @@ static MODCMD_FUNC(chan_opt_defaulttopic) && !match_ircglob(channel->channel_info->topic, channel->channel_info->topic_mask)) reply("CSMSG_TOPIC_MISMATCH", channel->name); } - SetChannelTopic(channel, chanserv, topic ? topic : "", 1); + SetChannelTopic(channel, chanserv, chanserv, topic ? topic : "", 1); } if(channel->channel_info->topic) @@ -5408,7 +5632,7 @@ static MODCMD_FUNC(chan_opt_modes) { memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes)); } - else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED))) + else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1,MCP_KEY_FREE|MCP_REGISTERED, 0))) { reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL)); return 0; @@ -5784,8 +6008,9 @@ static CHANSERV_FUNC(cmd_set) if(argc < 2) { - reply("CSMSG_CHANNEL_OPTIONS", channel->name); - reply("CSMSG_BAR"); + reply("CSMSG_CHANNEL_OPTIONS", channel->name); + if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN) + reply("CSMSG_BAR"); for(ii = 0; ii < set_shows_list.used; ii++) { subcmd = set_shows_list.list[ii]; @@ -5973,13 +6198,22 @@ static CHANSERV_FUNC(cmd_giveownership) struct userData *new_owner, *curr_user; struct chanData *cData = channel->channel_info; struct do_not_register *dnr; - unsigned int force; - unsigned short co_access; - char reason[MAXLEN]; + struct giveownership *giveownership; + unsigned int force, override; + unsigned short co_access, new_owner_old_access; + char reason[MAXLEN], transfer_reason[MAXLEN]; REQUIRE_PARAMS(2); curr_user = GetChannelAccess(cData, user->handle_info); force = IsHelping(user) && (argc > 2) && !irccasecmp(argv[2], "force"); + + struct userData *uData = _GetChannelUser(channel->channel_info, user->handle_info, 1, 0); + override = ((cmd->effective_flags & MODCMD_REQUIRE_CHANUSER) + && (uData->access > 500) + && (!(uData = _GetChannelUser(channel->channel_info, user->handle_info, 0, 0)) + || uData->access < 500)); + + if(!curr_user || (curr_user->access != UL_OWNER)) { struct userData *owner = NULL; @@ -6037,6 +6271,8 @@ static CHANSERV_FUNC(cmd_giveownership) chanserv_show_dnrs(user, cmd, NULL, new_owner_hi->handle); return 0; } + + new_owner_old_access = new_owner->access; if(new_owner->access >= UL_COOWNER) co_access = new_owner->access; else @@ -6045,18 +6281,47 @@ static CHANSERV_FUNC(cmd_giveownership) if(curr_user) curr_user->access = co_access; cData->ownerTransfer = now; + + giveownership = calloc(1, sizeof(*giveownership)); + giveownership->issued = now; + giveownership->old_owner = curr_user->handle->handle; + giveownership->target = new_owner_hi->handle; + giveownership->target_access = new_owner_old_access; + if(override) + { + if(argc > (2 + force)) + { + unsplit_string(argv + 2 + force, argc - 2 - force, transfer_reason); + giveownership->reason = strdup(transfer_reason); + } + giveownership->staff_issuer = strdup(user->handle_info->handle); + } + + giveownership->previous = channel->channel_info->giveownership; + channel->channel_info->giveownership = giveownership; + reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle); sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle); global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason); return 1; } +static void +chanserv_expire_user_suspension(void *data) +{ + struct userData *target = data; + + target->expires = 0; + target->flags &= ~USER_SUSPENDED; +} + static CHANSERV_FUNC(cmd_suspend) { struct handle_info *hi; struct userData *self, *target; + time_t expiry; - REQUIRE_PARAMS(2); + REQUIRE_PARAMS(3); if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0; self = GetChannelUser(channel->channel_info, user->handle_info); if(!(target = GetTrueChannelAccess(channel->channel_info, hi))) @@ -6079,6 +6344,24 @@ static CHANSERV_FUNC(cmd_suspend) target->present = 0; target->seen = now; } + if(!strcmp(argv[2], "0")) + expiry = 0; + else + { + unsigned int duration; + if(!(duration = ParseInterval(argv[2]))) + { + reply("MSG_INVALID_DURATION", argv[2]); + return 0; + } + expiry = now + duration; + } + + target->expires = expiry; + + if(target->expires) + timeq_add(target->expires, chanserv_expire_user_suspension, target); + target->flags |= USER_SUSPENDED; reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name); return 1; @@ -6109,6 +6392,7 @@ static CHANSERV_FUNC(cmd_unsuspend) } 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; } @@ -6163,7 +6447,7 @@ chanserv_refresh_topics(UNUSED_ARG(void *data)) if((refresh_num - cData->last_refresh) < (unsigned int)(1 << (opt - '1'))) continue; if(cData->topic) - SetChannelTopic(cData->channel, chanserv, cData->topic, 1); + SetChannelTopic(cData->channel, chanserv, chanserv, cData->topic, 1); cData->last_refresh = refresh_num; } timeq_add(now + chanserv_conf.refresh_period, chanserv_refresh_topics, NULL); @@ -6298,13 +6582,13 @@ void eightball(char *outcome, int method, unsigned int seed) case 4: strcpy(tmp, "A gross kind of mucky %s."); break; case 5: strcpy(tmp, "Brilliant whiteish %s."); - break; - case 6: case 7: case 8: case 9: strcpy(tmp, "%s."); - break; - case 10: strcpy(tmp, "Solid %s."); - break; - case 11: strcpy(tmp, "Transparent %s."); - break; + break; + case 6: case 7: case 8: case 9: strcpy(tmp, "%s."); + break; + case 10: strcpy(tmp, "Solid %s."); + break; + case 11: strcpy(tmp, "Transparent %s."); + break; default: strcpy(outcome, "An invalid random number was generated."); return; } @@ -6521,7 +6805,7 @@ handle_new_channel(struct chanNode *channel) mod_chanmode_announce(chanserv, cData->channel, &cData->modes); if(self->uplink && !self->uplink->burst && channel->channel_info->topic) - SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1); + SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1); } /* Welcome to my worst nightmare. Warning: Read (or modify) @@ -6546,14 +6830,38 @@ handle_join(struct modeNode *mNode) if(channel->members.used > cData->max) cData->max = channel->members.used; + /* Check for bans. If they're joining through a ban, one of two + * cases applies: + * 1: Join during a netburst, by riding the break. Kick them + * unless they have ops or voice in the channel. + * 2: They're allowed to join through the ban (an invite in + * ircu2.10, or a +e on Hybrid, or something). + * If they're not joining through a ban, and the banlist is not + * full, see if they're on the banlist for the channel. If so, + * kickban them. + */ + if(user->uplink->burst && !mNode->modes) + { + unsigned int ii; + for(ii = 0; ii < channel->banlist.used; ii++) + { + if(user_matches_glob(user, channel->banlist.list[ii]->ban, MATCH_USENICK)) + { + /* Riding a netburst. Naughty. */ + KickChannelUser(user, channel, chanserv, "User from far side of netsplit should have been banned - bye."); + return 1; + } + } + } + mod_chanmode_init(&change); change.argc = 1; if(channel->banlist.used < MAXBANS) { /* Not joining through a ban. */ for(bData = cData->bans; - bData && !user_matches_glob(user, bData->mask, 1); - bData = bData->next); + bData && !user_matches_glob(user, bData->mask, MATCH_USENICK); + bData = bData->next); if(bData) { @@ -6657,6 +6965,10 @@ handle_join(struct modeNode *mNode) uData->present = 1; } } + + /* If user joining normally (not during burst), apply op or voice, + * and send greeting/userinfo as appropriate. + */ if(!user->uplink->burst) { if(modes) @@ -6671,7 +6983,7 @@ handle_join(struct modeNode *mNode) change.args[0].u.member = mNode; mod_chanmode_announce(chanserv, channel, &change); } - if(greeting && !user->uplink->burst) + if(greeting) send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting); if(uData && info) send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info); @@ -6744,14 +7056,14 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) || IsSuspended(channel->channel_info)) continue; for(jj = 0; jj < channel->banlist.used; ++jj) - if(user_matches_glob(user, channel->banlist.list[jj]->ban, 1)) + if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK)) break; if(jj < channel->banlist.used) continue; for(ban = channel->channel_info->bans; ban; ban = ban->next) { char kick_reason[MAXLEN]; - if(!user_matches_glob(user, ban->mask, 1)) + if(!user_matches_glob(user, ban->mask,MATCH_USENICK | MATCH_VISIBLE)) continue; change.args[0].mode = MODE_BAN; change.args[0].u.hostmask = ban->mask; @@ -6851,7 +7163,7 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to if(bad_topic(channel, user, channel->topic)) { /* User doesnt have privs to set topics. Undo it */ send_message(user, chanserv, "CSMSG_TOPIC_LOCKED", channel->name); - SetChannelTopic(channel, chanserv, old_topic, 1); + SetChannelTopic(channel, chanserv, chanserv, old_topic, 1); return 1; } /* If there is a topic mask set, and the new topic doesnt match, @@ -6862,12 +7174,12 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to conform_topic(cData->topic_mask, channel->topic, new_topic); if(*new_topic) { - SetChannelTopic(channel, chanserv, new_topic, 1); + SetChannelTopic(channel, chanserv, chanserv, new_topic, 1); /* and fall through to topicsnarf code below.. */ } else /* Topic couldnt fit into mask, was too long */ { - SetChannelTopic(channel, chanserv, old_topic, 1); + SetChannelTopic(channel, chanserv, chanserv, old_topic, 1); send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT1", channel->name, cData->topic_mask); send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT2", TOPICLEN); return 1; @@ -6982,7 +7294,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, 1)) + if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK)) break; /* Need not act if we found one. */ if(jj < channel->banlist.used) @@ -6990,7 +7302,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, 1)) + if(!user_matches_glob(user, bData->mask, MATCH_USENICK | MATCH_VISIBLE)) continue; change.args[0].u.hostmask = bData->mask; mod_chanmode_announce(chanserv, channel, &change); @@ -7138,7 +7450,8 @@ chanserv_conf_read(void) str = "+nt"; safestrncpy(mode_line, str, sizeof(mode_line)); ii = split_line(mode_line, 0, ArrayLength(modes), modes); - if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE)) && (change->argc < 2)) + if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE, 0)) + && (change->argc < 2)) { chanserv_conf.default_modes = *change; mod_chanmode_free(change); @@ -7259,7 +7572,7 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) { struct handle_info *handle; struct userData *uData; - char *seen, *inf, *flags; + char *seen, *inf, *flags, *expires; time_t last_seen; unsigned short access; @@ -7280,6 +7593,7 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING); last_seen = seen ? (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); handle = get_handle_info(key); if(!handle) { @@ -7289,6 +7603,15 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan) uData = add_channel_user(chan, handle, access, last_seen, inf); uData->flags = flags ? strtoul(flags, NULL, 0) : 0; + uData->expires = expires ? strtoul(expires, NULL, 0) : 0; + + if((uData->flags & USER_SUSPENDED) && uData->expires) + { + if(uData->expires > now) + timeq_add(uData->expires, chanserv_expire_user_suspension, uData); + else + uData->flags &= ~USER_SUSPENDED; + } /* Upgrade: set autoop to the inverse of noautoop */ if(chanserv_read_version < 2) @@ -7360,10 +7683,36 @@ chanserv_read_suspended(dict_t obj) return suspended; } +static struct giveownership * +chanserv_read_giveownership(dict_t obj) +{ + struct giveownership *giveownership = calloc(1, sizeof(*giveownership)); + char *str; + dict_t previous; + + str = database_get_data(obj, KEY_STAFF_ISSUER, RECDB_QSTRING); + giveownership->staff_issuer = str ? strdup(str) : NULL; + + giveownership->old_owner = strdup(database_get_data(obj, KEY_OLD_OWNER, RECDB_QSTRING)); + + giveownership->target = strdup(database_get_data(obj, KEY_TARGET, RECDB_QSTRING)); + giveownership->target_access = atoi(database_get_data(obj, KEY_TARGET_ACCESS, RECDB_QSTRING)); + + str = database_get_data(obj, KEY_REASON, RECDB_QSTRING); + giveownership->reason = str ? strdup(str) : NULL; + str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING); + giveownership->issued = str ? (time_t)strtoul(str, NULL, 0) : 0; + + previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT); + giveownership->previous = previous ? chanserv_read_giveownership(previous) : NULL; + return giveownership; +} + static int chanserv_channel_read(const char *key, struct record_data *hir) { struct suspended *suspended; + struct giveownership *giveownership; struct mod_chanmode *modes; struct chanNode *cNode; struct chanData *cData; @@ -7489,6 +7838,12 @@ chanserv_channel_read(const char *key, struct record_data *hir) cData->flags &= ~CHANNEL_SUSPENDED; } + if((obj = database_get_data(hir->d.object, KEY_GIVEOWNERSHIP, RECDB_OBJECT))) + { + giveownership = chanserv_read_giveownership(obj); + cData->giveownership = giveownership; + } + if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) { struct mod_chanmode change; mod_chanmode_init(&change); @@ -7518,7 +7873,7 @@ chanserv_channel_read(const char *key, struct record_data *hir) if(!IsSuspended(cData) && (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING)) && (argc = split_line(str, 0, ArrayLength(argv), argv)) - && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE))) { + && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE, 0))) { cData->modes = *modes; if(off_channel > 0) cData->modes.modes_set |= MODE_REGISTERED; @@ -7650,6 +8005,8 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData) saxdb_write_int(ctx, KEY_SEEN, uData->seen); if(uData->flags) saxdb_write_int(ctx, KEY_FLAGS, uData->flags); + if(uData->expires) + saxdb_write_int(ctx, KEY_EXPIRES, uData->expires); if(uData->info) saxdb_write_string(ctx, KEY_INFO, uData->info); saxdb_end_record(ctx); @@ -7698,6 +8055,27 @@ chanserv_write_suspended(struct saxdb_context *ctx, const char *name, struct sus saxdb_end_record(ctx); } +static void +chanserv_write_giveownership(struct saxdb_context *ctx, const char *name, struct giveownership *giveownership) +{ + saxdb_start_record(ctx, name, 0); + if(giveownership->staff_issuer) + saxdb_write_string(ctx, KEY_STAFF_ISSUER, giveownership->staff_issuer); + if(giveownership->old_owner) + saxdb_write_string(ctx, KEY_OLD_OWNER, giveownership->old_owner); + if(giveownership->target) + saxdb_write_string(ctx, KEY_TARGET, giveownership->target); + if(giveownership->target_access) + saxdb_write_int(ctx, KEY_TARGET_ACCESS, giveownership->target_access); + if(giveownership->reason) + saxdb_write_string(ctx, KEY_REASON, giveownership->reason); + if(giveownership->issued) + saxdb_write_int(ctx, KEY_ISSUED, giveownership->issued); + if(giveownership->previous) + chanserv_write_giveownership(ctx, KEY_PREVIOUS, giveownership->previous); + saxdb_end_record(ctx); +} + static void chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel) { @@ -7722,6 +8100,8 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel) saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask); if(channel->suspended) chanserv_write_suspended(ctx, "suspended", channel->suspended); + if(channel->giveownership) + chanserv_write_giveownership(ctx, "giveownership", channel->giveownership); saxdb_start_record(ctx, KEY_OPTIONS, 0); saxdb_write_int(ctx, KEY_FLAGS, channel->flags); @@ -7774,21 +8154,22 @@ chanserv_write_note_type(struct saxdb_context *ctx, struct note_type *ntype) saxdb_start_record(ctx, ntype->name, 0); switch(ntype->set_access_type) { - case NOTE_SET_CHANNEL_ACCESS: - saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel); - break; - case NOTE_SET_CHANNEL_SETTER: - saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1); - break; - case NOTE_SET_PRIVILEGED: default: - saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv); - break; + case NOTE_SET_CHANNEL_ACCESS: + saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel); + break; + case NOTE_SET_CHANNEL_SETTER: + saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1); + break; + case NOTE_SET_PRIVILEGED: default: + saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv); + break; } switch(ntype->visible_type) { - case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break; - case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break; - case NOTE_VIS_PRIVILEGED: default: str = KEY_NOTE_VIS_PRIVILEGED; break; + case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break; + case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break; + case NOTE_VIS_PRIVILEGED: + default: str = KEY_NOTE_VIS_PRIVILEGED; break; } saxdb_write_string(ctx, KEY_NOTE_VISIBILITY, str); saxdb_write_int(ctx, KEY_NOTE_MAX_LENGTH, ntype->max_length);