#include "ldapdns.h"
#include "ip.h"
#include "env.h"
#include "config.h"
#include "sio.h"
#include "bin.h"

static int server_fd = -1;
static char tcpserver_ip[IP_LEN];
static int tcpserver_port;
static bin_t tcpserver_active;
#ifdef HAVE_IPV6
static int using_6 = 0;
#endif

void tp_close(dns_ctx *c)
{
	if (server_fd != -1) {
		/* shutdown this connection */
		pthread_mutex_lock(&c->lock);
		close(c->sock);
		c->sock = -1;
		pthread_mutex_unlock(&c->lock);
	} else {
		/* then exit; we're a uniprocess hosted by
		 * tcpserver or something */
		exit(0);
	}
}
void inline tp_housekeeping(long *now)
{
	dns_ctx *p;

	if (server_fd == -1)
		return;

	/* run a poll on all sockets */
reload_l:
	sio_flush(tcpserver_active);
	sio_add(tcpserver_active, server_fd, sio_read);

	for (p = handler; p; p = p->next) {
		if (p->sock == -1) continue;
		sio_add(tcpserver_active, p->sock, sio_read);
	}

	if (ldapdns.timeout_tcp > 0) {
		if (sio_block(tcpserver_active, ldapdns.timeout_tcp) == 0) {
			goto reload_l;
		}
	} else {
		sio_block(tcpserver_active, sio_infinity);
	}
}

void tp_initialize(void)
{
	char *x;
	int port;

	server_fd = -1;
	ldapdns.dns_threads = 1;	/* always 1; we use select */

	x = env_get("TCPREMOTEIP");
	if (!x || !ipv4_scan(x, tcpserver_ip) ) {
		tcpserver_ip[0] = 0;
		tcpserver_ip[1] = 0;
		tcpserver_ip[2] = 0;
		tcpserver_ip[3] = 0;
	} else {
		/* tcpserver or clone */
		ldapdns.ldap_threads = 1;
		ldapdns.handlers = 1;

		x = env_get("TCPREMOTEPORT");
		if (!x) {
			tcpserver_port = 0;
		} else if ((tcpserver_port = atoi(x)) < 1) {
			tcpserver_port = 0;
		}
		return;
	}

	if (socket_peer4(0, tcpserver_ip, &tcpserver_port)) {
		/* okay, we're running xinetd */
		ldapdns.ldap_threads = 1;
		ldapdns.dns_threads = 1;
		ldapdns.handlers = 1;
		return;
	}

	/* okay, we're not attached to a socket; let's change that */
	x = env_get("IP");
	if (!x)
		x = "0.0.0.0";
	if (!ipv4_scan(x, tcpserver_ip)) {
#ifdef HAVE_IPV6
		if (ipv6_scan(x, tcpserver_ip))
			using_6 = 1;
		else
#endif
		fatal("cannot parse IP: %s", x);
	}

	x = env_get("PORT");
	if (!x)
		port = 53;
	else {
		port = atoi(x);
		if (port < 1)
			fatal("cannot parse PORT: %s", x);
		if (port != 53)
			warning("running on non-standard port: %d", port);
	}

#ifdef HAVE_IPV6
	if (using_6)
		server_fd = socket_tcp6();
	else
#endif
	server_fd = socket_tcp4();
	if (server_fd == -1)
		cfatal("socket_tcp: %s");
#ifdef HAVE_IPV6
	if (using_6) {
		if (socket_bind6_reuse(server_fd, tcpserver_ip, port) == -1)
			cfatal("socket_bind4_reuse: %s");
	} else
#endif
	if (socket_bind4_reuse(server_fd, tcpserver_ip, port) == -1)
		cfatal("socket_bind4_reuse: %s");
	socket_listen(server_fd);
	ndelay_on(server_fd);
	bin_init(tcpserver_active);
}
int inline tp_write(dns_ctx *c)
{
	int x, r;
	unsigned short ntcplen;

	/* we lock here to make certain our socket isn't pulled out from
	 * under us
	 */
	pthread_mutex_lock(&c->lock);
	if (c->sock == -1) {
		pthread_mutex_unlock(&c->lock);
		return 0;
	}

	/* Clib */
	ntcplen = ntohs(clen(c->response));

	/* this COULD hang. however, i don't find it too likely to cause
	 * any problems: the client will hangup eventually and we'll get EPIPE
	 */

	do {
		r = write(c->sock, &ntcplen, 2);
	} while (r == -1 && errno == EINTR);
	pthread_mutex_unlock(&c->lock);

	for (x = 0; x < clen(c->response);) {
		/* we're spinning this lock because this process cannot
		 * tell when hangup occurs
		 */
		pthread_mutex_lock(&c->lock);
		if (c->sock == -1) {
			/* stolen out from under us */
			pthread_mutex_unlock(&c->lock);
			return 0;
		}
		/* still valid */
		r = write(c->sock,
			caddr(c->response) + x,
			clen(c->response) - x);
		pthread_mutex_unlock(&c->lock);

		if (r == -1) {
			if (errno == EPIPE || errno == EBADF || errno == EINVAL || errno == EFAULT) {
				/* failed output */
				return 0;
			}
			continue;
		}

		if (!r) {
			/* sleep for a moment;
			 * just in case it was kernel related */
			usleep(100);
		}

		x += r;
	}
	return 1;
}
static int inline trash_message (dns_ctx *c)
{
	register int i, j;

	if (c->tcplen == 0) return 0;
	if (c->tcppos < c->tcplen) return 0;

	/* move memory in request_buf */
	j = c->tcplen+2;
	for (i = 0; i < j;) {
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
		c->request_buf[i] = c->request_buf[j]; i++; if (i == j) break;
	}
	c->tcppos -= j;
	c->tcplen = 0;
	c->request_len = 0;
	c->request_pos = 0;
	if (c->tcppos == 0)
		return 0;
	return 1;
}
int inline tp_read(dns_ctx *c)
{
	int fd, len = 0;
	unsigned short ntcplen;
	list_t ax;
	str_t retbuf;

	if (server_fd == -1) {
		c->ip[0] = tcpserver_ip[0];
		c->ip[1] = tcpserver_ip[1];
		c->ip[2] = tcpserver_ip[2];
		c->ip[3] = tcpserver_ip[3];
		c->port = tcpserver_port;
		c->sock = 1;
		fd = 0;
	} else {
		/* could be hung at this point */
		pthread_mutex_lock(&c->lock);
		if (c->sock == -1) {
			pthread_mutex_unlock(&c->lock);

			/* see if we can accept server_fd */
			if (!sio_test(tcpserver_active, server_fd)) {
				/* we'll come back later */
				return -1;
			}
			sio_remove(tcpserver_active, server_fd);

			/* we'll have to do a round before this guy can have
			 * activity performed
			 */
#ifdef HAVE_IPV6
			if (using_6) {
				c->sock = socket_accept6(server_fd, c->ip, &c->port);
			} else
				c->sock = socket_accept4(server_fd, c->ip, &c->port);
#endif
			c->tcppos = 0;
			c->tcplen = 0;
			c->request_pos = 0;
			return 0;
		} else {
			/* both sides are socket */
			if (!sio_test(tcpserver_active, c->sock)) {
				pthread_mutex_unlock(&c->lock);
				if (trash_message(c))
					goto past_read_shortcut_l;
				return 0;
			}
			sio_remove(tcpserver_active, c->sock);

			/* fall through */
			fd = c->sock;
			pthread_mutex_unlock(&c->lock);
		}
	}

	trash_message(c);
reread_shortcut_l:
	do {
		/* fd cannot be stolen out from under us;
		 * because the thread that does this is the only
		 * one that closes the socket
		 */
		len = read(fd, /* stdin */
				c->request_buf + c->tcppos,
				512 - c->tcppos);
		/* infinite if 0 hangs up */
		if (len == -1) {
			/* we'll be back */
			if (errno == EAGAIN || errno == EINTR) {
				return 0;
			}

			if (errno == EBADF || errno == EINVAL
					|| errno == EFAULT)
				goto FATAL;
		} else if (len == 0) {
			/* hung up */
			goto FATAL;
		}
	} while (len <= 0);
	if (len >= 512) {
		warning("read too long");
		goto FATAL;
	}
past_read_shortcut_l:
	if (c->tcplen == 0) {
		/* the BEGINNING of a tcp message is packet length */
		c->request_len += len; /* fudge for now */
		if (!dns_packet_copy(c, (char *)&ntcplen, 2)) {
			/* not enough room yet... */
			if (server_fd == -1)
				goto reread_shortcut_l;
			return 0;
		}
		/* Clib */
		c->tcplen = ntohs(ntcplen);
		if (c->tcplen > 512) {
			warning("read too long");
			goto FATAL;
		}
	}

	c->tcppos += len;
	if (c->tcppos < c->tcplen) {
		if (server_fd == -1)
			goto reread_shortcut_l;
		/* incomplete message */
		return 0;
	}
	if (ldapdns.update)
		c->update = str_dup(ldapdns.update);
	else
		c->update = 0;
	if (ldapdns.axfr_base)
		c->axfr_base = str_dup(ldapdns.axfr_base);
	else
		c->axfr_base = 0;
	c->request_len = c->tcplen + 2;

	/* not in server mode? we don't need to do anything else */
	if (server_fd == -1)
		return 1;

	/* calculated AXFR support */
	for (ax = ldapdns.swaxfr; ax; ax = ax->next) {
		if (!ax->str) continue;
		if (ax->str[0] == 0x04) {
			if (ipv4_in_subnet(ax->str+1, c->ip)) {
				if (!ax->str[9]) {
					if (c->axfr_base) mem_free(c->axfr_base);
					c->axfr_base = 0;
					if (!ipv4_null(ax->str+1))
						break;
					continue;
				}
				name_to_dns(retbuf, ax->str + 9);
				if (c->axfr_base) mem_free(c->axfr_base);
				c->axfr_base = str(retbuf);
				if (!ipv4_null(ax->str+1))
					break;
			}
#ifdef HAVE_IPV6
		} else if (ax->str[0] == 0x06) {
			if (ipv6_in_subnet(ax->str+1, c->ip)) {
				if (!ax->str[33]) {
					if (c->axfr_base) mem_free(c->axfr_base);
					c->axfr_base = 0;
					if (!ipv4_null(ax->str+1))
						break;
					continue;
				}
				name_to_dns(retbuf, ax->str + 33);
				if (c->axfr_base) mem_free(c->axfr_base);
				c->axfr_base = str(retbuf);
				if (!ipv6_null(ax->str+1))
					break;
			}
#endif
		}
	}

	return 1;
FATAL:
	if (server_fd == -1)
		exit(0);

	/* lock to remove */
	pthread_mutex_lock(&c->lock);
	close(c->sock);
	c->sock = -1;
	pthread_mutex_unlock(&c->lock);
	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1