/*************************************************************

 Copyright (C) 2001, Eric Kratzer

 imp3sockserver.c
 Server socket program for imp3sh named-pipe input/output.

 This software is part of imp3sh and released under the GNU
 GPL. Please see the included 'COPYING' file for more details.


 Description:

   This server sits upon imp3sh's named-pipe I/O interface 
   serving as an interface for  remote clients to pass commands
   and recieve output from imp3sh.


 Example:

 1) Run imp3sh with filenames (to be used as named pipes)
    as args:

    >imp3sh -i ~/tempin -o ~/tempout &

    NOTE: imp3sh will create the named pipes if they do not
          already exist.

 2) Run 'imp3sockserver' (on the same machine) with TCP port and
    named-pipes as args:

    >imp3sockserver 9998 ~/tempin ~/tempout &

    NOTE: The selection of a port number is completely arbitrary,
          but should be above 1024. If you get an error when
          starting the server, try using a different port--as
          the one you chose might be in use by another
          application.

 3) Now remote clients can control the local imp3sh session 
    with the 'imp3sockclient' program (see 'imp3sockclient.c'
    for more information.)

**************************************************************/

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>

#define TRUE  1
#define FALSE 0

int maxfd, fd, fd2, fdserv, fdclient;
char client, buff[8192];

void int_sig_handler(int a)
{
    close(fdserv);
    if (client)
	close(fdclient);
    exit(0);
}

void setup_select(fd_set * read_set)
{
    FD_ZERO(read_set);
    maxfd = 0;

    FD_SET(fd2, read_set);
    if (fd2 >= maxfd)
	maxfd = fd2 + 1;

    if (client) {
	FD_SET(fdclient, read_set);
	if (fdclient >= maxfd)
	    maxfd = fdclient + 1;
    } else {
	FD_SET(fdserv, read_set);
	if (fdserv >= maxfd)
	    maxfd = fdserv + 1;
    }
}

void accept_client(fd_set * read_set)
{
    struct sockaddr client_adr;
    socklen_t client_len;

    if (FD_ISSET(fdserv, read_set)) {
	client_len = sizeof(client_adr);
	fdclient = accept(fdserv, &client_adr, &client_len);

	if (fdclient == -1)
	    fprintf(stderr, "Error in accept() : %s\n", strerror(errno));
	else
	    client = TRUE;
    }
}

void handle_client(fd_set * read_set)
{
    int len, ret;

    if (FD_ISSET(fdclient, read_set)) {
	len = read(fdclient, buff, 8192);

	if (len == 0) {
	    close(fdclient);
	    client = FALSE;
	} else if (len > 0) {
	    ret = write(fd, buff, len);

	    if (ret <= 0) {
		fprintf(stderr, "\nError writing to input pipe: exiting...\n");
		close(fdserv);
		close(fdclient);
		exit(1);
	    }
	}
    }
}

void monitor_outpipe(fd_set * read_set)
{
    int len, ret;

    if (FD_ISSET(fd2, read_set)) {
	len = read(fd2, buff, 8192);

	if (len == 0) {
	    fprintf(stderr, "\nError reading output pipe: exiting...\n");
	    close(fdserv);
	    if (client)
		close(fdclient);
	    exit(1);
	} else if (client && (len > 0)) {
	    ret = write(fdclient, buff, len);

	    if (ret == 0) {
		close(fdclient);
		client = FALSE;
	    }
	}
    }
}

int main(int argc, char *argv[])
{
    int port;
    fd_set read_set;
    struct sockaddr_in serv_adr;

    if (argc < 4) {
	fprintf(stderr, "Usage: %s <port> <in_fifo> <out_fifo>\n",
		argv[0]);
	exit(1);
    }

    port = atoi(argv[1]);
    if (port <= 0) {
	fprintf(stderr, "Invalid TCP port specified : \"%s\"\n", argv[1]);
	exit(1);
    }

    fd2 = open(argv[3], O_RDONLY | O_NONBLOCK);
    if (fd2 == -1) {
	fprintf(stderr, "Error opening \"%s\" : %s\n", argv[3],
		strerror(errno));
	exit(1);
    }

    fd = open(argv[2], O_WRONLY);
    if (fd == -1) {
	fprintf(stderr, "Error opening \"%s\" : %s\n", argv[2],
		strerror(errno));
	exit(1);
    }

    if ((fdserv = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	fprintf(stderr, "Error socket() failed : %s\n", strerror(errno));
	exit(1);
    }

    memset(&serv_adr, 0, sizeof(serv_adr));	/* Clear it out         */
    serv_adr.sin_family = AF_INET;	/* Set address type     */
    serv_adr.sin_addr.s_addr = INADDR_ANY;	/* Any interface        */
    serv_adr.sin_port = htons(port);

    if (bind(fdserv, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) < 0) {
	fprintf(stderr, "Error bind() failed : %s\n", strerror(errno));
	exit(1);
    }

    if (listen(fdserv, 1) < 0) {
	fprintf(stderr, "Error listen() failed : %s\n", strerror(errno));
	exit(1);
    }

    client = FALSE;
    signal(SIGINT, int_sig_handler);
    signal(SIGTERM, int_sig_handler);

    while (1) {

	setup_select(&read_set);
	select(maxfd, &read_set, NULL, NULL, NULL);

	if (client)
	    handle_client(&read_set);
	else
	    accept_client(&read_set);

	monitor_outpipe(&read_set);

    }

    return 0;
}
