/* This code unpacks a rule into a Perl hash, for passing to
 * a script for output and manipulation purposes.
 */

/*
 * Author: Derrik Pates <dpates@dsdk12.net>
 *
 *      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.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#define __USE_GNU
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

/* for iptables/ip6tables internals */
#include "local_types.h"

/* for getprotobynumber() */
#include <netdb.h>
/* for inet_ntop() */
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
/* for strtoul() */
#include <stdlib.h>
#include <netinet/in.h>

#include "unpacker.h"
#include "loader.h"
#include "module_iface.h"

/* Translate an address/netmask pair into a string */
static SV *addr_and_mask_to_sv(ADDR_TYPE addr, ADDR_TYPE mask,
		bool inv) {
	char *temp, *temp2, addrstr[ADDR_STRLEN + 1];
	unsigned char *mask_access;
	int i, j, maskwidth = 0, at_zeros = FALSE, contiguous = TRUE;
	SV *sv;

	/* We always translate the address into a string */
	inet_ntop(ADDR_FAMILY, (void *)&addr, addrstr, ADDR_STRLEN);
	temp = strdup(addrstr);
	mask_access = (void *)&mask;
	/* Do the magic work of converting the netmask to a width value, or a
	 * plain netmask, if it can't be stored as a width value */
	for (i = 0; i < sizeof(ADDR_TYPE) && mask_access[i] != 0; i++) {
		switch (mask_access[i]) {
		  case 0:
			at_zeros = TRUE;
			break;
		  case 0xFF:
			/* Optimize out whole bytes with all bits set */
			maskwidth += 8;
			if (at_zeros)
				contiguous = FALSE;
			break;
		  default:
			for (j = 7; j >= 0; j--) {
				if ((mask_access[i] >> j) & 1) {
					maskwidth++;
					if (at_zeros) {
						contiguous = FALSE;
						break;
					}
				}
				else
					at_zeros = TRUE;
			}
			break;
		}
		if (!contiguous)
			break;
	}
	if(maskwidth < sizeof(ADDR_TYPE) * 8) {
		/* Ok, this is not a host entry */
		if(contiguous) /* If it was contiguous, it can be expressed
			        * as a single mask value */
			asprintf(&temp2, "%s/%u", temp, maskwidth);
		else { /* Otherwise, express it as a full netmask value */
			inet_ntop(ADDR_FAMILY, &mask, addrstr, ADDR_STRLEN);
			asprintf(&temp2, "%s/%s", temp, addrstr);
		}
		free(temp);
		temp = temp2;
	}
	if(inv) {
		asprintf(&temp2, "%c%s", INVCHAR, temp);
		free(temp);
		temp = temp2;
	}
	sv = newSVpv(temp, 0);
	free(temp);
	return(sv);
}

/* We have a job, kids - to turn a (ENTRY *) into a hash... */
HV *ipt_do_unpack(ENTRY *entry, HANDLE *table) {
	SV *sv;
	HV *hash;
	AV *match_list = NULL;
	char *temp, *rawkey, *targetname = NULL, *protoname = NULL;
	struct protoent *protoinfo;
	ModuleDef *module = NULL;
	ENTRY_MATCH *match = NULL;
	ENTRY_TARGET *target = NULL;

	/* If the pointer is NULL, then we've got a slight problem. */
	if(!entry)
		return(NULL);
	
	hash = newHV();
	
	/* Ok, let's break this down point by point. First off, the source
	 * address... */
	if(entry->nfcache & NFC_IPx_SRC) {
		sv = addr_and_mask_to_sv(ENTRY_ADDR(entry).src, ENTRY_ADDR(entry).smsk,
				ENTRY_ADDR(entry).invflags & INV_SRCIP);
		hv_store(hash, "source", 6, sv, 0);
	}
	
	/* Now, the destination address */
	if(entry->nfcache & NFC_IPx_DST) {
		sv = addr_and_mask_to_sv(ENTRY_ADDR(entry).dst, ENTRY_ADDR(entry).dmsk,
				ENTRY_ADDR(entry).invflags & INV_DSTIP);
		hv_store(hash, "destination", 11, sv, 0);
	}
	
	/* Now, the packet incoming interface */
	if(entry->nfcache & NFC_IPx_IF_IN) {
		char *ifname = strdup(ENTRY_ADDR(entry).iniface);
		if(ENTRY_ADDR(entry).invflags & INV_VIA_IN) {
			asprintf(&temp, "%c%s", INVCHAR, ifname);
			free(ifname);
			ifname = temp;
		}
		hv_store(hash, "in-interface", 12, newSVpv(ifname, 0), 0);
		free(ifname);
	}
	
	/* Packet outgoing interface */
	if(entry->nfcache & NFC_IPx_IF_OUT) {
		char *ifname = strdup(ENTRY_ADDR(entry).outiface);
		if(ENTRY_ADDR(entry).invflags & INV_VIA_OUT) {
			asprintf(&temp, "%c%s", INVCHAR, ifname);
			free(ifname);
			ifname = temp;
		}
		hv_store(hash, "out-interface", 13, newSVpv(ifname, 0), 0);
		free(ifname);
	}
	
	/* Protocol */
	if(entry->nfcache & NFC_IPx_PROTO) {
		char *protostr;
		if((protoinfo = getprotobynumber(ENTRY_ADDR(entry).proto))) {
			protostr = strdup(protoinfo->p_name);
			protoname = protostr;
			if(ENTRY_ADDR(entry).invflags & INV_PROTO) {
				asprintf(&temp, "%c%s", INVCHAR, protostr);
				free(protostr);
				protostr = temp;
				protoname = protostr + 1;
			}
			protoname = strdup(protoname);
			sv = newSVpv(protostr, 0);
			free(protostr);
		}
		else if(ENTRY_ADDR(entry).invflags & INV_PROTO) {
			asprintf(&protostr, "%c%u", INVCHAR, ENTRY_ADDR(entry).proto);
			sv = newSVpv(protostr, 0);
			free(protostr);
		}
		else
			sv = newSViv(ENTRY_ADDR(entry).proto);
		hv_store(hash, "protocol", 8, sv, 0);
	}

#ifndef INET6
	/* Fragment flag */
	if(ENTRY_ADDR(entry).flags & IPT_F_FRAG) {
		hv_store(hash, "fragment", 8, newSViv(!(ENTRY_ADDR(entry).invflags &
										IPT_INV_FRAG)), 0);
	}
#endif /* !INET6 */

	/* Jump target */
	if((targetname = (char *)GET_TARGET(entry, table))) {
		target = (void *)entry + entry->target_offset;
		if(strcmp("", targetname))
			hv_store(hash, "jump", 4, newSVpv(targetname, 0), 0);

		module = ipt_find_module(targetname, MODULE_TARGET, table);
		/* If we didn't find a module for the target, stuff the raw target
		 * data into the hash with an appropriately-named key. */
		if(!module) {
			char *data;
			int data_size = target->u.target_size -
				ALIGN(sizeof(ENTRY_TARGET));
			if(data_size > 0) {
				asprintf(&rawkey, "%s" TARGET_RAW_POSTFIX, targetname);
				data = malloc(data_size);
				memcpy(data, target->data, data_size);
				hv_store(hash, rawkey, strlen(rawkey), newSVpv(data, data_size),
								0);
				free(rawkey);
				free(data);
			}
		}
		else if(module->get_fields)
			module->get_fields(hash, ((void *)entry + entry->target_offset),
							entry);
	}
	
	/* And now, iterate through the match modules */
	for(match = (void *)entry->elems;
			(void *)match < ((void *)entry + entry->target_offset);
			match = (void *)match + match->u.match_size) {
		/* If it's a protocol match, make sure it doesn't end up on the
		 * match list. */
		if(protoname ? strcmp(protoname, match->u.user.name) : TRUE) {
			/* If we haven't setup the match list already, create the array
			 * now. */
			if(!match_list)
				match_list = newAV();
			av_push(match_list, newSVpv(match->u.user.name, 0));
		}
		
		module = ipt_find_module(match->u.user.name, MODULE_MATCH, table);
		/* If we didn't find a module for the current match, stuff the raw
		 * match data into the hash with an appropriately-named key. */
		if(!module) {
			char *data;
			int data_size = match->u.match_size -
					ALIGN(sizeof(ENTRY_MATCH));
			asprintf(&rawkey, "%s" MATCH_RAW_POSTFIX, match->u.user.name);
			data = malloc(data_size);
			memcpy(data, match->data, data_size);
			hv_store(hash, rawkey, strlen(rawkey), newSVpv(data, data_size), 0);
			free(rawkey);
			free(data);
		}
		else if(module->get_fields)
			module->get_fields(hash, match, entry);
	}

	if(match_list)
		hv_store(hash, "matches", 7, newRV_noinc((SV *)match_list), 0);
	
	/* And the byte and packet counters */
	asprintf(&temp, "%llu", entry->counters.bcnt);
	hv_store(hash, "bcnt", 4, newSVpv(temp, 0), 0);
	free(temp);
	asprintf(&temp, "%llu", entry->counters.pcnt);
	hv_store(hash, "pcnt", 4, newSVpv(temp, 0), 0);
	free(temp);

	if(protoname)
		free(protoname);
	return hash;
}

/* vim: ts=4
 */