/***************************************************************************
 *   Copyright (C) 2004 by EVER Sp. z o.o.                                 *
 *                                                                         *
 *   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.             *
 ***************************************************************************/

#include "netmonitor.h"

extern "C" {
#include <pthread.h>
#include <signal.h>
}
#include <iostream>
#include <cstdlib>

using namespace std;

#define SOCKET_ERROR	(-1)

// maximum number of nms addresses
#define MAX_NMS	10

CLog		evtlog;
CConf		*pCfgMain=NULL;
pthread_t	pthMonitor;
sflow_ctl	afctl;			// main application control struct
char 		nmslist[MAX_NMS][256];
struct sockaddr_in sai;

int shutdown_system(int imode)
{
#ifdef _DEBUGMODE
	wall_msg_cust("SHUTDOWN HAS BEEN STARTED!!!!\n", NETCLN_VERSION_STRING);
#else
	switch(imode)
	{
	default:
	case sm_normal:
		{
			wall_msg_cust(TXT_INF_SHUTDOWN_NORMAL, NETCLN_VERSION_STRING);
			sleep(5);
			evtlog.new_msg(msg_info, TXT_INF_SHUTDOWN_NORMAL);
			
			char *s = pCfgMain->getcfgitemvalue("shutdown_normal_command");
			if (s==NULL || s=="") {
				if (system("shutdown -h now")==-1) {
					evtlog.new_msg(msg_error, TXT_ERR_SHUTDOWN_NORMAL);
				}
			} else {
				if (system(s)==-1) {
					evtlog.new_msg(msg_error, TXT_ERR_SHUTDOWN_NORMAL);
				}
			}
			break;
		}
			
	case sm_reboot:
		{
			wall_msg_cust(TXT_INF_SHUTDOWN_REBOOT, NETCLN_VERSION_STRING);
			sleep(5);
			evtlog.new_msg(msg_info, TXT_INF_SHUTDOWN_REBOOT);
			
			char *s = pCfgMain->getcfgitemvalue("reboot_command");
			if (s==NULL || s=="") {
				if (system("reboot")==-1) {
					evtlog.new_msg(msg_error, TXT_ERR_SHUTDOWN_REBOOT);
				}
			} else {
				if (system(s)==-1) {
					evtlog.new_msg(msg_error, TXT_ERR_SHUTDOWN_REBOOT);
				}
			}
			break;
		}
	}

	afctl.bMonitorActive=false;
	sleep(2);
	terminate(0);
#endif
	return 1;
}

// process command received from the authorized management station
int processCommand(int sock, struct sockaddr_in *psai, char *lpdata, int isize)
{
	char *data = lpdata;
	char szBuff1[4096] = "", szBuff2[512] = "", szBuff3[512] = "", 
		szTemp[256], *lpszTok;
	unsigned long ulCmd;
	int iret;

	char *magic = new char[64];
	if (!magic)
		return -1;
	else {
		sprintf(magic, APP_NET_MAGIC, APP_NET_VERSION_MAJOR, APP_NET_VERSION_MINOR);
		if (strncmp(lpdata, magic, strlen(magic))) {
			delete [] magic;
			return -1;
		} else
			data += strlen(magic);
		delete [] magic;
	}
	
	char *buffer = new char[2048];
	if (!buffer)
		return -1;
	
	sprintf(buffer, APP_NET_CMDFMT, APP_NET_MAGIC01, APP_NET_MAGIC02);
	if (strncmp(data, buffer, 4)) {
		delete [] buffer;
		return -1;
	} else
		data += 4;
	
	ulCmd = strtoul(data, NULL, 16);
	if (ulCmd)
		data += 12;

	switch(ulCmd)
	{
	case APP_NET_PING:
		{
			char szPingStr[] = APP_NET_PINGACK;
			int ret = sendto(sock, (const char*)szPingStr, strlen(szPingStr), 
				0, (struct sockaddr*)psai, sizeof(struct sockaddr_in));
			break;
		}
	
	case APP_NET_CMD_SHOWMESSAGE:
		{
			if (data!=NULL) {
				struct hostent *lphe;
				unsigned long ulAddr;
				
				ulAddr = inet_addr(inet_ntoa(sai.sin_addr));
				lphe = (struct hostent*)gethostbyaddr((char*)&ulAddr, 4, AF_INET);
				if (lphe!=NULL)
					sprintf(buffer, "\nMessage from \"%s\" host:\n \"%s\"", lphe->h_name, data);
				else
					sprintf(buffer, "\nMessage from \"%s\" host:\n \"%s\"", inet_ntoa(sai.sin_addr), data);
				
				if (!wall_msg_cust(buffer, NETCLN_VERSION_STRING)) {
					evtlog.new_msg(msg_error, TXT_ERR_FAILTOMESSAGE, errno);
				}
			}
			break;
		}

	case APP_NET_CMD_EXECUTE:
		{
			char *execute = new char[MAX_PATHBUFF];
			if (execute==NULL)
				break;
			char *lpTok = strtok(data, "\t");
			if (lpTok != NULL) {
				// command
				strcpy(execute, lpTok);
				if (system(execute)==-1) {
					evtlog.new_msg(msg_error, TXT_ERR_NETCOMMANDFAIL, execute);
				}
			}
			delete [] execute;
			break;
		}

	case APP_NET_CMD_DOWN:
		{
			shutdown_system(sm_normal);
			return 1;
		}
	
	case APP_NET_CMD_DOWNREBOOT:
		{
			shutdown_system(sm_reboot);
			return 1;
		}

	}
	return 0;
}

// check if message comes from the nms
int isSenderAuthorized(struct sockaddr_in *ps)
{
	struct hostent* lphe;
	struct in_addr ia;
	for (int i = 0; i < MAX_NMS; i++) {
		if (!strlen(nmslist[i]))
			return 0;
		
		if (isalpha(*nmslist[i])) {
			lphe = gethostbyname(nmslist[i]);
			if (lphe!=NULL)
				ia = *(struct in_addr *)*lphe->h_addr_list;
			else
				continue;
		} else {
			unsigned long ulAddr = inet_addr(nmslist[i]);
			lphe = gethostbyaddr((const char*)&ulAddr, 4, AF_INET);
			if (lphe!=NULL)
				ia = *(struct in_addr *)*lphe->h_addr_list;
			else
				ia.s_addr = ulAddr;
		}
		if (ps->sin_addr.s_addr == ia.s_addr) {
			return 1;
		}
	}
	return 0;
}

int receiveData(int sock, char *buff, int isize, int itimeout)
{
	int i = 0, nd = 0;
	fd_set frd;
	struct timeval tv;
	u_long ulfArg = 0;

	if (buff==NULL || isize<0 || itimeout<0)
		return -1;

	FD_ZERO(&frd);
	FD_SET(sock, &frd);
	nd = max(nd, sock);
	tv.tv_sec = itimeout;
	tv.tv_usec = 0;
	
	i = select(nd+1, &frd, NULL, NULL, &tv);
	if (i == SOCKET_ERROR)	// disconnection
		return -2;
	if (i == 0)				// timeout
		return 0;
	i = ioctl(sock, FIONREAD, &ulfArg);
	if (i == SOCKET_ERROR)
		return -3;
	if (ulfArg > (unsigned long)isize)
		ulfArg = (unsigned long)isize;
	memset(buff, 0, isize);
	socklen_t length = sizeof(sai);
	i = recvfrom(sock, buff, ulfArg, 0, (struct sockaddr*)&sai, &length);
	if (i != SOCKET_ERROR)
		return ulfArg;
	else
		return -4;
}

// main monitoring 
void netmonitor(void *lpv)
{
	int iret, s;// socket
	char dataBuff[512];
	struct sockaddr_in saiSrv;
	unsigned short usClnPort = 12321;
	
	memset(&sai, 0, sizeof(struct sockaddr_in));
	memset(&saiSrv, 0, sizeof(struct sockaddr_in));
	
	s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (s == -1) {
		evtlog.new_msg(msg_error, TXT_ERR_CREATESOCKET);
		return;
	}
	int yes = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)) < 0) {
		evtlog.new_msg(msg_error, TXT_ERR_CREATESOCKET);
        close(s);
        return;
    }

	saiSrv.sin_family = AF_INET;
	saiSrv.sin_addr.s_addr = INADDR_ANY;
	saiSrv.sin_port = htons(usClnPort);
	
	iret = bind(s, (struct sockaddr*)&saiSrv, sizeof(saiSrv));
	if (iret==SOCKET_ERROR) {
		evtlog.new_msg(msg_error, TXT_ERR_BINDFAIL);
		return;
	}
	
	afctl.iMonitorStatus = STATUS_ACTIVE;
	
	while(afctl.bMonitorActive) {
		bzero(dataBuff, sizeof(dataBuff));
		int ilen = receiveData(s, dataBuff, sizeof(dataBuff), 5);
		if (ilen > 0) {
			if (isSenderAuthorized(&sai)) {
				printf("Command passed for processing by authorized host [%s]\n", inet_ntoa(sai.sin_addr));
				processCommand(s, &sai, dataBuff, ilen);
			} else {
				evtlog.new_msg(msg_warning, "Command from unathorized host received (not processed)!");
			}
		} else {
		}
		// lets get milliseconds instead of microseconds
		usleep(INT_LOOP_INTERVAL*1000);
	}
	
	close(s);
	
	afctl.iMonitorStatus = STATUS_HASSTOPPED;
	
	return;
}

/* read main configuration */
int get_main_config()
{
	char *fp=get_config_filepath(APP_FILE_NETCLNCONFIG);
	if (fp==NULL) {
		error_report("%s (%s)", TXT_ERR_MEMALLOC, strerror(errno));
		return -1;
	}
	pCfgMain=new CConf;
	if (pCfgMain->init(fp)!=CONF_SUCCESS) {
		free(fp);
		evtlog.new_msg(msg_error, "%s (%s)", TXT_ERR_READAPPCONFIG, pCfgMain->geterrordesc());
		error_report("%s (%s)", TXT_ERR_READAPPCONFIG, pCfgMain->geterrordesc());
		if (pCfgMain!=NULL)
			delete pCfgMain;
		return -1;
	} else {
		free(fp);
		pCfgMain->parse_config();
	}
	
	// network management stations
	char szBuff[128] = "", *s;
	int inmscnt = 0;
	for (int i=0; i<MAX_NMS; i++) {
		sprintf(szBuff, "nms%d", i);
		s=pCfgMain->getcfgitemvalue((char *)szBuff);
		if (s!=NULL && strlen(s)>0) {
			strcpy(nmslist[inmscnt], s);
			inmscnt++;
		} else {
			continue;
		}
	}
	
	return 1;	// success
}

// startup routine - called from main
int service_start()
{
	unsigned long ulBuff;
	int icnt, iReturn = 0;	// buffer for return values
	pid_t actpid;			// action process pid
	
	signal(SIGSEGV, terminate);
	signal(SIGINT, terminate);
	signal(SIGQUIT, terminate);
	signal(SIGTERM, terminate);

	evtlog.open(NULL, APP_NETCLN);
	
	if (get_main_config()!=1) {
		goto __cleanup__;
	}

	evtlog.new_msg(msg_info, TXT_NETSERVICE_STARTING);
	
	/* start */
	afctl.bMonitorActive = true;
	afctl.iMonitorStatus = STATUS_INACTIVE;
	
	create_pid_file(APP_NETCLN);
	
	netmonitor(NULL);
	
	//stop_monitoring();

__cleanup__:
	
	if (pCfgMain!=NULL)
		delete pCfgMain;
	
	terminate(-1);
	
	return 0;
}

void stop_monitoring()
{
	const int timeout_sec = 10;
	
	rem_pid_file(APP_NETCLN);

	if (afctl.iMonitorStatus!=STATUS_INACTIVE) {
		/* wait for monitor */
		if (afctl.bMonitorActive)
			afctl.bMonitorActive=false;
		int icnt=timeout_sec;
		while(afctl.iMonitorStatus!=STATUS_HASSTOPPED) {
			sleep(1);
			if ((icnt--)<=0)
				break;
		}
	}
}

// handler for several configured signals
void terminate(int iCode)
{
	rem_pid_file(APP_NETCLN);
	
	if (iCode!=-1)
		stop_monitoring();
	
	if (iCode>-1) {
		evtlog.new_msg(msg_info, TXT_ERR_TERMINATION, iCode);
	}
	if (iCode>=-1) {
		evtlog.new_msg(msg_info, TXT_NETSERVICE_STOPPING);
		console_report("\n%s\n", TXT_NETSERVICE_STOPPING);
	}
	evtlog.close();
	if (iCode>-1) {
		exit(0);
	}
}
