/****************************************************************
 *                                                              *
 *  LIBDIST V1.0						*
 *                                                              *
 *  distrib.c -- a distributor for broadcast messages           *
 *                                                              *
 *  Last changed: 19.02.96                                      *
 *  Author: Frank Kargl                                         *
 *                                                              *
 *  Restrictions: works only for Solaris 2.6 or above           *
 *                                                              *
 ****************************************************************/

#include "libdist.h"
#include "config.h"

#include <arpa/inet.h>		/* for inet_ntoa etc. */

/***
 *** Function: wait on a port and forward all incoming messages
 ***           to a number of dynamically changeable other ports.
 ***/

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

    int servsock;		/* the server socket */
    int sendsock;		/* the socket for sending */
    struct sockaddr_in client;	/* an IEA */
    int	client_len;		/* length of client IEA */
    struct sockaddr_in forward;		/* an IEA */
    struct sockaddr_in *sin_ptr;	/* an IEA ptr */
    const int on=1;		/* for setsockopt */
    char buffer[DL_MAXLINE];	/* the receive buffer */
    int buffer_len;		/* number of bytes read */
    int addit;			/* flag used when adding entries to portlist */
    unsigned int ip,port;	/* ip number and port from protocol msg */
    
    struct portl_elem {
	int port;			/* what port */
	struct portl_elem *next;	/* next entry */
    };

    struct portl_elem *portl=NULL;	/* list of ports to distribute to */
    struct portl_elem *portl_ptr;	/* temporary pointer to list element */
    struct portl_elem *portl_tmp;	/* temporary pointer to list element */

#ifdef DEBUG
    fprintf(stderr,"LIBDIST-dist: distributor is starting\n");
#endif

    if (argc!=2) {
#ifdef DEBUG
	fprintf(stderr,"LIBDIST-dist: error calling distributor\n");
	fprintf(stderr,"Usage: distrib <port>\n");
#endif
	exit(1);
    }

    /* create the server socket */
    servsock = dl_sck_server(argv[1],DL_SCK_UDP,NULL);
    if (servsock==0) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-dist: error opening socket\n");
#endif

	exit(1);
    }

    /* allow broadcasts */
    setsockopt(servsock,SOL_SOCKET,SO_BROADCAST,(char *)&on,sizeof(on));

    /* create send socket */
    sendsock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
    
    /* endless loop */
    while (1) {

	/* get request */
	client_len=sizeof(client);
	buffer_len=recvfrom(servsock,buffer,sizeof(buffer),0,(struct sockaddr *)&client,&client_len);

	if (buffer_len < 0) {
	    /* error reading ?  -> die silently */

#ifdef DEBUG
	    fprintf(stderr,"LIBDIST-dist: error reading in distributor\n");
#endif

	    exit(1);
	}

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-dist: received message #%s#\n",buffer);
#endif

	switch (buffer[0]) {

	    /*** Add 'A port' ***/

	    case 'A':

		    /* parse input line */
		    if (sscanf(buffer,"A %d\r\n",&port) != 1) {

#ifdef DEBUG
			fprintf(stderr,"LIBDIST-dist: wrong message format\n");
#endif

			break;
		    }

		    /* add port to list */

		    portl_ptr=portl;
		    addit=1;
		    while (portl_ptr) {
			/* found element, so don't add it */
			if (portl_ptr->port == port) {
			    addit=0;
			    break; /* from while */
			}
			portl_ptr=portl_ptr->next;
		    }

		    if (addit) {
			portl_ptr = (struct portl_elem *) malloc(sizeof(struct portl_elem));
			portl_ptr->port=port;
			portl_ptr->next=portl;
			portl=portl_ptr;
		    }

		    break;

	    /*** Dump 'D' ***/

	    case 'D':

#ifdef DEBUG
		    fprintf(stderr,"Dumping portlist\n\n");

		    portl_tmp = portl;
		    while (portl_tmp) {
			fprintf(stderr,"Port : %d\n",portl_tmp->port);
			portl_tmp=portl_tmp->next;
		    }
		    fprintf(stderr,"\n");
#endif

		    break;

	    /*** Remove 'R port' ***/

	    case 'R':

		    /* parse input line */
		    if (sscanf(buffer,"R %d\r\n",&port) != 1) {

#ifdef DEBUG
			fprintf(stderr,"LIBDIST-dist: wrong message format\n");
#endif

			break;
		    }

		    /* remove port from list */
		    portl_ptr=portl;
		    while (portl_ptr) {
			/* found element, so delete it */
			if (portl_ptr->port == port) {
			    portl_ptr->port = portl_ptr->next->port;
			    portl_tmp = portl_ptr->next;
			    portl_ptr->next = portl_ptr->next->next;
			    free(portl_tmp);
			    break; /* from while */
			}
			portl_ptr=portl_ptr->next;
		    }

		    break; /* from switch */

	    /*** default: pass message to all members of list ***/

	    case 'E':
	    case 'W':

		    forward.sin_family=AF_INET;
		    forward.sin_addr.s_addr=INADDR_LOOPBACK;

		    portl_ptr=portl;

		    /* send to all ports */
		    while(portl_ptr) {

			sin_ptr=dl_sck_getifadr();

			/* check if election message should be */
			/* sent to origin of message and skip  */

			if (sscanf(buffer,"E %d %d\r\n",&ip,&port) == 2 &&
			    sin_ptr->sin_addr.s_addr == ip &&
			    portl_ptr->port == port) {

			    /* skip this port */
			    portl_ptr=portl_ptr->next;

#ifdef DEBUG
			    fprintf(stderr,"LIBDIST-dist: skipping message #%s# from %s to port %d\n",buffer,inet_ntoa(client.sin_addr),port);
#endif

			    continue;
			}

			forward.sin_port=portl_ptr->port;

#ifdef DEBUG
			fprintf(stderr,"LIBDIST-dist: forwarding message #%s# to port %d\n",buffer,forward.sin_port);
#endif

			sendto(sendsock,buffer,buffer_len,0, \
				(struct sockaddr *)&forward,sizeof(forward));

			portl_ptr=portl_ptr->next;
		    }
	}
    }
}
