Пример #1
0
/** Because Andy insists that services-compatible servers must
 * implement SVSNICK and SVSJOIN, that's exactly what we do :p
 */
bool TreeSocket::ForceNick(const std::string &prefix, std::deque<std::string> &params)
{
	if (params.size() < 3)
		return true;

	User* u = this->ServerInstance->FindNick(params[0]);

	if (u)
	{
		Utils->DoOneToAllButSender(prefix,"SVSNICK",params,prefix);

		if (IS_LOCAL(u))
		{
			std::deque<std::string> par;
			par.push_back(params[1]);

			if (!u->ForceNickChange(params[1].c_str()))
			{
				/* buh. UID them */
				if (!u->ForceNickChange(u->uuid.c_str()))
				{
					this->ServerInstance->Users->QuitUser(u, "Nickname collision");
					return true;
				}
			}

			u->age = atoi(params[2].c_str());
		}
	}

	return true;
}
Пример #2
0
/**
 * SAVE command - force nick change to UID on timestamp match
 */
bool TreeSocket::ForceNick(const std::string &prefix, parameterlist &params)
{
	if (params.size() < 2)
		return true;

	User* u = ServerInstance->FindNick(params[0]);
	time_t ts = atol(params[1].c_str());

	if ((u) && (!IS_SERVER(u)) && (u->age == ts))
	{
		Utils->DoOneToAllButSender(prefix,"SAVE",params,prefix);

		if (!u->ForceNickChange(u->uuid.c_str()))
		{
			ServerInstance->Users->QuitUser(u, "Nickname collision");
		}
	}

	return true;
}
Пример #3
0
	CmdResult Handle(const std::vector<std::string>& parameters, User *user)
	{
		User* target = ServerInstance->FindNick(parameters[0]);

		if ((!target) || (target->registered != REG_ALL))
		{
			user->WriteNotice("*** No such nickname: '" + parameters[0] + "'");
			return CMD_FAILURE;
		}

		/* Do local sanity checks and bails */
		if (IS_LOCAL(user))
		{
			if (!ServerInstance->IsNick(parameters[1]))
			{
				user->WriteNotice("*** Invalid nickname '" + parameters[1] + "'");
				return CMD_FAILURE;
			}

			user->WriteServ("947 %s %s :Nickname now locked.", user->nick.c_str(), parameters[1].c_str());
		}

		/* If we made it this far, extend the user */
		if (IS_LOCAL(target))
		{
			locked.set(target, 1);

			std::string oldnick = target->nick;
			if (target->ForceNickChange(parameters[1]))
				ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used NICKLOCK to change and hold "+oldnick+" to "+parameters[1]);
			else
			{
				std::string newnick = target->nick;
				ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used NICKLOCK, but "+oldnick+" failed nick change to "+parameters[1]+" and was locked to "+newnick+" instead");
			}
		}

		return CMD_SUCCESS;
	}
Пример #4
0
	/* SQL Request */
	virtual const char* OnRequest(Request* request) {
		if(strcmp(SQLRESID, request->GetId()) == 0) {
			SQLresult* res = static_cast<SQLresult*>(request);

			User* user = GetAssocUser(this, SQLutils, res->id).S().user;
			UnAssociate(this, SQLutils, res->id).S();

			if(user) {
				if(res->error.Id() == SQL_NO_ERROR) {
					std::string* wnick;
					bool result;

					if(res->Rows()) {
						int rowcount=res->Rows(),i;

						/* Clean Custom User Metadata */
						user->Shrink("sqlAllowedIdent");
						user->Shrink("sqlAllowedHost");
						user->Shrink("sqlvHost");
						user->Shrink("sqlTitle");
						user->Shrink("sqlumodes");

						std::string sqlvHost;
						std::string sqlTitle;
						std::string sqlumodes;
						std::string sqlAllowedIdent;
						std::string sqlAllowedHost;

						/* Get Data from SQL (using freeform query. "query" in modules.conf) */
						for (i=0; i<rowcount; ++i) {
							SQLfieldList& currow = res->GetRow();
							sqlAllowedIdent = currow[1].d.c_str();
							sqlAllowedHost = currow[2].d.c_str();
							sqlvHost = currow[3].d.c_str();
							sqlTitle = currow[4].d.c_str();
							sqlumodes = currow[5].d.c_str();
						}

						std::string* pAllowedIdent = new std::string(sqlAllowedIdent);
						std::string* pAllowedHost = new std::string(sqlAllowedHost);
						std::string* pvHost = new std::string(sqlvHost);
						std::string* pTitle = new std::string(sqlTitle);
						std::string* pumodes = new std::string(sqlumodes);

						user->Extend("sqlAllowedIdent",pAllowedIdent);
						user->Extend("sqlAllowedHost",pAllowedHost);
						user->Extend("sqlvHost",pvHost);
						user->Extend("sqlTitle",pTitle);
						user->Extend("sqlumodes",pumodes);

						/* Check Allowed Ident@Hostname from SQL */
						if (sqlAllowedIdent != "" && sqlAllowedHost != "") {
							char TheHost[MAXBUF];
							char TheIP[MAXBUF];
							char TheAllowedUHost[MAXBUF];

							snprintf(TheHost,MAXBUF,"%s@%s",user->ident.c_str(), user->host.c_str());
							snprintf(TheIP, MAXBUF,"%s@%s",user->ident.c_str(), user->GetIPString());
							snprintf(TheAllowedUHost, MAXBUF, "%s@%s", sqlAllowedIdent.c_str(), sqlAllowedHost.c_str());

							if (!OneOfMatches(TheHost,TheIP,TheAllowedUHost)) {
								if (killreasonUHost == "") { killreasonUHost = "Your ident or hostmask did not match the one registered to this nickname. Allowed: $allowedident@$allowedhost"; }
								std::string tmpKillReason = killreasonUHost;
								SearchAndReplace(tmpKillReason, "$allowedident", sqlAllowedIdent.c_str());
								SearchAndReplace(tmpKillReason, "$allowedhost", sqlAllowedHost.c_str());

								/* Run Failure SQL Insert Query (For Logging) */
								std::string repfquery = failurequery;
								if (repfquery != "") {
									if (user->GetExt("wantsnick", wnick)) {
										SearchAndReplace(repfquery, "$nick", *wnick);
									} else {
										SearchAndReplace(repfquery, "$nick", user->nick);
									}

									SearchAndReplace(repfquery, "$host", user->host);
									SearchAndReplace(repfquery, "$ip", user->GetIPString());
									SearchAndReplace(repfquery, "$reason", tmpKillReason.c_str());

									SQLrequest req = SQLrequest(this, SQLprovider, databaseid, SQLquery(repfquery));
									result = req.Send();
								}

								ServerInstance->Users->QuitUser(user, tmpKillReason);

								user->Extend("sqlauth_failed");
								return NULL;
							}
						}

						/* We got a result, auth user */
						user->Extend("sqlauthed");

						/* possible ghosting? */
						if (user->GetExt("wantsnick", wnick)) {
							/* no need to check ghosting, this is done in OnPreCommand
							 * and if ghosting is off, user wont have the Extend 
							 */
							User* InUse = ServerInstance->FindNickOnly(wnick->c_str());
							if (InUse) {
								/* change his nick to UUID so we can take it */
								//InUse->ForceNickChange(InUse->uuid.c_str());
								/* put user on cull list */
								ServerInstance->Users->QuitUser(InUse, "Ghosted by connecting user with same nick.");
							}
							/* steal the nick ;) */
							user->ForceNickChange(wnick->c_str());
							user->Shrink("wantsnick");
						}

						/* Set Account Name (for m_services_account +R/+M channels) */
						if (setaccount) {
							std::string* pAccount = new std::string(user->nick.c_str());

							user->Extend("accountname",pAccount);
						}

						/* Run Success SQL Update Query */
						std::string repsquery = successquery;
						if (successquery != "") {
							SearchAndReplace(repsquery, "$nick", user->nick);
							SearchAndReplace(repsquery, "$host", user->host);
							SearchAndReplace(repsquery, "$ip", user->GetIPString());

							SQLrequest req = SQLrequest(this, SQLprovider, databaseid, SQLquery(repsquery));
							result = req.Send();
						}

					/* Returned No Rows */
					} else {
						if (verbose) {
							/* No rows in result, this means there was no record matching the user */
							ServerInstance->SNO->WriteToSnoMask('A', "Forbidden connection from %s!%s@%s (SQL query returned no matches)", user->nick.c_str(), user->ident.c_str(), user->host.c_str());
						}

						/* Run Failure SQL Insert Query (For Logging) */
						std::string repfquery = failurequery;
						if (repfquery != "") {
							if (user->GetExt("wantsnick", wnick)) {
								SearchAndReplace(repfquery, "$nick", *wnick);
							} else {
								SearchAndReplace(repfquery, "$nick", user->nick);
							}

							SearchAndReplace(repfquery, "$host", user->host);
							SearchAndReplace(repfquery, "$ip", user->GetIPString());
							SearchAndReplace(repfquery, "$reason", killreason.c_str());

							SQLrequest req = SQLrequest(this, SQLprovider, databaseid, SQLquery(repfquery));
							result = req.Send();
						}

						/* Kill user that entered invalid credentials */
						ServerInstance->Users->QuitUser(user, killreason);

						user->Extend("sqlauth_failed");
					}
				/* SQL Failure */
				} else {
					if (verbose) {
						ServerInstance->SNO->WriteToSnoMask('A', "Forbidden connection from %s!%s@%s (SQL query failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), res->error.Str());
					}
					
					user->Extend("sqlauth_failed");
				}
			} else {
				return NULL;
			}

			if (!user->GetExt("sqlauthed")) {
				ServerInstance->Users->QuitUser(user, killreason);
			}
			return SQLSUCCESS;
		}
		return NULL;
	}
Пример #5
0
/*
 * Yes, this function looks a little ugly.
 * However, in some circumstances we may not have a User, so we need to do things this way.
 * Returns 1 if colliding local client, 2 if colliding remote, 3 if colliding both.
 * Sends SAVEs as appropriate and forces nickchanges too.
 */
int SpanningTreeUtilities::DoCollision(User* u, TreeServer* server, time_t remotets, const std::string& remoteident, const std::string& remoteip, const std::string& remoteuid)
{
	/*
	 * Under old protocol rules, we would have had to kill both clients.
	 * Really, this sucks.
	 * These days, we have UID. And, so what we do is, force nick change client(s)
	 * involved according to timestamp rules.
	 *
	 * RULES:
	 *  user@ip equal:
	 *   Force nick change on OLDER timestamped client
	 *  user@ip differ:
	 *   Force nick change on NEWER timestamped client
	 *  TS EQUAL:
	 *   FNC both.
	 *
	 * This stops abusive use of collisions, simplifies problems with loops, and so on.
	 *   -- w00t
	 */
	bool bChangeLocal = true;
	bool bChangeRemote = true;

	/* for brevity, don't use the User - use defines to avoid any copy */
	#define localts u->age
	#define localident u->ident
	#define localip u->GetIPString()

	/* mmk. let's do this again. */
	if (remotets == localts)
	{
		/* equal. f**k them both! do nada, let the handler at the bottom figure this out. */
	}
	else
	{
		/* f**k. now it gets complex. */

		/* first, let's see if ident@host matches. */
		bool SamePerson = (localident == remoteident)
				&& (localip == remoteip);

		/*
		 * if ident@ip is equal, and theirs is newer, or
		 * ident@ip differ, and ours is newer
		 */
		if((SamePerson && remotets < localts) ||
		   (!SamePerson && remotets > localts))
		{
			/* remote needs to change */
			bChangeLocal = false;
		}
		else
		{
			/* ours needs to change */
			bChangeRemote = false;
		}
	}

	/*
	 * Cheat a little here. Instead of a dedicated command to change UID,
	 * use SAVE and accept the losing client with its UID (as we know the SAVE will
	 * not fail under any circumstances -- UIDs are netwide exclusive).
	 *
	 * This means that each side of a collide will generate one extra NICK back to where
	 * they have just linked (and where it got the SAVE from), however, it will
	 * be dropped harmlessly as it will come in as :928AAAB NICK 928AAAB, and we already
	 * have 928AAAB's nick set to that.
	 *   -- w00t
	 */

	if (bChangeLocal)
	{
		/*
		 * Local-side nick needs to change. Just in case we are hub, and
		 * this "local" nick is actually behind us, send an SAVE out.
		 */
		CmdBuilder params("SAVE");
		params.push_back(u->uuid);
		params.push_back(ConvToStr(u->age));
		params.Broadcast();

		u->ForceNickChange(u->uuid);

		if (!bChangeRemote)
			return 1;
	}
	if (bChangeRemote)
	{
		User *remote = ServerInstance->FindUUID(remoteuid);
		/*
		 * remote side needs to change. If this happens, we will modify
		 * the UID or halt the propagation of the nick change command,
		 * so other servers don't need to see the SAVE
		 */
		TreeSocket* sock = server->GetSocket();
		sock->WriteLine(":"+ServerInstance->Config->GetSID()+" SAVE "+remoteuid+" "+ ConvToStr(remotets));

		if (remote)
		{
			/* nick change collide. Force change their nick. */
			remote->ForceNickChange(remoteuid);
		}

		if (!bChangeLocal)
			return 2;
	}

	return 3;
}
Пример #6
0
void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params)
{
	User* who = ServerInstance->FindUUID(prefix);
	std::string direction;

	if (!who)
	{
		TreeServer* ServerSource = Utils->FindServer(prefix);
		if (prefix.empty())
			ServerSource = MyRoot;

		if (ServerSource)
		{
			who = ServerSource->ServerUser;
		}
		else
		{
			/* It is important that we don't close the link here, unknown prefix can occur
			 * due to various race conditions such as the KILL message for a user somehow
			 * crossing the users QUIT further upstream from the server. Thanks jilles!
			 */

			if ((prefix.length() == UUID_LENGTH-1) && (isdigit(prefix[0])) &&
				((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE")))
			{
				/* Special case, we cannot drop these commands as they've been committed already on a
				 * part of the network by the time we receive them, so in this scenario pretend the
				 * command came from a server to avoid desync.
				 */

				who = ServerInstance->FindUUID(prefix.substr(0, 3));
				if (!who)
					who = this->MyRoot->ServerUser;
			}
			else
			{
				ServerInstance->Logs->Log("m_spanningtree", DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.",
					command.c_str(), prefix.c_str());
				return;
			}
		}
	}

	// Make sure prefix is still good
	direction = who->server;
	prefix = who->uuid;

	/*
	 * Check for fake direction here, and drop any instances that are found.
	 * What is fake direction? Imagine the following server setup:
	 *    0AA <-> 0AB <-> 0AC
	 * Fake direction would be 0AC sending a message to 0AB claiming to be from
	 * 0AA, or something similar. Basically, a message taking a path that *cannot*
	 * be correct.
	 *
	 * When would this be seen?
	 * Well, hopefully never. It could be caused by race conditions, bugs, or
	 * "miscreant" servers, though, so let's check anyway. -- w
	 *
	 * We also check here for totally invalid prefixes (prefixes that are neither
	 * a valid SID or a valid UUID, so that invalid UUID or SID never makes it
	 * to the higher level functions. -- B
	 */
	TreeServer* route_back_again = Utils->BestRouteTo(direction);
	if ((!route_back_again) || (route_back_again->GetSocket() != this))
	{
		if (route_back_again)
			ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Protocol violation: Fake direction '%s' from connection '%s'",
				prefix.c_str(),linkID.c_str());
		return;
	}

	/*
	 * First up, check for any malformed commands (e.g. MODE without a timestamp)
	 * and rewrite commands where necessary (SVSMODE -> MODE for services). -- w
	 */
	if (command == "SVSMODE") // This isn't in an "else if" so we still force FMODE for changes on channels.
		command = "MODE";

	// TODO move all this into Commands
	if (command == "MAP")
	{
		Utils->Creator->HandleMap(params, who);
	}
	else if (command == "SERVER")
	{
		this->RemoteServer(prefix,params);
	}
	else if (command == "ERROR")
	{
		this->Error(params);
	}
	else if (command == "AWAY")
	{
		this->Away(prefix,params);
	}
	else if (command == "PING")
	{
		this->LocalPing(prefix,params);
	}
	else if (command == "PONG")
	{
		TreeServer *s = Utils->FindServer(prefix);
		if (s && s->bursting)
		{
			ServerInstance->SNO->WriteGlobalSno('l',"Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", prefix.c_str());
			s->FinishBurst();
		}
		this->LocalPong(prefix,params);
	}
	else if (command == "VERSION")
	{
		this->ServerVersion(prefix,params);
	}
	else if (command == "ADDLINE")
	{
		this->AddLine(prefix,params);
	}
	else if (command == "DELLINE")
	{
		this->DelLine(prefix,params);
	}
	else if (command == "SAVE")
	{
		this->ForceNick(prefix,params);
	}
	else if (command == "OPERQUIT")
	{
		this->OperQuit(prefix,params);
	}
	else if (command == "IDLE")
	{
		this->Whois(prefix,params);
	}
	else if (command == "PUSH")
	{
		this->Push(prefix,params);
	}
	else if (command == "SQUIT")
	{
		if (params.size() == 2)
		{
			this->Squit(Utils->FindServer(params[0]),params[1]);
		}
	}
	else if (command == "SNONOTICE")
	{
		if (params.size() >= 2)
		{
			ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + who->nick + ": "+ params[1]);
			params[1] = ":" + params[1];
			Utils->DoOneToAllButSender(prefix, command, params, prefix);
		}
	}
	else if (command == "BURST")
	{
		// Set prefix server as bursting
		TreeServer* ServerSource = Utils->FindServer(prefix);
		if (!ServerSource)
		{
			ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got BURST from a non-server(?): %s", prefix.c_str());
			return;
		}

		ServerSource->bursting = true;
		Utils->DoOneToAllButSender(prefix, command, params, prefix);
	}
	else if (command == "ENDBURST")
	{
		TreeServer* ServerSource = Utils->FindServer(prefix);
		if (!ServerSource)
		{
			ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got ENDBURST from a non-server(?): %s", prefix.c_str());
			return;
		}

		ServerSource->FinishBurst();
		Utils->DoOneToAllButSender(prefix, command, params, prefix);
	}
	else if (command == "ENCAP")
	{
		this->Encap(who, params);
	}
	else if (command == "NICK")
	{
		if (params.size() != 2)
		{
			SendError("Protocol violation: Wrong number of parameters for NICK message");
			return;
		}

		if (IS_SERVER(who))
		{
			SendError("Protocol violation: Server changing nick");
			return;
		}

		if ((isdigit(params[0][0])) && (params[0] != who->uuid))
		{
			SendError("Protocol violation: User changing nick to an invalid UID - " + params[0]);
			return;
		}

		/* Update timestamp on user when they change nicks */
		who->age = atoi(params[1].c_str());

		/*
		 * On nick messages, check that the nick doesnt already exist here.
		 * If it does, perform collision logic.
		 */
		User* x = ServerInstance->FindNickOnly(params[0]);
		if ((x) && (x != who))
		{
			int collideret = 0;
			/* x is local, who is remote */
			collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid);
			if (collideret != 1)
			{
				/*
				 * Remote client lost, or both lost, parsing or passing on this
				 * nickchange would be pointless, as the incoming client's server will
				 * soon recieve SVSNICK to change its nick to its UID. :) -- w00t
				 */
				return;
			}
		}
		who->ForceNickChange(params[0].c_str());
		Utils->RouteCommand(route_back_again, command, params, who);
	}
	else
	{
		Command* cmd = ServerInstance->Parser->GetHandler(command);

		if (!cmd)
		{
			irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
			ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised S2S command :%s %s %s",
				who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
			SendError("Unrecognised command '" + command + "' -- possibly loaded mismatched modules");
			return;
		}

		if (params.size() < cmd->min_params)
		{
			irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
			ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Insufficient parameters for S2S command :%s %s %s",
				who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
			SendError("Insufficient parameters for command '" + command + "'");
			return;
		}

		if ((!params.empty()) && (params.back().empty()) && (!cmd->allow_empty_last_param))
		{
			// the last param is empty and the command handler doesn't allow that, check if there will be enough params if we drop the last
			if (params.size()-1 < cmd->min_params)
				return;
			params.pop_back();
		}

		CmdResult res = cmd->Handle(params, who);

		if (res == CMD_INVALID)
		{
			irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
			ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Error handling S2S command :%s %s %s",
				who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
			SendError("Error handling '" + command + "' -- possibly loaded mismatched modules");
		}
		else if (res == CMD_SUCCESS)
			Utils->RouteCommand(route_back_again, command, params, who);
	}
}