/*-
 * Copyright (c) 1998-2005 Joao Cabral
 * 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.
 *
 *      DHIS(c)  Dynamic Host Information System Release 5
 */


#include "dhisd.h"
#include "online.h"
#include "db.h"
#include "network.h"
#include "log.h"


int r=0;
int stage=0;
extern online_t *onbase;
extern db_t *dbase;

extern unsigned char logfile[256];
extern unsigned char dbase_file[256];

unsigned char services_file[256];
unsigned char pid_file[256];

int rport=DHISD_PORT;

/* R3 hashing function */
int pass_encrypt(unsigned char *pass,int n) {
	
	int res=0;
	
	while(*pass!='\0') 
		res += (*pass++ * (n%256));
	res += n+ 8 + (n%100);
	return(res);
}


int do_dgram(msg_t msg,int from) {

	DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Processing packet [OPCODE=%x] started\n",
		msg.hdr.opcode));

	/* First lets take care of DHIS R3 compatible messages */

	if(msg.hdr.version<DHIS_MIN_VERSION) return(0);

	if(msg.hdr.version<DHIS_R4) { /* DHIS R3 here */

		unsigned char *p;
		r3_online_req_t *m;	/* Same as offline_req anyway */	

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Message is in R3 format\n"));

		m=(r3_online_req_t *)&msg;
		if(msg.hdr.opcode!=R3_ONLINE_REQ && msg.hdr.opcode!=R3_OFFLINE_REQ) {

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Message opcode is unsupported. Discarded.\n"));

			return(0);
		}

		p=db_password(m->id);
		if(p==NULL) { 

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): HostID does not match database.\n"));

			return(0);
		}

		DSYSLOG(1,(LOG_DEBUG,"do_dgram: LTOD=%d\n",msg.hdr.rport));

		if(db_get_ltod(m->id) >= msg.hdr.rport) { 

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Security: Invalid ltod. Discarded.\n"));

			return(0);
		}
		db_set_ltod(m->id,msg.hdr.rport);

		if(pass_encrypt(p,msg.hdr.rport)!=m->pass) {

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Security: Invalid password. Discarded.\n"));

			return(0);
		}
		if(msg.hdr.opcode==R3_ONLINE_REQ) {

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Online Broadcast: callind on_update().\n"));

			on_update(m->id,from,0,3,0);
		}
		if(msg.hdr.opcode==R3_OFFLINE_REQ) {

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Offline Broadcast: callind on_delete().\n"));

			on_delete(m->id);
		}

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): End of R3 message processing.\n"));

		return(1);
	} /* end R3 */
	
	/* Now lets process R4 messages */
	

		if(msg.hdr.version>=40 && msg.hdr.version < 50)
			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Message is in R4 format\n"));

	if(msg.hdr.opcode==R4_OFFLINE_REQ) {
		r4_offline_req_t *mp;
		online_t *op;
		mp=(r4_offline_req_t *)&msg;

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): R4_OFFLINE_REQ received\n"));

		op=onbase;
		while(op!=NULL) { 
		if(op->sid==mp->sid && op->addr==from) break;
		op=op->next;
		}
		if(op!=NULL) {

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Calling on_delete() for %d\n",op->id));

			on_delete(op->id);
		}
		else {
			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Security: Invalid addr or sid. Discarded.\n"));
		}
		return(1);
	}
	if(msg.hdr.opcode==R4_ECHO_REQ) {
		r4_echo_ack_t m;

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): R4_ECHO_REQ received. Sending ECHO_ACK.\n"));

		m.hdr.opcode=R4_ECHO_ACK;
		m.oserial=msg.hdr.serial;
		net_write_message((msg_t *)&m,from,msg.hdr.rport);
		return(1);
	}

	if(msg.hdr.opcode==R4_CHECK_ACK) {
		online_t *p;
		r4_check_ack_t *mp;

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Received R4_CHECK_ACK.\n"));

		mp=(r4_check_ack_t *)&msg;
		p=onbase;	
		while(p!=NULL) {
		if(p->addr==from) break;
		p=p->next;
		} 
		if(p==NULL) return(0);
		if(mp->sid!=p->sid) return(0); 
		on_update(p->id,from,msg.hdr.rport,4,0);
		p->ka=time(NULL) + NEXT_CHECK;
		p->check_fails=0;
		return(1);
	}

	if(msg.hdr.opcode==R4_AUTH_SX) {
		db_t *dbp;
		r4_auth_sendx_t *mp;
		mpz_t x;
		unsigned char buff[1024];

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Received R4_AUTH_SX.\n"));

		dbp=dbase;
		mp=(r4_auth_sendx_t *)&msg;
		while(dbp!=NULL) {
		if(dbp->id==mp->id) break;
		dbp=dbp->next;
		}
		if(dbp==NULL) {
			r4_auth_deny_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_DENY. Invalid ID.\n"));

			m.hdr.opcode=R4_AUTH_DENY;
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(0);
		}

		if(dbp->atype!=AQRC || dbp->xstage==0) {
			r4_auth_deny_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_DENY. Not in X waiting mode.\n"));

			m.hdr.opcode=R4_AUTH_DENY;
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(0);
		}
		mpz_init(x);
		memcpy(buff,mp->x,200);
		buff[200]='\0';
		mpz_set_str(x,buff,10);
		if(mpz_cmp(x,dbp->x)) {
			r4_auth_deny_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_DENY. X and X do not match.\n"));

			m.hdr.opcode=R4_AUTH_DENY;
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			if(dbp->xstage) mpz_clear(dbp->x);
			dbp->xstage=0;
			mpz_clear(x);
			return(0);
		} else {
			r4_auth_ack_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_ACK. X and X match.\n"));

			mpz_clear(x);
			mpz_clear(dbp->x);
			dbp->xstage=0;
			m.hdr.opcode=R4_AUTH_ACK;
			if((m.sid=on_update(dbp->id,from,msg.hdr.rport,4,0))!=0) {
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(1);
			} else return(0);
		}
	}

	if(msg.hdr.opcode==R4_AUTH_REQ) {
		db_t *dbp;
		r4_auth_req_t *mp;
		mp=(r4_auth_req_t *)&msg;

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Received R4_AUTH_REQ for %d\n",mp->id));

		dbp=dbase;
		while(dbp!=NULL) {
			if(dbp->id==mp->id) break;
			dbp=dbp->next;
		}
		if(dbp==NULL) {
			r4_auth_deny_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_DENY. Invalid ID.\n"));

			m.hdr.opcode=R4_AUTH_DENY;
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(0);
		}
		mp->pass[MAX_PASS-1]='\0';
		if(dbp->atype==APASS) {

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Password Authentication.\n"));

		if(strcmp(mp->pass,dbp->pass)) {
			r4_auth_deny_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_DENY. Invalid Password.\n"));

			m.hdr.opcode=R4_AUTH_DENY;
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(0);
		} else {
			r4_auth_ack_t m;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_ACK.\n"));

			m.hdr.opcode=R4_AUTH_ACK;
			if((m.sid=on_update(dbp->id,from,msg.hdr.rport,4,0))!=0) {
			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(1);
			} else return(0);
		}
		}
		if(dbp->atype==AQRC) { 
			r4_auth_sendy_t m;
			mpz_t y;

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): QRC Authentication.\n"));

			if(dbp->xstage==1) { dbp->xstage=0; mpz_clear(dbp->x);}
			m.hdr.opcode=R4_AUTH_SY;
			dbp->xstage=1;
			mpz_init(dbp->x);
			qrc_genx(dbp->x,dbp->authn);
			mpz_init(y);
			qrc_geny(y,dbp->x,dbp->authn);
			qrc_fill_str(y,m.y,200);
			mpz_clear(y);

			DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending R4_AUTH_SY.\n"));

			net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(1);
		}
		return(0);
		
	}

	/* Finally process R5 messages */


		if(msg.hdr.version>=50)
		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Message is in R5 format\n"));

        if(msg.hdr.opcode==OFFLINE_REQ) {
                offline_req_t *mp;
                online_t *op;
                mp=(offline_req_t *)&msg;

                DSYSLOG(1,(LOG_DEBUG,"do_dgram(): OFFLINE_REQ received\n"));

                op=onbase;
                while(op!=NULL) { 
                if(op->sid==mp->sid && op->addr==from) break;
                op=op->next;
                }
                if(op!=NULL) { 

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Calling on_delete() for %d\n",op->id));

			on_delete(op->id);
		} 
                else {
                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Security: Invalid addr or sid. Discarded.\n"));
                }
                return(1);
        }

        if(msg.hdr.opcode==ECHO_REQ) {
                echo_ack_t m;

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): ECHO_REQ received. Sending ECHO_ACK.\n"));

                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(1);
        }

        if(msg.hdr.opcode==CHECK_ACK) {
                online_t *p;
                check_ack_t *mp;

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Received CHECK_ACK.\n"));

                mp=(check_ack_t *)&msg;
                p=onbase;
                while(p!=NULL) {
                if(p->addr==from && p->id==mp->hdr.hostid) break;
                p=p->next;
                } 
                if(p==NULL) return(0);
                if(mp->sid!=p->sid) return(0); 
                on_update(p->id,from,msg.hdr.rport,5,0);
                p->ka=time(NULL) + p->refresh;
                p->check_fails=0;
                return(1);
        }

        if(msg.hdr.opcode==AUTH_SX) {
                db_t *dbp;
                auth_sendx_t *mp;
                mpz_t x;
                unsigned char buff[1024];

		DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Received AUTH_SX.\n"));

                dbp=dbase;
                mp=(auth_sendx_t *)&msg;
                while(dbp!=NULL) {
                if(dbp->id==mp->hdr.hostid) break;
                dbp=dbp->next;
                }
                if(dbp==NULL) {
                        auth_deny_t m;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_DENY. Invalid ID.\n"));

                        m.hdr.opcode=AUTH_DENY;
			m.hdr.hostid=msg.hdr.hostid;
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
                        return(0);
                }

                if(dbp->atype!=AQRC || dbp->xstage==0) {
                        auth_deny_t m;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_DENY. Not in X waiting mode.\n"));

                        m.hdr.opcode=AUTH_DENY;
			m.hdr.hostid=msg.hdr.hostid;
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
                        return(0);
                }
                mpz_init(x);
                memcpy(buff,mp->x,200);
                buff[200]='\0';
                mpz_set_str(x,buff,10);
                if(mpz_cmp(x,dbp->x)) {
                        auth_deny_t m;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_DENY. X and X do not match.\n"));

                        m.hdr.opcode=AUTH_DENY;
			m.hdr.hostid=msg.hdr.hostid;
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
                        if(dbp->xstage) mpz_clear(dbp->x);
                        dbp->xstage=0;
                        mpz_clear(x);
                        return(0);
                } else {
                        auth_ack_t m;
                        auth_ack_51_t m51;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_ACK. X and X match.\n"));

                        mpz_clear(x);
                        mpz_clear(dbp->x);
                        dbp->xstage=0;
			if(msg.hdr.version>=51) {
                        m51.hdr.opcode=R51_AUTH_ACK;
			m51.hdr.hostid=msg.hdr.hostid;
			m51.raddr=from;
                        if((m51.sid=on_update(dbp->id,from,msg.hdr.rport,
				5,dbp->refresh))!=0) {
                        net_write_message((msg_t *)&m51,from,msg.hdr.rport);
			return(1);
			} else return(0);

			} else {
                        m.hdr.opcode=AUTH_ACK;
			m.hdr.hostid=msg.hdr.hostid;
                        if((m.sid=on_update(dbp->id,from,msg.hdr.rport,
				5,dbp->refresh))!=0) {
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(1); } else return(0);
			}
                }
        }

        if(msg.hdr.opcode==AUTH_REQ) {
                db_t *dbp;
                auth_req_t *mp;
                mp=(auth_req_t *)&msg;

                DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Received AUTH_REQ for %d\n",mp->hdr.hostid));
                DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Refresh rate is set to %d\n",mp->refresh));

                dbp=dbase;
                while(dbp!=NULL) {
                        if(dbp->id==mp->hdr.hostid) break;
                        dbp=dbp->next;
                }
                if(dbp==NULL) {
                        auth_deny_t m;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_DENY. Invalid ID.\n"));

                        m.hdr.opcode=AUTH_DENY;
			m.hdr.hostid=msg.hdr.hostid;
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
                        return(0);
                }
                mp->pass[MAX_PASS-1]='\0';
                if(dbp->atype==APASS) {

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Password Authentication.\n"));

                if(strcmp(mp->pass,dbp->pass)) {
                        auth_deny_t m;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_DENY. Invalid Password.\n"));
			m.hdr.opcode=AUTH_DENY;
			m.hdr.hostid=msg.hdr.hostid;
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
                        return(0);
                } else {
                        auth_ack_t m;
                        auth_ack_51_t m51;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_ACK.\n"));

			dbp->refresh=mp->refresh;
			if(msg.hdr.version>=DHIS_VERSION) {
                        m51.hdr.opcode=R51_AUTH_ACK;
			m51.hdr.hostid=msg.hdr.hostid;
			m51.raddr=from;
                        if((m51.sid=on_update(dbp->id,from,msg.hdr.rport,5,
			   dbp->refresh))!=0) {
                        net_write_message((msg_t *)&m51,from,msg.hdr.rport);
			return(1); } else return(0);
			} else {
                        m.hdr.opcode=AUTH_ACK;
			m.hdr.hostid=msg.hdr.hostid;
                        if((m.sid=on_update(dbp->id,from,msg.hdr.rport,5,
			   dbp->refresh))!=0) {
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
			return(1); } else return(0);
			}
                }
                }
                if(dbp->atype==AQRC) {
                        auth_sendy_t m;
                        mpz_t y;

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): QRC Authentication.\n"));

			dbp->refresh=mp->refresh;
                        if(dbp->xstage==1) { dbp->xstage=0; mpz_clear(dbp->x);}
                        m.hdr.opcode=AUTH_SY;
                        dbp->xstage=1;
                        mpz_init(dbp->x);
                        qrc_genx(dbp->x,dbp->authn);
                        mpz_init(y);
                        qrc_geny(y,dbp->x,dbp->authn);
                        qrc_fill_str(y,m.y,200);
                        mpz_clear(y);

                        DSYSLOG(1,(LOG_DEBUG,"do_dgram(): Sending AUTH_SY.\n"));

			m.hdr.hostid=msg.hdr.hostid;
                        net_write_message((msg_t *)&m,from,msg.hdr.rport);
                        return(1);
                }
                return(0);

        }



	return(1);
}

void sig_parse() {

	if(!stage) on_parse();
	signal(SIGALRM,sig_parse);
	alarm(PARSE_TIMEOUT);
}

void sig_hup() {

	if(!stage) db_reload();
	else stage=2;
	service_send_reload();
	signal(SIGHUP,sig_hup);
}

void sig_void() {

}

int debug = 0;
void sig_usr1() {
	debug++;
	syslog(LOG_DEBUG,"debug level %d\n", debug);
	signal(SIGUSR1,sig_usr1);
}

void sig_usr2() {
	debug = 0;
	syslog(LOG_DEBUG,"debug level %d\n", debug);
	signal(SIGUSR2,sig_usr2);
}

void sig_term() {

	signal(SIGCHLD,sig_void);
	on_free();
	db_free();
	service_send_term();
	service_free();
	net_close();
 	msg_log("SIGTERM received. Exiting ...");
	unlink(pid_file);
	exit(0);
}


void usage(unsigned char *s) {

	fprintf(stderr,"Syntax: %s [-D] [-p UDP_PORT] [-d dbase_file] [-s services_file] [-l log_file]\n",s); 
	fprintf(stderr,"        [-P pid_file]\n");
	exit(0);
}

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

	FILE *fp;
	unsigned char str[128];
	extern char *optarg;
	int c;

	if(getuid()) {
	DSYSLOG(0,(LOG_ERR,"main(): %s must be executed as root\n",argv[0])); 
	exit(0);
	}

	strcpy(logfile,DHISD_LOG);
	strcpy(pid_file,DHISD_PID);
	strcpy(dbase_file,DHISD_DB);
	strcpy(services_file,DHISD_SERVICES);

	while((c=getopt(argc,argv,"Dhp:P:l:d:s:")) !=EOF) {
	switch(c) {
	case('l'):strcpy(logfile,optarg);break;
	case('P'):strcpy(pid_file,optarg);break;
	case('p'):rport=atoi(optarg);break;
	case('d'):strcpy(dbase_file,optarg);break;
	case('D'):debug++;break;
	case('s'):strcpy(services_file,optarg);break;
	case('h'): usage(argv[0]);
	default: usage(argv[0]);
	}
	}
	

#ifndef	DONT_FORK
	setsid();
	if(fork()) _exit(0);
#endif

	if(net_init(rport)) {
		syslog(LOG_ERR,"Unable to initialise network");

		DSYSLOG(1,(LOG_DEBUG,"main(): Failed to initialise network\n")); 

		exit(255);
	}

	if(!read_services(services_file)) {
		syslog(LOG_ERR,"Unable to read any service");

		DSYSLOG(1,(LOG_DEBUG,"main(): Failed to read services\n")); 

		exit(255);
	}

	if(!init_services()) {
		syslog(LOG_ERR,"Unable to initialise services");

		DSYSLOG(1,(LOG_DEBUG,"main(): Failed to initialise services\n")); 

		exit(255);
	}

	if(!db_reload()) {
		syslog(LOG_ERR,"Unable to open database file");

		DSYSLOG(1,(LOG_DEBUG,"main(): Failed to read database\n")); 

		sig_term();
		exit(255);
	}
	
#ifndef	DONT_FORK
	close(0);
	close(1);
	close(2);
#endif

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

	

	sprintf(str,"Datagram Server Started [%d]",(int)getpid());
	msg_log(str);

	signal(SIGUSR1,sig_usr1);
	signal(SIGUSR2,sig_usr2);
	signal(SIGTERM,sig_term);
	signal(SIGHUP,sig_hup);
	signal(SIGALRM,sig_parse);
	alarm(PARSE_TIMEOUT);




	for(;;) {
		msg_t msg;
		int from;
		if(!net_read_message(&msg,&from)) continue;
		stage=1;
		do_dgram(msg,from);
		if(stage==2) { stage=0; sig_hup(); }
		else stage=0;
	}
}

