aboutsummaryrefslogtreecommitdiffstats
path: root/src/command_parse.cpp
generated by cgit v1.3.1-10-gc9f91 (git 2.54.0) at 2026-08-07 14:59:01 +0000
href='#n233'>233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
/*
 * InspIRCd -- Internet Relay Chat Daemon
 *
 *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
 *   Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net>
 *   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
 *   Copyright (C) 2005-2008 Craig Edwards <craigedwards@brainbox.cc>
 *   Copyright (C) 2006-2007 Dennis Friis <peavey@inspircd.org>
 *
 * This file is part of InspIRCd.  InspIRCd 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, version 2.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */


#include "inspircd.h"

bool InspIRCd::PassCompare(Extensible* ex, const std::string& data, const std::string& input, const std::string& hashtype)
{
	ModResult res;
	FIRST_MOD_RESULT(OnPassCompare, res, (ex, data, input, hashtype));

	/* Module matched */
	if (res == MOD_RES_ALLOW)
		return true;

	/* Module explicitly didnt match */
	if (res == MOD_RES_DENY)
		return false;

	/* We dont handle any hash types except for plaintext - Thanks tra26 */
	if (!hashtype.empty() && hashtype != "plaintext")
		return false;

	return TimingSafeCompare(data, input);
}

bool CommandParser::LoopCall(User* user, Command* handler, const CommandBase::Params& parameters, unsigned int splithere, int extra, bool usemax)
{
	if (splithere >= parameters.size())
		return false;

	/* First check if we have more than one item in the list, if we don't we return false here and the handler
	 * which called us just carries on as it was.
	 */
	if (parameters[splithere].find(',') == std::string::npos)
		return false;

	/** Some lame ircds will weed out dupes using some shitty O(n^2) algorithm.
	 * By using std::set (thanks for the idea w00t) we can cut this down a ton.
	 * ...VOOODOOOO!
	 *
	 * Only check for duplicates if there is one list (allow them in JOIN).
	 */
	insp::flat_set<std::string, irc::insensitive_swo> dupes;
	bool check_dupes = (extra < 0);

	/* Create two sepstreams, if we have only one list, then initialize the second sepstream with
	 * an empty string. The second parameter of the constructor of the sepstream tells whether
	 * or not to allow empty tokens.
	 * We allow empty keys, so "JOIN #a,#b ,bkey" will be interpreted as "JOIN #a", "JOIN #b bkey"
	 */
	irc::commasepstream items1(parameters[splithere]);
	irc::commasepstream items2(extra >= 0 ? parameters[extra] : "", true);
	std::string item;
	unsigned int max = 0;
	LocalUser* localuser = IS_LOCAL(user);

	/* Attempt to iterate these lists and call the command handler
	 * for every parameter or parameter pair until there are no more
	 * left to parse.
	 */
	while (items1.GetToken(item) && (!usemax || max++ < ServerInstance->Config->MaxTargets))
	{
		if ((!check_dupes) || (dupes.insert(item).second))
		{
			CommandBase::Params new_parameters(parameters);
			new_parameters[splithere] = item;

			if (extra >= 0)
			{
				// If we have two lists then get the next item from the second list.
				// In case it runs out of elements then 'item' will be an empty string.
				items2.GetToken(item);
				new_parameters[extra] = item;
			}

			CommandBase::Params params(new_parameters, parameters.GetTags());
			CmdResult result = handler->Handle(user, params);
			if (localuser)
			{
				// Run the OnPostCommand hook with the last parameter being true to indicate
				// that the event is being called in a loop.
				item.clear();
				FOREACH_MOD(OnPostCommand, (handler, new_parameters, localuser, result, true));
			}
		}
	}

	return true;
}

Command* CommandParser::GetHandler(const std::string &commandname)
{
	CommandMap::iterator n = cmdlist.find(commandname);
	if (n != cmdlist.end())
		return n->second;

	return NULL;
}

// calls a handler function for a command

CmdResult CommandParser::CallHandler(const std::string& commandname, const CommandBase::Params& parameters, User* user, Command** cmd)
{
	CommandMap::iterator n = cmdlist.find(commandname);

	if (n != cmdlist.end())
	{
		if ((!parameters.empty()) && (parameters.back().empty()) && (!n->second->allow_empty_last_param))
			return CMD_INVALID;

		if (parameters.size() >= n->second->min_params)
		{
			bool bOkay = false;

			if (IS_LOCAL(user) && n->second->flags_needed)
			{
				/* if user is local, and flags are needed .. */

				if (user->IsModeSet(n->second->flags_needed))
				{
					/* if user has the flags, and now has the permissions, go ahead */
					if (user->HasPermission(commandname))
						bOkay = true;
				}
			}
			else
			{
				/* remote or no flags required anyway */
				bOkay = true;
			}

			if (bOkay)
			{
				if (cmd)
					*cmd = n->second;

				ClientProtocol::TagMap tags;
				return n->second->Handle(user, CommandBase::Params(parameters, tags));
			}
		}
	}
	return CMD_INVALID;
}

void CommandParser::ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& command_p)
{
	/* find the command, check it exists */
	Command* handler = GetHandler(command);

	// Penalty to give if the command fails before the handler is executed
	unsigned int failpenalty = 0;

	/* Modify the user's penalty regardless of whether or not the command exists */
	if (!user->HasPrivPermission("users/flood/no-throttle"))
	{
		// If it *doesn't* exist, give it a slightly heftier penalty than normal to deter flooding us crap
		unsigned int penalty = (handler ? handler->Penalty * 1000 : 2000);
		user->CommandFloodPenalty += penalty;

		// Increase their penalty later if we fail and the command has 0 penalty by default (i.e. in Command::Penalty) to
		// throttle sending ERR_* from the command parser. If the command does have a non-zero penalty then this is not
		// needed because we've increased their penalty above.
		if (penalty == 0)
			failpenalty = 1000;
	}

	if (!handler)
	{
		ModResult MOD_RESULT;
		FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false));
		if (MOD_RESULT == MOD_RES_DENY)
			return;

		/*
		 * This double lookup is in case a module (abbreviation) wishes to change a command.
		 * Sure, the double lookup is a bit painful, but bear in mind this only happens for unknowns anyway.
		 *
		 * Thanks dz for making me actually understand why this is necessary!
		 * -- w00t
		 */
		handler = GetHandler(command);
		if (!handler)
		{
			if (user->registered == REG_ALL)
				user->WriteNumeric(ERR_UNKNOWNCOMMAND, command, "Unknown command");
			ServerInstance->stats.Unknown++;
			return;
		}
	}

	// If we were given more parameters than max_params then append the excess parameter(s)
	// to command_p[maxparams-1], i.e. to the last param that is still allowed
	if (handler->max_params && command_p.size() > handler->max_params)
	{
		/*
		 * command_p input (assuming max_params 1):
		 *	this
		 *	is
		 *	a
		 *	test
		 */

		// Iterator to the last parameter that will be kept
		const CommandBase::Params::iterator lastkeep = command_p.begin() + (handler->max_params - 1);
		// Iterator to the first excess parameter
		const CommandBase::Params::iterator firstexcess = lastkeep + 1;

		// Append all excess parameter(s) to the last parameter, seperated by spaces
		for (CommandBase::Params::const_iterator i = firstexcess; i != command_p.end(); ++i)
		{
			lastkeep->push_back(' ');
			lastkeep->append(*i);
		}

		// Erase the excess parameter(s)
		command_p.erase(firstexcess, command_p.end());
	}

	/*
	 * We call OnPreCommand here seperately if the command exists, so the magic above can
	 * truncate to max_params if necessary. -- w00t
	 */
	ModResult MOD_RESULT;
	FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false));
	if (MOD_RESULT == MOD_RES_DENY)
		return;

	/* activity resets the ping pending timer */
	user->nping = ServerInstance->Time() + user->MyClass->GetPingTime();

	if (handler->flags_needed)
	{
		if (!user->IsModeSet(handler->flags_needed))
		{
			user->CommandFloodPenalty += failpenalty;
			user->WriteNumeric(ERR_NOPRIVILEGES, "Permission Denied - You do not have the required operator privileges");
			return;
		}

		if (!user->HasPermission(command))
		{
			user->CommandFloodPenalty += failpenalty;
			user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Oper type %s does not have access to command %s",
				user->oper->name.c_str(), command.c_str()));
			return;
		}
	}

	if ((user->registered == REG_ALL) && (!user->IsOper()) && (handler->IsDisabled()))
	{
		/* command is disabled! */
		user->CommandFloodPenalty += failpenalty;
		if (ServerInstance->Config->DisabledDontExist)
		{
			user->WriteNumeric(ERR_UNKNOWNCOMMAND, command, "Unknown command");
		}
		else
		{
			user->WriteNumeric(ERR_UNKNOWNCOMMAND, command, "This command has been disabled.");
		}

		ServerInstance->SNO->WriteToSnoMask('a', "%s denied for %s (%s@%s)",
				command.c_str(), user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
		return;
	}

	if ((!command_p.empty()) && (command_p.back().empty()) && (!handler->allow_empty_last_param))
		command_p.pop_back();

	if (command_p.size() < handler->min_params)
	{
		user->CommandFloodPenalty += failpenalty;
		user->WriteNumeric(ERR_NEEDMOREPARAMS, command, "Not enough parameters.");
		if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (handler->syntax.length()))
			user->WriteNumeric(RPL_SYNTAX, handler->name, handler->syntax);
		return;
	}

	if ((user->registered != REG_ALL) && (!handler->WorksBeforeReg()))
	{
		user->CommandFloodPenalty += failpenalty;
		user->WriteNumeric(ERR_NOTREGISTERED, command, "You have not registered");
	}
	else
	{
		/* passed all checks.. first, do the (ugly) stats counters. */
		handler->use_count++;

		/* module calls too */
		FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, true));
		if (MOD_RESULT == MOD_RES_DENY)
			return;

		/*
		 * WARNING: be careful, the user may be deleted soon
		 */
		CmdResult result = handler->Handle(user, command_p);

		FOREACH_MOD(OnPostCommand, (handler, command_p, user, result, false));
	}
}

void CommandParser::RemoveCommand(Command* x)
{
	CommandMap::iterator n = cmdlist.find(x->name);
	if (n != cmdlist.end() && n->second == x)
		cmdlist.erase(n);
}

CommandBase::CommandBase(Module* mod, const std::string& cmd, unsigned int minpara, unsigned int maxpara)
	: ServiceProvider(mod, cmd, SERVICE_COMMAND)
	, flags_needed(0)
	, min_params(minpara)
	, max_params(maxpara)
	, use_count(0)
	, disabled(false)
	, works_before_reg(false)
	, allow_empty_last_param(true)
	, Penalty(1)
{
}

CommandBase::~CommandBase()
{
}

void CommandBase::EncodeParameter(std::string& parameter, unsigned int index)
{
}

RouteDescriptor CommandBase::GetRouting(User* user, const Params& parameters)
{
	return ROUTE_LOCALONLY;
}

Command::Command(Module* mod, const std::string& cmd, unsigned int minpara, unsigned int maxpara)
	: CommandBase(mod, cmd, minpara, maxpara)
	, force_manual_route(false)
{
}

Command::~Command()
{
	ServerInstance->Parser.RemoveCommand(this);
}

void Command::RegisterService()
{
	if (!ServerInstance->Parser.AddCommand(this))
		throw ModuleException("Command already exists: " + name);
}

void CommandParser::ProcessBuffer(LocalUser* user, const std::string& buffer)
{
	ClientProtocol::ParseOutput parseoutput;
	if (!user->serializer->Parse(user, buffer, parseoutput))
		return;

	std::string& command = parseoutput.cmd;
	std::transform(command.begin(), command.end(), command.begin(), ::toupper);

	CommandBase::Params parameters(parseoutput.params, parseoutput.tags);
	ProcessCommand(user, command, parameters);
}

bool CommandParser::AddCommand(Command *f)
{
	/* create the command and push it onto the table */
	if (cmdlist.find(f->name) == cmdlist.end())
	{
		cmdlist[f->name] = f;
		return true;
	}
	return false;
}

CommandParser::CommandParser()
{
}

std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const CommandBase::Params& source, bool prefix_final, CommandBase* custom_translator)
{
	std::vector<TranslateType>::const_iterator types = to.begin();
	std::string dest;

	for (unsigned int i = 0; i < source.size(); i++)
	{
		TranslateType t = TR_TEXT;
		// They might supply less translation types than parameters,
		// in that case pretend that all remaining types are TR_TEXT
		if (types != to.end())
		{
			t = *types;
			types++;
		}

		bool last = (i == (source.size() - 1));
		if (prefix_final && last)
			dest.push_back(':');

		TranslateSingleParam(t, source[i], dest, custom_translator, i);

		if (!last)
			dest.push_back(' ');
	}

	return dest;
}

void CommandParser::TranslateSingleParam(TranslateType to, const std::string& item, std::string& dest, CommandBase* custom_translator, unsigned int paramnumber)
{
	switch (to)
	{
		case TR_NICK:
		{
			/* Translate single nickname */
			User* user = ServerInstance->FindNick(item);
			if (user)
				dest.append(user->uuid);
			else
				dest.append(item);
			break;
		}
		case TR_CUSTOM:
		{
			if (custom_translator)
			{
				std::string translated = item;
				custom_translator->EncodeParameter(translated, paramnumber);
				dest.append(translated);
				break;
			}
			// If no custom translator was given, fall through
		}
		case TR_TEXT:
		default:
			/* Do nothing */
			dest.append(item);
		break;
	}
}