#define __USE_GNU
#include "../module_iface.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ip_nat.h>
#include <netdb.h>
#include <limits.h>

#define MODULE_TYPE MODULE_TARGET
#define MODULE_DATATYPE struct ip_nat_multi_range
#define MODULE_NAME "SNAT"

#if MODULE_TYPE == MODULE_TARGET
#  define MODULE_ENTRYTYPE struct ipt_entry_match
#else 
#  if MODULE_TYPE == MODULE_MATCH
#    define MODULE_ENTRYTYPE struct ipt_entry_target
#  else
#    error MODULE_TYPE is unknown!
#  endif
#endif

static void setup(void *myinfo, unsigned int *nfcache) {
	*nfcache |= NFC_UNKNOWN;
}

static int parse_nat_range(char *string, struct ip_nat_range *range,
		struct ipt_entry *entry) {
	char *sep, *asep, *psep, *ptext, *atext, *extent, *temp;
	int port;

	sep = strchr(string, ':');
	if(sep) {
		struct protoent *proto = getprotobynumber(entry->ip.proto);
		if(strcmp(proto->p_name, "tcp") && strcmp(proto->p_name, "udp")) {
			SET_ERRSTR("to-source: Protocol must be TCP or UDP to specify "
							"ports");
			return(FALSE);
		}
		
		range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED;

		ptext = sep + 1;
		psep = strchr(ptext, '-');

		port = strtoul(ptext, &extent, 10);
		if(extent < (psep ? psep : (ptext + strlen(ptext))) || port == 0 ||
						port > USHRT_MAX)
			return(FALSE);

		range->min.tcp.port = range->max.tcp.port = htons(port);

		if(psep) {
			ptext = psep + 1;
			port = strtoul(ptext, &extent, 10);

			if(extent < (ptext + strlen(ptext)) || port == 0 ||
							port > USHRT_MAX || port < range->min.tcp.port)
				return(FALSE);

			range->max.tcp.port = htons(port);
		}
	}

	if(sep > string || !sep) {
		range->flags |= IP_NAT_RANGE_MAP_IPS;
		temp = atext = strndup(string, sep - string);
		asep = strchr(atext, '-');
		if(asep)
			*asep = '\0';
		if(inet_pton(AF_INET, atext, &range->min_ip) <= 0) {
			free(temp);
			return(FALSE);
		}
		range->max_ip = range->min_ip;

		if(asep) {
			atext = asep + 1;
			if(inet_pton(AF_INET, atext, &range->max_ip) <= 0) {
				free(temp);
				return(FALSE);
			}
		}
		free(temp);
	}

	if(!range->flags)
		return(FALSE);
	
	return(TRUE);
}

static int parse_field(char *field, SV *value, void *myinfo,
		unsigned int *nfcache, struct ipt_entry *entry, int *flags) {
	struct ipt_entry_target **targinfo = myinfo;
	MODULE_DATATYPE *info = (void *)(*(MODULE_ENTRYTYPE **)myinfo)->data;
	int i;
	
	if(strcmp(field, "to-source"))
		return(FALSE);

	*flags = 1;

	if(SvROK(value) && (SvTYPE(SvRV(value)) == SVt_PVAV)) {
		SV **svp;
		AV *av = (AV *)SvRV(value);
		struct ip_nat_range *range;
		for(i = 0; i <= av_len(av); i++) {
			char *rangestr, *temp;
			STRLEN len;

			svp = av_fetch(av, i, 0);
			if(!svp || !SvPOK(*svp)) {
				SET_ERRSTR("%s: Array element %d must be a string", field, i);
				return(FALSE);
			}

			/* Get a pointer to the range base */
			if(info->rangesize) {
				(*targinfo)->u.target_size +=
					IPT_ALIGN(sizeof(struct ip_nat_range));
				*targinfo = realloc(*targinfo, (*targinfo)->u.target_size);
				info = (MODULE_DATATYPE *)(*targinfo)->data;
				range = &(info->range[info->rangesize]);
				memset(range, 0, IPT_ALIGN(sizeof(struct ip_nat_range)));
			}
			else
				range = info->range;
			
			temp = SvPV(*svp, len);
			rangestr = malloc(len + 1);
			strncpy(rangestr, temp, len);
			rangestr[len] = '\0';

			if(!parse_nat_range(rangestr, range, entry)) {
				if(!strcmp(SvPV_nolen(perl_get_sv("!", 0)), ""))
					SET_ERRSTR("%s: Unable to parse element %d", field, i);
				free(rangestr);
				return(FALSE);
			}
			free(rangestr);
			info->rangesize++;
		}
		return(TRUE);
	}
	else if(SvPOK(value)) {
		char *temp, *rangestr;
		STRLEN len;

		temp = SvPV(value, len);
		rangestr = malloc(len + 1);
		strncpy(rangestr, temp, len);
		rangestr[len] = '\0';
		
		if(!parse_nat_range(rangestr, info->range, entry)) {
			SET_ERRSTR("%s: Unable to parse value", field);
			free(rangestr);
			return(FALSE);
		}
		free(rangestr);
		info->rangesize = 1;
		return(TRUE);
	}

	SET_ERRSTR("%s: Arg must be string or array ref", field);
	return(FALSE);
}

static SV *string_from_nat_range(struct ip_nat_range *range) {
	SV *sv;
	char *string, *temp, *temp2;
	
	if(range->flags & IP_NAT_RANGE_MAP_IPS) {
		string = malloc(INET_ADDRSTRLEN + 1);
		inet_ntop(AF_INET, &range->min_ip, string, INET_ADDRSTRLEN);
		if(range->min_ip != range->max_ip) {
			temp = string;
			temp2 = malloc(INET_ADDRSTRLEN + 1);
			inet_ntop(AF_INET, &range->max_ip, temp2, INET_ADDRSTRLEN);
			asprintf(&string, "%s-%s", temp, temp2);
			free(temp);
			free(temp2);
		}
	}
	if(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
		asprintf(&temp, ":%u", ntohs(range->min.tcp.port));
		if(range->min.tcp.port < range->max.tcp.port) {
			asprintf(&temp2, "%s-%u", temp, ntohs(range->max.tcp.port));
			free(temp);
			temp = temp2;
		}

		if(string) {
			asprintf(&temp2, "%s%s", string, temp);
			free(string);
			free(temp);
			string = temp2;
		}
		else
			string = temp;
	}
	
	sv = newSVpv(string, 0);
	free(string);
	return(sv);
}

static void get_fields(HV *ent_hash, void *myinfo, struct ipt_entry *entry) {
	MODULE_DATATYPE *info = (void *)((MODULE_ENTRYTYPE *)myinfo)->data;
	SV *sv;

	if(info->rangesize > 1) {
		AV *av;
		int i;

		av = newAV();
		for(i = 0; i < info->rangesize; i++)
			av_store(av, i, string_from_nat_range(&info->range[i]));
		sv = newRV((SV *)av);
	}
	else if(info->rangesize == 1)
		sv = string_from_nat_range(info->range);

	hv_store(ent_hash, "to-source", 9, sv, 0);
}

static int final_check(void *myinfo, int flags) {
	if(!flags) {
		SET_ERRSTR("SNAT target requires 'to-source'");
		return(FALSE);
	}

	return(TRUE);
}

ModuleDef _module = {
	NULL, /* always NULL */
	MODULE_TYPE,
	MODULE_NAME,
	IPT_ALIGN(sizeof(MODULE_DATATYPE)),
	IPT_ALIGN(sizeof(MODULE_DATATYPE)),
	setup,
	parse_field,
	get_fields,
	final_check
};

ModuleDef *init(void) {
	return(&_module);
}
/* vim: ts=4
 */
