Index: src/chanserv.c =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/chanserv.c,v retrieving revision 1.6 diff -u -r1.6 chanserv.c --- src/chanserv.c 28 Jun 2004 22:45:20 -0000 1.6 +++ src/chanserv.c 12 Aug 2004 17:43:28 -0000 @@ -172,8 +172,9 @@ { "CSMSG_ILLEGAL_CHANNEL", "$b%s$b is an illegal channel, and cannot be registered." }, { "CSMSG_GODMODE_UP", "You may not use $b%s$b to op yourself unless you are on the user list. Use the $bop$b command instead." }, { "CSMSG_ALREADY_OPPED", "You are already opped in $b%s$b." }, + { "CSMSG_ALREADY_HALFOPPED", "You are already halfopped in $b%s$b." }, { "CSMSG_ALREADY_VOICED", "You are already voiced in $b%s$b." }, - { "CSMSG_ALREADY_DOWN", "You are not opped or voiced in $b%s$b." }, + { "CSMSG_ALREADY_DOWN", "You are not opped, halfopped, or voiced in $b%s$b." }, { "CSMSG_ALREADY_OPCHANNED", "There has been no net.join since the last opchan in $b%s$b." }, { "CSMSG_OPCHAN_DONE", "I have (re-)opped myself in $b%s$b." }, @@ -240,7 +241,8 @@ { "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 GiveOps (%d)." }, + { "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." }, @@ -255,9 +257,11 @@ { "CSMSG_SET_OFFCHANNEL", "$bOffChannel $b %s" }, { "CSMSG_SET_USERINFO", "$bUserInfo $b %d" }, { "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" }, { "CSMSG_SET_ENFOPS", "$bEnfOps $b %d" }, + { "CSMSG_SET_ENFHALFOPS", "$bEnfHalfOps $b %d" }, { "CSMSG_SET_GIVE_OPS", "$bGiveOps $b %d" }, { "CSMSG_SET_ENFMODES", "$bEnfModes $b %d" }, { "CSMSG_SET_ENFTOPIC", "$bEnfTopic $b %d" }, @@ -275,9 +279,12 @@ { "CSMSG_USER_PROTECTED", "Sorry, $b%s$b is protected." }, { "CSMSG_OPBY_LOCKED", "You may not op users who lack op or greater access." }, + { "CSMSG_HOPBY_LOCKED", "You may not halfop users who lack halfop or greater access." }, { "CSMSG_PROCESS_FAILED", "$b$C$b could not process some of the nicks you provided." }, { "CSMSG_OPPED_USERS", "Opped users in $b%s$b." }, + { "CSMSG_HALFOPPED_USERS", "Halfopped users in $b%s$b." }, { "CSMSG_DEOPPED_USERS", "Deopped users in $b%s$b." }, + { "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_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." }, @@ -568,6 +575,7 @@ char ch; } accessLevels[] = { { "peon", "Peon", UL_PEON, '+' }, + { "halfop", "HalfOp", UL_HALFOP, '%' }, { "op", "Op", UL_OP, '@' }, { "manager", "Manager", UL_MANAGER, '%' }, { "coowner", "Coowner", UL_COOWNER, '*' }, @@ -584,8 +592,10 @@ 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 }, { "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 }, @@ -1438,6 +1448,24 @@ } static int +validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode *victim) +{ + struct chanData *cData = channel->channel_info; + struct userData *cs_victim; + + if((!(cs_victim = GetChannelUser(cData, victim->handle_info)) + || (cs_victim->access < cData->lvlOpts[lvlGiveHalfOps])) + && !check_user_level(channel, user, lvlEnfHalfOps, 0, 0)) + { + send_message(user, chanserv, "CSMSG_HOPBY_LOCKED"); + return 0; + } + + return 1; +} + + +static int validate_deop(struct userNode *user, struct chanNode *channel, struct userNode *victim) { if(IsService(victim)) @@ -1455,6 +1483,24 @@ return 1; } +static int +validate_dehop(struct userNode *user, struct chanNode *channel, struct userNode *victim) +{ + if(IsService(victim)) + { + send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick); + return 0; + } + + if(protect_user(victim, user, channel->channel_info)) + { + send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick); + return 0; + } + + return 1; +} + static struct do_not_register * chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason) { @@ -2374,6 +2420,12 @@ return cmd_mdel_user(user, channel, UL_PEON, UL_PEON, argv[1], cmd); } +static CHANSERV_FUNC(cmd_mdelhalfop) +{ + return cmd_mdel_user(user, channel, UL_HALFOP, UL_HALFOP, argv[1], cmd); +} + + static int cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long duration) { @@ -2518,6 +2570,11 @@ change.args[0].mode = MODE_CHANOP; errmsg = "CSMSG_ALREADY_OPPED"; } + else if(uData->access >= channel->channel_info->lvlOpts[lvlGiveHalfOps]) + { + change.args[0].mode = MODE_HALFOP; + errmsg = "CSMSG_ALREADY_HALFOPPED"; + } else if(uData->access >= channel->channel_info->lvlOpts[lvlGiveVoice]) { change.args[0].mode = MODE_VOICE; @@ -2635,11 +2692,21 @@ return modify_users(CSFUNC_ARGS, validate_op, MODE_CHANOP, "CSMSG_OPPED_USERS"); } +static CHANSERV_FUNC(cmd_hop) +{ + return modify_users(CSFUNC_ARGS, validate_halfop, MODE_HALFOP, "CSMSG_HALFOPPED_USERS"); +} + static CHANSERV_FUNC(cmd_deop) { return modify_users(CSFUNC_ARGS, validate_deop, MODE_REMOVE|MODE_CHANOP, "CSMSG_DEOPPED_USERS"); } +static CHANSERV_FUNC(cmd_dehop) +{ + return modify_users(CSFUNC_ARGS, validate_dehop, MODE_REMOVE|MODE_HALFOP, "CSMSG_DEHALFOPPED_USERS"); +} + static CHANSERV_FUNC(cmd_voice) { return modify_users(CSFUNC_ARGS, NULL, MODE_VOICE, "CSMSG_VOICED_USERS"); @@ -2913,7 +2980,7 @@ change = mod_chanmode_alloc(victimCount + 1); for(n = 0; n < victimCount; ++n) { - change->args[n].mode = MODE_REMOVE|MODE_CHANOP|MODE_VOICE; + change->args[n].mode = MODE_REMOVE|MODE_CHANOP|MODE_HALFOP|MODE_VOICE; change->args[n].member = victims[n]; } if(!exists) @@ -3206,6 +3273,8 @@ { if(uData->access >= cData->lvlOpts[lvlGiveOps]) string_buffer_append(&sbuf, 'o'); + else if(uData->access >= cData->lvlOpts[lvlGiveHalfOps]) + string_buffer_append(&sbuf, 'h'); else if(uData->access >= cData->lvlOpts[lvlGiveVoice]) string_buffer_append(&sbuf, 'v'); } @@ -3531,9 +3600,14 @@ return cmd_list_users(CSFUNC_ARGS, UL_OP, UL_MANAGER-1); } +static CHANSERV_FUNC(cmd_hlist) +{ + return cmd_list_users(CSFUNC_ARGS, UL_HALFOP, UL_OP-1); +} + static CHANSERV_FUNC(cmd_plist) { - return cmd_list_users(CSFUNC_ARGS, 1, UL_OP-1); + return cmd_list_users(CSFUNC_ARGS, 1, UL_HALFOP-1); } static CHANSERV_FUNC(cmd_bans) @@ -4093,6 +4167,25 @@ changes->args[used++].member = mn; } } + else if(!cData->lvlOpts[lvlGiveHalfOps] + || (uData && uData->access >= cData->lvlOpts[lvlGiveHalfOps])) + { + if(!(mn->modes & MODE_HALFOP)) + { + changes->args[used].mode = MODE_HALFOP; + changes->args[used++].member = mn; + } + if(mn->modes & MODE_CHANOP) + { + changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_CHANOP); + changes->args[used++].member = mn; + } + if(mn->modes & MODE_VOICE) + { + changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE); + changes->args[used++].member = mn; + } + } else if(!cData->lvlOpts[lvlGiveVoice] || (uData && uData->access >= cData->lvlOpts[lvlGiveVoice])) { @@ -4101,6 +4194,11 @@ changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE); changes->args[used++].member = mn; } + if(mn->modes & MODE_HALFOP) + { + changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE); + changes->args[used++].member = mn; + } if(!(mn->modes & MODE_VOICE)) { changes->args[used].mode = MODE_VOICE; @@ -5049,6 +5147,13 @@ 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]) { @@ -5081,11 +5186,21 @@ return channel_level_option(lvlEnfOps, CSFUNC_ARGS); } +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); @@ -5902,6 +6017,8 @@ } else if(cData->lvlOpts[lvlGiveOps] == 0) modes |= MODE_CHANOP; + else if(cData->lvlOpts[lvlGiveHalfOps] == 0) + modes |= MODE_HALFOP; else if(cData->lvlOpts[lvlGiveVoice] == 0) modes |= MODE_VOICE; @@ -5931,6 +6048,8 @@ { if(uData->access >= cData->lvlOpts[lvlGiveOps]) modes |= MODE_CHANOP; + if(uData->access >= cData->lvlOpts[lvlGiveHalfOps]) + modes |= MODE_HALFOP; else if(uData->access >= cData->lvlOpts[lvlGiveVoice]) modes |= MODE_VOICE; } @@ -5951,8 +6070,10 @@ { if(modes) { - if(modes & MODE_CHANOP) + if(modes & MODE_CHANOP) { + modes &= ~MODE_HALFOP; modes &= ~MODE_VOICE; + } change.args[0].mode = modes; change.args[0].member = mNode; mod_chanmode_announce(chanserv, channel, &change); @@ -6005,6 +6126,8 @@ { if(channel->access >= cn->channel_info->lvlOpts[lvlGiveOps]) change.args[0].mode = MODE_CHANOP; + else if(channel->access >= cn->channel_info->lvlOpts[lvlGiveHalfOps]) + change.args[0].mode = MODE_HALFOP; else if(channel->access >= cn->channel_info->lvlOpts[lvlGiveVoice]) change.args[0].mode = MODE_VOICE; else @@ -6023,7 +6146,7 @@ struct chanNode *channel = user->channels.list[ii]->channel; struct banData *ban; - if((user->channels.list[ii]->modes & (MODE_CHANOP|MODE_VOICE)) + if((user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) || !channel->channel_info) continue; for(jj = 0; jj < channel->banlist.used; ++jj) @@ -6237,7 +6360,7 @@ { 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_VOICE)) + 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)) @@ -6411,8 +6534,8 @@ /* free form text */ "DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", /* options based on user level */ - "PubCmd", "InviteMe", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", - "EnfModes", "EnfTopic", "TopicSnarf", "Setters", "CtcpUsers", + "PubCmd", "InviteMe", "UserInfo", "GiveVoice", "GiveHalfOps", "GiveOps", "EnfOps", + "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters", "CtcpUsers", /* multiple choice options */ "CtcpReaction", "Protect", "Toys", "TopicRefresh", /* binary options */ @@ -6690,6 +6813,7 @@ case 'n': lvl = UL_OWNER+1; break; case 'o': lvl = UL_OP; break; case 'p': lvl = UL_PEON; break; + case 'h': lvl = UL_HALFOP; break; case 'w': lvl = UL_OWNER; break; default: lvl = 0; break; } @@ -7146,6 +7270,7 @@ DEFINE_COMMAND(mdelmanager, 2, MODCMD_REQUIRE_CHANUSER, "access", "coowner", NULL); DEFINE_COMMAND(mdelop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); + DEFINE_COMMAND(mdelhalfop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); DEFINE_COMMAND(trim, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL); DEFINE_COMMAND(opchan, 1, MODCMD_REQUIRE_REGCHAN|MODCMD_NEVER_CSUSPEND, "access", "1", NULL); @@ -7156,8 +7281,10 @@ DEFINE_COMMAND(down, 1, MODCMD_REQUIRE_REGCHAN, NULL); DEFINE_COMMAND(upall, 1, MODCMD_REQUIRE_AUTHED, NULL); DEFINE_COMMAND(downall, 1, MODCMD_REQUIRE_AUTHED, NULL); + DEFINE_COMMAND(hop, 2, MODCMD_REQUIRE_CHANNEL, "access", "op", NULL); DEFINE_COMMAND(op, 2, MODCMD_REQUIRE_CHANNEL, "access", "op", NULL); DEFINE_COMMAND(deop, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL); + DEFINE_COMMAND(dehop, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL); DEFINE_COMMAND(voice, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL); DEFINE_COMMAND(devoice, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL); @@ -7192,6 +7319,7 @@ DEFINE_COMMAND(clist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); DEFINE_COMMAND(mlist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); DEFINE_COMMAND(olist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); + DEFINE_COMMAND(hlist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); DEFINE_COMMAND(plist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); DEFINE_COMMAND(info, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); DEFINE_COMMAND(seen, 2, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL); @@ -7226,7 +7354,9 @@ DEFINE_CHANNEL_OPTION(usergreeting); DEFINE_CHANNEL_OPTION(modes); DEFINE_CHANNEL_OPTION(enfops); + DEFINE_CHANNEL_OPTION(enfhalfops); DEFINE_CHANNEL_OPTION(giveops); + DEFINE_CHANNEL_OPTION(givehalfops); DEFINE_CHANNEL_OPTION(protect); DEFINE_CHANNEL_OPTION(enfmodes); DEFINE_CHANNEL_OPTION(enftopic); Index: src/chanserv.h =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/chanserv.h,v retrieving revision 1.3 diff -u -r1.3 chanserv.h --- src/chanserv.h 26 Jun 2004 21:23:22 -0000 1.3 +++ src/chanserv.h 12 Aug 2004 17:43:28 -0000 @@ -25,6 +25,7 @@ enum UL_ALIASES { UL_PEON = 100, + UL_HALFOP = 150, UL_OP = 200, UL_MANAGER = 300, UL_PRESENT = UL_MANAGER, @@ -35,8 +36,10 @@ enum levelOption { lvlGiveVoice, + lvlGiveHalfOps, lvlGiveOps, lvlEnfOps, + lvlEnfHalfOps, lvlEnfModes, lvlEnfTopic, lvlPubCmd, @@ -60,11 +63,14 @@ #define CHANNEL_SUSPENDED 0x00000002 /* (1 << 1) */ #define CHANNEL_INFO_LINES 0x00000004 /* (1 << 2) - DEPRECATED */ #define CHANNEL_VOICE_ALL 0x00000008 /* (1 << 3) - DEPRECATED */ + /* No longer used. */ /* (1 << 4) */ #define CHANNEL_DYNAMIC_LIMIT 0x00000020 /* (1 << 5) */ #define CHANNEL_TOPIC_SNARF 0x00000040 /* (1 << 6) - DEPRECATED */ #define CHANNEL_PEON_INVITE 0x00000080 /* (1 << 7) - DEPRECATED */ #define CHANNEL_OFFCHANNEL 0x00000100 /* (1 << 8) */ +#define CHANNEL_HOP_ALL 0x00000200 /* (1 << 9) */ + /* Flags with values over 0x20000000 or (1 << 29) will not work * because chanData.flags is a 30-bit field. */ Index: src/chanserv.help =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/chanserv.help,v retrieving revision 1.5 diff -u -r1.5 chanserv.help --- src/chanserv.help 28 Jun 2004 22:45:21 -0000 1.5 +++ src/chanserv.help 12 Aug 2004 17:43:29 -0000 @@ -23,6 +23,7 @@ " $bCLIST$b Show all COOWNERs of a channel.", " $bMLIST$b Show all MANAGERs of a channel.", " $bOLIST$b Show all OPs of a channel.", + " $bHLIST$b Show all HALFOPs of a channel.", " $bPLIST$b Show all PEONs of a channel.", " $bBANS$b Show all $ulasting$u bans in a channel.", " $bACCESS$b Check someone's access level in a channel.", @@ -31,6 +32,7 @@ " $bSEEN$b Find out the last time a user was in a channel.", "$b$b", " $bADDPEON$b Add a user as a PEON in a channel.", + " $bADDHOP$b Add a user as an HALFOP in a channel.", " $bADDOP$b Add a user as an OP in a channel.", " $bADDMANAGER$b Add a user as a MANAGER in a channel.", " $bADDCOOWNER$b Add a user as COOWNER in a channel.", @@ -42,6 +44,7 @@ " $bMDELCOOWNER$b Mass-delete coowners with accounts matching a mask.", " $bMDELMANAGER$b Mass-delete managers with accounts matching a mask.", " $bMDELOP$b Mass-delete ops with accounts matching a mask.", + " $bMDELHOP$b Mass-delete halfops with accounts matching a mask.", " $bMDELPEON$b Mass-delete peons with accounts matching a mask.", " $bDELBAN$b Remove the specified $upermanent$u ban from memory.", " $bWIPEINFO$b Remove a lower-ranked user's infoline.", @@ -69,6 +72,8 @@ " $bDOWNALL$b Remove your ops in all channels.", " $bOP$b Give ops to the specified user.", " $bDEOP$b Remove ops from the specified user.", + " $bHOP$b Give halfops to the specified user.", + " $bDEHOP$b Remove halfops from the specified user.", " $bVOICE$b Give voice to the specified user.", " $bDEVOICE$b Remove voice from the specified user.", " $bRESYNC$b Synchronize ops and voice with the channel userlist.", @@ -186,6 +191,9 @@ "ADDOP" ("/msg $C ADDOP <#channel> ", "This command adds someone to the channel user list with OP access level. (You may only add users to levels less than your own.)", "$uSee Also:$u adduser, deluser, users"); +"ADDHOP" ("/msg $C ADDHOP <#channel> ", + "This command adds someone to the channel user list with HALFOP access level. (You may only add users to levels less than your own.)", + "$uSee Also:$u adduser, deluser, users"); "ADDTIMEDBAN" ("/msg $C ADDTIMEDBAN <#channel> [Reason]", "Adds an automatically expiring ban to the channel ban list. This command behaves in the exact same fashion as ADDBAN with the exception that the bans are automatically removed after the user-supplied duration. If it exactly matches an existing ban already in the list, the reason will be updated. If the existing ban was a timed ban, it will be extended. Timed bans can be removed with the DELBAN command, as with permanent bans.", "$uSee Also:$u addban, bans, delban, durations"); @@ -254,11 +262,14 @@ "DEOP" ("/msg $C DEOP <#channel> [nick]...", "Deops the specified user[s].", "$uSee Also:$u down, op"); +"DEHOP" ("/msg $C DEHOP <#channel> [nick]...", + "Dehalfops the specified user[s].", + "$uSee Also:$u down, hop"); "DEVOICE" ("/msg $C DEVOICE <#channel> [nick]...", "This command will make $C devoice the selected user[s].", "$uSee Also:$u addpeon, delpeon, deop, voice"); "DOWN" ("/msg $C DOWN <#channel>", - "This command will devoice/deop you in the selected channel.", + "This command will devoice/dehalfop/deop you in the selected channel.", "$uSee Also:$u downall, up, upall"); "DOWNALL" ("/msg $C DOWNALL", "Executes the $bdown$b command for each channel you have access to.", @@ -281,6 +292,12 @@ "HELPERS" ("/msg $C HELPERS", "Lists all the helpers currently online. Nicknames enclosed in parentheses are away, and likely unavailable.", "$uSee Also:$u staff"); +"HLIST" ("/msg $C HLIST <#channel> [mask]", + "This command lists all users of level $bHalfop$b on a channel's userlist. If a mask is supplied, only halfops matching the mask will be shown.", + "$uSee Also:$u addhop, delhop, mdelhop, users"); +"HOP" ("/msg $C HOP <#channel> [nick]...", + "This command makes $C halfop the specified user.", + "$uSee Also:$u addhop, delhop, dehop"); "INFO" ("/msg $C INFO <#channel>", "This command responds with various pieces of information about a channel's users, status, and registration."); "INVITE" ("/msg $C INVITE <#channel> [nick [reason]]", @@ -299,6 +316,8 @@ "MDELCOOWNER" ("/msg $C MDELCOOWNER <#channel> ", "Deletes all coowners with accounts matching the given pattern from the channel user list.", "$uSee Also:$u clist, delcoowner"); +"MDELHOP" ("/msg $C MDELHOP <#channel> ", + "Deletes all halfops with accounts matching the given pattern from the channel user list."); "MDELMANAGER" ("/msg $C MDELMANAGER <#channel> ", "Deletes all managers with accounts matching the given pattern from the channel user list.", "$uSee Also:$u mdelban, mdelcoowner, mdelop, mdelowner, mdelpeon"); @@ -399,8 +418,10 @@ "INVITEME: Minimum level for users to use INVITEME or AUTOINVITE.", "USERINFO: Minimum level for users to show an infoline on join.", "GIVEVOICE: The users that $b$C$b will autovoice.", + "GIVEHOPS: The users that $b$C$b will autohalfop.", "GIVEOPS: The users that $b$C$b will autoop.", "ENFOPS: Restrictions for opping users.", + "ENFHALFOPS: Restrictions for halfopping users.", "ENFMODES: Restrictions to change the default modes.", "ENFTOPIC: Restrictions on changing the topic.", "TOPICSNARF: Topics set manually (by /TOPIC #channel ...) by users this level and above change the default $b$C$b topic.", @@ -414,7 +435,7 @@ "$bIRCOP ONLY$b:", "NODELETE: Prevents channel deletion.", "If you wish to reset your channel to the default settings, you can use the $bSET DEFAULTS$b command.", - "$uSee Also:$u set pubcmd, set giveops, set enfops, set enfmodes, set enftopic, set protect, set toys, set setters, set topicrefresh, set ctcpusers, set ctcpreaction, set defaults"); + "$uSee Also:$u set pubcmd, set giveops, set givehalfops, set enfops, set enfhalfops, set enfmodes, set enftopic, set protect, set toys, set setters, set topicrefresh, set ctcpusers, set ctcpreaction, set defaults"); "SET DEFAULTTOPIC" ("/msg $C SET <#channel> DEFAULTTOPIC ", "This changes the default topic for the channel. $C will set the IRC topic to this value when the $btopic$b command is used with no arguments, when the topic refresh happens (if you have $bset topicrefresh$b), or when an unauthorized user changes the topic to something else.", "$uSee Also:$u set, set topicrefresh, set enftopic, set topicmask"); @@ -436,9 +457,15 @@ "SET ENFOPS" ("/msg $C SET <#channel> ENFOPS ", "This setting restricts who may op users who are not at least ops on the userlist. If $C sees someone with access below the specified access op someone not on the userlist (or who is a peon), it will deop the second user. It is a numeric user level, or 501 to mean no one may op unrecognized users.", "$uSee Also:$u set"); +"SET ENFHALFOPS" ("/msg $C SET <#channel> ENFHALFOPS ", + "This setting restricts who may halfop users who are not at least halfops on the userlist. If $C sees someone with access below the specified access halfop someone not on the userlist (or who is a peon), it will dehalfop the second user. It is a numeric user level, or 501 to mean no one may op unrecognized users.", + "$uSee Also:$u set"); "SET GIVEOPS" ("/msg $C SET <#channel> GIVEOPS ", "This setting restricts the minimum access someone must be to be automatically op'ed by $C. It is a numeric user level.", "$uSee Also:$u set"); +"SET GIVEHOPS" ("/msg $C SET <#channel> GIVEHOPS ", + "This setting restricts the minimum access someone must be to be automatically halfop'ed by $C. It is a numeric user level.", + "$uSee Also:$u set"); "SET ENFMODES" ("/msg $C SET <#channel> ENFMODES ", "This setting restricts the minimum access someone must have to change the channel modes from what is specified in the channel settings. It is a numeric user level, or 501 to mean no one may override the default modes.", "$uSee Also:$u set"); @@ -511,7 +538,7 @@ "Displays up to a certain limit, all channels registered with $b$C$b that have not been visited within a certain duration. If a duration is not provided, a default will be used.", "$uSee Also:$u expire, search, durations"); "UP" ("/msg $C UP <#channel>", - "Grants you your normal channel privileges. If your access in the channel is less than the GiveVoice setting, this does nothing. Otherwise, if your access is less than the GiveOps setting, $b$C$b will give you voice. If your access is at least GiveOps, $b$C$b will give you ops."); + "Grants you your normal channel privileges. If your access in the channel is less than the GiveVoice setting, this does nothing. Otherwise, if your access is less than the GiveOps setting, $b$C$b will give you an halfop. If your access is as GiveHalfOps, $b$C$b will give you halfops. If your access is at least GiveOps, $b$C$b will give you ops."); "UPALL" ("/msg $C UPALL", "Executes the $bup$b command for each channel you have access in.", "$uSee Also:$u up, down, downall"); Index: src/hash.c =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/hash.c,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 hash.c --- src/hash.c 3 Jun 2004 17:13:59 -0000 1.1.1.1 +++ src/hash.c 12 Aug 2004 17:43:30 -0000 @@ -315,6 +315,7 @@ if ((mn->modes & MODE_CHANOP) && IsService(mn->user) && IsLocal(mn->user)) argc++; } + if (argc) { struct mod_chanmode *change; Index: src/hash.h =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/hash.h,v retrieving revision 1.3 diff -u -r1.3 hash.h --- src/hash.h 14 Jun 2004 07:32:05 -0000 1.3 +++ src/hash.h 12 Aug 2004 17:43:30 -0000 @@ -48,6 +48,7 @@ #define MODE_NOQUITMSGS 0x00100000 /* +Q suppress messages from quit notices */ #define MODE_NOAMSG 0x00200000 /* +T no multi-target messages */ #define MODE_SSLONLY 0x00400000 /* +z ssl only */ +#define MODE_HALFOP 0x00800000 /* +h USER */ #define MODE_REMOVE 0x80000000 #define FLAGS_OPER 0x0001 /* Operator +O */ Index: src/modcmd.c =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/modcmd.c,v retrieving revision 1.2 diff -u -r1.2 modcmd.c --- src/modcmd.c 3 Jun 2004 23:34:03 -0000 1.2 +++ src/modcmd.c 12 Aug 2004 17:43:32 -0000 @@ -2325,6 +2325,7 @@ service_make_alias(service, "addcoowner", "*chanserv.adduser", "$1", "coowner", NULL); service_make_alias(service, "addmanager", "*chanserv.adduser", "$1", "manager", NULL); service_make_alias(service, "addop", "*chanserv.adduser", "$1", "op", NULL); + service_make_alias(service, "addhop", "*chanserv.adduser", "$1", "halfop", NULL); service_make_alias(service, "addpeon", "*chanserv.adduser", "$1", "peon", NULL); service_make_alias(service, "delowner", "*chanserv.deluser", "owner", "$1", NULL); service_make_alias(service, "delcoowner", "*chanserv.deluser", "coowner", "$1", NULL); Index: src/modules-list.h =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/modules-list.h,v retrieving revision 1.4 diff -u -r1.4 modules-list.h --- src/modules-list.h 23 Jul 2004 18:56:59 -0000 1.4 +++ src/modules-list.h 12 Aug 2004 17:43:32 -0000 @@ -1,3 +1 @@ -WITH_MODULE(snoop) -WITH_MODULE(memoserv) Index: src/opserv.c =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/opserv.c,v retrieving revision 1.2 diff -u -r1.2 opserv.c --- src/opserv.c 10 Jun 2004 20:14:33 -0000 1.2 +++ src/opserv.c 12 Aug 2004 17:43:36 -0000 @@ -101,6 +101,8 @@ { "OSMSG_NO_CHANNEL_MODES", "Channel $b%s$b had no modes to clear." }, { "OSMSG_DEOP_DONE", "Deopped the requested lusers." }, { "OSMSG_DEOPALL_DONE", "Deopped everyone on $b%s$b." }, + { "OSMSG_DEHOP_DONE", "Dehalfopped the requested lusers." }, + { "OSMSG_DEHOPALL_DONE", "Dehalfopped everyone on $b%s$b." }, { "OSMSG_NO_DEBUG_CHANNEL", "No debug channel has been configured." }, { "OSMSG_INVITE_DONE", "Invited $b%s$b to $b%s$b." }, { "OSMSG_ALREADY_THERE", "You are already in $b%s$b." }, @@ -112,6 +114,8 @@ { "OSMSG_MODE_SET", "I have set the modes for $b%s$b." }, { "OSMSG_OP_DONE", "Opped the requested lusers." }, { "OSMSG_OPALL_DONE", "Opped everyone on $b%s$b." }, + { "OSMSG_HOP_DONE", "Halfopped the requested lusers." }, + { "OSMSG_HOPALL_DONE", "Halfopped everyone on $b%s$b." }, { "OSMSG_WHOIS_IDENT", "%s (%s@%s) from %d.%d.%d.%d" }, { "OSMSG_WHOIS_NICK", "Nick : %s" }, { "OSMSG_WHOIS_HOST", "Host : %s@%s" }, @@ -177,6 +181,7 @@ { "OSMSG_CLONE_JOINED", "$b%s$b has joined $b%s$b." }, { "OSMSG_CLONE_PARTED", "$b%s$b has left $b%s$b." }, { "OSMSG_OPS_GIVEN", "I have given ops in $b%s$b to $b%s$b." }, + { "OSMSG_HOPS_GIVEN", "I have given halfops in $b%s$b to $b%s$b." }, { "OSMSG_CLONE_SAID", "$b%s$b has spoken to $b%s$b." }, { "OSMSG_UNKNOWN_SUBCOMMAND", "$b%s$b is not a valid subcommand of $b%s$b." }, { "OSMSG_UNKNOWN_OPTION", "$b%s$b has not been set." }, @@ -461,13 +466,18 @@ send_message_type(4, user, cmd->parent->bot, " @%s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname); } for (n=0; nmembers.used; n++) { + moden = channel->members.list[n]; + if ((moden->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) == MODE_HALFOP) + send_message_type(4, user, cmd->parent->bot, " %s%s (%s@%s)", "%", moden->user->nick, moden->user->ident, moden->user->hostname); + } + for (n=0; nmembers.used; n++) { moden = channel->members.list[n]; - if ((moden->modes & (MODE_CHANOP|MODE_VOICE)) == MODE_VOICE) + if ((moden->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) == MODE_VOICE) send_message_type(4, user, cmd->parent->bot, " +%s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname); } for (n=0; nmembers.used; n++) { moden = channel->members.list[n]; - if ((moden->modes & (MODE_CHANOP|MODE_VOICE)) == 0) + if ((moden->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) == 0) send_message_type(4, user, cmd->parent->bot, " %s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname); } return 1; @@ -570,6 +580,31 @@ return 1; } +static MODCMD_FUNC(cmd_dehop) +{ + struct mod_chanmode *change; + unsigned int arg, count; + + change = mod_chanmode_alloc(argc-1); + for (arg = 1, count = 0; arg < argc; ++arg) { + struct userNode *victim = GetUserH(argv[arg]); + struct modeNode *mn; + if (!victim || IsService(victim) + || !(mn = GetUserMode(channel, victim)) + || !(mn->modes & MODE_HALFOP)) + continue; + change->args[count].mode = MODE_REMOVE | MODE_HALFOP; + change->args[count++].member = mn; + } + if (count) { + change->argc = count; + modcmd_chanmode_announce(change); + } + mod_chanmode_free(change); + reply("OSMSG_DEHOP_DONE"); + return 1; +} + static MODCMD_FUNC(cmd_deopall) { struct mod_chanmode *change; @@ -592,6 +627,28 @@ return 1; } +static MODCMD_FUNC(cmd_dehopall) +{ + struct mod_chanmode *change; + unsigned int ii, count; + + change = mod_chanmode_alloc(channel->members.used); + for (ii = count = 0; ii < channel->members.used; ++ii) { + struct modeNode *mn = channel->members.list[ii]; + if (IsService(mn->user) || !(mn->modes & MODE_HALFOP)) + continue; + change->args[count].mode = MODE_REMOVE | MODE_HALFOP; + change->args[count++].member = mn; + } + if (count) { + change->argc = count; + modcmd_chanmode_announce(change); + } + mod_chanmode_free(change); + reply("OSMSG_DEHOPALL_DONE", channel->name); + return 1; +} + static MODCMD_FUNC(cmd_rehash) { extern char *services_config; @@ -856,6 +913,8 @@ } if (mn->modes & MODE_CHANOP) buff[count++] = '@'; + if (mn->modes & MODE_HALFOP) + buff[count++] = '%'; if (mn->modes & MODE_VOICE) buff[count++] = '+'; memcpy(buff+count, mn->channel->name, here_len); @@ -1127,6 +1186,33 @@ return 1; } +static MODCMD_FUNC(cmd_hop) +{ + struct mod_chanmode *change; + unsigned int arg, count; + + change = mod_chanmode_alloc(argc-1); + for (arg = 1, count = 0; arg < argc; ++arg) { + struct userNode *victim; + struct modeNode *mn; + if (!(victim = GetUserH(argv[arg]))) + continue; + if (!(mn = GetUserMode(channel, victim))) + continue; + if (mn->modes & MODE_HALFOP) + continue; + change->args[count].mode = MODE_HALFOP; + change->args[count++].member = mn; + } + if (count) { + change->argc = count; + modcmd_chanmode_announce(change); + } + mod_chanmode_free(change); + reply("OSMSG_HOP_DONE"); + return 1; +} + static MODCMD_FUNC(cmd_opall) { struct mod_chanmode *change; @@ -1149,6 +1235,28 @@ return 1; } +static MODCMD_FUNC(cmd_hopall) +{ + struct mod_chanmode *change; + unsigned int ii, count; + + change = mod_chanmode_alloc(channel->members.used); + for (ii = count = 0; ii < channel->members.used; ++ii) { + struct modeNode *mn = channel->members.list[ii]; + if (mn->modes & MODE_HALFOP) + continue; + change->args[count].mode = MODE_HALFOP; + change->args[count++].member = mn; + } + if (count) { + change->argc = count; + modcmd_chanmode_announce(change); + } + mod_chanmode_free(change); + reply("OSMSG_HOPALL_DONE", channel->name); + return 1; +} + static MODCMD_FUNC(cmd_whois) { struct userNode *target; @@ -1229,7 +1337,7 @@ change = mod_chanmode_alloc(channel->members.used); for (ii = count = 0; ii < channel->members.used; ++ii) { struct modeNode *mn = channel->members.list[ii]; - if (mn->modes & (MODE_CHANOP|MODE_VOICE)) + if (mn->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) continue; change->args[count].mode = MODE_VOICE; change->args[count++].member = mn; @@ -2298,6 +2406,24 @@ reply("OSMSG_OPS_GIVEN", channel->name, clone->nick); return 1; } + if (!irccasecmp(argv[1], "HOP")) { + struct mod_chanmode change; + if (!channel) { + reply("MSG_CHANNEL_UNKNOWN", argv[3]); + return 0; + } + mod_chanmode_init(&change); + change.argc = 1; + change.args[0].mode = MODE_HALFOP; + change.args[0].member = GetUserMode(channel, clone); + if (!change.args[0].member) { + reply("OSMSG_NOT_ON_CHANNEL", clone->nick, channel->name); + return 0; + } + modcmd_chanmode_announce(&change); + reply("OSMSG_HOPS_GIVEN", channel->name, clone->nick); + return 1; + } if (argc < 5) { reply("MSG_MISSING_PARAMS", argv[1]); OPSERV_SYNTAX(); @@ -2958,11 +3084,17 @@ case '#': goto find_channel; case '-': - discrim->chan_no_modes |= MODE_CHANOP | MODE_VOICE; + discrim->chan_no_modes |= MODE_CHANOP | MODE_HALFOP | MODE_VOICE; break; case '+': discrim->chan_req_modes |= MODE_VOICE; discrim->chan_no_modes |= MODE_CHANOP; + discrim->chan_no_modes |= MODE_HALFOP; + break; + case '%': + discrim->chan_req_modes |= MODE_HALFOP; + discrim->chan_no_modes |= MODE_CHANOP; + discrim->chan_no_modes |= MODE_VOICE; break; case '@': discrim->chan_req_modes |= MODE_CHANOP; @@ -4056,6 +4188,8 @@ opserv_define_func("DELTRUST", cmd_deltrust, 800, 0, 2); opserv_define_func("DEOP", cmd_deop, 100, 2, 2); opserv_define_func("DEOPALL", cmd_deopall, 400, 2, 0); + opserv_define_func("DEHOP", cmd_dehop, 100, 2, 2); + opserv_define_func("DEHOPALL", cmd_dehopall, 400, 2, 0); opserv_define_func("DEVOICEALL", cmd_devoiceall, 300, 2, 0); opserv_define_func("DIE", cmd_die, 900, 0, 2); opserv_define_func("DUMP", cmd_dump, 999, 0, 2); @@ -4079,6 +4213,8 @@ opserv_define_func("MODE", cmd_mode, 100, 2, 2); opserv_define_func("OP", cmd_op, 100, 2, 2); opserv_define_func("OPALL", cmd_opall, 400, 2, 0); + opserv_define_func("HOP", cmd_hop, 100, 2, 2); + opserv_define_func("HOPALL", cmd_hopall, 400, 2, 0); opserv_define_func("PART", cmd_part, 601, 0, 2); opserv_define_func("QUERY", cmd_query, 0, 0, 0); opserv_define_func("RAW", cmd_raw, 999, 0, 2); Index: src/proto-common.c =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/proto-common.c,v retrieving revision 1.2 diff -u -r1.2 proto-common.c --- src/proto-common.c 10 Jun 2004 20:14:33 -0000 1.2 +++ src/proto-common.c 12 Aug 2004 17:43:37 -0000 @@ -593,11 +593,13 @@ } break; case MODE_CHANOP: + case MODE_HALFOP: case MODE_VOICE: - case MODE_VOICE|MODE_CHANOP: + case MODE_VOICE|MODE_CHANOP|MODE_HALFOP: case MODE_REMOVE|MODE_CHANOP: + case MODE_REMOVE|MODE_HALFOP: case MODE_REMOVE|MODE_VOICE: - case MODE_REMOVE|MODE_VOICE|MODE_CHANOP: + case MODE_REMOVE|MODE_VOICE|MODE_CHANOP|MODE_HALFOP: if (change->args[ii].mode & MODE_REMOVE) change->args[ii].member->modes &= ~change->args[ii].mode; else Index: src/proto-p10.c =================================================================== RCS file: /cvsroot/x2serv/srvx2/src/proto-p10.c,v retrieving revision 1.3 diff -u -r1.3 proto-p10.c --- src/proto-p10.c 14 Jun 2004 07:32:06 -0000 1.3 +++ src/proto-p10.c 12 Aug 2004 17:43:39 -0000 @@ -96,8 +96,10 @@ #define CMD_VERSION "VERSION" #define CMD_WALLCHOPS "WALLCHOPS" #define CMD_WALLOPS "WALLOPS" +#define CMD_WALLHOPS "WALLHOPS" #define CMD_WALLUSERS "WALLUSERS" #define CMD_WALLVOICES "WALLVOICES" +#define CMD_WALLHOPS "WALLHOPS" #define CMD_WHO "WHO" #define CMD_WHOIS "WHOIS" #define CMD_WHOWAS "WHOWAS" @@ -178,8 +180,10 @@ #define TOK_VERSION "V" #define TOK_WALLCHOPS "WC" #define TOK_WALLOPS "WA" +#define TOK_WALLHOPS "WH" #define TOK_WALLUSERS "WU" #define TOK_WALLVOICES "WV" +#define TOK_WALLHOPS "WH" #define TOK_WHO "H" #define TOK_WHOIS "W" #define TOK_WHOWAS "X" @@ -271,6 +275,7 @@ #define P10_VERSION TYPE(VERSION) #define P10_WALLCHOPS TYPE(WALLCHOPS) #define P10_WALLOPS TYPE(WALLOPS) +#define P10_WALLHOPS TYPE(WALLHOPS) #define P10_WALLUSERS TYPE(WALLUSERS) #define P10_WALLVOICES TYPE(WALLVOICES) #define P10_WHO TYPE(WHO) @@ -600,6 +605,8 @@ burst_line[pos++] = ':'; if (last_mode & MODE_CHANOP) burst_line[pos++] = 'o'; + if (last_mode & MODE_HALFOP) + burst_line[pos++] = 'h'; if (last_mode & MODE_VOICE) burst_line[pos++] = 'v'; } @@ -1051,9 +1058,10 @@ case '+': { const char *pos; int n_modes; - for (pos=argv[next], n_modes = 1; *pos; pos++) + for (pos=argv[next], n_modes = 1; *pos; pos++) { if ((*pos == 'k') || (*pos == 'l')) n_modes++; + } unsplit_string(argv+next, n_modes, modes); next += n_modes; break; @@ -1063,6 +1071,7 @@ } } + in_timestamp = atoi(argv[2]); if ((cNode = dict_find(unbursted_channels, argv[1], NULL))) { cNode->timestamp = in_timestamp; @@ -1080,6 +1089,8 @@ while ((sep = *end++)) { if (sep == 'o') mode |= MODE_CHANOP; + else if (sep == 'h') + mode |= MODE_HALFOP; else if (sep == 'v') mode |= MODE_VOICE; else @@ -1576,9 +1587,13 @@ dict_insert(irc_func_dict, TOK_WALLCHOPS, cmd_dummy); dict_insert(irc_func_dict, CMD_WALLVOICES, cmd_dummy); dict_insert(irc_func_dict, TOK_WALLVOICES, cmd_dummy); + dict_insert(irc_func_dict, CMD_WALLHOPS, cmd_dummy); + dict_insert(irc_func_dict, TOK_WALLHOPS, cmd_dummy); /* Ignore opers being silly. */ dict_insert(irc_func_dict, CMD_WALLOPS, cmd_dummy); dict_insert(irc_func_dict, TOK_WALLOPS, cmd_dummy); + dict_insert(irc_func_dict, CMD_WALLHOPS, cmd_dummy); + dict_insert(irc_func_dict, TOK_WALLHOPS, cmd_dummy); /* We have reliable clock! Always! Wraaa! */ dict_insert(irc_func_dict, CMD_SETTIME, cmd_dummy); dict_insert(irc_func_dict, TOK_SETTIME, cmd_dummy); @@ -2159,14 +2174,21 @@ change->args[ch_arg].mode |= MODE_REMOVE; change->args[ch_arg++].hostmask = modes[in_arg++]; break; - case 'o': case 'v': + case 'o': case 'h': case 'v': { struct userNode *victim; if (!(flags & MCP_ALLOW_OVB)) goto error; if (in_arg >= argc) goto error; - change->args[ch_arg].mode = (modes[0][ii] == 'o') ? MODE_CHANOP : MODE_VOICE; + + if (modes[0][ii] == 'o') + change->args[ch_arg].mode = MODE_CHANOP; + else if (modes[0][ii] == 'h') + change->args[ch_arg].mode = MODE_HALFOP; + else if (modes[0][ii] == 'v') + change->args[ch_arg].mode = MODE_VOICE; + if (!add) change->args[ch_arg].mode |= MODE_REMOVE; if (flags & MCP_FROM_SERVER) @@ -2289,6 +2311,8 @@ default: if (change->args[arg].mode & MODE_CHANOP) mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->numeric); + if (change->args[arg].mode & MODE_HALFOP) + mod_chanmode_append(&chbuf, 'h', change->args[arg].member->user->numeric); if (change->args[arg].mode & MODE_VOICE) mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->numeric); break; @@ -2339,6 +2363,8 @@ default: if (change->args[arg].mode & MODE_CHANOP) mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->numeric); + if (change->args[arg].mode & MODE_HALFOP) + mod_chanmode_append(&chbuf, 'h', change->args[arg].member->user->numeric); if (change->args[arg].mode & MODE_VOICE) mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->numeric); break; @@ -2430,6 +2456,7 @@ for (remove = 0; *modes; modes++) { switch (*modes) { case 'o': remove |= MODE_CHANOP; break; + case 'h': remove |= MODE_HALFOP; break; case 'v': remove |= MODE_VOICE; break; case 'p': remove |= MODE_PRIVATE; break; case 's': remove |= MODE_SECRET; break; @@ -2474,8 +2501,8 @@ } /* Remove member modes. */ - if ((remove & (MODE_CHANOP | MODE_VOICE)) && channel->members.used) { - int mask = ~(remove & (MODE_CHANOP | MODE_VOICE)); + if ((remove & (MODE_CHANOP | MODE_HALFOP | MODE_VOICE)) && channel->members.used) { + int mask = ~(remove & (MODE_CHANOP | MODE_HALFOP | MODE_VOICE)); unsigned int i; for (i = 0; i < channel->members.used; i++)