/*
 * Do the basic I/O ops.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "qick.h"

static char rcsid[] = "$Id: qio.c,v 2.2 2000/08/18 19:27:34 dgc Exp $";

/*
 * To debug, or not to debug
 */
void
_qi_debug(char *fmt, ...)
{
	va_list	 vp;

	va_start(vp, fmt);
	vfprintf(stderr, fmt, vp);
	va_end(vp);
	fflush(stderr);
}

/*
 * Open a connection with a server.
 */
int
qi_open(qi_t *qi)
{
#ifndef __GNUC__
# define __FUNCTION__	"qi_open"
#endif
	int			 s;
	struct sockaddr_in	 sin;
	struct hostent		*hp;
	int			 options = 1;
	char			*p;
	const char		*nohost = __FUNCTION__ ": requires hostname";
	const char		*nosuch = __FUNCTION__ ": no such host";
	const char		*nosock = __FUNCTION__ ": cannot make socket";
	const char		*noconn = __FUNCTION__ ": cannot connect";
	const char		*noopen = __FUNCTION__ ": cannot reopen socket";

	/* Return if no host. */
	if (qi->qi_host == NULL || *qi->qi_host == '\0') {
		qi_error(qi, (char *)nohost);
		return 0;
	}

	qi_debug("= host name: %s\n", qi->qi_host);

	/* Get host info. */
	if ((sin.sin_addr.s_addr = inet_addr(qi->qi_host)) == -1) {
		/* Not a dotted quad. */
		if ((hp = gethostbyname(qi->qi_host)) == NULL) {
			qi_error(qi, (char *)nosuch);
			return 0;
		} else {
			memset(&sin, 0, sizeof(sin));
			memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
			sin.sin_family = hp->h_addrtype;
		}
	} else {
		/* Is a dotted quad. */
		sin.sin_family = AF_INET;
	}

	/* Set port. */
	sin.sin_port = htons(105);

	/* Dr Feelgood says debug it all. */
	qi_debug("= host addr: %s\n", S(inet_ntoa(sin.sin_addr)));

	/* Make a socket. */
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		qi_error(qi, (char *)nosock);
		return 0;
	}
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &options, sizeof(options));
	setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &options, sizeof(options));

	/* Connect socket. */
	if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
		qi_error(qi, (char *)noconn);
		close(s);
		return 0;
	}

	/* Reopen as filehandles. */
	if ((qi->qi_to = fdopen(s, "w")) == NULL) {
		qi_error(qi, (char *)noopen);
		close(s);
		return 0;
	}
	if ((qi->qi_from = fdopen(s, "r")) == NULL) {
		qi_error(qi, (char *)noopen);
		close(s);
		fclose(qi->qi_to);
		qi->qi_to = NULL;
		return 0;
	}

	qi->qi_fd = s;

	return 1;
}

/*
 * Reset communications with a single server (in qi_t).
 */
void
qi_reset(qi_t *qi)
{
	fclose(qi->qi_to);
	fclose(qi->qi_from);
	qi_error(qi, "");
	qi->qi_to = qi->qi_from = NULL;
	qi->qi_count = 0;
	return;
}

/*
 * Close communications with a single server (in qi_t).
 */
void
qi_quit(qi_t *qi)
{
	fclose(qi->qi_to);
	fclose(qi->qi_from);
	qi->qi_host = NULL;
	qi->qi_to = qi->qi_from = NULL;
	qi->qi_count = 0;
	qi_error(qi, "");
	return;
}

/*
 * Close communications with all servers.
 */
void
qi_quit_all(qick_t *qick)
{
	list_t	*list, *save;
	qi_t	*qi;

	LIST_FOREACH_NO_INC(list, qick->q_servers) {
		if (LIST_DATUM(list) != NULL)
			qi_quit((qi_t *)LIST_DATUM(list));
		save = list;
		list = LIST_NEXT(list);
		free(save);
	}

	qick->q_servers = NULL;
	return;
}

/*
 * Write to qi (guts).
 */
int
qi_vwrite(qi_t *qi, char *fmt, va_list vp)
{
	int	 err = 0;

	if (qi_cxn_check(qi) == 0)
		return NULL;

#ifdef DEBUG
	fprintf(stderr, "> ");
	vfprintf(stderr, fmt, vp);
	fprintf(stderr, "\n");
	fflush(stderr);
#endif
	if (vfprintf(qi->qi_to, fmt, vp) == EOF)
		--err;
	if (fprintf(qi->qi_to, "\n") == EOF)
		--err;
	fflush(qi->qi_to);

	return err;
}

/*
 * Write to qi (interface).
 */
int
qi_write(qi_t *qi, char *fmt, ...)
{
	va_list	 vp;
	int	 err;

	va_start(vp, fmt);
	err = qi_vwrite(qi, fmt, vp);
	va_end(vp);
	return err;
}

/*
 * Read a line from a server, and break it out into its components.
 * If a qim_t is passed, it must include a qim->qim_data of at least
 * 1024.  Otherwise, one is created for you.  On failure, any NEW
 * qim and qim_data are destroyed; on success, qim is returned.
 * You may call qi_read once with a NULL argument, then again with
 * the return from a previous call, to avoid lots of malloc ops.
 * A passed-in qim will NOT be destroyed on failure by qi_read().
 */
qim_t *
qi_read(qi_t *qi, qim_t *qimp)
{
	qim_t		*qim;
	char		*p, *q;
	static char	 data[1024];

	if (qi_cxn_check(qi) == 0)
		return NULL;

	if (qimp != NULL) {
		qim = qimp;
	} else if ((qim = qim_new()) == NULL) {
		syslog(LOG_ERR, "qi_read: cannot alloc for qi response (1)");
		return NULL;
	}

	data[1023] = '\0';

	while (1) {
		if (fgets(data, 1022, qi->qi_from) == NULL) {
			syslog(LOG_ERR, "qi_read: hangup?");
			if (qimp == NULL)
				qim_free(qim);
			return NULL;
		}
		if ((p = strrchr(data, '\n')) != NULL)
			*p = '\0';
		else if ((p = strrchr(data, '\r')) != NULL)
			*p = '\0';

		qi_debug("< %s\n", data);

		p = data;
		if ((q = strchr(p, ':')) == NULL) {
			break;
		}
		*q = '\0';
		qim->qim_code = atoi(p);
		p = ++q;
	
		/*
		 * 100-level codes are preliminary responses, and should
		 * be ignored.
		 */
		if (qim->qim_code >= 100 && qim->qim_code < 200) {

			qi_debug(": pass\n");
			continue;
		}

		/*
		 * Successful queries return -200 codes, which are
		 * formatted as "<code>:<subcode>:<field>:<value>".
		 */
		if (qim->qim_code == -200) {
			/* get subcode */
			if ((q = strchr(p, ':')) == NULL)
				break;
			*q = '\0';
			qim->qim_subcode = atoi(p);
			p = ++q;

			/* get field */
			while (isspace(*p))
				++p;
			if ((q = strchr(p, ':')) == NULL)
				break;
			*q = '\0';
			qim->qim_field = strdup(p);
			p = ++q;

		/*
		 * Others (code >= 200) have only code and value.
		 */
		} else {
			qim->qim_subcode = 0;
			qim->qim_field = NULL;
		}

		/* Only message remains, either way */
		while (isspace(*p))
			++p;
		qim->qim_value = strdup(p);

		qi_debug(":    code = %d\n", qim->qim_code);
		qi_debug(": subcode = %d\n", qim->qim_subcode);
		qi_debug(":   field = %s\n", S(qim->qim_field));
		qi_debug(":   value = %s\n", S(qim->qim_value));

		/* OK, good. */
		return qim;
	}

	syslog(LOG_ERR, "qi_read: bad response from qi");
	if (qimp == NULL)
		qim_free(qim);
	return NULL;
}


