/*
 * Routines for getting the server info from a server config file
 * and connecting to it.
 */

#include "icb.h"
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>


/************************************************************************
 * Utils for reading the server file
 ***********************************************************************/


/* read a line from the server file and populate serv.
 */
int readserverent(FILE *fd, struct server *serverdata)
{
	char line[256];
	int index;

	if (!fd || !serverdata) {
		return -1;
	}

	for (;;) {
		if (!fgets(line,256,fd)) {
			return 0;
		}

		index = 0;

		while (line[index] && isspace(line[index]))
			++index;

		if (!line[index] || line[index] == '#') {
			continue;
		}
			
		if ((sscanf(line, "\"%[^\"]\" %s %d", serverdata->name, serverdata->host,
			&(serverdata->port))!=3) &&
			(sscanf(line, "%s %s %d", serverdata->name, serverdata->host,&(serverdata->port))!=3))
		{
			serverdata->port = DEFAULT_PORT;
			if ((sscanf(line, "\"%[^\"]\" %s", serverdata->name, serverdata->host)!=2) &&
				(sscanf(line, "%s %s", serverdata->name, serverdata->host)!=2))
			{
				if (sscanf(line, "%s", serverdata->host) == 1)
				{
					strcpy(serverdata->name, serverdata->host);
				}
				else
				{
					fprintf(stderr,"Bad server entry: %sSkipping...\n",line);
					continue;
				}
			}
		}

		return 1;
	}
}

/* open the server config file
 * return NULL and set errno if it couldn't be opened
 *
 * if they have a .icbservrc in their home directory, cat it and the
 * system one together and then use the output of the cat.
 */
FILE *openserverfile()
{
	struct stat statbuf;
	char command[256];
	char pwd[PATH_MAX + 1];
	FILE *ret;

	getcwd(pwd, PATH_MAX+1);

	chdir(getenv("HOME"));

	if (!stat(PERSONALSL,&statbuf)) {
		sprintf(command,"/bin/cat %s %s\n", PERSONALSL, SERVERLIST);
		ret= popen(command,"r");
	}
	else
		ret= fopen(SERVERLIST,"r");

	chdir(pwd);
	return ret;
}


/* read from the server config file, looking for a named entry.
 * If it finds it, it returns a pointer to the server entry on
 * success and NULL on failure.
 */
int
getserver(char *name, struct server *serverdata)
{
	int ret = -1;
	FILE *serverfile;

	if (!name || !serverdata)
		return -1;

	if ( (serverfile = openserverfile()) == NULL)
		return -1;

	while (readserverent(serverfile, serverdata)) {
		if (name) {
			if (!strcmp(name, serverdata->name)) {
				ret = 0;
				break;
			}
			else
			{
			    if (!strcmp(name, serverdata->host))
			    {
				ret = 0;
				myserver = strdup(serverdata->name);
				break;
			    }
			}
		} else {
			ret = 0;
			break;
		}
	}

	fclose(serverfile);
	return ret;
}


/* print out a list of the entries in the server config file.
 */
void listservers()
{
	FILE *serverfile;
	struct server serverbuf;

	if ( (serverfile = openserverfile()) == NULL) {
		printf("Can't open server file.\n");
		return;
	} else {
		rewind(serverfile);
	}

	printf("%-16s%-32s%5s\n", "SERVER", "HOST", "PORT");
	while (readserverent(serverfile, &serverbuf)) {
		printf("%-16s%-32s%5d\n", serverbuf.name, serverbuf.host, serverbuf.port);
	}
	fclose(serverfile);
}


/************************************************************************
 * Connect to the server
 ***********************************************************************/

/* Given a string, return an internet address.
 * This accepts address in the form of 
 *     num.num.num.num
 *     [num.etc]
 *     machine.dom.dom.dom
 * Returns NULL if the address couldn't be determined.
 */
struct in_addr *
get_address(char *spoo)
{
	static struct in_addr iaddr;
	struct hostent *hp;

	/* handle case of "[num.num.num.num]" */
	if (*spoo == '[') {
		char *p;

		spoo++;
		for (p = spoo; *p != '\0'; p++)
			if (*p == ']') {
				*p = '\0';
				break;
			}
	}

	/* handle case of "num.num.num.num" */
	if (*spoo >= '0' && *spoo <= '9') {
		if ((iaddr.s_addr = (unsigned long)inet_addr(spoo)) == -1)   
			return(NULL);  
		return(&iaddr);
	} 

	/* handle a symbolic address */
	if ((hp = gethostbyname(spoo)) == (struct hostent *) 0) 
		return(NULL); 

	/* copy address into inet address struct */
	memcpy (&iaddr.s_addr, hp->h_addr, hp->h_length);

	return((struct in_addr *) &iaddr);
}


/* Connect to a server port.
 * Return a file descriptor on success
 * Return -1 on fail
 */
int
server_connect (char * host_name, int port_number, char * bind_host)
{
    int s;
    struct sockaddr_in saddr;
    struct in_addr * addr;

    fprintf (stderr, "Trying to connect to port %d of host %s.\n",
	     port_number, host_name);
    myhost = strdup (host_name);

    /* get the client host inet address */
    if ((addr = get_address (host_name)) == NULL) {
	fprintf (stderr, "icb: can't get address of %s\n", host_name);
	return -1;
    }

    /* insert hostname into address */
    memset (&saddr, 0, sizeof saddr);
    memcpy (&saddr.sin_addr, addr, sizeof *addr);

    /* fill in socket domain and port number */
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons ((in_port_t) port_number);

    /* create a socket */
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
	fprintf (stderr, "icb: couldn't connect to %s server.\n", host_name);
	return -1;
    }

    if (bind_host != NULL) {
	struct sockaddr_in bind_saddr;

	if ((addr = get_address (bind_host)) == NULL) {
	    fprintf (stderr, "icb: can't get address of %s\n", bind_host);
	    return -1;
	}

	/* insert bind_host hostname into address */
	memset (&bind_saddr, '\0', sizeof bind_saddr);
	memcpy (&bind_saddr.sin_addr, addr, sizeof addr);

	/* fill in socket domain */
	bind_saddr.sin_family = AF_INET;

	if (bind (s, (struct sockaddr *) &bind_saddr, sizeof bind_saddr) < 0) {
	    fprintf (stderr, "icb: can't bind socket to %s\n", bind_host);
	    return -1;
	}
    }

    /* Try to set keepalive.  No big deal if it fails. */

#ifdef SO_KEEPALIVE
    {
	int val = 1;
	if (setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof val) < 0) {
	    fprintf (stderr, "Warning: can't set socket options: %s\n",
		     strerror (errno));
	}
    }
#endif

    /* connect it to the server inet address */
    if (connect (s, (struct sockaddr *) &saddr, sizeof saddr) < 0) {
	fprintf (stderr, "icb: can't connect to %s.\n", host_name);
	return -1;
    }

    /* make it non-blocking */
#ifdef O_NONBLOCK
    if (fcntl (s, F_SETFL, O_NONBLOCK) < 0) {
	return -1;
    }
#endif

    return s;
}


/************************************************************************
 * Main entry point
 ***********************************************************************/

/* look up server in the server file list and connect to it.
 * returns the file descriptor of the socket to the
 * server on success, -1 on error.
 */

int 
connect_to_server(int use_nick, char *name, int port, char *bind_host)
{
    struct server serverdata;

    if (use_nick) {
        if ( getserver(name, &serverdata) < 0)
            return -1;
		else {
			name = serverdata.host;
			port = serverdata.port; 
		}
    } else {
        if (!name) {
			/* if there is no name, then get the default server */
			if ( getserver(NULL, &serverdata) < 0) {
				name = DEFAULT_HOST;
				getserver(name, &serverdata);
			}
		}
		port = (port ? port : DEFAULT_PORT);
    }

    return server_connect(name, port, bind_host);
}
