/*
 * (C) 2005-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
 *
 * This program 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.
 */

#include "internal/internal.h"

/*
 * XML output sample:
 *
 * <flow>
 * 	<meta direction="original">
 * 		<layer3 protonum="2" protoname="IPv4">
 * 			<src>192.168.0.1</src>
 * 			<dst>192.168.0.2</dst>
 * 		</layer3>
 * 		<layer4 protonum="16" protoname"udp">
 * 			<sport>80</sport>
 * 			<dport>56665</dport>
 * 		</layer4>
 * 		<counters>
 * 			<bytes>10</bytes>
 * 			<packets>1</packets>
 * 		</counters>
 * 	</meta>
 * 	<meta direction="reply">
 * 		<layer3 protonum="2" protoname="IPv4">
 * 			<src>192.168.0.2</src>
 * 			<dst>192.168.0.1</dst>
 * 		</layer3>
 * 		<layer4 protonum="16" protoname="udp">
 * 			<sport>80</sport>
 * 			<dport>56665</dport>
 * 		</layer4>
 * 		<counters>
 * 			<bytes>5029</bytes>
 * 			<packets>12</packets>
 *		</counters>
 * 	</meta>
 * 	<meta direction="independent">
 * 		<state>ESTABLISHED</state>
 * 		<timeout>100</timeout>
 * 		<mark>1</mark>
 * 		<secmark>0</secmark>
 * 		<id>453281439</id>
 * 		<use>1</use>
 * 		<assured/>
 * 	</meta>
 * </flow>
 */

const char *__proto2str(uint8_t protonum)
{
	const char *str = NULL;

	if (protonum < ARRAY_SIZE(proto2str))
		str = proto2str[protonum];

	if (str == NULL)
		str = "unknown";

	return str;
}

const char *__l3proto2str(uint8_t protonum)
{
	const char *str = NULL;

	if (protonum < ARRAY_SIZE(l3proto2str))
		str = l3proto2str[protonum];

	if (str == NULL)
		str = "unknown";

	return str;
}

static int __snprintf_ipv4_xml(char *buf,
			       unsigned int len,
			       const struct __nfct_tuple *tuple,
			       unsigned int type)
{
	struct in_addr addr = { 
		.s_addr = (type == __ADDR_SRC) ? tuple->src.v4 : tuple->dst.v4,
	};

	return snprintf(buf, len, "%s", inet_ntoa(addr));
}

static int __snprintf_ipv6_xml(char *buf,
			       unsigned int len,
			       const struct __nfct_tuple *tuple,
			       unsigned int type)
{
	struct in6_addr addr;
	static char tmp[INET6_ADDRSTRLEN];
	const void *p = (type == __ADDR_SRC) ? &tuple->src.v6 : &tuple->dst.v6;

	memcpy(&addr, p, sizeof(struct in6_addr));

	if (!inet_ntop(AF_INET6, &addr, tmp, sizeof(tmp)))
		return -1;

	return snprintf(buf, len, "%s", tmp);
}

int __snprintf_addr_xml(char *buf, unsigned int len,
			const struct __nfct_tuple *tuple,
			enum __nfct_addr type)
{
	int ret;
	unsigned int size = 0, offset = 0;
	const char *tag = (type == __ADDR_SRC) ? "src" : "dst";

	ret = snprintf(buf, len, "<%s>", tag);
	BUFFER_SIZE(ret, size, len, offset);

	switch (tuple->l3protonum) {
	case AF_INET:
		ret = __snprintf_ipv4_xml(buf+offset, len, tuple, type);
		BUFFER_SIZE(ret, size, len, offset);
		break;
	case AF_INET6:
		ret = __snprintf_ipv6_xml(buf+offset, len, tuple, type);
		BUFFER_SIZE(ret, size, len, offset);
		break;
	}

	ret = snprintf(buf+offset, len, "</%s>", tag);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

int __snprintf_proto_xml(char *buf, unsigned int len,
			 const struct __nfct_tuple *tuple,
			 enum __nfct_addr type)
{
	int ret = 0;
	unsigned int size = 0, offset = 0;

	switch(tuple->protonum) {
	case IPPROTO_TCP:
	case IPPROTO_UDP:
	case IPPROTO_UDPLITE:
	case IPPROTO_SCTP:
	case IPPROTO_DCCP:
		if (type == __ADDR_SRC) {
			ret = snprintf(buf, len, "<sport>%u</sport>", 
				       ntohs(tuple->l4src.tcp.port));
			BUFFER_SIZE(ret, size, len, offset);
		} else {
			ret = snprintf(buf, len, "<dport>%u</dport>",
				       ntohs(tuple->l4dst.tcp.port));
			BUFFER_SIZE(ret, size, len, offset);
		}
		break;
	case IPPROTO_GRE:
		if (type == __ADDR_SRC) {
			ret = snprintf(buf, len, "<srckey>0x%x</srckey>", 
				       ntohs(tuple->l4src.all));
			BUFFER_SIZE(ret, size, len, offset);
		} else {
			ret = snprintf(buf, len, "<dstkey>0x%x</dstkey>",
				       ntohs(tuple->l4dst.all));
			BUFFER_SIZE(ret, size, len, offset);
		}
		break;
	}

	return ret;
}

static int __snprintf_counters_xml(char *buf,
				   unsigned int len,
				   const struct nf_conntrack *ct,
				   unsigned int type)
{
	int ret;
	unsigned int size = 0, offset = 0;

	ret = snprintf(buf, len, "<packets>%llu</packets>",
		       (unsigned long long)ct->counters[type].packets);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<bytes>%llu</bytes>",
		       (unsigned long long)ct->counters[type].bytes);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int
__snprintf_timestamp_start(char *buf, unsigned int len,
			   const struct nf_conntrack *ct)
{
	int ret;
	unsigned int size = 0, offset = 0;

	ret = snprintf(buf, len, "<start>%llu</start>",
		       (unsigned long long)ct->timestamp.start);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int
__snprintf_timestamp_stop(char *buf, unsigned int len,
			  const struct nf_conntrack *ct)
{
	int ret;
	unsigned int size = 0, offset = 0;

	ret = snprintf(buf, len, "<stop>%llu</stop>",
		       (unsigned long long)ct->timestamp.stop);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int
__snprintf_deltatime_now(char *buf, unsigned int len,
			 const struct nf_conntrack *ct)
{
	int ret;
	unsigned int size = 0, offset = 0;
	time_t now, delta_time;

	time(&now);
        delta_time = now - (time_t)(ct->timestamp.start / NSEC_PER_SEC);

	ret = snprintf(buf+offset, len, "<deltatime>%llu</deltatime>",
		(unsigned long long)delta_time);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int
__snprintf_deltatime(char *buf, unsigned int len, const struct nf_conntrack *ct)
{
	int ret;
	unsigned int size = 0, offset = 0;
	time_t delta_time = (time_t)((ct->timestamp.stop -
				ct->timestamp.start) / NSEC_PER_SEC);

	ret = snprintf(buf+offset, len, "<deltatime>%llu</deltatime>",
		(unsigned long long)delta_time);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int
__snprintf_helper_name(char *buf, unsigned int len, const struct nf_conntrack *ct)
{
	int ret;
	unsigned int size = 0, offset = 0;

	ret = snprintf(buf+offset, len, "<helper>%s</helper>", ct->helper_name);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

int
__snprintf_localtime_xml(char *buf, unsigned int len, const struct tm *tm)
{
	int ret = 0;
	unsigned int size = 0, offset = 0;

	ret = snprintf(buf+offset, len, "<hour>%d</hour>", tm->tm_hour);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<min>%02d</min>", tm->tm_min);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<sec>%02d</sec>", tm->tm_sec);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<wday>%d</wday>", tm->tm_wday + 1);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<day>%d</day>", tm->tm_mday);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<month>%d</month>", tm->tm_mon + 1);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "<year>%d</year>", 1900 + tm->tm_year);
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int __snprintf_tuple_xml(char *buf,
				unsigned int len,
				const struct nf_conntrack *ct,
				unsigned int dir, bool zone_incl)
{
	int ret;
	unsigned int size = 0, offset = 0;
	const struct __nfct_tuple *tuple = NULL;

	switch(dir) {
	case __DIR_ORIG:
		tuple = &ct->head.orig;
		break;
	case __DIR_REPL:
		tuple = &ct->repl;
		break;
	}
	ret = snprintf(buf, len, "<meta direction=\"%s\">",
		       dir == __DIR_ORIG ? "original" : "reply");
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, 
		       "<layer3 protonum=\"%d\" protoname=\"%s\">",
		       tuple->l3protonum, __l3proto2str(tuple->l3protonum));
	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_addr_xml(buf+offset, len, tuple, __ADDR_SRC);
	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_addr_xml(buf+offset, len, tuple, __ADDR_DST);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "</layer3>");
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, 
		       "<layer4 protonum=\"%d\" protoname=\"%s\">",
		       tuple->protonum, __proto2str(tuple->protonum));
	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_proto_xml(buf+offset, len, tuple, __DIR_ORIG);
	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_proto_xml(buf+offset, len, tuple, __DIR_REPL);
	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf+offset, len, "</layer4>");
	BUFFER_SIZE(ret, size, len, offset);

	if (zone_incl) {
		ret = snprintf(buf+offset, len, "<zone>%u</zone>", tuple->zone);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_ORIG_COUNTER_PACKETS, ct->head.set) &&
	    test_bit(ATTR_ORIG_COUNTER_BYTES, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<counters>");
		BUFFER_SIZE(ret, size, len, offset);

		ret = __snprintf_counters_xml(buf+offset, len, ct, dir);
		BUFFER_SIZE(ret, size, len, offset);

		ret = snprintf(buf+offset, len, "</counters>");
		BUFFER_SIZE(ret, size, len, offset);
	}

	ret = snprintf(buf+offset, len, "</meta>");
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

static int
__snprintf_clabels_xml(char *buf, unsigned int len,
		       const struct nf_conntrack *ct, struct nfct_labelmap *map)
{
	const struct nfct_bitmask *b = nfct_get_attr(ct, ATTR_CONNLABELS);
	int ret, size = 0, offset = 0;

	if (!b)
		return 0;

	ret = snprintf(buf, len, "<labels>");
	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_connlabels(buf + offset, len, map, b, "<label>%s</label>");

	BUFFER_SIZE(ret, size, len, offset);

	ret = snprintf(buf + offset, len, "</labels>");
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}

int __snprintf_conntrack_xml(char *buf,
			     unsigned int len,
			     const struct nf_conntrack *ct,
			     const unsigned int msg_type,
			     const unsigned int flags,
			     struct nfct_labelmap *map)
{
	int ret = 0;
	unsigned int size = 0, offset = 0;

	switch(msg_type) {
		case NFCT_T_NEW:
			ret = snprintf(buf, len, "<flow type=\"new\">");
			break;
		case NFCT_T_UPDATE:
			ret = snprintf(buf, len, "<flow type=\"update\">");
			break;
		case NFCT_T_DESTROY:
			ret = snprintf(buf, len, "<flow type=\"destroy\">");
			break;
		default:
			ret = snprintf(buf, len, "<flow>");
			break;
	}

	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_tuple_xml(buf+offset, len, ct, __DIR_ORIG,
				   test_bit(ATTR_ORIG_ZONE, ct->head.set));
	BUFFER_SIZE(ret, size, len, offset);

	ret = __snprintf_tuple_xml(buf+offset, len, ct, __DIR_REPL,
				   test_bit(ATTR_REPL_ZONE, ct->head.set));
	BUFFER_SIZE(ret, size, len, offset);

	if (test_bit(ATTR_TCP_STATE, ct->head.set) ||
	    test_bit(ATTR_SCTP_STATE, ct->head.set) ||
	    test_bit(ATTR_DCCP_STATE, ct->head.set) ||
	    test_bit(ATTR_TIMEOUT, ct->head.set) ||
	    test_bit(ATTR_MARK, ct->head.set) ||
	    test_bit(ATTR_SECMARK, ct->head.set) ||
	    test_bit(ATTR_ZONE, ct->head.set) ||
	    test_bit(ATTR_USE, ct->head.set) ||
	    test_bit(ATTR_STATUS, ct->head.set) ||
	    test_bit(ATTR_ID, ct->head.set) ||
	    test_bit(ATTR_CONNLABELS, ct->head.set) ||
	    test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
	    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
		ret = snprintf(buf+offset, len, 
			       "<meta direction=\"independent\">");
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_TCP_STATE, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<state>%s</state>",
			       ct->protoinfo.tcp.state < TCP_CONNTRACK_MAX ?
			       states[ct->protoinfo.tcp.state] :
			       states[TCP_CONNTRACK_NONE]);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_SCTP_STATE, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<state>%s</state>",
			       ct->protoinfo.sctp.state < SCTP_CONNTRACK_MAX ?
			       states[ct->protoinfo.sctp.state] :
			       states[SCTP_CONNTRACK_NONE]);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_DCCP_STATE, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<state>%s</state>",
			       ct->protoinfo.sctp.state < DCCP_CONNTRACK_MAX ?
			       states[ct->protoinfo.dccp.state] :
			       states[DCCP_CONNTRACK_NONE]);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_TIMEOUT, ct->head.set)) {
		ret = snprintf(buf+offset, len,
				"<timeout>%u</timeout>", ct->timeout);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_MARK, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<mark>%u</mark>", ct->mark);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (map && test_bit(ATTR_CONNLABELS, ct->head.set)) {
		ret = __snprintf_clabels_xml(buf+offset, len, ct, map);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_SECMARK, ct->head.set)) {
		ret = snprintf(buf+offset, len, 
				"<secmark>%u</secmark>", ct->secmark);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_SECCTX, ct->head.set)) {
		ret = snprintf(buf+offset, len,
				"<secctx>%s</secctx>", ct->secctx);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_ZONE, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<zone>%u</zone>", ct->zone);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_USE, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<use>%u</use>", ct->use);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_ID, ct->head.set)) {
		ret = snprintf(buf+offset, len, "<id>%u</id>", ct->id);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_STATUS, ct->head.set)
	    && ct->status & IPS_ASSURED) {
		ret = snprintf(buf+offset, len, "<assured/>");
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_STATUS, ct->head.set) 
	    && !(ct->status & IPS_SEEN_REPLY)) {
		ret = snprintf(buf+offset, len, "<unreplied/>");
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (flags & NFCT_OF_TIMESTAMP) {
		if (test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
		    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
			ret = snprintf(buf+offset, len, "<timestamp>");
			BUFFER_SIZE(ret, size, len, offset);
		}
		if (test_bit(ATTR_TIMESTAMP_START, ct->head.set)) {
	 		ret = __snprintf_timestamp_start(buf+offset, len, ct);
			BUFFER_SIZE(ret, size, len, offset);
		}
		if (test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
	 		ret = __snprintf_timestamp_stop(buf+offset, len, ct);
			BUFFER_SIZE(ret, size, len, offset);
		}
		if (test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
		    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
			ret = snprintf(buf+offset, len, "</timestamp>");
			BUFFER_SIZE(ret, size, len, offset);
		}
	}
	if (test_bit(ATTR_TIMESTAMP_START, ct->head.set) &&
	    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
		ret = __snprintf_deltatime(buf+offset, len, ct);
		BUFFER_SIZE(ret, size, len, offset);
	} else if (test_bit(ATTR_TIMESTAMP_START, ct->head.set)) {
		ret = __snprintf_deltatime_now(buf+offset, len, ct);
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_TCP_STATE, ct->head.set) ||
	    test_bit(ATTR_SCTP_STATE, ct->head.set) ||
	    test_bit(ATTR_DCCP_STATE, ct->head.set) ||
	    test_bit(ATTR_TIMEOUT, ct->head.set) ||
	    test_bit(ATTR_MARK, ct->head.set) ||
	    test_bit(ATTR_SECMARK, ct->head.set) ||
	    test_bit(ATTR_ZONE, ct->head.set) ||
	    test_bit(ATTR_USE, ct->head.set) ||
	    test_bit(ATTR_STATUS, ct->head.set) ||
	    test_bit(ATTR_ID, ct->head.set) ||
	    test_bit(ATTR_CONNLABELS, ct->head.set) ||
	    test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
	    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
	    	ret = snprintf(buf+offset, len, "</meta>");
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (flags & NFCT_OF_TIME) {
		time_t t;
		struct tm tm;

		t = time(NULL);
		if (localtime_r(&t, &tm) == NULL)
			goto err_out;

		ret = snprintf(buf+offset, len, "<when>");
		BUFFER_SIZE(ret, size, len, offset);

		ret = __snprintf_localtime_xml(buf+offset, len, &tm);
		BUFFER_SIZE(ret, size, len, offset);

		ret = snprintf(buf+offset, len, "</when>");
		BUFFER_SIZE(ret, size, len, offset);
	}

	if (test_bit(ATTR_HELPER_NAME, ct->head.set)) {
		ret = __snprintf_helper_name(buf+offset, len, ct);
		BUFFER_SIZE(ret, size, len, offset);
	}
err_out:
	ret = snprintf(buf+offset, len, "</flow>");
	BUFFER_SIZE(ret, size, len, offset);

	return size;
}
