X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/31f23f1347e0542b7d19ff8ec2e60da9f979dbf0..00bce4cd757448fe394d880c9cf8d7c9c5d1d85e:/src/chanserv.c diff --git a/src/chanserv.c b/src/chanserv.c index 06dd5ed..a21aec5 100644 --- a/src/chanserv.c +++ b/src/chanserv.c @@ -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" @@ -257,11 +262,6 @@ static const struct message_entry msgtab[] = { { "CSMSG_CONFIRM_DEFAULTS", "To reset %s's settings to the defaults, you must use 'set defaults %s'." }, { "CSMSG_SETTINGS_DEFAULTED", "All settings for %s have been reset to default values." }, { "CSMSG_BAD_SETLEVEL", "You cannot change any setting to above your level." }, - /* - { "CSMSG_BAD_GIVEVOICE", "You cannot change GiveVoice to above GiveHalfOps (%d)." }, - { "CSMSG_BAD_GIVEHOPS", "You cannot change GiveHalfOps to below GiveOps (%d)." }, - { "CSMSG_BAD_GIVEOPS", "You cannot change GiveOps to below GiveVoice (%d)." }, - */ { "CSMSG_BAD_SETTERS", "You cannot change Setters to above your level." }, { "CSMSG_INVALID_MODE_LOCK", "$b%s$b is an invalid mode lock." }, { "CSMSG_INVALID_NUMERIC", "$b%d$b is not a valid choice. Choose one:" }, @@ -274,27 +274,21 @@ static const struct message_entry msgtab[] = { { "CSMSG_SET_DYNLIMIT", "$bDynLimit $b %s - +l joinflood protection." }, { "CSMSG_SET_OFFCHANNEL", "$bOffChannel $b %s" }, { "CSMSG_SET_USERINFO", "$bUserInfo $b %d - and above userinfos are shown." }, - /* - { "CSMSG_SET_GIVE_VOICE", "$bGiveVoice $b %d" }, - { "CSMSG_SET_GIVE_HALFOPS", "$bGiveHalfOps $b %d" }, - */ { "CSMSG_SET_TOPICSNARF", "$bTopicSnarf $b %d" }, { "CSMSG_SET_INVITEME", "$bInviteMe $b %d - Userlevel required to invite self." }, { "CSMSG_SET_ENFOPS", "$bEnfOps $b %d - level and above can op unknown users." }, { "CSMSG_SET_ENFHALFOPS", "$bEnfHalfOps $b %d - level and above can hop unknown users." }, - /* - { "CSMSG_SET_GIVE_OPS", "$bGiveOps $b %d" }, - */ { "CSMSG_SET_ENFMODES", "$bEnfModes $b %d - and above can change channel modes." }, { "CSMSG_SET_ENFTOPIC", "$bEnfTopic $b %d - and above can set the topic." }, { "CSMSG_SET_PUBCMD", "$bPubCmd $b %d - and above can use public commands." }, { "CSMSG_SET_SETTERS", "$bSetters $b %d - and above can change these settings." }, - { "CSMSG_SET_VOICE", "$bvoice $b %d - %s" }, + { "CSMSG_SET_AUTOMODE", "$bAutoMode $b %d - %s" }, { "CSMSG_SET_PROTECT", "$bProtect $b %d - %s" }, { "CSMSG_SET_TOYS", "$bToys $b %d - %s" }, { "CSMSG_SET_CTCPREACTION", "$bCTCPReaction$b %d - %s" }, { "CSMSG_SET_TOPICREFRESH", "$bTopicRefresh$b %d - %s" }, { "CSMSG_SET_BANTIMEOUT", "$bBanTimeout $b %d - %s" }, + { "CSMSG_USET_AUTOOP", "$bAutoOp $b %s" }, { "CSMSG_USET_AUTOVOICE", "$bAutoVoice $b %s" }, { "CSMSG_USET_AUTOINVITE", "$bAutoInvite $b %s" }, @@ -311,9 +305,14 @@ static const struct message_entry msgtab[] = { { "CSMSG_DEHALFOPPED_USERS", "DeHalfopped users in $b%s$b." }, { "CSMSG_VOICED_USERS", "Voiced users in $b%s$b." }, { "CSMSG_DEVOICED_USERS", "Devoiced users in $b%s$b." }, - { "CSMSG_VOICE_NONE", "Noone will be auto-voiced" }, - { "CSMSG_VOICE_PEON", "PEONs will be auto-voiced" }, - { "CSMSG_VOICE_ALL", "Everyone will be auto-voiced" }, + + { "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", "#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." }, { "CSMSG_PROTECT_LOWER", "Users will be protected from those of lower access." }, @@ -433,6 +432,10 @@ 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_PEEK_INFO", "$bStatus of %s$b" }, @@ -639,28 +642,27 @@ static const struct { unsigned int old_flag; unsigned short flag_value; } levelOptions[] = { -// { "CSMSG_SET_GIVE_VOICE", "givevoice", 100, ~0, CHANNEL_VOICE_ALL, 0 }, -// { "CSMSG_SET_GIVE_HALFOPS", "givehalfops", 150, ~0, CHANNEL_HOP_ALL, 0 }, -// { "CSMSG_SET_GIVE_OPS", "giveops", 200, 2, 0, 0 }, // these 3 need removed, but causes segs if its still in the db.. - { "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_CTCPUSERS", "ctcpusers", 0, 9, 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 } }; struct charOptionValues { char value; char *format_name; -} voiceValues[] = { - { 'n', "CSMSG_VOICE_NONE" }, - { 'p', "CSMSG_VOICE_PEON" }, - { 'a', "CSMSG_VOICE_ALL" } +} automodeValues[] = { + { 'n', "CSMSG_AUTOMODE_NONE" }, + { 'y', "CSMSG_AUTOMODE_NORMAL" }, + { 'v', "CSMSG_AUTOMODE_VOICE" }, + { 'h', "CSMSG_AUTOMODE_HOP" }, + { 'o', "CSMSG_AUTOMODE_OP" }, + { 'm', "CSMSG_AUTOMODE_MUTE" } }, protectValues[] = { { 'a', "CSMSG_PROTECT_ALL" }, { 'e', "CSMSG_PROTECT_EQUAL" }, @@ -699,7 +701,7 @@ static const struct { unsigned char count; struct charOptionValues *values; } charOptions[] = { - { "CSMSG_SET_VOICE", "voice", 'p', 99, ArrayLength(voiceValues), voiceValues }, + { "CSMSG_SET_AUTOMODE", "automode", 'y', 99, ArrayLength(automodeValues), automodeValues }, { "CSMSG_SET_PROTECT", "protect", 'l', 0, ArrayLength(protectValues), protectValues }, { "CSMSG_SET_TOYS", "toys", 'p', 6, ArrayLength(toysValues), toysValues }, { "CSMSG_SET_TOPICREFRESH", "topicrefresh", 'n', 8, ArrayLength(topicRefreshValues), topicRefreshValues }, @@ -1480,37 +1482,6 @@ expire_ban(void *data) /* lamer.. */ del_channel_ban(bd); } -void expire_bans(UNUSED_ARG(void* data)) /* Real bans, not lamers */ -{ - unsigned int jj; - struct banNode *bn; - struct chanData *channel; - time_t bantimeout; - - for(channel = channelList; channel; channel = channel->next) { - switch(channel->chOpts[chBanTimeout]) - { - default: case '0': continue; - case '1': bantimeout = now - (10 * 60); break; /* 10 minutes */ - case '2': bantimeout = now - (2 * 60 * 60); break; /* 2 hours */ - case '3': bantimeout = now - (4 * 60 * 60); break; /* 4 hours */ - case '4': bantimeout = now - (24 * 60 * 60); break; /* 24 hours */ - case '5': bantimeout = now - (7 * 24 * 60 * 60); break; /* 1 week */ - } - for (jj=0; jj < channel->channel->banlist.used; ++jj) { - bn = channel->channel->banlist.list[jj]; - if (bn->set < bantimeout) { - banList_remove(&channel->channel->banlist, bn); - free(bn); - jj--; - } - } - } - if(chanserv_conf.ban_timeout_frequency) - timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL); -} - - static void chanserv_expire_suspension(void *data); static void @@ -2426,8 +2397,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 @@ -2887,17 +2870,17 @@ static CHANSERV_FUNC(cmd_up) reply("CSMSG_GODMODE_UP", argv[0]); return 0; } - else if(uData->access >= UL_OP /*channel->channel_info->lvlOpts[lvlGiveOps]*/) + else if(uData->access >= UL_OP) { change.args[0].mode = MODE_CHANOP; errmsg = "CSMSG_ALREADY_OPPED"; } - else if(uData->access >= UL_HALFOP /*channel->channel_info->lvlOpts[lvlGiveHalfOps]*/) + else if(uData->access >= UL_HALFOP ) { change.args[0].mode = MODE_HALFOP; errmsg = "CSMSG_ALREADY_HALFOPPED"; } - else if(uData->access >= UL_PEON && (channel->channel_info->chOpts[chVoice] == 'p' || channel->channel_info->chOpts[chVoice] == 'a')) + else if(uData->access >= UL_PEON && (channel->channel_info->chOpts[chAutomode] != 'm' )) { change.args[0].mode = MODE_VOICE; errmsg = "CSMSG_ALREADY_VOICED"; @@ -3040,7 +3023,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; @@ -3142,18 +3125,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)) { @@ -3441,6 +3424,68 @@ find_matching_bans(struct banList *bans, struct userNode *actee, const char *mas return change; } +void expire_bans(UNUSED_ARG(void* data)) /* Real bans, not lamers */ +{ + unsigned int jj, ii, count; + struct banNode *bn; + struct chanData *channel; + time_t bantimeout; + struct mod_chanmode *change; + + log_module(CS_LOG, LOG_DEBUG, "Checking for expired bans"); + /* Walk through every channel */ + for(channel = channelList; channel; channel = channel->next) { + switch(channel->chOpts[chBanTimeout]) + { + default: case '0': continue; /* Dont remove bans in this chan */ + case '1': bantimeout = now - (10 * 60); break; /* 10 minutes */ + case '2': bantimeout = now - (2 * 60 * 60); break; /* 2 hours */ + case '3': bantimeout = now - (4 * 60 * 60); break; /* 4 hours */ + case '4': bantimeout = now - (24 * 60 * 60); break; /* 24 hours */ + case '5': bantimeout = now - (7 * 24 * 60 * 60); break; /* 1 week */ + } + count = 0; + /* First find out how many bans were going to unset */ + for (jj=0; jj < channel->channel->banlist.used; ++jj) { + if(channel->channel->banlist.list[jj]->set < bantimeout) + count++; + } + if(count > 0) { + /* At least one ban, so setup a removal */ + change = mod_chanmode_alloc(count); + ii = 0; + /* Walk over every ban in this channel.. */ + for (jj=0; jj < channel->channel->banlist.used; ++jj) { + bn = channel->channel->banlist.list[jj]; + if (bn->set < bantimeout) { + log_module(CS_LOG, LOG_DEBUG, "Removing ban %s from %s", bn->ban, channel->channel->name); + + /* Add this ban to the mode change */ + change->args[ii].mode = MODE_REMOVE | MODE_BAN; + change->args[ii].u.hostmask = strdup(bn->ban); + ii++; + /* Pull this ban out of the list */ + banList_remove(&(channel->channel->banlist), bn); + jj--; + free(bn); + } + } + /* Send the modes to IRC */ + mod_chanmode_announce(chanserv, channel->channel, change); + + /* free memory from strdup above */ + for(ii = 0; ii < count; ++ii) + free((char*)change->args[ii].u.hostmask); + + mod_chanmode_free(change); + } + } + /* Set this function to run again */ + if(chanserv_conf.ban_timeout_frequency) + timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL); +} + + static int unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action) { @@ -3622,11 +3667,11 @@ static CHANSERV_FUNC(cmd_myaccess) string_buffer_append(&sbuf, 's'); if(IsUserAutoOp(uData)) { - if(uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/) + if(uData->access >= UL_OP ) string_buffer_append(&sbuf, 'o'); - else if(uData->access >= UL_HALFOP /*cData->lvlOpts[lvlGiveHalfOps]*/) + else if(uData->access >= UL_HALFOP ) string_buffer_append(&sbuf, 'h'); - else if(uData->access >= UL_PEON /*cData->lvlOpts[lvlGiveVoice]*/) + else if(uData->access >= UL_PEON ) string_buffer_append(&sbuf, 'v'); } if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe])) @@ -4354,6 +4399,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]; @@ -4395,9 +4465,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); @@ -4416,6 +4487,13 @@ static CHANSERV_FUNC(cmd_info) reply("CSMSG_CHANNEL_SUSPENDED", channel->name); show_suspension_info(cmd, user, cData->suspended); } + 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); + } reply("CSMSG_CHANNEL_END"); return 1; } @@ -4564,7 +4642,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]; @@ -4573,59 +4652,128 @@ static CHANSERV_FUNC(cmd_resync) if(IsService(mn->user)) continue; + uData = GetChannelAccess(cData, mn->user->handle_info); - if(uData && uData->access >= UL_OP /* cData->lvlOpts[lvlGiveOps]*/) + + /* 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 /*cData->lvlOpts[lvlGiveHalfOps]*/) - { - 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; - } - /* why cant halfops keep voice - if(mn->modes & MODE_VOICE) - { - changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE); - 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 /* cData->lvlOpts[lvlGiveVoice]*/) + 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; + } } - if(!(mn->modes & MODE_VOICE)) + 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; + } + } } } } @@ -5579,42 +5727,19 @@ channel_level_option(enum levelOption option, struct userNode *user, struct chan } switch(option) { - /* removing these level sets.. - case lvlGiveVoice: - if(value > cData->lvlOpts[lvlGiveOps]) - { - reply("CSMSG_BAD_GIVEVOICE", cData->lvlOpts[lvlGiveOps]); - return 0; - } - break; - case lvlGiveHalfOps: - if(value < cData->lvlOpts[lvlGiveVoice]) - { - reply("CSMSG_BAD_GIVEHOPS", cData->lvlOpts[lvlGiveHalfOps]); - return 0; - } - break; - case lvlGiveOps: - if(value < cData->lvlOpts[lvlGiveVoice]) - { - reply("CSMSG_BAD_GIVEOPS", cData->lvlOpts[lvlGiveVoice]); - return 0; - } - break; - */ - case lvlSetters: - /* This test only applies to owners, since non-owners - * trying to set an option to above their level get caught - * by the CSMSG_BAD_SETLEVEL test above. - */ - if(value > uData->access) - { - reply("CSMSG_BAD_SETTERS"); - return 0; - } - break; - default: - break; + case lvlSetters: + /* This test only applies to owners, since non-owners + * trying to set an option to above their level get caught + * by the CSMSG_BAD_SETLEVEL test above. + */ + if(value > uData->access) + { + reply("CSMSG_BAD_SETTERS"); + return 0; + } + break; + default: + break; } cData->lvlOpts[option] = value; } @@ -5631,17 +5756,6 @@ static MODCMD_FUNC(chan_opt_enfhalfops) { return channel_level_option(lvlEnfHalfOps, CSFUNC_ARGS); } -/* -static MODCMD_FUNC(chan_opt_giveops) -{ - return channel_level_option(lvlGiveOps, CSFUNC_ARGS); -} - -static MODCMD_FUNC(chan_opt_givehalfops) -{ - return channel_level_option(lvlGiveHalfOps, CSFUNC_ARGS); -} -*/ static MODCMD_FUNC(chan_opt_enfmodes) { return channel_level_option(lvlEnfModes, CSFUNC_ARGS); @@ -5667,13 +5781,6 @@ static MODCMD_FUNC(chan_opt_userinfo) return channel_level_option(lvlUserInfo, CSFUNC_ARGS); } -/* -static MODCMD_FUNC(chan_opt_givevoice) -{ - return channel_level_option(lvlGiveVoice, CSFUNC_ARGS); -} -*/ - static MODCMD_FUNC(chan_opt_topicsnarf) { return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS); @@ -5737,9 +5844,9 @@ channel_multiple_option(enum charOption option, struct userNode *user, struct ch return 1; } -static MODCMD_FUNC(chan_opt_voice) +static MODCMD_FUNC(chan_opt_automode) { - return channel_multiple_option(chVoice, CSFUNC_ARGS); + return channel_multiple_option(chAutomode, CSFUNC_ARGS); } static MODCMD_FUNC(chan_opt_protect) @@ -5996,13 +6103,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; @@ -6060,6 +6176,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 @@ -6068,18 +6186,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))) @@ -6102,6 +6249,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; @@ -6132,6 +6297,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; } @@ -6569,36 +6735,6 @@ 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. - */ - /* This is really, really stupid. not all banned people are kicked. - * sometimes we like to leave them unkicked. - * I tried to explain this to the srvx developers and - * got insulted.. hence one reason for this fork. - * - 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, 1)) - { - ** 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) @@ -6655,13 +6791,16 @@ handle_join(struct modeNode *mNode) timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData); } - if(channel->join_flooded) + /* Give automodes exept during join-floods */ + if(!channel->join_flooded) { - /* don't automatically give non users ops or voice during a join flood */ + if(cData->chOpts[chAutomode] == 'v') + modes |= MODE_VOICE; + else if(cData->chOpts[chAutomode] == 'h') + modes |= MODE_HALFOP; + else if(cData->chOpts[chAutomode] == 'o') + modes |= MODE_CHANOP; } - /* EVERYONE is to get voice */ - else if(cData->chOpts[chVoice] == 'a') - modes |= MODE_VOICE; greeting = cData->greeting; if(user->handle_info) @@ -6684,14 +6823,14 @@ handle_join(struct modeNode *mNode) uData = GetTrueChannelAccess(cData, handle); if(uData && !IsUserSuspended(uData)) { - /* non users getting voice are handled above. */ - if(IsUserAutoOp(uData)) + /* non users getting automodes are handled above. */ + if(IsUserAutoOp(uData) && cData->chOpts[chAutomode] != 'n') { - if(uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/) + if(uData->access >= UL_OP ) modes |= MODE_CHANOP; - if(uData->access >= UL_HALFOP /*cData->lvlOpts[lvlGiveHalfOps]*/) + else if(uData->access >= UL_HALFOP ) modes |= MODE_HALFOP; - else if(uData->access >= UL_PEON && cData->chOpts[chVoice] == 'p') + else if(uData->access >= UL_PEON && cData->chOpts[chAutomode] != 'm') modes |= MODE_VOICE; } if(uData->access >= UL_PRESENT) @@ -6711,10 +6850,12 @@ handle_join(struct modeNode *mNode) { if(modes) { + /* -- I'd rather have ops get voice too, if automode is v. -Rubin if(modes & MODE_CHANOP) { modes &= ~MODE_HALFOP; modes &= ~MODE_VOICE; } + */ change.args[0].mode = modes; change.args[0].u.member = mNode; mod_chanmode_announce(chanserv, channel, &change); @@ -6765,11 +6906,11 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) if(IsUserAutoOp(channel)) { - if(channel->access >= UL_OP /* cn->channel_info->lvlOpts[lvlGiveOps] */) + if(channel->access >= UL_OP ) change.args[0].mode = MODE_CHANOP; - else if(channel->access >= UL_HALFOP /* cn->channel_info->lvlOpts[lvlGiveHalfOps]*/) + else if(channel->access >= UL_HALFOP ) change.args[0].mode = MODE_HALFOP; - else if(channel->access >= UL_PEON /* cn->channel_info->lvlOpts[lvlGiveVoice]*/) + else if(channel->access >= UL_PEON ) change.args[0].mode = MODE_VOICE; else change.args[0].mode = 0; @@ -7021,7 +7162,9 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick)) { channel = user->channels.list[ii]->channel; /* Need not check for bans if they're opped or voiced. */ - if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) + /* TODO: does this make sense in automode v, h, and o? * + * lets still enforce on voice people anyway, and see how that goes -Rubin */ + if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP /*|MODE_VOICE */)) continue; /* Need not check for bans unless channel registration is active. */ if(!channel->channel_info || IsSuspended(channel->channel_info)) @@ -7199,10 +7342,10 @@ chanserv_conf_read(void) /* free form text */ "DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", /* options based on user level */ - "PubCmd", "InviteMe", "UserInfo",/* "GiveVoice", "GiveHalfOps", "GiveOps", */ "EnfOps", - "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters", /*"CtcpUsers", */ + "PubCmd", "InviteMe", "UserInfo","EnfOps", + "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters", /* multiple choice options */ - "CtcpReaction", "Protect", "Toys", "TopicRefresh", + "AutoMode", "CtcpReaction", "Protect", "Toys", "TopicRefresh", /* binary options */ "DynLimit", "NoDelete", "BanTimeout", /* delimiter */ @@ -7305,7 +7448,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; @@ -7326,6 +7469,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) { @@ -7335,6 +7479,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) @@ -7406,10 +7559,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; @@ -7535,6 +7714,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); @@ -7696,6 +7881,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); @@ -7744,6 +7931,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) { @@ -7768,6 +7976,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); @@ -8066,16 +8276,11 @@ init_chanserv(const char *nick) DEFINE_CHANNEL_OPTION(modes); DEFINE_CHANNEL_OPTION(enfops); DEFINE_CHANNEL_OPTION(enfhalfops); - /*DEFINE_CHANNEL_OPTION(giveops); - DEFINE_CHANNEL_OPTION(givehalfops); - */ - DEFINE_CHANNEL_OPTION(voice); + DEFINE_CHANNEL_OPTION(automode); DEFINE_CHANNEL_OPTION(protect); DEFINE_CHANNEL_OPTION(enfmodes); DEFINE_CHANNEL_OPTION(enftopic); DEFINE_CHANNEL_OPTION(pubcmd); - /*DEFINE_CHANNEL_OPTION(givevoice); - */ DEFINE_CHANNEL_OPTION(userinfo); DEFINE_CHANNEL_OPTION(dynlimit); DEFINE_CHANNEL_OPTION(topicsnarf);