/****************************************************************************
 * Copyright (C) 1998 WIDE Project. All rights reserved.
 * Copyright (C) 1999,2000,2001,2002 University of Tromso. All rights reserved.
 * Copyright (C) 2002 Invenia Innovation AS. All rights reserved.
 *
 * Author: Feike W. Dillema, feico@pasta.cs.uit.no.
 *         based on newbie code by Yusuke DOI, Keio Univ. Murai Lab.
 ****************************************************************************/

/*
 * <$Id: tcp_request.c,v 3.58 2005/01/27 11:50:29 dillema Exp $>
 */

#include "totd.h"

int tcp_request_start (struct context *cont) {
	const char *fn = "tcp_request_start()";
	struct sockaddr *sa;
	const int on = 1;
	int sock, timeout;

	syslog (LOG_DEBUG, "%s: start", fn);

	cont->process = tcp_request_wait_connect_process;
	cont->retry = tcp_request_wait_connect_retry;

	/*
	 * in case something goes wrong in setting up the TCP
	 * we will retry immediately with zero timeout (with next forwarder)
	 */
	timeout = 0;

	/*
	 * Note that this socket will be closed by context_destroy()
	 * which is called by request_abort() or request_finish()
	 */
	sa = (struct sockaddr *) cont->current_ns->list_data;
	sock = socket (sa->sa_family, SOCK_STREAM, 0);

	/* non-blocking IO, or die */
	if (sock == -1 || ioctl (sock, FIONBIO, (char *) &on) == -1) {
		syslog (LOG_WARNING, "%s: can't open socket: %m", fn);
		/* FAILURE */
		return (request_abort (cont, -1));
	}

	/* try to connect it */
	if (!connect (sock, sa, SOCKADDR_SIZEOF(*sa)) || errno == EINPROGRESS) {
		cont->conn_sock = sock;
		timeout = cont->timeout;

		if (ev_tcp_out_register (sock, cont) < 0)
			return (request_abort (cont, -1));
	} else {
		syslog (LOG_WARNING, "%s: can't connect: %m", fn);
                timeout = 0; /* force immediate retry */
	}

	if (context_timeout_register (cont, timeout) < 0)
		return (request_abort (cont, -1));

	syslog (LOG_DEBUG, "%s: Query will time out in %d seconds.",
			fn, cont->timeout);

	return 0;
}

int tcp_request_wait_connect_process (Context *cont) {
	char *fn = "tcp_request_wait_connect_process()";
	socklen_t errlen;
	int sockerr;

	syslog (LOG_DEBUG, "%s: start", fn);

	/* disable old timeout */
	if (cont->tout)
		cont->tout->handler = NULL;

	/* and add a new fresh one */
	if (context_timeout_register (cont, cont->timeout))
		return (request_abort (cont, -1));

	/* check whether connect() actually succeeded or not. */
	errlen = sizeof (sockerr);
	if (getsockopt (cont->conn_sock, SOL_SOCKET, SO_ERROR,
			(void *) &sockerr, &errlen))
		return (request_abort (cont, -1));

	switch (sockerr) {
	case 0:
		syslog (LOG_DEBUG, "%s: TCP connect succeeded", fn);
		/* change state to writing */
		cont->process = tcp_request_writing_process;
		cont->retry = tcp_request_writing_retry;

		/* 
		 * Reset wp to make sure writing
		 * process begins from first byte
		 */
		cont->wp = NULL;
		return 0;
	case ETIMEDOUT:
		syslog (LOG_DEBUG, "%s: TCP forwarder connect timed out", fn);
		return tcp_request_wait_connect_retry(cont);
	case ECONNREFUSED:
		syslog (LOG_WARNING, "Forwarder refused TCP connection");
		return tcp_request_wait_connect_retry(cont);
	case ENETUNREACH:
		syslog (LOG_WARNING, "TCP forwarder unreachable");
		return tcp_request_wait_connect_retry(cont);
	default:
		/*
		 * All else is local server failure. Not worth a retry,
		 * not even to a different forwarder
		 */
		return (request_abort (cont, -1));
	}
}

int tcp_request_wait_connect_retry (Context *cont) {
	char *fn = "tcp_request_wait_connect_retry";
	struct sockaddr *sa;
        const int on = 1;
	int sock, timeout;

	syslog (LOG_DEBUG, "%s: start", fn);

	/* disable old timeout, in case it is still there */
	if (cont->tout)
		cont->tout->handler = NULL;

	/* remove old I/O List */
	if (cont->conn_sock >= 0) {
		ev_tcp_out_remove (cont->conn_sock);
		/* stop current connection */
		close (cont->conn_sock);
		cont->conn_sock = -1;
	}

        if (request_retry (cont) < 0)
                return (request_abort (cont, -1));

	/*
	 * in case something goes wrong in setting up the TCP
	 * we will retry immediately with zero timeout (with next forwarder)
	 */
	timeout = 0;

	/* create new socket: non-blocking IO, or die */
	sa = (struct sockaddr *) cont->current_ns->list_data;
	sock = socket (sa->sa_family, SOCK_STREAM, 0);

	if (sock == -1 || ioctl (sock, FIONBIO, (char *) &on) == -1) {
		syslog (LOG_WARNING, "%s: can't open socket: %m", fn);
		return (request_abort (cont, -1));
	}

	/* try to connect it */
	if (!connect (sock, sa, SOCKADDR_SIZEOF(*sa))
	    || errno == EINPROGRESS) {

		cont->conn_sock = sock;
		timeout = cont->timeout;

		/* put me to input list */
		if (ev_tcp_out_register (cont->conn_sock, cont) < 0)
			return (request_abort (cont, -1));
	}

	if (!timeout) {
		syslog (LOG_WARNING, "%s: can't TCP connect: %m", fn);
                syslog (LOG_DEBUG, "%s: force retry at zero timeout", fn);
	}

	/* put me to timeout list */
	if (context_timeout_register (cont, timeout) < 0)
		return (request_abort (cont, -1));

	syslog (LOG_DEBUG, "%s: Query will time out in %d seconds.",
		fn, cont->timeout);

	/* SUCCESS */
	return 0;
}

int tcp_request_writing_process (Context *cont) {
        char *fn = "tcp_request_writing_process";

	syslog (LOG_DEBUG, "%s: start", fn);

	/* renew timeout */
	if (cont->tout)
		cont->tout->handler = NULL;

	if (!context_timeout_register (cont, cont->timeout)) {
		switch (tcp_writemesg (cont, cont->conn_sock)) {
		case 0: /* continue this process */
			syslog (LOG_DEBUG, "%s: return, continue writing", fn);
			return 0;
		case 1: /* all data written */
			if (!ev_tcp_conn_in_register (cont->conn_sock, cont)) {
				/* delete old I/O list */
				ev_tcp_out_remove (cont->conn_sock);

				/* go to readlen context */
				cont->process = tcp_request_readlen_process;
				cont->retry = tcp_request_readlen_retry;

				/* SUCCESS */
				return 0;
			}
			/* FAILURE */
			break;
		default:
			/* FAILURE */
			break;
		}
	}

	return (request_abort (cont, -1));

}

int tcp_request_writing_retry (Context *cont) {
	syslog (LOG_NOTICE, "tcp connection not responding.... closed");
	return (request_abort (cont, -1));
}

int tcp_request_readlen_process (Context *cont) {
	char *fn = "tcp_request_readlen_process()";
	uint16_t length_buf;

	syslog (LOG_DEBUG, "%s: start", fn);

	/* renew timeout */
	if (cont->tout)
		cont->tout->handler = NULL;

	if (context_timeout_register (cont, cont->timeout) < 0)
		return (request_abort (cont, -1));

	/* read length buffer */
	if (read (cont->conn_sock, (u_char *) (&length_buf), sizeof (uint16_t))
	    < sizeof (uint16_t)) {
		syslog (LOG_NOTICE, "cannot read length on TCP connection.");
		return (request_abort (cont, -1));
	}
	length_buf = ntohs (length_buf);

	syslog (LOG_DEBUG, "%s: data length = %d", fn, length_buf);

	free (cont->mesg.p);
	cont->mesg_len = 0;

	cont->mesg.p = malloc (length_buf);
	if (!cont->mesg.p) {
		syslog (LOG_ERR, "%s: Cannot allocate memory", fn);
		return (request_abort (cont, -1));
	}
	cont->mesg_len = length_buf;
	cont->wp = cont->mesg.p;

	/* next state */
	cont->process = tcp_request_reading_process;
	cont->retry = tcp_request_reading_retry;
	return 0;
}

int tcp_request_readlen_retry (Context *cont) {
	char *fn = "tcp_request_readlen_retry()";

	syslog (LOG_NOTICE, "TCP connection not respond.... closed");

	/* Fast GiveUp Hack */
	if (cont->q_type == RT_AAAA || cont->q_type == RT_A6) {
		/*
		 * Some nameservers just do not like IPv6
		 * address records. We guess that is the case
		 * here and tell the parent to continue if it
		 * can.
		 *
		 * We perform the code of request_finish()
		 * here, but without the parsing of the
		 * response (we have none).
		 *
		 * Note that this means that a timeout for a IPv6
		 * address record request never causes a shift to
		 * the next forwarder! Seems reasonable in the
		 * for the majority of cases.
		 */

		syslog (LOG_DEBUG, "Giving up quickly on IPv6 address record");

		/* re-initialize answer, nameserver, additional record lists */
		list_destroy (cont->an_list, rrset_freev);
		list_destroy (cont->ns_list, rrset_freev);
		list_destroy (cont->ar_list, rrset_freev);
		cont->an_list = list_init ();
		cont->ns_list = list_init ();
		cont->ar_list = list_init ();
		if (!cont->an_list || !cont->ns_list || !cont->ar_list)
			return request_abort (cont, -1);

		if (cont->parent) {
			syslog (LOG_DEBUG, "%s: process parent context", fn);
			cont->parent->process (cont->parent);
		}

		/* ... so we cleanup ourselves in any case */
		context_destroy (cont);
		return 0; /* SUCCESS */
	}
	return (request_abort (cont, -1));
}

int tcp_request_reading_process (Context *cont) {
	const char *fn = "tcp_request_reading_process()";

	syslog (LOG_DEBUG, "%s: start", fn);

	/* renew timeout */
	if (cont->tout)
		cont->tout->handler = NULL;

	if (!context_timeout_register (cont, cont->timeout)) {
		int len;

		len = read (cont->conn_sock, cont->wp,
			    cont->mesg_len - (cont->wp - cont->mesg.p));

		if (len <= 0)
			syslog (LOG_NOTICE, "%s: Read failed on TCP: %m", fn);
		else {
			cont->wp += len;
			if (cont->wp < (cont->mesg.p + cont->mesg_len))
				return 0; /* continue reading */
			else
				return request_finish (cont);
		}
	}

	/* FAILURE */
	return (request_abort (cont, -1));
}

int tcp_request_reading_retry (Context *cont) {
	syslog (LOG_NOTICE, "connection not responding... closed");
	return (request_abort (cont, -1));
}

int tcp_writemesg (Context *cont, int sock) {
	char *fn = "tcp_writemesg()";
	uint16_t lenbuf_nbo;
	int len;

	if (!cont->wp) {
		/* first time: write length */
		lenbuf_nbo = htons (cont->mesg_len);
		len = write (sock, (u_char *)(&lenbuf_nbo), sizeof (uint16_t));
		if (len < 0) {
			syslog (LOG_INFO, "%s: write length failed: %m", fn);
			return -1;
		}
		cont->wp = cont->mesg.p;
		return 0;
	}

	/* been here before */
	len = write (sock, cont->wp,
		     cont->mesg_len - (cont->wp - cont->mesg.p));
	if (len < 0) {
		syslog (LOG_INFO, "%s: write failed: %m", fn);
		return -1;
	}
	cont->wp += len;

	if (cont->wp < cont->mesg.p + cont->mesg_len)
		return 0;
	else
		return 1;

	/* NOTREACHED */
	abort ();
}



syntax highlighted by Code2HTML, v. 0.9.1