/* This code will pack the contents of a Perl hash into a (ENTRY)
 * for use in rule-adding andd rule-modifying operations.
 */

/*
 * 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() and getprotobyname() */
#include <netdb.h>
/* for inet_pton() */
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
/* for strtoul() */
#include <stdlib.h>
/* for htonl() */
#include <netinet/in.h>
#include <stdio.h>

#include "packer.h"
#include "loader.h"

#define ADDRTEXTWIDTH ADDR_STRLEN * 2 + 1

typedef struct {
	ENTRY_MATCH *match;
	int flags;
} MatchElem;

typedef MatchElem * MatchList;

/* Parse out an IP address/mask pair into (ADDR_TYPE)s */
static int parse_addr(SV *addrsv, ADDR_TYPE *addr, ADDR_TYPE *mask,
				bool *inv) {
	char *sep, *maskstr, *maskend, *addrstr, *base, *temp;
	unsigned int maskwidth;
	unsigned char *mask_access;
	STRLEN len;

	*inv = FALSE;
	/* Make sure the value is of the right type */
	if(!SvPOK(addrsv)) {
		SET_ERRSTR("Must be passed as string");
		return(FALSE);
	}

	temp = SvPV(addrsv, len);
	base = addrstr = malloc(len + 1);
	strncpy(addrstr, temp, len);
	addrstr[len] = '\0';

	/* If the first character is our designated invert
	 * character, set the invert flag */
	if(addrstr[0] == INVCHAR) {
		*inv = TRUE;
		addrstr++;
	}

	/* Is there a slash? If so, we have a netmask or mask width value to
	 * read in */
	if((sep = strchr(addrstr, '/'))) {
		maskstr = sep + 1;
		/* Try to read an int from the mask side */
		maskwidth = strtoul(maskstr, &maskend, 10);
		/* If the integer ends before the mask does, then assume it's
		 * a full netmask */
		if(strlen(maskstr) > maskend - maskstr) {
			if(inet_pton(ADDR_FAMILY, maskstr, mask) < 1) {
				SET_ERRSTR("Couldn't parse mask '%s'", maskstr);
				goto pa_failed;
			}
		}
		/* If not, it's a single-value mask */
		else {
			if (maskwidth > sizeof(ADDR_TYPE) * 8) {
				SET_ERRSTR("Impossible mask width %d", maskwidth);
				goto pa_failed;
			}
			/* Zero the whole mask */
			memset(mask, 0, sizeof(ADDR_TYPE));
			/* Set all whole bytes to 0xFF right away */
			memset(mask, 0xFF, maskwidth / 8);
			mask_access = (void *)mask;
			/* Do shift/invert to get the trailing bits */
			mask_access[maskwidth / 8] = ~(0xFF >> (maskwidth % 8)) & 0xFF;
		}
	}
	/* If not, all mask bits must be set, because it's a host rule */
	else
		memset(mask, 0xFF, sizeof(ADDR_TYPE));
	if(sep)
		*sep = '\0';
	if(inet_pton(ADDR_FAMILY, addrstr, addr) < 1) {
		SET_ERRSTR("Couldn't parse address '%s'", addrstr);
		goto pa_failed;
	}
	if(sep)
		*sep = '/';
	free(base);
	return(TRUE);
pa_failed:
	free(base);
	return(FALSE);
}

/* Parse an interface name */
static int parse_iface(SV *ifacesv, char *ifnam, unsigned char *ifmask,
				bool *inv) {
	char *wc_off, *ifacestr, *base, *temp;
	int maskwidth;
	STRLEN len;

	*inv = FALSE;
	/* Make sure the parameter we got can be coerced into a string. */
	if(!SvPOK(ifacesv)) {
		SET_ERRSTR("Must be passed as string");
		return(FALSE);
	}
	
	temp = SvPV(ifacesv, len);
	base = ifacestr = malloc(len + 1);
	strncpy(ifacestr, temp, len);
	ifacestr[len] = '\0';

	/* If the parameter is prefixed with the invert character, tell the caller
	 * to set the invert flag for the field. */
	if(ifacestr[0] == INVCHAR) {
		*inv = TRUE;
		ifacestr++;
	}
	/* Is there a wildcard character? If so, set the mask width
	 * appropriately and limit the string to just before the wildcard */
	if((wc_off = strchr(ifacestr, '+')))
		maskwidth = wc_off - ifacestr;
	else
		maskwidth = strlen(ifacestr) + 1;
	/* Copy the interface name */
	strncpy(ifnam, ifacestr, IFNAMSIZ - 1);
	/* Fill in the mask */
	memset(ifmask, 0xFF, maskwidth > IFNAMSIZ ? IFNAMSIZ : maskwidth);
	free(base);
	return(TRUE);
}

/* Build a (ENTRY *) from a Perl hash */
int ipt_do_pack(HV *hash, ENTRY **entry, HANDLE *table) {
	SV *sv = NULL, **svp = NULL;
	bool gotproto = FALSE, inv;
	char *h_key, *rawkey, *targetname = strdup(""), *protoname = NULL;
	unsigned int h_keylen, size = 0, psize = 0, datalen = 0;
	int rval, i, n_matches = 0;
	ModuleDef *module = NULL;
	MatchList matches = NULL;
	ENTRY_MATCH *match = NULL;
	int target_flags = 0;
	ENTRY_TARGET *target = NULL;
	void *datazone = NULL;

	/* Give $! an undef value. If it's not still undef by the end of the
	 * call, then a module was called, tried to parse a field, and couldn't. */
	sv_setsv(ERROR_SV, &PL_sv_undef);
	/* Setup the base allocation size, and allocate the necessary zone. */
	*entry = calloc(1, sizeof(ENTRY));

	/* Must handle the protocol first! */
	if((svp = hv_fetch(hash, "protocol", 8, FALSE))) {
		struct protoent *protoinfo;
		/* If the protocol is being passed as an integer val, confirm that
		 * the protocol exists, and store it. */
		if(SvIOK(*svp)) {
			if((protoinfo = getprotobynumber(SvIV(*svp)))) // LEAK
				protoname = protoinfo->p_name;
			ENTRY_ADDR(*entry).proto = SvIV(*svp);
		}
		/* If it's a string, use a different approach to parse the field. */
		else if(SvPOK(*svp)) {
			int protonum;
			char *extent, *protostr, *base, *temp;
			STRLEN len;

			temp = SvPV(*svp, len);
			base = protostr = malloc(len + 1);
			strncpy(protostr, temp, len);
			protostr[len] = '\0';
			
			/* If the first character is the "magic" invert char, set the
			 * protocol invert flag, and advance the pointer by one */
			if(protostr[0] == INVCHAR) {
				ENTRY_ADDR(*entry).invflags |= INV_PROTO;
				protostr++;
			}
			
			/* Does this string contain a protocol name? If so, store that */
			if((protoinfo = getprotobyname(protostr))) { // LEAK
				ENTRY_ADDR(*entry).proto = protoinfo->p_proto;
				protoname = protoinfo->p_name;
			}
			/* Is it a number? Maybe it's a protocol number... */
			else {
				protonum = strtoul(protostr, &extent, 10);
				if(protostr + strlen(protostr) != extent) {
					/* Then again, maybe not. */
					SET_ERRSTR("proto: Unable to parse '%s'", protostr);
					free(base);
					goto pack_fail;
				}
				if((protoinfo = getprotobynumber(protonum)))
					protoname = protoinfo->p_name;
				ENTRY_ADDR(*entry).proto = protonum;
			}
			free(base);
		}
		/* If the data type is wrong, pass back an error and drop out now. */
		else {
			SET_ERRSTR("protocol: Must be passed as integer or string");
			goto pack_fail;
		}
		(*entry)->nfcache |= NFC_IPx_PROTO;

		/* Does the protocol have a name? If so, then we want to see if
		 * it's got raw data to copy. */
		if(protoname) {
			datazone = NULL;
			datalen = 0;
			asprintf(&rawkey, "%s" MATCH_RAW_POSTFIX, protoname);
			/* Check and see if there's any raw data to go with the
			 * match module for the protocol. */
			svp = hv_fetch(hash, rawkey, strlen(rawkey), FALSE);
			free(rawkey);
			if(svp) {
				/* Check and see if the raw data field is, or at least
				 * can be coerced to be, a string. */
				if(!SvPOK(*svp)) {
					SET_ERRSTR("%s: Must be passed as a string", rawkey);
					goto pack_fail;
				}
				datazone = (void *)SvPV(*svp, datalen);
				/* Extend the list of matches by one. */
				matches = realloc(matches, ++n_matches *
				    sizeof(MatchElem));
				/* Establish the match data zone's length. */
				size = ALIGN(sizeof(ENTRY_MATCH) + datalen);
				matches[n_matches - 1].match = match = calloc(1, size);
				match->u.match_size = size;
				strncpy(match->u.user.name, protoname,
								TARGET_NAME_LEN);
				memcpy(match->data, datazone, datalen);
				/* Make sure it's confirmed that we have a match entry
				 * for the selected protocol setup. */
				gotproto = TRUE;
			}
		}
	}

	/* If a list of matches to be used has been provided, load up those
	 * match modules. */
	if((svp = hv_fetch(hash, "matches", 7, FALSE))) {
		AV *av;
		/* Check the parameter's data type before we go further. */
		if(!SvROK(*svp) || (SvTYPE((av = (AV *)SvRV(*svp))) != SVt_PVAV)) {
			SET_ERRSTR("matches: Must be passed as array ref");
			goto pack_fail;
		}
		/* Iterate through the list of match module names. */
		for(i = 0; i <= av_len(av); i++) {
			char *matchname, *temp;
			STRLEN len;
			/* Fetch an item from the list */
			svp = av_fetch(av, i, FALSE);

			/* Do some type checking to make sure we're getting a string */
			if(!svp || !SvPOK(*svp)) {
				SET_ERRSTR("matches: Element %u must be passed as string", i);
				goto pack_fail;
			}

			temp = SvPV(*svp, len);
			matchname = malloc(len + 1);
			strncpy(matchname, temp, len);
			matchname[len] = '\0';
			module = ipt_find_module(matchname, MODULE_MATCH, table);

			/* See if there's raw data for this match module */
			datazone = NULL;
			datalen = 0;
			asprintf(&rawkey, "%s" MATCH_RAW_POSTFIX, matchname);
			svp = hv_fetch(hash, rawkey, strlen(rawkey), FALSE);
			free(rawkey);
			if(svp) {
				if(!SvPOK(*svp)) {
					SET_ERRSTR("%s: Must be passed as string", rawkey);
					free(matchname);
					goto pack_fail;
				}
				datazone = (void *)SvPV(*svp, datalen);
			}
			else if(!module) {
				free(matchname);
				goto pack_fail;
			}

			if(module && module->size < datalen)
				datalen = module->size;

			/* Allocate storage for the match's data, then stick it onto the
			 * array of matches */
			size = ALIGN(sizeof(ENTRY_MATCH)
							+ ((module && module->size > datalen) ?
									module->size : datalen));
			matches = realloc(matches, ++n_matches *
							sizeof(MatchElem));
			matches[n_matches - 1].match = match = calloc(1, size);
			matches[n_matches - 1].flags = 0;
			match->u.match_size = size;
			strncpy(match->u.user.name, matchname, TARGET_NAME_LEN);
			if(module && module->setup)
				module->setup((void *)match, &(*entry)->nfcache);

			/* If there was raw match data, copy it to where it belongs. */
			if(datazone)
				memcpy(match->data, datazone, datalen);
			free(matchname);
		}
	}
	
	/* Setup the target info */
	if((svp = hv_fetch(hash, "jump", 4, FALSE))) {
		char *temp;
		STRLEN len;
		if(!SvPOK(*svp)) {
			SET_ERRSTR("target: Must be passed as string");
			goto pack_fail;
		}

		free(targetname);
		temp = SvPV(*svp, len);
		targetname = malloc(len + 1);
		strncpy(targetname, temp, len);
		targetname[len] = '\0';
	}
	
	module = ipt_find_module(targetname, MODULE_TARGET, table);
	
	/* See if there's a key containing raw data for the target we're
	 * using for this rule */
	datazone = NULL;
	datalen = 0;
	asprintf(&rawkey, "%s" TARGET_RAW_POSTFIX, targetname);
	svp = hv_fetch(hash, rawkey, strlen(rawkey), FALSE);
	free(rawkey);
	if(svp) {
		if(!SvPOK(*svp)) {
			SET_ERRSTR("%s: Must be passed as string", rawkey);
			goto pack_fail;
		}
		datazone = (void *)SvPV(*svp, datalen);
	}
	else if(!module)
		goto pack_fail;

	if(module && module->size < datalen)
		datalen = module->size;
	
	/* Allocate the target info struct */
	size = ALIGN(sizeof(ENTRY_TARGET))
			+ ALIGN(((module && module->size > datalen) ? module->size :
									datalen));
	target = calloc(1, size);
	target->u.target_size = size;
	strncpy(target->u.user.name, targetname, TARGET_NAME_LEN);

	/* Call the target module's setup routine, so it can initialize its
	 * data area correctly (if we loaded the module) */
	if(module && module->setup)
		module->setup((void *)target, &(*entry)->nfcache);

	/* If we got raw data, copy it into its respective place */
	if(datazone)
		memcpy(target->data, datazone, datalen);

	/* Now, we go through all the hash keys */
	hv_iterinit(hash);
	while((sv  = hv_iternextsv(hash, &h_key, (I32 *)&h_keylen))) {
		rval = FALSE;
		/* Ok, give the match modules the first crack at parsing this field. */
		for(i = 0; i < n_matches; i++) {
			match = matches[i].match;
			/* Try to look up the module for the current match. */
			module = ipt_find_module(match->u.user.name,
							MODULE_MATCH, table);
			if(!module || !module->parse_field)
				continue;
			rval = module->parse_field(h_key, sv, &match,
							&(*entry)->nfcache, *entry, &matches[i].flags);
			/* Make sure to copy the pointer back into the array, in
			 * case the match module changed something. If it did, the
			 * struct probably got realloc'd. */
			matches[i].match = match;
			if(rval)
				break;
		}
		/* If rval is TRUE, then one of the match modules successfully
		 * parsed the current field, so go on to the next. */
		if(rval)
			continue;

		/* If the match modules didn't parse the key, try the target module. */
		module = ipt_find_module(targetname, MODULE_TARGET, table);
		if(module && module->parse_field && module->parse_field(h_key, sv,
								&target, &(*entry)->nfcache, *entry,
								&target_flags))
			continue;

		/* Parse the source address */
		if(!strcmp(h_key, "source")) {
			/* Try to parse the address and mask out */
			if(!parse_addr(sv, &ENTRY_ADDR(*entry).src, &ENTRY_ADDR(*entry).smsk, &inv)) {
				char *temp = strdup(SvPV_nolen(ERROR_SV));
				SET_ERRSTR("%s: %s", h_key, temp);
				free(temp);
				goto pack_fail;
			}
			if(inv)
				ENTRY_ADDR(*entry).invflags |= INV_SRCIP;
			(*entry)->nfcache |= NFC_IPx_SRC;
		}

		/* Destination address */
		else if(!strcmp(h_key, "destination")) {
			/* Try to parse the address and mask out */
			if(!parse_addr(sv, &ENTRY_ADDR(*entry).dst, &ENTRY_ADDR(*entry).dmsk, &inv)) {
				char *temp = strdup(SvPV_nolen(ERROR_SV));
				SET_ERRSTR("%s: %s", h_key, temp);
				free(temp);
				goto pack_fail;
			}
			if(inv)
				ENTRY_ADDR(*entry).invflags |= INV_DSTIP;
			(*entry)->nfcache |= NFC_IPx_DST;
		}

		/* Incoming interface */
		else if(!strcmp(h_key, "in-interface")) {
			if(!parse_iface(sv, ENTRY_ADDR(*entry).iniface, ENTRY_ADDR(*entry).iniface_mask,
									&inv)) {
				char *temp = strdup(SvPV_nolen(ERROR_SV));
				SET_ERRSTR("%s: %s", h_key, temp);
				free(temp);
				goto pack_fail;
			}
			if(inv)
				ENTRY_ADDR(*entry).invflags |= INV_VIA_IN;
			(*entry)->nfcache |= NFC_IPx_IF_IN;
		}

		/* Outgoing interface */
		else if(!strcmp(h_key, "out-interface")) {
			if(!parse_iface(sv, ENTRY_ADDR(*entry).outiface,
									ENTRY_ADDR(*entry).outiface_mask, &inv)) {
				char *temp = strdup(SvPV_nolen(ERROR_SV));
				SET_ERRSTR("%s: %s", h_key, temp);
				free(temp);
				goto pack_fail;
			}
			if(inv)
				ENTRY_ADDR(*entry).invflags |= INV_VIA_OUT;
			(*entry)->nfcache |= NFC_IPx_IF_OUT;
		}

#ifndef INET6
		/* Fragment flag */
		else if(!strcmp(h_key, "fragment")) {
			if(!SvIOK(sv)) {
				SET_ERRSTR("%s: Must be passed as integer", h_key);
				goto pack_fail;
			}
			ENTRY_ADDR(*entry).flags |= IPT_F_FRAG;
			if(!SvIV(sv))
				ENTRY_ADDR(*entry).invflags |= IPT_INV_FRAG;
			(*entry)->nfcache |= NFC_IP_FRAG;
		}
#endif /* !INET6 */

		else if(!strcmp(h_key, "bcnt")) {
			if(SvIOK(sv))
				(*entry)->counters.bcnt = SvIV(sv);
			else if(SvPOK(sv))
				sscanf(SvPV_nolen(sv), "%Lu", &(*entry)->counters.bcnt);
			else {
				SET_ERRSTR("%s: Must be passed as integer or string", h_key);
				goto pack_fail;
			}
		}

		else if(!strcmp(h_key, "pcnt")) {
			if(SvIOK(sv))
				(*entry)->counters.pcnt = SvIV(sv);
			else if(SvPOK(sv))
				sscanf(SvPV_nolen(sv), "%Lu", &(*entry)->counters.pcnt);
			else {
				SET_ERRSTR("%s: Must be passed as integer or string", h_key);
				goto pack_fail;
			}
		}

		/* All these are NOPs, because they've already been handled
		 * elsewhere, so we just need to make sure that they don't
		 * reach the fallthrough case in this loop. */
		else if(!strcmp(h_key, "jump") || !strcmp(h_key, "matches") ||
		    !strcmp(h_key, "protocol"))
			/* Yes, that's right. NOTHING. */;
		
		/* Check to guarantee that this raw target data actually goes
		 * with the target this rule is using. The actual process of
		 * putting the raw data where it belongs is rolled in with
		 * the rest of the target init process earlier on. */
		else if(strstr(h_key, TARGET_RAW_POSTFIX)) {
			asprintf(&rawkey, "%s" TARGET_RAW_POSTFIX, targetname);
			rval = strcmp(h_key, rawkey);
			free(rawkey);
			if(rval) {
				SET_ERRSTR("%s: Mismatched raw target data", h_key);
				goto pack_fail;
			}
		}

		/* This will check to make sure that the any raw match data
		 * keys are associated with matches we are using. The actual
		 * process of getting the match data is rolled in with the
		 * rest of the match init process */
		else if(strstr(h_key, MATCH_RAW_POSTFIX)) {
			bool matched = FALSE;
			for(i = 0; i < n_matches; i++) {
				asprintf(&rawkey, "%s" MATCH_RAW_POSTFIX,
								matches[i].match->u.user.name);
				rval = strcmp(h_key, rawkey);
				free(rawkey);
				if(!rval) {
					matched = TRUE;
					break;
				}
			}
			/* If the name on the raw match data field isn't found, then
			 * pass back an error, and fail now */
			if(!matched) {
				SET_ERRSTR("%s: Mismatched raw match data", h_key);
				goto pack_fail;
			}
		}
		else {
			rval = FALSE;
			/* If we get here, and the protocol match module hasn't already
			 * been loaded, then load it and init its match module info space.
			 * This has to be done to make the icmp protocol match module work
			 * right. This is more than a little weird. I originally just did
			 * this at the start, with the rest of the match modules - but if
			 * I do it that way, then you can't just match all ICMP packets.
			 * Rusty, why did you do this?!? */
			if(!gotproto && protoname && (module = ipt_find_module(protoname,
											MODULE_MATCH, table))) {
				int i;
				gotproto = TRUE;
				size = ALIGN(sizeof(ENTRY_MATCH)) + module->size;
				matches = realloc(matches, ++n_matches *
								sizeof(MatchElem));
				i = n_matches - 1;
				match = calloc(1, size);
				matches[i].flags = 0;
				match->u.match_size = size;
				strncpy(match->u.user.name, protoname, TARGET_NAME_LEN);
				if(module->setup)
					module->setup((void *)match, &(*entry)->nfcache);
				if(module->parse_field)
					rval = module->parse_field(h_key, sv, &match,
									&(*entry)->nfcache, *entry,
									&matches[i].flags);
				matches[i].match = match;
			}
			/* Oops. If we get here, there was a key that we're not supposed
			 * to get, so we return a FALSE to denote that we had a problem
			 * interpreting the hash's contents */
			if(!rval) {
				if(!SvOK(ERROR_SV))
					SET_ERRSTR("%s: field unknown", h_key);
				goto pack_fail;
			}
		}
	}

	/* Before we call it good, call the final_check() routine for each
	 * match and target module, so they can make sure everything is
	 * kosher */
	for(i = 0; i < n_matches; i++) {
		match = matches[i].match;
		module = ipt_find_module(match->u.user.name, MODULE_MATCH, table);
		if(module && module->final_check &&
						!module->final_check(match, matches[i].flags))
				goto pack_fail;
	}
	module = ipt_find_module(target->u.user.name, MODULE_TARGET, table);
	if(module && module->final_check &&
					!module->final_check(target, target_flags))
			goto pack_fail;

	/* Generate final data structure to be passed back */
	size = ALIGN(sizeof(ENTRY));
	/* If there are match modules, reallocate the ipt_entry to make room
	 * for them, and copy them into place */
	for(i = 0; i < n_matches; i++) {
		psize = size;
		size += matches[i].match->u.match_size;
		*entry = realloc(*entry, size);
		memcpy((void *)*entry + psize, matches[i].match,
						matches[i].match->u.match_size);
		free(matches[i].match);
	}
	free(matches);
	/* Put the target into place as well (no conditional, there's always
	 * a target of some sort */
	psize = size;
	size += target->u.target_size;
	*entry = realloc(*entry, size);
	memcpy((void *)*entry + psize, target, target->u.target_size);
	free(target);
	(*entry)->target_offset = psize;
	(*entry)->next_offset = size;
	
	/* Ok, we made it to the end, so it must've been a well-formed hash,
	 * and all the data elements must have been parsable, so now we return
	 * TRUE to signify that we are OK */
	free(targetname);
	return(TRUE);
pack_fail:
	if (matches) {
		for(i = 0; i < n_matches; i++) {
			psize = size;
			size += matches[i].match->u.match_size;
			*entry = realloc(*entry, size);
			memcpy((void *)*entry + psize, matches[i].match,
							matches[i].match->u.match_size);
			free(matches[i].match);
		}
		free(matches);
	}
	if (target)
		free(target);
	if (*entry)
		free(*entry);
	if (targetname)
		free(targetname);
	return(FALSE);
}

/* vim: ts=4
 */