/*
 * PtokaX - hub server for Direct Connect peer to peer network.

 * Copyright (C) 2004-2022  Petr Kozelka, PPK at PtokaX dot org

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.

 * 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 "stdinc.h"
//---------------------------------------------------------------------------
#include "GlobalDataQueue.h"
#include "hashBanManager.h"
#include "LanguageManager.h"
#include "ServerManager.h"
#include "SettingManager.h"
#include "UdpDebug.h"
#include "User.h"
#include "utility.h"
//---------------------------------------------------------------------------
#include "DeFlood.h"
//---------------------------------------------------------------------------

bool DeFloodCheckForFlood(User * pUser, const uint8_t ui8DefloodType, const int16_t ui16Action,
                          uint16_t &ui16Count, uint64_t &ui64LastOkTick,
                          const int16_t ui16DefloodCount, const uint32_t ui32DefloodTime, const char * sOtherNick/* = NULL*/)
{
	if (ui16Count == 0)
	{
		ui64LastOkTick = ServerManager::m_ui64ActualTick;
	}
	else if (ui16Count == ui16DefloodCount)
	{
		if ((ui64LastOkTick + ui32DefloodTime) > ServerManager::m_ui64ActualTick)
		{
			DeFloodDoAction(pUser, ui8DefloodType, ui16Action, ui16Count, sOtherNick);
			return true;
		}
		else
		{
			ui64LastOkTick = ServerManager::m_ui64ActualTick;
			ui16Count = 0;
		}
	}
	else if (ui16Count > ui16DefloodCount)
	{
		if ((ui64LastOkTick + ui32DefloodTime) > ServerManager::m_ui64ActualTick)
		{
			if (ui16Action == 2 && ui16Count == (ui16DefloodCount * 2))
			{
				pUser->m_ui32DefloodWarnings++;

				if (DeFloodCheckForWarn(pUser, ui8DefloodType, sOtherNick) == true)
				{
					return true;
				}
				ui16Count -= ui16DefloodCount;
			}
			ui16Count++;
			return true;
		}
		else
		{
			ui64LastOkTick = ServerManager::m_ui64ActualTick;
			ui16Count = 0;
		}
	}
	else if ((ui64LastOkTick + ui32DefloodTime) <= ServerManager::m_ui64ActualTick)
	{
		ui64LastOkTick = ServerManager::m_ui64ActualTick;
		ui16Count = 0;
	}

	ui16Count++;
	return false;
}
//---------------------------------------------------------------------------

bool DeFloodCheckForSameFlood(User * pUser, const uint8_t ui8DefloodType, const int16_t ui16Action,
                              uint16_t &ui16Count, const uint64_t &ui64LastOkTick,
                              const int16_t ui16DefloodCount, const uint32_t ui32DefloodTime,
                              const char * sNewData, const size_t ui32NewDataLen,
                              const char * sOldData, const uint16_t ui16OldDataLen, bool &bNewData, const char * sOtherNick/* = NULL*/)
{
	if (ui16OldDataLen == 0 && ui32NewDataLen == 0)
		return false;
	if ((uint32_t)ui16OldDataLen == ui32NewDataLen && (ServerManager::m_ui64ActualTick >= ui64LastOkTick &&
	        (ui64LastOkTick + ui32DefloodTime) > ServerManager::m_ui64ActualTick) &&
	        memcmp(sNewData, sOldData, ui16OldDataLen) == 0)
	{
		if (ui16Count < ui16DefloodCount)
		{
			ui16Count++;

			return false;
		}
		else if (ui16Count == ui16DefloodCount)
		{
			DeFloodDoAction(pUser, ui8DefloodType, ui16Action, ui16Count, sOtherNick);
			if (pUser->m_ui8State < User::STATE_CLOSING)
			{
				ui16Count++;
			}

			return true;
		}
		else
		{
			if (ui16Action == 2 && ui16Count == (ui16DefloodCount * 2))
			{
				pUser->m_ui32DefloodWarnings++;

				if (DeFloodCheckForWarn(pUser, ui8DefloodType, sOtherNick) == true)
				{
					return true;
				}
				ui16Count -= ui16DefloodCount;
			}
			ui16Count++;

			return true;
		}
	}
	else
	{
		bNewData = true;
		return false;
	}
}
//---------------------------------------------------------------------------

bool DeFloodCheckForDataFlood(User * pUser, const uint8_t ui8DefloodType, const int16_t ui16Action,
                              uint32_t &ui32Count, uint64_t &ui64LastOkTick,
                              const int16_t ui16DefloodCount, const uint32_t ui32DefloodTime)
{
	if ((uint16_t)(ui32Count / 1024) >= ui16DefloodCount)
	{
		if ((ui64LastOkTick + ui32DefloodTime) > ServerManager::m_ui64ActualTick)
		{
			if ((pUser->m_ui32BoolBits & User::BIT_RECV_FLOODER) == User::BIT_RECV_FLOODER)
			{
				return true;
			}
			pUser->m_ui32BoolBits |= User::BIT_RECV_FLOODER;
			uint16_t ui16Count = (uint16_t)ui32Count;
			DeFloodDoAction(pUser, ui8DefloodType, ui16Action, ui16Count, NULL);
			return true;
		}
		else
		{
			pUser->m_ui32BoolBits &= ~User::BIT_RECV_FLOODER;
			ui64LastOkTick = ServerManager::m_ui64ActualTick;
			ui32Count = 0;
			return false;
		}
	}
	else if ((ui64LastOkTick + ui32DefloodTime) <= ServerManager::m_ui64ActualTick)
	{
		pUser->m_ui32BoolBits &= ~User::BIT_RECV_FLOODER;
		ui64LastOkTick = ServerManager::m_ui64ActualTick;
		ui32Count = 0;
		return false;
	}

	return false;
}
//---------------------------------------------------------------------------

void DeFloodDoAction(User * pUser, const uint8_t ui8DefloodType, const int16_t ui16Action, uint16_t &ui16Count, const char * sOtherNick)
{
	switch (ui16Action)
	{
	case 1:
	{
		pUser->SendFormatCheckPM("DeFloodDoAction1", sOtherNick, true, "<%s> %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], DeFloodGetMessage(ui8DefloodType, 0));

		if (ui8DefloodType != DEFLOOD_MAX_DOWN)
		{
			ui16Count++;
		}
		return;
	}
	case 2:
		pUser->m_ui32DefloodWarnings++;

		if (DeFloodCheckForWarn(pUser, ui8DefloodType, sOtherNick) == false && ui8DefloodType != DEFLOOD_MAX_DOWN)
		{
			ui16Count++;
		}

		return;
	case 3:
	{
		pUser->SendFormatCheckPM("DeFloodDoAction2", sOtherNick, false, "<%s> %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], DeFloodGetMessage(ui8DefloodType, 0));

		DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_DISCONNECTED]);

		pUser->Close();
		return;
	}
	case 4:
	{
		BanManager::m_Ptr->TempBan(pUser, DeFloodGetMessage(ui8DefloodType, 1), NULL, 0, 0, false);

		pUser->SendFormatCheckPM("DeFloodDoAction3", sOtherNick, false, "<%s> %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC],  LanguageManager::m_Ptr->m_sTexts[LAN_YOU_BEING_KICKED_BCS], DeFloodGetMessage(ui8DefloodType, 1));

		DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_KICKED]);

		pUser->Close();
		return;
	}
	case 5:
	{
		BanManager::m_Ptr->TempBan(pUser, DeFloodGetMessage(ui8DefloodType, 1), NULL, SettingManager::m_Ptr->m_i16Shorts[SETSHORT_DEFLOOD_TEMP_BAN_TIME], 0, false);

		pUser->SendFormatCheckPM("DeFloodDoAction4", sOtherNick, false, "<%s> %s: %s %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_HAD_BEEN_TEMP_BANNED_TO], formatTime(SettingManager::m_Ptr->m_i16Shorts[SETSHORT_DEFLOOD_TEMP_BAN_TIME]),
		                         LanguageManager::m_Ptr->m_sTexts[LAN_BECAUSE_LWR], DeFloodGetMessage(ui8DefloodType, 1));

		DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_TEMPORARY_BANNED]);

		pUser->Close();
		return;
	}
	case 6:
	{
		BanManager::m_Ptr->Ban(pUser, DeFloodGetMessage(ui8DefloodType, 1), NULL, false);

		pUser->SendFormatCheckPM("DeFloodDoAction5", sOtherNick, false, "<%s> %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_ARE_BEING_BANNED_BECAUSE], DeFloodGetMessage(ui8DefloodType, 1));

		DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_BANNED]);

		pUser->Close();
		return;
	}
	}
}
//---------------------------------------------------------------------------

bool DeFloodCheckForWarn(User * pUser, const uint8_t ui8DefloodType, const char * sOtherNick)
{
	if (pUser->m_ui32DefloodWarnings < (uint32_t)SettingManager::m_Ptr->m_i16Shorts[SETSHORT_DEFLOOD_WARNING_COUNT])
	{
		pUser->SendFormat("DeFloodCheckForWarn", true, "<%s> %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], DeFloodGetMessage(ui8DefloodType, 0));
		return false;
	}
	else
	{
		switch (SettingManager::m_Ptr->m_i16Shorts[SETSHORT_DEFLOOD_WARNING_ACTION])
		{
		case 0:
		{
			pUser->SendFormatCheckPM("DeFloodCheckForWarn1", sOtherNick, false, "<%s> %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_ARE_BEING_DISCONNECTED_BECAUSE], DeFloodGetMessage(ui8DefloodType, 1));

			DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_DISCONNECTED]);

			break;
		}
		case 1:
		{
			BanManager::m_Ptr->TempBan(pUser, DeFloodGetMessage(ui8DefloodType, 1), NULL, 0, 0, false);

			pUser->SendFormatCheckPM("DeFloodCheckForWarn2", sOtherNick, false, "<%s> %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_BEING_KICKED_BCS], DeFloodGetMessage(ui8DefloodType, 1));

			DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_KICKED]);

			break;
		}
		case 2:
		{
			BanManager::m_Ptr->TempBan(pUser, DeFloodGetMessage(ui8DefloodType, 1), NULL, SettingManager::m_Ptr->m_i16Shorts[SETSHORT_DEFLOOD_TEMP_BAN_TIME], 0, false);

			pUser->SendFormatCheckPM("DeFloodCheckForWarn3", sOtherNick, false, "<%s> %s: %s %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_HAD_BEEN_TEMP_BANNED_TO], formatTime(SettingManager::m_Ptr->m_i16Shorts[SETSHORT_DEFLOOD_TEMP_BAN_TIME]),
			                         LanguageManager::m_Ptr->m_sTexts[LAN_BECAUSE_LWR], DeFloodGetMessage(ui8DefloodType, 1));

			DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_TEMPORARY_BANNED]);

			break;
		}
		case 3:
		{
			BanManager::m_Ptr->Ban(pUser, DeFloodGetMessage(ui8DefloodType, 1), NULL, false);

			pUser->SendFormatCheckPM("DeFloodCheckForWarn4", sOtherNick, false, "<%s> %s: %s!|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_ARE_BEING_BANNED_BECAUSE], DeFloodGetMessage(ui8DefloodType, 1));

			DeFloodReport(pUser, ui8DefloodType, LanguageManager::m_Ptr->m_sTexts[LAN_WAS_BANNED]);

			break;
		}
		}

		pUser->Close();
		return true;
	}
}
//---------------------------------------------------------------------------

const char * DeFloodGetMessage(const uint8_t ui8DefloodType, const uint8_t ui8MsgId)
{
	switch (ui8DefloodType)
	{
	case DEFLOOD_GETNICKLIST:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_GetNickList];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_GetNickList_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_GetNickList_FLOODER];
		}
		break;
	case DEFLOOD_MYINFO:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_MyINFO];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_MyINFO_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_MyINFO_FLOODER];
		}
		break;
	case DEFLOOD_SEARCH:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_SEARCHES];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SEARCH_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SEARCH_FLOODER];
		}
		break;
	case DEFLOOD_CHAT:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_CHAT];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_CHAT_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_CHAT_FLOODER];
		}
		break;
	case DEFLOOD_PM:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_PM];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PM_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PM_FLOODER];
		}
		break;
	case DEFLOOD_SAME_SEARCH:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_SAME_SEARCHES];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_SEARCH_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_SEARCH_FLOODER];
		}
		break;
	case DEFLOOD_SAME_PM:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_SAME_PM];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_PM_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_PM_FLOODER];
		}
		break;
	case DEFLOOD_SAME_CHAT:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_SAME_CHAT];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_CHAT_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_CHAT_FLOODER];
		}
		break;
	case DEFLOOD_SAME_MULTI_PM:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_SAME_MULTI_PM];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_MULTI_PM_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_MULTI_PM_FLOODER];
		}
		break;
	case DEFLOOD_SAME_MULTI_CHAT:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_SAME_MULTI_CHAT];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_MULTI_CHAT_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SAME_MULTI_CHAT_FLOODER];
		}
		break;
	case DEFLOOD_CTM:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_CTM];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_CTM_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_CTM_FLOODER];
		}
		break;
	case DEFLOOD_RCTM:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_RCTM];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_RCTM_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_RCTM_FLOODER];
		}
		break;
	case DEFLOOD_SR:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_SR];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SR_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_SR_FLOODER];
		}
		break;
	case DEFLOOD_MAX_DOWN:
		switch (ui8MsgId)
		{
		case 0:
			return LanguageManager::m_Ptr->m_sTexts[LAN_PLS_DONT_FLOOD_WITH_DATA];
		case 1:
			return LanguageManager::m_Ptr->m_sTexts[LAN_DATA_FLOODING];
		case 2:
			return LanguageManager::m_Ptr->m_sTexts[LAN_DATA_FLOODER];
		}
		break;
	case INTERVAL_CHAT:
		return LanguageManager::m_Ptr->m_sTexts[LAN_SECONDS_BEFORE_NEXT_CHAT_MSG];
	case INTERVAL_PM:
		return LanguageManager::m_Ptr->m_sTexts[LAN_SECONDS_BEFORE_NEXT_PM];
	case INTERVAL_SEARCH:
		return LanguageManager::m_Ptr->m_sTexts[LAN_SECONDS_BEFORE_NEXT_SEARCH];
	}
	return "";
}
//---------------------------------------------------------------------------

void DeFloodReport(User * pUser, const uint8_t ui8DefloodType, const char *sAction)
{
	if (SettingManager::m_Ptr->m_bBools[SETBOOL_DEFLOOD_REPORT] == true)
	{
		GlobalDataQueue::m_Ptr->StatusMessageFormat("DeFloodReport", "<%s> *** %s %s %s %s %s.|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], DeFloodGetMessage(ui8DefloodType, 2), pUser->m_sNick, LanguageManager::m_Ptr->m_sTexts[LAN_WITH_IP], pUser->m_sIP, sAction);
	}

	UdpDebug::m_Ptr->BroadcastFormat("[SYS] Flood type %hu from %s (%s) - user closed.", (uint16_t)ui8DefloodType, pUser->m_sNick, pUser->m_sIP);
}
//---------------------------------------------------------------------------

bool DeFloodCheckInterval(User * pUser, const uint8_t ui8DefloodType, uint16_t &ui16Count, uint64_t &ui64LastOkTick, const int16_t ui16DefloodCount, const uint32_t ui32DefloodTime, const char * sOtherNick/* = NULL*/)
{
	if (ui16Count == 0)
	{
		ui64LastOkTick = ServerManager::m_ui64ActualTick;
	}
	else if (ui16Count >= ui16DefloodCount)
	{
		if ((ui64LastOkTick + ui32DefloodTime) > ServerManager::m_ui64ActualTick)
		{
			ui16Count++;

			pUser->SendFormatCheckPM("DeFloodCheckInterval", sOtherNick, true, "<%s> %s %" PRIu64 " %s.|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_PLEASE_WAIT], (ui64LastOkTick + ui32DefloodTime) - ServerManager::m_ui64ActualTick, DeFloodGetMessage(ui8DefloodType, 0));

			return true;
		}
		else
		{
			ui64LastOkTick = ServerManager::m_ui64ActualTick;
			ui16Count = 0;
		}
	}
	else if ((ui64LastOkTick + ui32DefloodTime) <= ServerManager::m_ui64ActualTick)
	{
		ui64LastOkTick = ServerManager::m_ui64ActualTick;
		ui16Count = 0;
	}

	ui16Count++;
	return false;
}
//---------------------------------------------------------------------------

V1042 This file is marked with copyleft license, which requires you to open the derived source code.