/** * BANCommand.cc * * 13/01/2001 - Greg Sikorski * Initial Version. * 20/01/2001 - David Henriksen * BAN Command started. * 23/01/2001 - Greg Sikorski * Cleanups/rewrites - don't allow adding of less specific bans. * 10/02/2001 - David Henriksen * Minor bug fixes. * 01/03/01 - Daniel Simard * Fixed Language module stuff. * 2001-03-16: Perry Lorier * Fixed ban's without a reason defaulting to level 75 * * Bans a user on a channel, adds this ban to the internal banlist. * * Caveats: None. * * This program 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * * $Id: BANCommand.cc,v 1.49 2009/06/25 19:05:23 mrbean_ Exp $ */ #include #include #include #include #include #include "StringTokenizer.h" #include "cservice.h" #include "Network.h" #include "levels.h" #include "misc.h" #include "responses.h" #include "match.h" #include "ip.h" #include "cidr.h" const char BANCommand_cc_rcsId[] = "$Id: BANCommand.cc,v 1.49 2009/06/25 19:05:23 mrbean_ Exp $" ; namespace gnuworld { using std::string ; using std::endl ; using std::ends ; using std::stringstream ; using namespace level; bool BANCommand::Exec( iClient* theClient, const string& Message ) { bot->incStat("COMMANDS.BAN"); StringTokenizer st( Message ) ; if( st.size() < 3 ) { Usage(theClient); return true; } /* Is the user authorised? */ sqlUser* theUser = bot->isAuthed(theClient, true); if(!theUser) { return false; } /* Do not allow bans on * channel */ if(st[1][0] != '#') { bot->Notice(theClient, bot->getResponse(theUser, language::inval_chan_name).c_str()); return false; } /* Is the channel registered? */ sqlChannel* theChan = bot->getChannelRecord(st[1]); if(!theChan) { bot->Notice(theClient, bot->getResponse( theUser, language::chan_not_reg).c_str(), st[1].c_str()); return false; } /* Check the bot is in the channel. */ if (!theChan->getInChan()) { bot->Notice( theClient, bot->getResponse(theUser, language::i_am_not_on_chan).c_str() ); return false; } /* * Check the channel currently exists on the network, if so - we can * start kicking. */ Channel* theChannel = Network->findChannel(theChan->getName()); if (!theChannel) { bot->Notice(theClient, bot->getResponse(theUser, language::chan_is_empty).c_str(), theChan->getName().c_str()); return false; } /* * Check we're actually opped first.. */ ChannelUser* tmpBotUser = theChannel->findUser(bot->getInstance()); if (!tmpBotUser) return false; if(!tmpBotUser->getMode(ChannelUser::MODE_O)) { bot->Notice(theClient, bot->getResponse(theUser, language::im_not_opped, "I'm not opped in %s").c_str(), theChan->getName().c_str()); return false; } int oCount = 0; unsigned int banTime = (3*3600); int banLevel = 75; string banReason = "No Reason"; if(st.size() >= 6) oCount = 3; if(st.size() == 5) oCount = 2; if(st.size() == 4) oCount = 1; switch(oCount) { case 1: { /* * Just one parameter supplied, its either a duration, * or we'll also accept a reason and set defaults. */ if(!IsTimeSpec(st[3])) { banReason = st.assemble(3); } else { banTime = extractTime(st[3], 3600); } break; } case 2: { /* * Two parameters supplied, it's either reason, * duration + reason or duration and level. */ if(!IsTimeSpec(st[3])) { /* first param is not duration, must be reason */ banReason = st.assemble(3); break; } if(!IsNumeric(st[4])) { /* first param IS duration (see above), 2nd param is * NOT level - must be reason */ banReason = st.assemble(4); banTime = extractTime(st[3], 3600); break; } /* if we reach here, first param is duration, 2nd param is level * and no reason is specified. */ banTime = extractTime(st[3], 3600); banLevel = atoi(st[4].c_str()); break; } case 3: { /* * All parameters supplied, it's either reason, duration + * reason or duration + level + reason. */ if(!IsTimeSpec(st[3])) { /* first param is NOT duration, must be reason */ banReason = st.assemble(3); break; } if(!IsNumeric(st[4])) { /* first param IS duration (see above), 2nd param is * NOT level - must be reason. */ banReason = st.assemble(4); banTime = extractTime(st[3], 3600); break; } /* if we reach here, we have duration, level and reason */ banTime = extractTime(st[3], 3600); banLevel = atoi(st[4].c_str()); banReason = st.assemble(5); break; } } // switch() // Check level. int level = bot->getEffectiveAccessLevel(theUser, theChan, true); if(level < level::ban) { bot->Notice(theClient, bot->getResponse(theUser, language::insuf_access).c_str() ); return false; } // TODO: Violation of the rule of numbers if(banLevel < 1 || banLevel > level || 500 < banLevel) { bot->Notice(theClient, bot->getResponse(theUser,language::ban_level_range).c_str(), (500 < level) ? 500 : level); return true; } //if(banTime > 0 || banTime > bot->getConfigVar("MAX_BAN_DURATION")->asInt()) //if((banTime < 300) && (banTime > 0)) || banTime > bot->getConfigVar("MAX_BAN_DURATION")->asInt()) if ((banTime < 0) || ((banTime < 300) && (banTime > 0)) || banTime > bot->getConfigVar("MAX_BAN_DURATION")->asInt()) { // int maxbanhours = (bot->getConfigVar("MAX_BAN_DURATION")->asInt() / 3600); int maxbanhours = (bot->getConfigVar("MAX_BAN_DURATION")->asInt() / 86400); bot->Notice(theClient, bot->getResponse(theUser, language::ban_duration).c_str(), maxbanhours ); return true; } //int max_ban_reason_length = bot->getConfigVar("MAX_BAN_REASON_LENGTH")->asInt(); // TODO: Violation of the rule of numbers if(banReason.size() > 300) //if((banReason.size() > max_ban_reason_length) { bot->Notice(theClient, bot->getResponse(theUser, language::ban_reason_size).c_str() // language::ban_reason_size).c_str(),max_ban_reason_length); ); return true; } /* check if the banlist is full */ int max_bans = 0; int global_max_bans = bot->getConfigVar("MAX_BANS")->asInt(); int local_max_bans = theChan->getMaxBans(); if (local_max_bans > global_max_bans) max_bans = local_max_bans; else max_bans = global_max_bans; int ban_count = 0; if (max_bans > 0) { /* if we have a max ban limit, get a count of the bans */ std::map < int,sqlBan* >::iterator ptr = theChan->banList.begin(); while (ptr != theChan->banList.end()) { ban_count++; /* increment ban counter */ ++ptr; } if (ban_count >= max_bans) { /* banlist is full */ bot->Notice(theClient, "Sorry, The channel banlist is full (%i bans)", ban_count); return true; } } StringTokenizer st2( st[2], ',' ); StringTokenizer::size_type counter = 0; //int max_multibans = bot->getConfigVar("MAX_MULTIBANS")->asInt(); int max_multibans = 10; if (st2.size() > max_multibans) { bot->Notice(theClient, "Sorry, you can only add %i bans with one BAN command.", max_multibans); return true; } if ((st2.size() + ban_count) > max_bans) { bot->Notice(theClient, "Sorry, the channel banlist is full (%i set, adding %i, maximum %i)", ban_count, st2.size(), max_bans); return true; } bool flagNoTake = false; #ifdef USE_NOTAKE if (theChan->getFlag(sqlChannel::F_NOTAKE)) flagNoTake = true; #endif for( ; counter < st2.size() ; counter++ ) { string banTarget = st2[counter]; elog << "ST " << counter << ": " << st2[counter] << endl; // *** Take Over Protection *** bool takeOver = false; if (level < 500) if (!matchall(banTarget) && validUserMask(banTarget) && ((banLevel >= 75) && (flagNoTake))) { //Revenge is Ignore if (theChan->getNoTake() == 1) return true; takeOver = true; unsigned short banLevel = (unsigned short)level::set::notake; unsigned int banExpire = 100 * 86400; banReason = "### Take Over Triggered ###"; string suspendReason = "\002*** TAKE OVER ATTEMPT ***\002"; if (theChan->getNoTake() > 1) bot->doInternalBanAndKick(theChan, theClient, banLevel, banExpire, banReason); if (theChan->getNoTake() > 2) bot->doInternalSuspend(theChan, theClient, banLevel, banExpire, suspendReason); return true; } // *** End of Take Over Protection part **** bool isNick = bot->validUserMask( banTarget ) ? false : true ; if( isNick ) { iClient* aNick = Network->findNick(banTarget); if(!aNick) { bot->Notice(theClient, bot->getResponse(theUser, language::cant_find_on_chan).c_str(), st2[counter].c_str(), theChan->getName().c_str() ); continue; } /* Ban and kick this user */ banTarget = Channel::createUniformBan(aNick); } /* * Get a list of all bans on this channel, try and match this ban and * find overlapping bans. */ xCIDR cidr = xCIDR(banTarget); std::map < int,sqlBan* >::iterator ptr = theChan->banList.begin(); vector oldBans; bool banExists = false; while (ptr != theChan->banList.end()) { sqlBan* theBan = ptr->second; if(string_lower(banTarget) == string_lower(theBan->getBanMask())) { bot->Notice(theClient, bot->getResponse(theUser, language::ban_exists).c_str() ); banExists = true; break; } if(!cidr.GetValid()) { /* * Overlapping ban? We just remove the ban from our internal tables, as * setting this ban to ircu will cause a default removal of overlapping * bans. */ // Matched overlapping ban. if(match(banTarget, theBan->getBanMask()) == 0) { // If we have access to remove the overlapper.. if (theBan->getLevel() <= level) { // Update GNUWorld. oldBans.push_back(theBan); } } // More specific ban? else if ( match(theBan->getBanMask(), banTarget) == 0) { bot->Notice(theClient, bot->getResponse(theUser, language::ban_covered).c_str(), banTarget.c_str(), theBan->getBanMask().c_str()); banExists = true; break; } // Carry on regardless. } //cidr.GetValid ++ptr; } // while() if (banExists) { continue; } /* Go over the bans that needs to be removed and remove them */ vector::iterator banIterator = oldBans.begin(); for( ; banIterator != oldBans.end() ; ++banIterator ) { sqlBan* theBan = *banIterator; // First, remove the ban from the gnuworld::Channel if( !theChannel->removeBan(theBan->getBanMask()) ) { elog << "cservice::BANCommand> Unable to find " << "ban: " << theBan->getBanMask() << endl ; continue ; } // Lookup the ban in the sqlBan table in mod.cservice sqlChannel::sqlBanMapType::iterator sqlBanIterator = theChan->banList.find( theBan->getID() ) ; if( sqlBanIterator == theChan->banList.end() ) { elog << "cservice::BANCommand> Unable to find " << "ban in sqlChannel, id " << theBan->getID() << ", mask: " << theBan->getBanMask() << endl ; continue ; } // Erase the sqlBan from the cservice ban table theChan->banList.erase( sqlBanIterator ); // Erase the ban from the database theBan->deleteRecord(); // Free allocated memory delete(theBan); theBan = 0 ; } // TODO: Violation of rule of numbers if (banLevel == 42) { // TODO: Perhaps put this into the .conf banReason = "..I'll have a pan-galactic gargleblaster please!"; } /* * Fill out new ban details. */ sqlBan* newBan = new (std::nothrow) sqlBan(bot->SQLDb); assert( newBan != 0 ) ; // TODO: Use a decent constructor for this newBan->setChannelID(theChan->getID()); newBan->setBanMask(banTarget); if (flagNoTake) { if (takeOver) newBan->setSetBy(""); else newBan->setSetBy(theUser->getUserName()); } else newBan->setSetBy(theUser->getUserName()); newBan->setSetTS(bot->currentTime()); newBan->setLevel(banLevel); //Leave 0 to 0, meaning a permanent ban. if (banTime > 0) banTime = banTime + bot->currentTime(); //newBan->setExpires(banTime+bot->currentTime()); newBan->setExpires(banTime); newBan->setReason(banReason); vector< iClient* > clientsToKick ; for(Channel::userIterator chanUsers = theChannel->userList_begin(); chanUsers != theChannel->userList_end(); ++chanUsers) { ChannelUser* tmpUser = chanUsers->second; /* * Iterate over channel members, find a match and boot them.. */ if( newBan->getMatcher()->matches(tmpUser->getClient()) ) { /* Don't kick +k things */ if( !tmpUser->getClient()->getMode(iClient::MODE_SERVICES) ) { clientsToKick.push_back(tmpUser->getClient()); } } } // for() /* * If this ban level is < 75, we don't kick the user, we simply don't * allow any of the matching hosts to be opped anymore. */ // TODO: Violation of rule of numbers if (banLevel < 75) { bot->DeOp(theChannel, clientsToKick); } else { /* * Otherwise, > 100 bans result in the user being kicked out * and a ban placed on the channel. */ string finalReason = "(" + theUser->getUserName() + ") " + banReason; if( !clientsToKick.empty() ) { /* ban them */ bot->Write("%s M %s +b :%s", bot->getCharYYXXX().c_str(), theChannel->getName().c_str(), banTarget.c_str()); /* add ban to channel banlist */ theChannel->setBan(banTarget); /* kick the users */ bot->Kick( theChannel, clientsToKick, finalReason ) ; } } //theChan->banList[newBan->getID()] = newBan; /* Insert this new record into the database. */ newBan->insertRecord(); /* Insert to our internal List. */ theChan->banList.insert(std::map::value_type(newBan->getID(),newBan)); bot->Notice(theClient, bot->getResponse(theUser, language::ban_added, "Added ban %s to %s at level %i").c_str(), newBan->getBanMask().c_str(), theChannel->getName().c_str(), newBan->getLevel()); } return true ; } } // Namespace GNUWorld.