/*-
 * Copyright (c) 1998-2008 DHIS, Dynamic Host Information System
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include "dhid.h"

extern struct conf_t *base;

int rport=DEF_ISPORT;
char pid_file[256];
char conf_file[256];


void cmd_fork(char *s) {

	int pid;
	int status;

	if(*s=='\0') return;
	switch(pid=fork()) {
	case(-1): syslog(LOG_INFO,"Unable to fork for %s",s); break;

	case(0): 
		system(s);
		exit(0);

	default: waitpid(pid,&status,0);
	}
}

void sig_usr2() {
	
	struct conf_t *cp;
	cp=base;
	while(cp!=NULL) {

	if(cp->stage==ON_STAGE) {
		offline_req_t msg;
		msg.hdr.opcode=OFFLINE_REQ;
		msg.hdr.hostid=cp->id;
		msg.sid=cp->sid;
		if(cp->cserver!=NULL)
		net_write_message((msg_t *)&msg,cp->cserver->addr,cp->cserver->port);
	}
	cp->sid=0;
	cp->stage=POLL_STAGE;
	cp->cserver=NULL;
	cp->timeout=60;
	{
                        char linecmd[512];
                        struct in_addr sa;
                        sa.s_addr=cp->laddr;
                        sprintf(linecmd,"%s %d %s %s",cp->off_cmd,cp->id,
                        inet_ntoa((struct in_addr)sa)
                        ,cp->off_cmdp);
                        cmd_fork(linecmd);
        }

	cp=cp->next;
	}
	syslog(LOG_INFO,"-> Offline (USR2)");
}

void sig_term() {
	
	struct conf_t *cp;
	cp=base;
	while(cp!=NULL) {
	if(cp->stage==ON_STAGE) {
		offline_req_t msg;
		msg.hdr.opcode=OFFLINE_REQ;
		msg.hdr.hostid=cp->id;
		msg.sid=cp->sid;
		if(cp->cserver!=NULL)
		net_write_message((msg_t *)&msg,cp->cserver->addr,cp->cserver->port);
	}
	cp->sid=0;
	cp->stage=POLL_STAGE;
	cp->cserver=NULL;
	cp->timeout=60;
	{
                        char linecmd[512];
                        struct in_addr sa;
                        sa.s_addr=cp->laddr;
                        sprintf(linecmd,"%s %d %s %s",cp->off_cmd,cp->id,
                        inet_ntoa((struct in_addr)sa)
                        ,cp->off_cmdp);
                        cmd_fork(linecmd);
        }
	cp=cp->next;
	}
	free_conf();
	syslog(LOG_INFO,"daemon exiting");
	unlink(pid_file);
	exit(0);
}

void usage(char *argv[]) {
	
	printf("Syntax: %s [-h] [-p localport] [-P pid_file] [-f conf_file]\n",argv[0]);
	exit(0);
} 
	
void check_message(void) {

	msg_t msg;
	int from;
	struct conf_t *cp;

	cp=base;

	if(!net_check_message()) return;
	if(!net_read_message(&msg,&from)) return;
	
	if(msg.hdr.opcode==ECHO_REQ) {
		echo_ack_t m;
		m.hdr.opcode=ECHO_ACK;
		m.oserial=msg.hdr.serial;
		m.hdr.hostid=msg.hdr.hostid;
		net_write_message((msg_t *)&m,from,msg.hdr.rport);
		return;
	}

	while(cp!=NULL) {

	if(cp->id==msg.hdr.hostid) { 
	   struct ser_t *sp=NULL;
	   sp=cp->servers;
	   while(sp!=NULL) {
	   	if(sp->addr==from && sp->port==msg.hdr.rport) break;
		sp=sp->next;
	   }
	   if(sp!=NULL) break;
	}
	cp=cp->next;
	}
	if(cp==NULL) { 
		syslog(LOG_INFO,"dhid: Packet recieved from an unknown server or server port! Ignoring ... ");
		return;
	}

	if(msg.hdr.opcode==AUTH_DENY) {
		syslog(LOG_ERR,"Authentication failed for %d, retrying ...",
			msg.hdr.hostid);
		cp->stage=POLL_STAGE;
		cp->cserver=NULL;
		if(!cp->refresh)
		cp->timeout=POLL_FREQ;
		else
		cp->timeout=cp->refresh;
		return;
	}

	if(msg.hdr.opcode==AUTH_ACK) {
		auth_ack_t *p2;
		p2=(auth_ack_t *)&msg;
		if(!cp->refresh)
		cp->timeout=POLL_FREQ*FAIL_ALLOW;
		else
		cp->timeout=cp->refresh*FAIL_ALLOW;
		cp->stage=ON_STAGE;	
		syslog(LOG_INFO,"[%d] -> Online",msg.hdr.hostid);
		cp->sid=p2->sid;
		cp->laddr=p2->raddr;
		if(cp->on_cmd[0]!='\0') 
		{
		char linecmd[512];
		struct in_addr sa;
		sa.s_addr=cp->laddr;
		sprintf(linecmd,"%s %d %s %s",cp->on_cmd,cp->id,
		inet_ntoa((struct in_addr)sa)
		,cp->on_cmdp);
		cmd_fork(linecmd);
		}
		return;
	}

	if(msg.hdr.opcode==ECHO_ACK && cp->stage==ON_STAGE) {
		
		echo_ack_t *m; m=(echo_ack_t *)&msg; 
		if(m->oserial) {
			if(!cp->refresh)
				cp->timeout=POLL_FREQ*FAIL_ALLOW;
			else
				cp->timeout=cp->refresh*FAIL_ALLOW;
		}
	}

	if(msg.hdr.opcode==ECHO_ACK && cp->stage==POLL_STAGE) {
		auth_req_t m;
		struct ser_t *sp;
		if(cp->cserver!=NULL) return;
		sp=cp->servers;
		while(sp!=NULL) {
			if(sp->addr==from && sp->port==msg.hdr.rport) break;
			sp=sp->next;
		}
		if(sp==NULL) 
			return;
		cp->cserver=sp;
		cp->stage=AUTH_STAGE;
		if(cp->atype==APASS) strcpy(m.pass,cp->pass);
		else memset(m.pass,0,16);
		m.hdr.opcode=AUTH_REQ;
		m.hdr.hostid=cp->id;
		m.refresh=cp->refresh;
		net_write_message((msg_t *)&m,from,msg.hdr.rport);
		if(!cp->refresh)
			cp->timeout=POLL_FREQ;
		else
			cp->timeout=cp->refresh;
		return;
	}

#ifdef	QRC
	if(msg.hdr.opcode==AUTH_SY) {
		mpz_t x1,x2,y,x,n;
		auth_sendx_t m;
		auth_sendy_t *mp;
		char buff[1024];
		
		if(cp->atype!=AQRC) return;

		mpz_init(x1);
		mpz_init(x2);
		mpz_init(y);
		mpz_init(x);
		mpz_init(n);

		mp=(auth_sendy_t *)&msg;
		memcpy(buff,mp->y,200);
		buff[200]='\0';
		mpz_set_str(y,buff,10);

		qrc_sqrty(x1,y,cp->authp);
		qrc_sqrty(x2,y,cp->authq);

		qrc_crt(y,x1,x2,cp->authp,cp->authq);

		mpz_clear(x1);
		mpz_clear(x2);

		mpz_mul(n,cp->authp,cp->authq);
		
		mpz_mod(x,y,n);

		mpz_clear(y);
		mpz_clear(n);
		
		m.hdr.opcode=AUTH_SX;
		m.hdr.hostid=cp->id;
		qrc_fill_str(x,m.x,200);
		mpz_clear(x);
		net_write_message((msg_t *)&m,from,msg.hdr.rport);
		return;
	}
#endif

	
	return;

}


int main(int argc,char *argv[]) {

	FILE *fp;
	int c;
	extern char *optarg;

	strcpy(conf_file,DHID_CONF);
	strcpy(pid_file,DHID_PID);

	while((c=getopt(argc,argv,"hf:p:P:"))!=EOF)
	switch(c) {
	case('p'): rport=atoi(optarg); break;
	case('P'): strncpy(pid_file,optarg,sizeof(pid_file)); break;
	case('f'): strncpy(conf_file,optarg,sizeof(conf_file)); break;
	case('h'): usage(argv);
	default: usage(argv);
	}

	
	close(0);
	close(1);
	close(2);

	setsid();

	if(fork()) _exit(0);	/* Run in background */
	

	syslog(LOG_INFO,"daemon started");

	read_conf(conf_file);
	
	if(net_init(rport)) {
		syslog(LOG_ERR,"failed to open incoming udp socket on port %d",
			rport);
		exit(255);
	}
	
	signal(SIGUSR1,SIG_IGN);
	signal(SIGUSR2,sig_usr2);
	signal(SIGTERM,sig_term);

	unlink(pid_file);
	fp=fopen(pid_file,"w");
	if(fp!=NULL) {
		fprintf(fp,"%d",(int)getpid());
		fclose(fp);
	}


	for(;;) {
		struct conf_t *cp;

		check_message();
		cp=base;
		while(cp!=NULL) {

		if(cp->stage==POLL_STAGE && cp->timeout<=0) {
			struct ser_t *sp;
			echo_req_t m; m.hdr.opcode=ECHO_REQ;
			m.hdr.hostid=cp->id;
			sp=cp->servers;
			while(sp!=NULL) {
			if(sp->addr==0) {
				struct hostent *h;
				if ( (h = gethostbyname(sp->hostname)) != NULL )
				memcpy( &sp->addr, h->h_addr, 4 );
				else {
				sp=sp->next;
				continue;
				}
			}
			net_write_message((msg_t *)&m,sp->addr,sp->port);
			sp=sp->next;
			}
			if(!cp->refresh)
				cp->timeout=POLL_FREQ;
			else
				cp->timeout=cp->refresh;
		}
		if(cp->timeout>0) cp->timeout--;

		if(cp->timeout<=0 && cp->stage==AUTH_STAGE) {
			cp->stage=POLL_STAGE;
			cp->cserver=NULL;
		}

		if(cp->timeout<=0 && cp->stage==ON_STAGE) {
			syslog(LOG_INFO,"[%d] -> Offline (timeout)",cp->id);
			cp->stage=POLL_STAGE;
			cp->cserver=NULL;
			{
                	char linecmd[512];
                	struct in_addr sa;
                	sa.s_addr=cp->laddr;
                	sprintf(linecmd,"%s %d %s %s",cp->off_cmd,cp->id,
                	inet_ntoa((struct in_addr)sa),cp->off_cmdp);
                	cmd_fork(linecmd);
                	}

		}

		/* If we haven't reached 0 but are a multiple of 
		   the poll frequency, send an echo_req out */
		if(cp->timeout>0 && cp->stage==ON_STAGE) { 
			int freq;
			if(!cp->refresh) freq=POLL_FREQ;
			else freq=cp->refresh;
			if(!(cp->timeout % freq)) {

			struct ser_t *sp;
			echo_req_t m; m.hdr.opcode=ECHO_REQ;
			m.hdr.hostid=cp->id;
			sp=cp->servers;
			while(sp!=NULL) {
			if(sp->addr==0) {
				struct hostent *h;
				if ( (h = gethostbyname(sp->hostname)) != NULL )
				memcpy( &sp->addr, h->h_addr, 4 );
				else {
				sp=sp->next;
				continue;
				}
			}
			net_write_message((msg_t *)&m,sp->addr,sp->port);
			sp=sp->next;
			}
	
			}
		}

		cp=cp->next;
		}
		sleep(1);
	}
}

