/* alived.c - daemon for listening and tracking hosts
   Copyright (C) 2003, Kris Foster <kris@krweb.net>

   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 <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "alived.h"

#define SERVER		1
#define CLIENT		2

void pversion(void);
void usage(void);                       // Explain the command line options
void invalid_switch(char option);       // The user tried something stupid
void daemonize(void);                   // Turn ourselves into a daemon

/* no, I'm not proud of what I've done here... ugh what a mess
   maybe I should just give up and make the watchlist global
   but then we lose the ability to maintain several lists */
struct watchlist *keepalive_client(struct watchlist *first_node, struct message *node, struct sockaddr_in *node_addr);
void lookup_client(struct watchlist *watch_node, struct message *node, struct sockaddr_in req_addr, int sockfd);
int remove_node(struct watchlist *watch_node, char *id);
struct watchlist *add_node(struct watchlist *watch_node, struct message *new_node, struct sockaddr_in *node_addr);  
int refresh_node(struct watchlist *watch_node, struct message *node, struct sockaddr_in *node_addr);
int watchlist_dog(struct watchlist *firstnode);
void dump_list(struct watchlist *watch_node, struct sockaddr_in req_addr, int sockfd);


int 
main(int argc, char *argv[])
{
	char *option;
	int i;
	int sockfd;
	int mode;
	int nbytes;
        int server_port=ALIVE_PORT;
        int m_len, a_len, addr_len;
        struct sockaddr_in d_addr;	// The daemon's address
	struct sockaddr_in r_addr;	// Remote address
        struct message dgram;
        struct watchlist *c_list;	// linked list of clients
	struct watchlist *first_node;
        
        m_len=sizeof(struct message);
        a_len=sizeof(struct sockaddr);

        c_list=NULL;
        first_node=c_list;
        c_list=malloc(sizeof(struct watchlist));
        if(c_list==NULL) {
                printf("Could not find enough memory\n");
                exit(1);
        }
        c_list->ip_addr=0;
        strcpy(c_list->id,NOTFOUNDCHAR);
        c_list->next = NULL;
        first_node=c_list;
        
	if(argc>1) {
		for(i=1;i<argc;i++) {
			option=argv[i];
			switch(option[1]) {
				case 'd': daemonize(); break;
				case 'h': usage(); exit(0);
				case 'v': pversion(); exit(0);
				case 's': mode=SERVER; break;
				case 'c': mode=CLIENT; break;
				default: invalid_switch(option[1]); exit(0);
			}
		}
	}

        sockfd=socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd==-1) {
                perror("socket");
                exit(1);
        }

	fcntl(sockfd, F_SETFL, O_NONBLOCK);	// non-blocking.. still not pretty

	d_addr.sin_family=AF_INET;
	d_addr.sin_port=htons(server_port);
	d_addr.sin_addr.s_addr=INADDR_ANY;	// make this user definable at some point
	memset(&(d_addr.sin_zero), '\0', 8);

	if(bind(sockfd, (struct sockaddr *)&d_addr, a_len)==-1) {
		perror("bind");
		exit(1);
	}

	addr_len=sizeof(struct sockaddr);

	for(;;) {
		nbytes=recvfrom(sockfd,&dgram,m_len,0,(struct sockaddr *)&r_addr,&addr_len);
                if(nbytes==-1){
                        sleep(SLEEPTIME);
                } else {
			dgram.type=ntohl(dgram.type);
			switch(dgram.type) {
                                case KEEPALIVE: first_node=keepalive_client(first_node,&dgram,&r_addr); break;
                                case LOOKUP: lookup_client(first_node,&dgram,r_addr,sockfd); break;
                                case REMOVE: remove_node(first_node,dgram.id); break;
				case DUMP: dump_list(first_node,r_addr,sockfd);
                                default: break;		// hmmm... someone sent us garbage
                        }
                }
	}

        // Not actually getting here.. need to quit on kill signal
	close(sockfd);

	return 0;
}

struct watchlist *
keepalive_client(struct watchlist *first_node, struct message *node, struct sockaddr_in *node_addr)
{
	long ip_addr;

	if(refresh_node(first_node,node,node_addr)==1) {
		first_node=add_node(first_node,node,node_addr);
	}
        
        return first_node;
}

void 
lookup_client(struct watchlist *watch_node, struct message *node, struct sockaddr_in req_addr, int sockfd)
{
        int nbytes;
        int m_len, a_len;

        m_len=sizeof(struct message);
        a_len=sizeof(struct sockaddr);

     	// this is ugly.. and dumb
	for(;;) {
		if(!strcmp(watch_node->id, node->id)) 
			break;
		if(watch_node->next==NULL)
			break;
		watch_node=watch_node->next;
	}
		
        nbytes=sendto(sockfd, watch_node, m_len, 0, (struct sockaddr *)&req_addr, a_len);
        if(nbytes==-1) {
                perror("sendto");
	}
}

int 
remove_node(struct watchlist *watch_node, char *id)
{
	struct watchlist *prev_node=watch_node;

	for(;;) {
                if(!strcmp(watch_node->id, id))
                        break;
                if(watch_node->next==NULL)
                        return 1;
		prev_node=watch_node;		// prev_node points to the current good node
		watch_node=watch_node->next;	// watch_node now points to the next node
        }
	
	prev_node->next=watch_node->next;	// set th previous good node to whatever the
                                                // node to be removed is pointing to
	free(watch_node);

	return 0;
}

struct watchlist *
add_node(struct watchlist *watch_node, struct message *new_node, struct sockaddr_in *node_addr)
{
	struct watchlist *add_node;

	add_node=malloc(sizeof(struct watchlist));
	if(add_node==NULL) {
                return watch_node;
        }
	strcpy(add_node->id,new_node->id);
	add_node->next=watch_node;
        
        // Do this in refresh_node() since we can add additional junk their easily
        refresh_node(add_node, new_node, node_addr);

	return add_node;
}

int 
refresh_node(struct watchlist *watch_node, struct message *node, struct sockaddr_in *node_addr)
{
       for(;;) {
                if(!strcmp(watch_node->id,node->id)) {
                        // replace the existing IP in case we have a new one
                        watch_node->ip_addr=node_addr->sin_addr.s_addr;
                        // update the time
                        watch_node->last_heard=htonl(time(NULL));
                        return 0;
                }
                if(watch_node->next==NULL) {
                        return 1;
                }
                watch_node=watch_node->next;
        }
}

int 
watchlist_dog(struct watchlist *firstnode)
{
        // not implemented
}

void 
dump_list(struct watchlist *watch_node, struct sockaddr_in req_addr, int sockfd)
{
 	// I haven't written anything to take advantage of this yet
	// not sure whether to add it to alookup or create a seperate tool    	

        int nbytes;
        int m_len, a_len;
        struct message dgram;

        for(;;) {
               	// send each node, stop after the last one
		nbytes=sendto(sockfd,watch_node,m_len,0,(struct sockaddr *)&req_addr,a_len);
                if(nbytes==-1) {
                        perror("sendto");
                } 
                if(watch_node->next==NULL)
                        break;
                watch_node=watch_node->next;
        }
}

void 
pversion(void)
{
        printf("alived version %s\n", VERSION);
}

void 
usage(void)
{
        printf("Usage: alived [options]\n");
        printf("Options:\n");
        printf("  -d\t\tRun server as a daemon\n");
        printf("  -h\t\tDisplay help on options\n");
}

void 
invalid_switch(char option)
{
        // Let the user know what ugly option they tried
        printf("alived: invalid option -- %c\n", option);
        printf("Try `alived -h' for more information.\n");
}

void 
daemonize(void)
{
        pid_t pid;

        pid=fork();

        if(pid==-1) {			// Oops
                perror("fork");
                exit(-1);
        }
        else if(pid!=0)			// We're the parent so lets exit
                exit(0);
}

