/* ============================================================================
 * << File: gems_server.c >>
 * -------------------------
 *  Authors: Emiliano Castagnari (aka Torian) <ecastag@fi.uba.ar>
 *           Diego Essaya <dessaya@fi.uba.ar>
 *     Date: 29/12/03
 *
 *  Description:
 *    Main server functions.
 * ============================================================================
 * ============================================================================
 *
 * ChangeLog: View Changelog
 *
 * ============================================================================
 * Copyright (C) 2003, 2004 Diego Essaya, Emiliano Castagnari
 * 
 * This file is part of gems.
 * 
 * gems 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.
 *
 * gems 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 Library 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 <stdlib.h>				/* malloc() */
#include <string.h>				/* bzero(), strncmp() */
#include <unistd.h>				/* close(), read() */
#include <sys/socket.h>			/* accept(), socket(), ...  */
#include <arpa/inet.h>			/* inet_addr()              */
#include <errno.h>				/* Errores - variable errno */
#include <pty.h>				/* struct winsize           */
#include <sys/wait.h>			/* wait() */


#include "version_server.h"
#include "defaults.h"
#include "sock_info.h"
#include "log.h"
#include "call_script.h"
#include "sighandlers.h"

#include "gems-server.h"

/* Macro to add a FD to the 'open_fds' fd_set, which is in the data structure,
 * updating data->maxfd at the same time. */
#define FD_ADD(fd, data) \
{ \
	FD_SET((fd), &((data)->open_fds)); \
	(data)->fdmax = MAX((data)->fdmax, (fd)); \
}



/*****************************************************************************
 * init_sock_server()
 *
 * Opens and initializes a socket for incoming connections. Returns the
 * socket file descriptor, or -1 in case of error. */
status_t init_sock_server(t_server_data *data, t_options *options)
{
	struct sockaddr_in server;	/* socket data */
	int sockopt = 1;			/* for setsockopt() -- see below */

	/* Create the new socket: */
	if ((data->sock_server = socket(AF_INET, SOCK_STREAM, PROTO_TCP)) == -1)
	{
		perror(APPNAME);
		return EXIT_FAIL;
	}

	/* Set the SO_REUSEADDR flag. Necessary to be able to reconnect whithout
	 * having to wait for the OS to close the port (?) */
	setsockopt(data->sock_server, SOL_SOCKET, SO_REUSEADDR, 
		&sockopt, sizeof(int));

	/* If an IP address was not specified, listen any: */
	server.sin_addr.s_addr = 
			(options->ip.ip_str == NULL) ? 
			SERVER_DEF_IP : inet_addr(options->ip.ip_str);
	server.sin_family = AF_INET;				/* Protocol family: IPv4  */
	server.sin_port = htons(options->port);		/* Listening port */

	bzero(&(server.sin_zero), 8);	/* Initialize structure */

	/* Bind the socket to the TCP port: */
	if (bind(data->sock_server, 
			(struct sockaddr *)&server, sizeof(server)) == -1)
	{
		perror(APPNAME);
		return EXIT_FAIL;
	}

	/* Start listening: */
	listen(data->sock_server, options->maxconn);

	/* Add sock_server to open_fds: */
	FD_ADD(data->sock_server, data);

	g_log(LOG_SOCK_C, options->port);

	return CONTINUE;
}


/*****************************************************************************
 * close_sockets()
 *
 * Close all open fds, including sockets. */
void close_sockets(t_server_data *data)
{
	g_log(LOG_CLOSING_ALL);

	for ( ; data->client_list != NULL; )
		del_client(data->client_list, data);

	data->clients = 0;

	if (data->sock_server > 0) close(data->sock_server);
	if (data->input_fd > 0) close(data->input_fd);
}


/*****************************************************************************
 * validate_client()
 *
 * Once the connection has been accept()ed, this function follows the gems
 * protocol, making the connection initialization. Returns TRUE if the
 * client has been accepted, according to the gems protocol. */
fbool_t validate_client(int fd, int max_conn, t_server_data *data)
{
	char *client_version;
	fbool_t r;

	/* Check for maximum allowed connections */
	if (data->clients >= max_conn)
	{
		g_log(LOG_MAXCONN);
		if (send_disconnect(fd) != TRUE) return FAIL;
		return FALSE;
	}

	/* Send server version */
	if (send_version(fd) != TRUE) return FAIL;
	if ((r = get_ack(fd)) != TRUE) 
	{
		if (r == FALSE) g_log(LOG_SERVER_VERSION);
		return r;
	}

	/* Get client version and compare */
	if ((r = get_peer_version(fd, &client_version)) != TRUE) return r;
	if (strncmp(client_version, PROTOCOL_VERSION, strlen(PROTOCOL_VERSION)))
	{
		/* Different versions. */
		/* If client version is "1.0b", accept. (That version uses the same
		 * protocol): */
		if (strcmp(client_version, "1.0b"))
		{
			g_log(LOG_CLIENT_VERSION);
			if (send_disconnect(fd) != TRUE) return FAIL;
			return FALSE;
		}
	}
	if (send_ack(fd) != TRUE) return FAIL;

	/* Send terminal size: */
	if (send_winsize(fd) != TRUE) return FAIL;
	if ((r = get_ack(fd)) != TRUE)
	{
		if (r == FALSE) g_log(LOG_WINSIZE);
		return r;
	}

	return TRUE;
}


/*****************************************************************************
 * add_waiting_client()
 *
 * Add a client to the waiting queue. */
t_waiting_client *add_waiting_client(t_server_data *data, pid_t pid,
	int socket, struct sockaddr_in *addr_data)
{
	t_waiting_client *new_wc;

	/* New waiting client client */
	if (!(new_wc = (t_waiting_client *) malloc(sizeof(t_waiting_client))))
	{
		g_log(ERR_MEMORY);
		return NULL;
	}

	new_wc->pid = pid;
	new_wc->socket = socket;
	new_wc->addr_data = *addr_data;

	/* Add the node to the start of the list (this results in a LIFO queue,
	 * but who cares): */
	new_wc->next = data->waiting_client_list;
	new_wc->prev = NULL;
	if (data->waiting_client_list != NULL) 
			data->waiting_client_list->prev = new_wc;
	data->waiting_client_list = new_wc;

	return new_wc;
}


/*****************************************************************************
 * accept_new_client()
 *
 * Make all necessary operations to accept a client connection. */
fbool_t accept_new_client(t_server_data *data, int max_conn) 
{
	int fd;
	struct sockaddr_in client_data;
	unsigned int addr_size = sizeof(client_data);  /* for accept() */
	pid_t pid;

	if ((fd = accept(data->sock_server, 
			(struct sockaddr *)&client_data, &addr_size)) == -1)
	{
		perror(APPNAME);
		return FAIL;
	}

	if (!(pid = fork()))
	{
		/* Child must validate the client and exit: */
		if (validate_client(fd, max_conn, data) != TRUE)
			_exit(1);

		_exit(0); /* Client accepted */
	}
	else if (pid > 0)
	{
		/* Father: must add the client to the waiting queue: */
		add_waiting_client(data, pid, fd, &client_data);
		return TRUE;
	}

	/* WTF? Error in fork()! */
	perror(APPNAME);
	close(fd);
	return FAIL;
}


/*****************************************************************************
 * add_client()
 *
 * Adds a client to the list of connected clients */
struct t_clsock *add_client(int socket, struct sockaddr_in *client_data, 
				t_server_data *data)
{
	struct t_clsock *new_client;
	char *ip_str;
	int ip_len;

	/* New connected client */
	if (!(new_client = (struct t_clsock *) malloc(sizeof(struct t_clsock))))
	{
		g_log(ERR_MEMORY);
		return NULL;
	}

	init_clsock(new_client);

	/* Get IP address: */
	ip_str = inet_ntoa(client_data->sin_addr);
	ip_len = strlen(ip_str) + 1; /* + 1 for the '\0' character */

	/* Store client info: */
	if (!(new_client->ip.ip_str = malloc(sizeof(char) * ip_len)))
	{
		g_log(ERR_MEMORY);
		return NULL;
	}
	memcpy(new_client->ip.ip_str, ip_str, ip_len);
	new_client->socket = socket;

	/* Add the node to the start of the list: */
	new_client->next = data->client_list;
	new_client->prev = NULL;
	if (data->client_list != NULL) 
			data->client_list->prev = new_client;
	data->client_list = new_client;

	data->clients++;

#ifdef DEBUG
	g_log(DEBUG_ADD_C, new_client->prev, new_client, new_client->next);
#endif
	g_log(LOG_NEW_C, data->clients, new_client->ip.ip_str);

	return new_client;
}


/*****************************************************************************
 * remove_waiting_client()
 *
 * Removes a client from the waiting queue */
void remove_waiting_client(t_server_data *data, t_waiting_client *wc)
{
	t_waiting_client *prev;
	t_waiting_client *next;

	if (wc == NULL) return;

	/* Store prev & next pointers: */
	if (wc->prev == NULL) data->waiting_client_list = wc->next;
	else
	{
		prev = wc->prev;
		prev->next = wc->next;
	}

	if (wc->next != NULL)
	{
		next = wc->next;
		next->prev = wc->prev;
	}

	free(wc);
}

/*****************************************************************************
 * check_new_client(t_server_data *data)
 *
 * This function is called after receiving SIGCHLD.
 * It wait()s, retrieves the child PID and removes the client from the
 * waiting queue.
 * A child exit status of 0 means that the associated client has been accepted.
 * In this case, this function must add the client to the connected clients
 * list. */
fbool_t check_new_client(t_server_data *data, int expected_connections)
{
	pid_t pid;
	int status;
	t_waiting_client *wc;

	pid = wait(&status);
	
	/* Search PID in the waiting queue: */
	for (wc = data->waiting_client_list; wc != NULL; wc = wc->next)
		if (wc->pid == pid) break;

	if (!wc) return FALSE; /* PID not found */
	
	/* Add the client to the connected clients list: */
	if (add_client(wc->socket, &(wc->addr_data), data) == NULL)
	{
		close(wc->socket);
		return FAIL;
	}
	FD_ADD(wc->socket, data);

	/* Remove client from the waiting queue: */
	remove_waiting_client(data, wc);
	
	/* If we must wait for a specified number of connections (-wait N
	 * option), check if this condition is met.
	 * To implement this, we simply add the input_fd file descriptor to
	 * open_fds when we want to start transmission. */
	if ((expected_connections > 0) &&
		!(FD_ISSET(data->input_fd, &(data->open_fds))))
	{
		if (data->clients == expected_connections) 
			FD_ADD(data->input_fd, data); /* Start transmission. */
	}

	return TRUE;
}


/*****************************************************************************
 * kill_waiting_clients()
 *
 * Kills all children processes that are waiting for client response. */
void kill_waiting_clients(t_server_data *data)
{
	t_waiting_client *wc;
	pid_t pid;

	/* Send SIGTERM to all children: */
	for (wc = data->waiting_client_list; wc != NULL; wc = wc->next) 
		kill(data->waiting_client_list->pid, SIGTERM);

	/* wait() until the queue is empty: */
	while (data->waiting_client_list != NULL)
	{
		pid = wait(NULL);
		for (wc = data->waiting_client_list; wc != NULL; wc = wc->next) 
		{
			if (wc->pid == pid)
			{
				remove_waiting_client(data, wc);
			}
		}
	}
}


/*****************************************************************************
 * init_clsock()
 *
 * Initialize a client node */
void init_clsock(struct t_clsock *client_sock)
{
	if (client_sock != NULL)
	{
		client_sock->socket = -1;

		client_sock->ip.ip_str = NULL;
		client_sock->ip.s_addr = 0;

		client_sock->prev = NULL;
		client_sock->next = NULL;
	}
}


/*****************************************************************************
 * del_client()
 *
 * Removes a client from the list of connected clients, closing its associated
 * socket */
void del_client(t_clsock *client, t_server_data *data)
{
	struct t_clsock *prev;
	struct t_clsock *next;

	if (client == NULL) return;

	close(client->socket);
	FD_CLR(client->socket, &(data->open_fds));

#ifdef DEBUG
	g_log(DEBUG_DEL_C, client->prev, client, client->next);
#endif
	g_log(LOG_CLOSE_C, client->ip.ip_str);

	/* Store prev & next pointers: */
	if (client->prev == NULL) data->client_list = client->next;
	else
	{
		prev = client->prev;
		prev->next = client->next;
	}

	if (client->next != NULL)
	{
		next = client->next;
		next->prev = client->prev;
	}

	free(client->ip.ip_str);
	free(client);

	data->clients--;
}


/*****************************************************************************
 * send_to_all()
 *
 * Send a message to all clients */
fbool_t send_to_all(gems_msg_type_t type, char *buffer, int n, 
		t_server_data *data)
{
	t_gems_msg msg;
	t_clsock *client, *next;

	msg.type = type;
	msg.len = n;
	msg.data = buffer;

	for (client = data->client_list; client; client = next)
	{
		/* Save pointer to next client, in case this one is deleted: */
		next = client->next;
		if (send_msg(client->socket, &msg) != TRUE)
			/* Must remove client from list: */
			del_client(client, data);
	}

	return TRUE;
}


/*****************************************************************************
 * send_input_to_clients()
 *
 * Reads data from input stream and transmits to all clients. */
fbool_t send_input_to_clients(t_server_data *data)
{
	int n;

	n = read(data->input_fd, gems_data_buffer, MSG_MAX_DATA_SIZE);

	if (n == 0) return FALSE;  /* EOF */
	else if (n == -1)
	{
		perror(APPNAME);
		return FAIL;
	}

	send_to_all(GEMS_DATA, gems_data_buffer, n, data);
	return TRUE;
}


/*****************************************************************************
 * attend_client()
 *
 * Recieves data from a client, and in case of EOF or error, closes the
 * connection. */
void attend_client(t_clsock *client, t_server_data *data)
{
	if (read(client->socket, gems_msg_buffer, MSG_MAX_DATA_SIZE) <= 0)
		/* Error or EOF */
		del_client(client, data);
}


/*****************************************************************************
 * send_winsize_to_clients()
 *
 * Sends the terminal size to all clients. */
void send_winsize_to_clients(t_server_data *data)
{
	struct winsize win;
	int size[2];

	ioctl(0, TIOCGWINSZ, (char *) &win);
	size[0] = (int) win.ws_col;
	size[1] = (int) win.ws_row;

	send_to_all(GEMS_WINSIZE, (char *)size, 2 * sizeof(int), data);
}


/*****************************************************************************
 * init_data()
 *
 * Initialize the server_data structure. */
void init_data(t_server_data *data, int input_fd, int expected_connections)
{
	data->clients = 0;
	data->client_list = NULL;
	data->waiting_client_list = NULL;
	data->input_fd = input_fd;
	data->sock_server = -1;
	FD_ZERO(&(data->open_fds));
	data->fdmax = 0;

	/* Is the -wait option enabled? If so, we must not add input_fd to
	 * open_fds, so that select() does not check this fd. */
	if (expected_connections == 0)
		/* Must not wait => start transmission immediately. */
		FD_ADD(data->input_fd, data);
}

/*****************************************************************************
 * check_winsize()
 *
 * Checks local terminal size and outputs a warning if greater than 80x25 */
void check_winsize()
{
	struct winsize win;

	/* Get terminal size: */
	ioctl(0, TIOCGWINSZ, (char *) &win);
	if ((win.ws_col > SUGGESTED_MAX_COLS) || (win.ws_row > SUGGESTED_MAX_ROWS))
	{
		fprintf(stderr, _("%s: warning: terminal size is greater "
			"than %dx%d.\n"), 
			APPNAME, SUGGESTED_MAX_COLS, SUGGESTED_MAX_ROWS);
	}
}


/*****************************************************************************
 * gems_server()
 *
 * Server main function. */
status_t gems_server(int input_fd, t_options *options)
{
	t_server_data data;
	fd_set rfds;				/* Temporary fd list for select() */
	struct t_clsock *client = NULL; /* List iterator */
	struct t_clsock *next_client = NULL; /* List iterator */
	status_t status = CONTINUE;	/* Program status and return value. */
	fbool_t r;

	/**************** Server initialization: ********************/
	/* Initialize structure with server info: */
	init_data(&data, input_fd, options->expected_connections);
	check_winsize();  /* Check terminal size: */
	status = init_sock_server(&data, options);  /* Start listening */
	install_sighandlers();  /* Install signal handlers */

	/********************** Main loop: **************************/
	for ( ; status == CONTINUE ; )
	{
		/* Wait until one of the fds has something to read: */
		rfds = data.open_fds;
		if (select(data.fdmax + 1, &rfds, NULL, NULL, NULL) == -1)
		{
			if (errno != EINTR) status = EXIT_FAIL;  /* error */
		}

		if (sigint_raised) status = EXIT_OK;
		else if (winsize_changed)
		{
			send_winsize_to_clients(&data);
			winsize_changed = 0;
		}
		else if (sigchld_raised)
		{
			/* A child process has finished. This means that a new client
			 * has (or not) been accepted. */
			check_new_client(&data, options->expected_connections);
			sigchld_raised--;
		}
		else if (FD_ISSET(data.input_fd, &rfds))
		{
			/* Must send data to all clients: */
			if ((r = send_input_to_clients(&data)) == FAIL)
				status = EXIT_FAIL;
			else if (r == FALSE) /* EOF */
				status = EXIT_OK;
		}
		else if (FD_ISSET(data.sock_server, &rfds))
			/* A new connection must be accept()ed: */
			accept_new_client(&data, options->maxconn);
		else
			/* Check clients: */
			for (client = data.client_list; client; client = next_client)
			{
				/* Save pointer to next client in case this client is deleted */
				next_client = client->next;
				if (FD_ISSET(client->socket, &rfds))
					attend_client(client, &data);
			}
	}

	kill_waiting_clients(&data);

	close_sockets(&data);
	return status;
}

/* vim: set ts=4 sw=4: */
