/*
 * Module_Firewall.c
 *
 *  Created on: 2022/12/9
 *      Author: folus
 */
#include	"yuarel.h"
#include	"define.h"

enum
{
	TMR_IDX_CONFIG,
	TMR_IDX_CNT
};

#define is_error(ptr) 					((unsigned long)ptr > (unsigned long)-4000L)
#define ARRAY_SIZE(A)					(sizeof(A) / sizeof(A[0]))
#define PASS							1
#define FAIL							-1
#define YES								1
#define NO								0
#define ON								1

#define DEBUG_INFO(format, args...) StoreLogMsg("[%s:%d][%s][Info] "format, (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)), __LINE__, __FUNCTION__, ##args)
#define DEBUG_WARN(format, args...) StoreLogMsg("[%s:%d][%s][Warn] "format, (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)), __LINE__, __FUNCTION__, ##args)
#define DEBUG_ERROR(format, args...) StoreLogMsg("[%s:%d][%s][Error] "format, (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)), __LINE__, __FUNCTION__, ##args)

struct SysConfigAndInfo			*ShmSysConfigAndInfo;
struct StatusCodeData 			*ShmStatusCodeData;

struct timespec					tmr[TMR_IDX_CNT];
uint8_t 						natInterface = 0;

struct FirewallPresentSetting
{
	unsigned char			isEnalbleFirewall;					// 0: Disable	1: Enable
	unsigned char			FirewallAcceptAddr[10][128];		// Max accepted server address is 10
}firewallPresentInfo;

/**
 *
 * @param fmt
 * @return
 */
int StoreLogMsg(const char *fmt, ...)
{
	char Buf[4096+256];
	char buffer[4096];
	time_t CurrentTime;
	struct tm *tm;
	va_list args;

	va_start(args, fmt);
	int rc = vsnprintf(buffer, sizeof(buffer), fmt, args);
	va_end(args);

	memset(Buf,0,sizeof(Buf));
	CurrentTime = time(NULL);
	tm=localtime(&CurrentTime);
	sprintf(Buf,"echo -n \"[%04d.%02d.%02d %02d:%02d:%02d]%s\" >> /Storage/SystemLog/[%04d.%02d]Firewall_SystemLog",
			tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec,
			buffer,
			tm->tm_year+1900,tm->tm_mon+1);
#ifdef SystemLogMessage
	system(Buf);
#endif

#ifdef ConsloePrintLog
	printf("[%04d.%02d.%02d %02d:%02d:%02d] - %s", tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec, buffer);
#endif

	return rc;
}

/**
 *
 * @return
 */
int InitShareMemory()
{
	int result = PASS;
	int MeterSMId;

	//creat ShmSysConfigAndInfo
	if ((MeterSMId = shmget(ShmSysConfigAndInfoKey, sizeof(struct SysConfigAndInfo),  0777)) < 0)
    {
		DEBUG_ERROR("shmget ShmSysConfigAndInfo NG\n");
		result = FAIL;
	}
    else if ((ShmSysConfigAndInfo = shmat(MeterSMId, NULL, 0)) == (void *) -1)
    {
    	DEBUG_ERROR("shmat ShmSysConfigAndInfo NG\n");
    	result = FAIL;
   	 }
    else
    {}

   	 //creat ShmStatusCodeData
   	 if ((MeterSMId = shmget(ShmStatusCodeKey, sizeof(struct StatusCodeData),  0777)) < 0)
    {
   		DEBUG_ERROR("shmget ShmStatusCodeData NG\n");
   		result = FAIL;
	}
    else if ((ShmStatusCodeData = shmat(MeterSMId, NULL, 0)) == (void *) -1)
    {
    	DEBUG_ERROR("shmat ShmStatusCodeData NG\n");
    	result = FAIL;
   	}
    else
    {}

    return result;
}

/**
 *
 * @param cmd
 * @return
 */
int runShellCmd(const char*cmd)
{
	int result = FAIL;
	char buf[256];
	FILE *fp;

	fp = popen(cmd, "r");
	if(fp != NULL)
	{
		while(fgets(buf, sizeof(buf), fp) != NULL)
		{
			DEBUG_INFO("%s\n", buf);
		}

		result = PASS;
	}
	pclose(fp);

	return result;
}

/**
 *
 * @param timer
 */
void refreshStartTimer(struct timespec *timer)
{
	clock_gettime(CLOCK_MONOTONIC, timer);
}

/**
 *
 * @param timer
 * @return
 */
int getDiffSecNow(struct timespec timer)
{
	struct timespec timerNow;

	clock_gettime(CLOCK_MONOTONIC, &timerNow);

	return (int)((((unsigned long)(timerNow.tv_sec - timer.tv_sec) * 1000) + ((unsigned long)((timerNow.tv_nsec / 1000000) - (timer.tv_nsec / 1000000))))/1000);
}

/**
 *
 * @return
 */
unsigned int isKernelSupportNAT()
{
	unsigned int result = NO;
	unsigned int version = 0;
	FILE *fp;
	char cmd[256];
	char buf[512];

	// Get IP address & net mask
	strcpy(cmd, "uname -v");
	fp = popen(cmd, "r");
	if(fp != NULL)
	{
		if(fgets(buf, sizeof(buf), fp) != NULL)
		{
			sscanf(buf, "#%d", &version);
			//DEBUG_INFO("Kernel version: %d\n", version);

			if(version >= 30)
				result = YES;
		}
	}
	pclose(fp);

	return result;
}

/**
 *
 * @return
 */
int firewallConfig()
{
	struct yuarel url;
	char urlBuf[512] = {0};
	char cmdBuf[512] = {0};

	runShellCmd("/sbin/iptables -F");

	if(ShmSysConfigAndInfo->SysConfig.isEnalbleFirewall)
	{
		// Other address mandatory configure
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d 192.168.0.0/255.255.0.0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d 8.8.8.8/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d 180.76.76.76/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d evsocket.phihong.com.tw/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d ocpp.phihong.com.tw/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d ftp.phihong.com.tw/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d time.windows.com/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d cn.ntp.org.cn/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d tock.stdtime.gov.tw/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d 0.europe.pool.ntp.org/255.255.255.255 -j ACCEPT");
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d curl.haxx.se/255.255.255.255 -j ACCEPT");

		runShellCmd("/sbin/iptables -A INPUT -p all -s 192.168.0.0/255.255.0.0 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s 8.8.8.8/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s 180.76.76.76/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s evsocket.phihong.com.tw/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s ocpp.phihong.com.tw/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s ftp.phihong.com.tw/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s time.windows.com/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s cn.ntp.org.cn/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s tock.stdtime.gov.tw/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s 0.europe.pool.ntp.org/255.255.255.255 -d any/0 -j ACCEPT");
		runShellCmd("/sbin/iptables -A INPUT -p all -s curl.haxx.se/255.255.255.255 -d any/0 -j ACCEPT");

		// Back end server URL parse
		if((ShmSysConfigAndInfo->SysConfig.OcppServerURL != NULL) && (strcmp((const char *)ShmSysConfigAndInfo->SysConfig.OcppServerURL,"") != 0) )
		{
			memcpy(urlBuf, ShmSysConfigAndInfo->SysConfig.OcppServerURL, ARRAY_SIZE(ShmSysConfigAndInfo->SysConfig.OcppServerURL));
			if(yuarel_parse(&url, urlBuf) != -1)
			{
				DEBUG_INFO("Add %s to allow rule.\n", url.host);
				sprintf(cmdBuf, "/sbin/iptables -A OUTPUT -p all -s any/0 -d %s/255.255.255.255 -j ACCEPT", url.host);
				runShellCmd(cmdBuf);
				sprintf(cmdBuf, "/sbin/iptables -A INPUT -p all -s %s/255.255.255.255 -d any/0 -j ACCEPT", url.host);
				runShellCmd(cmdBuf);
			}
		}

		if((ShmSysConfigAndInfo->SysConfig.MaintainServerURL != NULL) && (strcmp((const char *)ShmSysConfigAndInfo->SysConfig.MaintainServerURL,"") != 0) )
		{
			memcpy(urlBuf, ShmSysConfigAndInfo->SysConfig.MaintainServerURL, ARRAY_SIZE(ShmSysConfigAndInfo->SysConfig.MaintainServerURL));
			if(yuarel_parse(&url, urlBuf) != -1)
			{
				DEBUG_INFO("Add %s to allow rule.\n", url.host);
				sprintf(cmdBuf, "/sbin/iptables -A OUTPUT -p all -s any/0 -d %s/255.255.255.255 -j ACCEPT", url.host);
				runShellCmd(cmdBuf);
				sprintf(cmdBuf, "/sbin/iptables -A INPUT -p all -s %s/255.255.255.255 -d any/0 -j ACCEPT", url.host);
				runShellCmd(cmdBuf);
			}
		}

		// User custom configure address
		for(uint8_t idx=0;idx<ARRAY_SIZE(ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr);idx++)
		{
			if(strlen((char*)ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr[idx]) > 0)
			{
				DEBUG_INFO("Add %s to allow rule.\n", ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr[idx]);
				sprintf(cmdBuf, "/sbin/iptables -A OUTPUT -p all -s any/0 -d %s/255.255.255.255 -j ACCEPT", ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr[idx]);
				runShellCmd(cmdBuf);
				sprintf(cmdBuf, "/sbin/iptables -A INPUT -p all -s %s/255.255.255.255 -d any/0 -j ACCEPT", ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr[idx]);
				runShellCmd(cmdBuf);
			}
		}

		// Deny any address
		runShellCmd("/sbin/iptables -A OUTPUT -p all -s any/0 -d any/0 -j DROP");
		runShellCmd("/sbin/iptables -A INPUT -p all -s any/0 -d any/0 -j DROP");

		// List present rule
		runShellCmd("/sbin/iptables -L -n");
	}

	return PASS;
}

/**
 *
 * @return
 */
int isChangeConfiguration()
{
	int result = NO;

	if((firewallPresentInfo.isEnalbleFirewall != ShmSysConfigAndInfo->SysConfig.isEnalbleFirewall) ||
		(memcmp(firewallPresentInfo.FirewallAcceptAddr, ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr, ARRAY_SIZE(firewallPresentInfo.FirewallAcceptAddr)*ARRAY_SIZE(firewallPresentInfo.FirewallAcceptAddr[0])) != 0))
	{

		firewallPresentInfo.isEnalbleFirewall = ShmSysConfigAndInfo->SysConfig.isEnalbleFirewall;
		memcpy(firewallPresentInfo.FirewallAcceptAddr, ShmSysConfigAndInfo->SysConfig.FirewallAcceptAddr, ARRAY_SIZE(firewallPresentInfo.FirewallAcceptAddr)*ARRAY_SIZE(firewallPresentInfo.FirewallAcceptAddr[0]));

		result = YES;
	}
	return result;
}


/**
 *
 * @return
 */
int main(void)
{
	//==========================================
	//Initialization share memory
	//==========================================
	if(InitShareMemory() == FAIL)
	{
		DEBUG_ERROR("InitShareMemory NG\n");

		if(ShmStatusCodeData!=NULL)
		{
			ShmStatusCodeData->AlarmCode.AlarmEvents.bits.FailToCreateShareMemory=1;
		}
		sleep(5);

		return 0;
	}
	DEBUG_INFO("Module_Firewall version: V0.01\n");
	DEBUG_INFO("Module_Firewall initialized...\n");

	for(;;)
	{
		if(isChangeConfiguration())
		{
			DEBUG_INFO("Firewall rule changed.\n");
			sleep(3);
			firewallConfig();
		}

		usleep(100000);
	}

	return -1;
}