/* IPFW2 Backward Compatibility */

/* Convert to and from IPFW2 structures. */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/socketvar.h>

#include <sys/types.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_fw.h>
#include <netinet/tcp.h>

#include "ip_fw2_compat.h"

#define FW2_DEBUG_VERBOSE 0

/*
 * _s_x is a structure that stores a string <-> token pairs, used in
 * various places in the parser. Entries are stored in arrays,
 * with an entry with s=NULL as terminator.
 * The search routines are match_token() and match_value().
 * Often, an element with x=0 contains an error string.
 *
 */
struct _s_x {
	char const *s;
	int x;
};

#define NO_VERSION_STR "IP_FW_VERSION_NONE"
#define VERSION_ZERO_STR "IP_FW_VERSION_0"
#define VERSION_ONE_STR "IP_FW_VERSION_1"
#define CURRENT_API_VERSION_STR "IP_FW_CURRENT_API_VERSION"

static struct _s_x f_tcpflags[] = {
	{ "syn", TH_SYN },
	{ "fin", TH_FIN },
	{ "ack", TH_ACK },
	{ "psh", TH_PUSH },
	{ "rst", TH_RST },
	{ "urg", TH_URG },
	{ "tcp flag", 0 },
	{ NULL,	0 }
};

static struct _s_x f_tcpopts[] = {
	{ "mss",	IP_FW_TCPOPT_MSS },
	{ "maxseg",	IP_FW_TCPOPT_MSS },
	{ "window",	IP_FW_TCPOPT_WINDOW },
	{ "sack",	IP_FW_TCPOPT_SACK },
	{ "ts",		IP_FW_TCPOPT_TS },
	{ "timestamp",	IP_FW_TCPOPT_TS },
	{ "cc",		IP_FW_TCPOPT_CC },
	{ "tcp option",	0 },
	{ NULL,	0 }
};

/*
 * IP options span the range 0 to 255 so we need to remap them
 * (though in fact only the low 5 bits are significant).
 */
static struct _s_x f_ipopts[] = {
	{ "ssrr",	IP_FW_IPOPT_SSRR},
	{ "lsrr",	IP_FW_IPOPT_LSRR},
	{ "rr",		IP_FW_IPOPT_RR},
	{ "ts",		IP_FW_IPOPT_TS},
	{ "ip option",	0 },
	{ NULL,	0 }
};

static struct _s_x f_iptos[] = {
	{ "lowdelay",	IPTOS_LOWDELAY},
	{ "throughput",	IPTOS_THROUGHPUT},
	{ "reliability", IPTOS_RELIABILITY},
	{ "mincost",	IPTOS_MINCOST},
	{ "congestion",	IPTOS_CE},
	{ "ecntransport", IPTOS_ECT},
	{ "ip tos option", 0},
	{ NULL,	0 }
};

static struct _s_x limit_masks[] = {
	{"all",		DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT},
	{"src-addr",	DYN_SRC_ADDR},
	{"src-port",	DYN_SRC_PORT},
	{"dst-addr",	DYN_DST_ADDR},
	{"dst-port",	DYN_DST_PORT},
	{NULL,		0}
};

static void
ipfw_print_fw_flags(u_int flags)
{
	/* print action */
	switch (flags & IP_FW_F_COMMAND_COMPAT) {
		case IP_FW_F_ACCEPT_COMPAT:
			printf("IP_FW_F_ACCEPT_COMPAT\n");
			break;
		case IP_FW_F_COUNT_COMPAT:
			printf("IP_FW_F_COUNT_COMPAT\n");
			break;
		case IP_FW_F_PIPE_COMPAT:
			printf("IP_FW_F_PIPE_COMPAT\n");
			break;
		case IP_FW_F_QUEUE_COMPAT:
			printf("IP_FW_F_QUEUE_COMPAT\n");
			break;
		case IP_FW_F_SKIPTO_COMPAT:
			printf("IP_FW_F_SKIPTO_COMPAT\n");
			break;
		case IP_FW_F_DIVERT_COMPAT:
			printf("IP_FW_F_DIVERT_COMPAT\n");
			break;
		case IP_FW_F_TEE_COMPAT:
			printf("IP_FW_F_TEE_COMPAT\n");
			break;
		case IP_FW_F_FWD_COMPAT:
			printf("IP_FW_F_FWD_COMPAT\n");
			break;
		case IP_FW_F_DENY_COMPAT:
			printf("IP_FW_F_DENY_COMPAT\n");
			break;
		case IP_FW_F_REJECT_COMPAT:
			printf("IP_FW_F_REJECT_COMPAT\n");
			break;
		case IP_FW_F_CHECK_S_COMPAT:
			printf("IP_FW_F_CHECK_S_COMPAT\n");
			break;
		default:
			printf("No action given\n");
			break;
	}

	/* print commands */
	if (flags & IP_FW_F_IN_COMPAT) {
		printf("IP_FW_F_IN_COMPAT\n");
	}
	if (flags & IP_FW_F_OUT_COMPAT) {
		printf("IP_FW_F_OUT_COMPAT\n");
	}
	if (flags & IP_FW_F_IIFACE_COMPAT) {
		printf("IP_FW_F_IIFACE_COMPAT\n");
	}
	if (flags & IP_FW_F_OIFACE_COMPAT) {
		printf("IP_FW_F_OIFACE_COMPAT\n");
	}
	if (flags & IP_FW_F_PRN_COMPAT) {
		printf("IP_FW_F_PRN_COMPAT\n");
	}
	if (flags & IP_FW_F_SRNG_COMPAT) {
		printf("IP_FW_F_SRNG_COMPAT\n");
	}
	if (flags & IP_FW_F_DRNG_COMPAT) {
		printf("IP_FW_F_DRNG_COMPAT\n");
	}
	if (flags & IP_FW_F_FRAG_COMPAT) {
		printf("IP_FW_F_FRAG_COMPAT\n");
	}
	if (flags & IP_FW_F_IIFNAME_COMPAT) {
		printf("IP_FW_F_IIFNAME_COMPAT\n");
	}
	if (flags & IP_FW_F_OIFNAME_COMPAT) {
		printf("IP_FW_F_OIFNAME_COMPAT\n");
	}
	if (flags & IP_FW_F_INVSRC_COMPAT) {
		printf("IP_FW_F_INVSRC_COMPAT\n");
	}
	if (flags & IP_FW_F_INVDST_COMPAT) {
		printf("IP_FW_F_INVDST_COMPAT\n");
	}
	if (flags & IP_FW_F_ICMPBIT_COMPAT) {
		printf("IP_FW_F_ICMPBIT_COMPAT\n");
	}
	if (flags & IP_FW_F_UID_COMPAT) {
		printf("IP_FW_F_UID_COMPAT\n");
	}
	if (flags & IP_FW_F_RND_MATCH_COMPAT) {
		printf("IP_FW_F_RND_MATCH_COMPAT\n");
	}
	if (flags & IP_FW_F_SMSK_COMPAT) {
		printf("IP_FW_F_SMSK_COMPAT\n");
	}
	if (flags & IP_FW_F_DMSK_COMPAT) {
		printf("IP_FW_F_DMSK_COMPAT\n");
	}
	if (flags & IP_FW_BRIDGED_COMPAT) {
		printf("IP_FW_BRIDGED_COMPAT\n");
	}
	if (flags & IP_FW_F_KEEP_S_COMPAT) {
		printf("IP_FW_F_KEEP_S_COMPAT\n");
	}
	if (flags & IP_FW_F_CHECK_S_COMPAT) {
		printf("IP_FW_F_CHECK_S_COMPAT\n");
	}
	if (flags & IP_FW_F_SME_COMPAT) {
		printf("IP_FW_F_SME_COMPAT\n");
	}
	if (flags & IP_FW_F_DME_COMPAT) {
		printf("IP_FW_F_DME_COMPAT\n");
	}
}

static void
print_fw_version(u_int32_t api_version)
{
	switch (api_version) {
		case IP_FW_VERSION_0:
			printf("Version: %s\n", VERSION_ZERO_STR);
			break;
		case IP_FW_VERSION_1:
			printf("Version: %s\n", VERSION_ONE_STR);
			break;
		case IP_FW_CURRENT_API_VERSION:
			printf("Version: %s\n", CURRENT_API_VERSION_STR);
			break;
		case IP_FW_VERSION_NONE:
			printf("Version: %s\n", NO_VERSION_STR);
			break;
		default:
			printf("Unrecognized version\n");
			break;
	}
}

static void
ipfw_print_vers1_struct(struct ip_fw_compat *vers1_rule)
{
	char ipv4str[MAX_IPv4_STR_LEN];
	print_fw_version(vers1_rule->version);
	printf("Rule #%d\n", vers1_rule->fw_number);
	
	ipfw_print_fw_flags(vers1_rule->fw_flg);
	
	printf("fw_pcnt: %d\n", vers1_rule->fw_pcnt);
	printf("fw_bcnt: %d\n", vers1_rule->fw_bcnt);
	printf("fw_src: %s\n",
		inet_ntop(AF_INET, &vers1_rule->fw_src, ipv4str, sizeof(ipv4str)));
	printf("fw_dst: %s\n",
		inet_ntop(AF_INET, &vers1_rule->fw_dst, ipv4str, sizeof(ipv4str)));
	printf("fw_smsk: %s\n",
		inet_ntop(AF_INET, &vers1_rule->fw_smsk, ipv4str, sizeof(ipv4str)));
	printf("fw_dmsk: %s\n",
		inet_ntop(AF_INET, &vers1_rule->fw_dmsk, ipv4str, sizeof(ipv4str)));
	
	if (vers1_rule->fw_flg & IP_FW_F_ICMPBIT_COMPAT) {
		int type_index;
		int first = 1;

		printf(" icmptype");

		for (type_index = 0; 
				type_index < (IP_FW_ICMPTYPES_DIM_COMPAT * sizeof(unsigned) * 8); 
				++type_index) {
			if (vers1_rule->fw_uar_compat.fw_icmptypes[type_index / (sizeof(unsigned) * 8)] &
				(1U << (type_index % (sizeof(unsigned) * 8)))) {
				printf("%c%d", first == 1 ? ' ' : ',', type_index);
				first = 0;
			}
		}
	} else {
		int i, nsp, ndp;
		
		nsp = IP_FW_GETNSRCP_COMPAT(vers1_rule);
		for (i = 0; i < nsp; i++) {
			printf("source ports: fw_uar_compat.fw_pts: %04x", vers1_rule->fw_uar_compat.fw_pts[i]);
			if (i == 0 && (vers1_rule->fw_flg & IP_FW_F_SRNG_COMPAT))
				printf("-");
			else if (i == 0 && (vers1_rule->fw_flg & IP_FW_F_SMSK_COMPAT))
				printf(":");
			else
				printf(",");
		}
		
		printf("\n");
		
		ndp = IP_FW_GETNDSTP_COMPAT(vers1_rule);
		for (i = 0; i < ndp; i++) {
			printf("source ports: fw_uar_compat.fw_pts: %04x", vers1_rule->fw_uar_compat.fw_pts[nsp+i]);
			if (i == 0 && (vers1_rule->fw_flg & IP_FW_F_DRNG_COMPAT))
				printf("-");
			else if (i == 0 && (vers1_rule->fw_flg & IP_FW_F_DMSK_COMPAT))
				printf(":");
			else
				printf(",");
		}

		printf("\n");
	}
	
	printf("fw_ipflg: %d\n", vers1_rule->fw_ipflg);
	printf("fw_ipopt: %d\n", vers1_rule->fw_ipopt);
	printf("fw_ipnopt: %d\n", vers1_rule->fw_ipnopt);
	printf("fw_tcpopt: %d\n", vers1_rule->fw_tcpopt);
	printf("fw_tcpnopt: %d\n", vers1_rule->fw_tcpnopt);
	printf("fw_tcpf: %d\n", vers1_rule->fw_tcpf);
	printf("fw_tcpnf: %d\n", vers1_rule->fw_tcpnf);
	printf("timestamp: %d\n", vers1_rule->timestamp);

	if ((vers1_rule->fw_flg & IF_FW_F_VIAHACK_COMPAT) == IF_FW_F_VIAHACK_COMPAT) {
		printf("fw_in_if: ");
		inet_ntop(AF_INET, &vers1_rule->fw_in_if.fu_via_ip, ipv4str,
				  sizeof(ipv4str));
		printf("fu_via_ip: %s\n", ipv4str);
		printf("fu_via_if_compat.name: %s\n", vers1_rule->fw_in_if.fu_via_if_compat.name);
		printf("fu_via_if_compat.unit: %d\n", vers1_rule->fw_in_if.fu_via_if_compat.unit);
	} else {
		if (vers1_rule->fw_flg & IP_FW_F_IIFACE_COMPAT) {
			printf("fw_in_if: ");
			printf("fu_via_ip: %s\n",
				   inet_ntop(AF_INET, &vers1_rule->fw_in_if.fu_via_ip, ipv4str,
							 sizeof(ipv4str)));
			printf("fu_via_if_compat.name: %s\n", vers1_rule->fw_in_if.fu_via_if_compat.name);
			printf("fu_via_if_compat.unit: %d\n", vers1_rule->fw_in_if.fu_via_if_compat.unit);
		}
		if (vers1_rule->fw_flg & IP_FW_F_OIFACE_COMPAT) {
			printf("fw_out_if: ");
			printf("fu_via_ip: %s\n",
				   inet_ntop(AF_INET, &vers1_rule->fw_out_if.fu_via_ip,
				   			 ipv4str, sizeof(ipv4str)));
			printf("fu_via_if_compat.name: %s\n", vers1_rule->fw_out_if.fu_via_if_compat.name);
			printf("fu_via_if_compat.unit: %d\n", vers1_rule->fw_out_if.fu_via_if_compat.unit);
		}
	}
	
	printf("fw_prot: %d\n", vers1_rule->fw_prot);
	printf("fw_nports: %d\n", vers1_rule->fw_nports);
	printf("pipe_ptr: %x\n", vers1_rule->pipe_ptr);
	printf("next_rule_ptr: %x\n", vers1_rule->next_rule_ptr);
	printf("fw_uid: %d\n", vers1_rule->fw_uid);
	printf("fw_logamount: %d\n", vers1_rule->fw_logamount);
	printf("fw_loghighest: %d\n", vers1_rule->fw_loghighest);
}

static void
print_icmptypes(ipfw_insn_u32 *cmd)
{
	int i;
	char sep= ' ';

	printf(" icmptypes");
	for (i = 0; i < 32; i++) {
		if ( (cmd->d[0] & (1 << (i))) == 0)
			continue;
		printf("%c%d", sep, i);
		sep = ',';
	}
}

/*
 * print flags set/clear in the two bitmasks passed as parameters.
 * There is a specialized check for f_tcpflags.
 */
static void
print_flags(char const *name, ipfw_insn *cmd, struct _s_x *list)
{
	char const *comma = "";
	int i;
	uint8_t set = cmd->arg1 & 0xff;
	uint8_t clear = (cmd->arg1 >> 8) & 0xff;

	if (list == f_tcpflags && set == TH_SYN && clear == TH_ACK) {
		printf(" setup");
		return;
	}

	printf(" %s ", name);
	for (i=0; list[i].x != 0; i++) {
		if (set & list[i].x) {
			set &= ~list[i].x;
			printf("%s%s", comma, list[i].s);
			comma = ",";
		}
		if (clear & list[i].x) {
			clear &= ~list[i].x;
			printf("%s!%s", comma, list[i].s);
			comma = ",";
		}
	}
}

static int
contigmask(uint8_t *p, int len)
{
	int i, n;

	for (i=0; i<len ; i++)
		if ( (p[i/8] & (1 << (7 - (i%8)))) == 0) /* first bit unset */
			break;
	for (n=i+1; n < len; n++)
		if ( (p[n/8] & (1 << (7 - (n%8)))) != 0)
			return -1; /* mask not contiguous */
	return i;
}

/*
 * Print the ip address contained in a command.
 */
static void
print_ip(ipfw_insn_ip *cmd)
{
	int len = F_LEN((ipfw_insn *)cmd);
	uint32_t *a = ((ipfw_insn_u32 *)cmd)->d;
	char ipv4str[MAX_IPv4_STR_LEN];

	printf("%s ", cmd->o.len & F_NOT ? " not": "");

	if (cmd->o.opcode == O_IP_SRC_ME || cmd->o.opcode == O_IP_DST_ME) {
		printf("me");
		return;
	}

	/*
	 * len == 2 indicates a single IP, whereas lists of 1 or more
	 * addr/mask pairs have len = (2n+1). We convert len to n so we
	 * use that to count the number of entries.
	 */
    for (len = len / 2; len > 0; len--, a += 2) {
	int mb =	/* mask length */
	    (cmd->o.opcode == O_IP_SRC || cmd->o.opcode == O_IP_DST) ?
		32 : contigmask((uint8_t *)&(a[1]), 32);
	if (mb == 0) {	/* any */
		printf("any");
	} else {		/* numeric IP followed by some kind of mask */
		printf("%s", inet_ntop(AF_INET, &a[0], ipv4str, sizeof(ipv4str)));
		if (mb < 0)
			printf(":%s", inet_ntop(AF_INET, &a[1], ipv4str, sizeof(ipv4str)));
		else if (mb < 32)
			printf("/%d", mb);
	}
	if (len > 1)
		printf(",");
    }
}

/*
 * prints a MAC address/mask pair
 */
static void
print_mac(uint8_t *addr, uint8_t *mask)
{
	int l = contigmask(mask, 48);

	if (l == 0)
		printf(" any");
	else {
		printf(" %02x:%02x:%02x:%02x:%02x:%02x",
		    addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
		if (l == -1)
			printf("&%02x:%02x:%02x:%02x:%02x:%02x",
			    mask[0], mask[1], mask[2],
			    mask[3], mask[4], mask[5]);
		else if (l < 48)
			printf("/%d", l);
	}
}

static void
ipfw_print_vers2_struct(struct ip_fw *vers2_rule)
{
	int			l;
	ipfw_insn		*cmd;
	ipfw_insn_log	*logptr = NULL;
	char			ipv4str[MAX_IPv4_STR_LEN];
	
	print_fw_version(vers2_rule->version);

	printf("act_ofs: %d\n", vers2_rule->act_ofs);
	printf("cmd_len: %d\n", vers2_rule->cmd_len);
	printf("rulenum: %d\n", vers2_rule->rulenum);
	printf("set: %d\n", vers2_rule->set);
	printf("pcnt: %d\n", vers2_rule->pcnt);
	printf("bcnt: %d\n", vers2_rule->bcnt);
	printf("timestamp: %d\n", vers2_rule->timestamp);
	
	/*
	 * first print actions
	 */
	for (l = vers2_rule->cmd_len - vers2_rule->act_ofs, cmd = ACTION_PTR(vers2_rule);
			l > 0 ; l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
		switch(cmd->opcode) {
			case O_CHECK_STATE:
				printf("check-state");
				break;
	
			case O_ACCEPT:
				printf("allow");
				break;
	
			case O_COUNT:
				printf("count");
				break;
	
			case O_DENY:
				printf("deny");
				break;
	
			case O_REJECT:
				if (cmd->arg1 == ICMP_REJECT_RST)
					printf("reset");
				else if (cmd->arg1 == ICMP_UNREACH_HOST)
					printf("reject");
				else
					printf("unreach %u", cmd->arg1);
				break;
	
			case O_SKIPTO:
				printf("skipto %u", cmd->arg1);
				break;
	
			case O_PIPE:
				printf("pipe %u", cmd->arg1);
				break;
	
			case O_QUEUE:
				printf("queue %u", cmd->arg1);
				break;
	
			case O_DIVERT:
				printf("divert %u", cmd->arg1);
				break;
	
			case O_TEE:
				printf("tee %u", cmd->arg1);
				break;
	
			case O_FORWARD_IP:
			{
				ipfw_insn_sa *s = (ipfw_insn_sa *)cmd;
	
				printf("fwd %s",
					   inet_ntop(AF_INET, &s->sa.sin_addr, ipv4str,
					   			 sizeof(ipv4str)));
				if (s->sa.sin_port)
					printf(",%d", s->sa.sin_port);
				break;
			}
	
			case O_LOG: /* O_LOG is printed last */
				logptr = (ipfw_insn_log *)cmd;
				break;
	
			default:
				printf("** unrecognized action %d len %d",
					cmd->opcode, cmd->len);
		}
	}
	if (logptr) {
		if (logptr->max_log > 0)
			printf(" log logamount %d", logptr->max_log);
		else
			printf(" log");
	}

	/*
	 * then print the body.
	 */
	for (l = vers2_rule->act_ofs, cmd = vers2_rule->cmd ;
		l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) {
		/* useful alias */
		ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd;

		switch(cmd->opcode) {
			case O_PROB:
				break;	/* done already */
	
			case O_PROBE_STATE:
				break; /* no need to print anything here */
	
			case O_MACADDR2: 
			{
				ipfw_insn_mac *m = (ipfw_insn_mac *)cmd;
	
				if (cmd->len & F_NOT)
					printf(" not");
				printf(" MAC");
				print_mac(m->addr, m->mask);
				print_mac(m->addr + 6, m->mask + 6);
				printf("\n");
				break;
			}
			case O_MAC_TYPE:
			{
				uint16_t *p = ((ipfw_insn_u16 *)cmd)->ports;
				int i;
	
				for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
					printf("0x%04x", p[0]);
					if (p[0] != p[1]) {
						printf("-");
						printf("0x%04x", p[1]);
					}
					printf(",");
				}
				break;
			}
			case O_IP_SRC:
			case O_IP_SRC_MASK:
			case O_IP_SRC_ME:
				print_ip((ipfw_insn_ip *)cmd);
				break;
	
			case O_IP_DST:
			case O_IP_DST_MASK:
			case O_IP_DST_ME:
				print_ip((ipfw_insn_ip *)cmd);
				break;
	
			case O_IP_DSTPORT:
			case O_IP_SRCPORT:
			{
				uint16_t *p = ((ipfw_insn_u16 *)cmd)->ports;
				int i;
	
				for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
					printf("0x%04x", p[0]);
					if (p[0] != p[1]) {
						printf("-");
						printf("0x%04x", p[1]);
					}
					printf(",");
				}
				break;
			}
			case O_PROTO: 
			{
				printf("O_PROTO");
				
				if (cmd->len & F_NOT)
					printf(" not");
	
				printf(" %u", cmd->arg1);
					
				break;
			}
	
			default: /*options ... */
			{
				if (cmd->len & F_NOT && cmd->opcode != O_IN)
					printf(" not");
				switch(cmd->opcode) {
					case O_FRAG:
						printf("O_FRAG");
						break;
		
					case O_IN:
						printf(cmd->len & F_NOT ? " out" : " O_IN");
						break;
		
					case O_LAYER2:
						printf(" O_LAYER2");
						break;
					case O_XMIT:
					case O_RECV:
					case O_VIA: 
					{
						char const *s;
						ipfw_insn_if *cmdif = (ipfw_insn_if *)cmd;
		
						if (cmd->opcode == O_XMIT)
							s = "O_XMIT";
						else if (cmd->opcode == O_RECV)
							s = "O_RECV";
						else /* if (cmd->opcode == O_VIA) */
							s = "O_VIA";
						if (cmdif->name[0] == '\0') {
							printf(" %s %s", s,
								   inet_ntop(AF_INET, &cmdif->p.ip, ipv4str,
								   			 sizeof(ipv4str)));
						}
						else if (cmdif->p.unit == -1)
							printf(" %s %s*", s, cmdif->name);
						else
							printf(" %s %s%d", s, cmdif->name,
								cmdif->p.unit);
					}
						break;
		
					case O_IPID:
						if (F_LEN(cmd) == 1)
							printf(" ipid %u", cmd->arg1 );
						else {
							uint16_t *p = ((ipfw_insn_u16 *)cmd)->ports;
							int i;
				
							for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
								printf("0x%04x", p[0]);
								if (p[0] != p[1]) {
									printf("-");
									printf("0x%04x", p[1]);
								}
								printf(",");
							}
						}
						
						break;
		
					case O_IPTTL:
						if (F_LEN(cmd) == 1)
							printf(" ipttl %u", cmd->arg1 );
						else {
							uint16_t *p = ((ipfw_insn_u16 *)cmd)->ports;
							int i;
				
							for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
								printf("0x%04x", p[0]);
								if (p[0] != p[1]) {
									printf("-");
									printf("0x%04x", p[1]);
								}
								printf(",");
							}
						}
						
						break;
		
					case O_IPVER:
						printf(" ipver %u", cmd->arg1 );
						break;
		
					case O_IPPRECEDENCE:
						printf(" ipprecedence %u", (cmd->arg1) >> 5 );
						break;
		
					case O_IPLEN:
						if (F_LEN(cmd) == 1)
							printf(" iplen %u", cmd->arg1 );
						else {
							uint16_t *p = ((ipfw_insn_u16 *)cmd)->ports;
							int i;
				
							for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) {
								printf("0x%04x", p[0]);
								if (p[0] != p[1]) {
									printf("-");
									printf("0x%04x", p[1]);
								}
								printf(",");
							}
						}
						
						break;
		
					case O_IPOPT:
						print_flags("ipoptions", cmd, f_ipopts);
						break;
		
					case O_IPTOS:
						print_flags("iptos", cmd, f_iptos);
						break;
		
					case O_ICMPTYPE:
						print_icmptypes((ipfw_insn_u32 *)cmd);
						break;
		
					case O_ESTAB:
						printf(" established");
						break;
		
					case O_TCPFLAGS:
						print_flags("tcpflags", cmd, f_tcpflags);
						break;
		
					case O_TCPOPTS:
						print_flags("tcpoptions", cmd, f_tcpopts);
						break;
		
					case O_TCPWIN:
						printf(" tcpwin %d", ntohs(cmd->arg1));
						break;
		
					case O_TCPACK:
						printf(" tcpack %ld", ntohl(cmd32->d[0]));
						break;
		
					case O_TCPSEQ:
						printf(" tcpseq %ld", ntohl(cmd32->d[0]));
						break;
		
					case O_UID:
						printf(" uid %u", cmd32->d[0]);
						break;
		
					case O_GID:
						printf(" gid %u", cmd32->d[0]);
						break;
		
					case O_VERREVPATH:
						printf(" verrevpath");
						break;
		
					case O_IPSEC:
						printf(" ipsec");
						break;
		
					case O_NOP:
						break;
		
					case O_KEEP_STATE:
						printf(" keep-state");
						break;
		
					case O_LIMIT:
					{
						struct _s_x *p = limit_masks;
						ipfw_insn_limit *c = (ipfw_insn_limit *)cmd;
						uint8_t x = c->limit_mask;
						char const *comma = " ";
		
						printf(" limit");
						for (; p->x != 0 ; p++)
							if ((x & p->x) == p->x) {
								x &= ~p->x;
								printf("%s%s", comma, p->s);
								comma = ",";
							}
						printf(" %d", c->conn_limit);
						
						break;
					}
		
					default:
						printf(" [opcode %d len %d]",
							cmd->opcode, cmd->len);
				} /* switch */
			} /* default */
		} /* switch */
	} /* for */
}

/*
 * helper function, updates the pointer to cmd with the length
 * of the current command, and also cleans up the first word of
 * the new command in case it has been clobbered before.
 * from ipfw2.c
 */
static ipfw_insn *
next_cmd(ipfw_insn *cmd)
{
	cmd += F_LEN(cmd);
	bzero(cmd, sizeof(*cmd));
	return cmd;
}

/*
 * A function to fill simple commands of size 1.
 * Existing flags are preserved.
 * from ipfw2.c
 */
static void
fill_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, uint16_t arg)
{
	cmd->opcode = opcode;
	cmd->len =  ((cmd->len) & (F_NOT | F_OR)) | 1;
	cmd->arg1 = arg;
}


static u_int32_t
fill_compat_tcpflags(u_int32_t flags) {
	u_int32_t	flags_compat = 0;
	
	if (flags & TH_FIN)
		flags_compat |= IP_FW_TCPF_FIN_COMPAT;
	if (flags & TH_SYN)
		flags_compat |= IP_FW_TCPF_SYN_COMPAT;
	if (flags & TH_RST)
		flags_compat |= IP_FW_TCPF_RST_COMPAT;
	if (flags & TH_PUSH)
		flags_compat |= IP_FW_TCPF_PSH_COMPAT;
	if (flags & TH_ACK)
		flags_compat |= IP_FW_TCPF_ACK_COMPAT;
	if (flags & TH_URG)
		flags_compat |= IP_FW_TCPF_URG_COMPAT;
		
	return flags_compat;
}


/* ********************************************
 * *********** Convert from Latest ************
 * ********************************************/

/*
 * Things we're actively ignoring:
 *	sets, sets of addresses, blocks (NOT, OR)
 */
static void
ipfw_map_from_cmds(struct ip_fw *curr_rule, struct ip_fw_compat *compat_rule)
{
	int 		l;
	ipfw_insn	*cmd;

	for (l = curr_rule->act_ofs, cmd = curr_rule->cmd ;
		l > 0 ; 
		l -= F_LEN(cmd) , cmd += F_LEN(cmd)) {
		/* useful alias */
		ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd;

		switch (cmd->opcode) {
			case O_PROTO:
				/* protocol */
				compat_rule->fw_prot = cmd->arg1;
				break;
			
			case O_IP_SRC_ME:
				compat_rule->fw_flg |= IP_FW_F_SME_COMPAT;
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_INVSRC_COMPAT;
				}
				break;
			
			case O_IP_SRC_MASK:
			{
				/* addr/mask */
				ipfw_insn_ip	*ip = (ipfw_insn_ip *)cmd;
				
				compat_rule->fw_src = ip->addr;
				compat_rule->fw_smsk = ip->mask;
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_INVSRC_COMPAT;
				}
				break;
			}
			
			case O_IP_SRC:
				/* one IP */
				/* source - 
				 * for now we only deal with one address
				 * per rule and ignore sets of addresses
				 */
				compat_rule->fw_src.s_addr = cmd32->d[0];
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_INVSRC_COMPAT;
				}
				break;
			
			case O_IP_SRCPORT:
			{
				/* source ports */
				ipfw_insn_u16	*ports = (ipfw_insn_u16 *)cmd;
				uint16_t		*p = ports->ports;
				int				i, j;
				
				/* copy list of ports */
				for (i = F_LEN(cmd) - 1, j = 0; i > 0; i--, j++, p += 2) {
					if (p[0] != p[1]) {
						/* this is a range */
						compat_rule->fw_flg |= IP_FW_F_SRNG_COMPAT;
						compat_rule->fw_uar_compat.fw_pts[j++] = p[0];
						compat_rule->fw_uar_compat.fw_pts[j] = p[1];
					} else {
						compat_rule->fw_uar_compat.fw_pts[j] = p[0];
					}
				}
				IP_FW_SETNSRCP_COMPAT(compat_rule, j);
				
				break;
			}

			case O_IP_DST_ME:
			/* destination */
				compat_rule->fw_flg |= IP_FW_F_DME_COMPAT;
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_INVDST_COMPAT;
				}
				break;

			case O_IP_DST_MASK:
			{
				/* addr/mask */
				ipfw_insn_ip	*ip = (ipfw_insn_ip *)cmd;
				
				compat_rule->fw_dst = ip->addr;
				compat_rule->fw_dmsk = ip->mask;
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_INVDST_COMPAT;
				}
				break;
			}
			case O_IP_DST:
				/* one IP */
				/* dest - 
				 * for now we only deal with one address
				 * per rule, and ignore sets of addresses
				 */
				compat_rule->fw_dst.s_addr = cmd32->d[0];
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_INVDST_COMPAT;
				}
				break;
				
			case O_IP_DSTPORT:
			{
				/* dest. ports */
				ipfw_insn_u16	*ports = (ipfw_insn_u16 *)cmd;
				uint16_t		*p = ports->ports;
				int				i, 
								j = IP_FW_GETNSRCP_COMPAT(compat_rule);
				
				/* copy list of ports */
				for (i = F_LEN(cmd) - 1; i > 0; i--, j++, p += 2) {
					if (p[0] != p[1]) {
						/* this is a range */
						compat_rule->fw_flg |= IP_FW_F_DRNG_COMPAT;
						compat_rule->fw_uar_compat.fw_pts[j++] = p[0];
						compat_rule->fw_uar_compat.fw_pts[j] = p[1];
					} else {
						compat_rule->fw_uar_compat.fw_pts[j] = p[0];
					}
				}
				IP_FW_SETNDSTP_COMPAT(compat_rule, (j - IP_FW_GETNSRCP_COMPAT(compat_rule)));
				
				break;
			}
			
			case O_LOG:
			{
				ipfw_insn_log *c = (ipfw_insn_log *)cmd;
				
				compat_rule->fw_flg |= IP_FW_F_PRN_COMPAT;
				compat_rule->fw_logamount = c->max_log;
				break;
			}	
			case O_UID:
				compat_rule->fw_flg |= IP_FW_F_UID_COMPAT;
				compat_rule->fw_uid = cmd32->d[0];
				break;
			
			case O_IN:
				if (cmd->len & F_NOT) {
					compat_rule->fw_flg |= IP_FW_F_OUT_COMPAT;
				} else {
					compat_rule->fw_flg |= IP_FW_F_IN_COMPAT;
				}
				break;
			
			case O_KEEP_STATE:
				compat_rule->fw_flg |= IP_FW_F_KEEP_S_COMPAT;
				break;

			case O_LAYER2:
				compat_rule->fw_flg |= IP_FW_BRIDGED_COMPAT;
				break;
			
			case O_XMIT:
			{
				ipfw_insn_if	*ifcmd = (ipfw_insn_if *)cmd;
				union ip_fw_if_compat	ifu;
				
				if ((ifcmd->o.len == 0) && (ifcmd->name[0] == '\0')) {
					/* any */
					compat_rule->fw_flg |= IP_FW_F_OIFACE_COMPAT;
					ifu.fu_via_ip.s_addr = 0;
				}
				else if (ifcmd->p.ip.s_addr != 0) {
					compat_rule->fw_flg |= IP_FW_F_OIFACE_COMPAT;
					ifu.fu_via_ip = ifcmd->p.ip;
				} else {
					compat_rule->fw_flg |= IP_FW_F_OIFNAME_COMPAT;
					strncpy(ifu.fu_via_if_compat.name, ifcmd->name, sizeof(ifu.fu_via_if_compat.name));
					ifu.fu_via_if_compat.unit = ifcmd->p.unit;
				}
				compat_rule->fw_out_if = ifu;
				
				break;
			}
			
			case O_RECV:
			{
				ipfw_insn_if	*ifcmd = (ipfw_insn_if *)cmd;
				union ip_fw_if_compat	ifu;
				
				if ((ifcmd->o.len == 0) && (ifcmd->name[0] == '\0')) {
					/* any */
					compat_rule->fw_flg |= IP_FW_F_IIFACE_COMPAT;
					ifu.fu_via_ip.s_addr = 0;
				}
				else if (ifcmd->p.ip.s_addr != 0) {
					compat_rule->fw_flg |= IP_FW_F_IIFACE_COMPAT;
					ifu.fu_via_ip = ifcmd->p.ip;
				} else {
					compat_rule->fw_flg |= IP_FW_F_IIFNAME_COMPAT;
					strncpy(ifu.fu_via_if_compat.name, ifcmd->name, sizeof(ifu.fu_via_if_compat.name));
					ifu.fu_via_if_compat.unit = ifcmd->p.unit;
				}
				compat_rule->fw_in_if = ifu;
				
				break;
			}
			
			case O_VIA:
			{
				ipfw_insn_if			*ifcmd = (ipfw_insn_if *)cmd;
				union ip_fw_if_compat	ifu;
				
				if ((ifcmd->o.len == 0) && (ifcmd->name[0] == '\0')) {
					/* any */
					ifu.fu_via_ip.s_addr = 0;
				}
				else if (ifcmd->name[0] != '\0') {
					compat_rule->fw_flg |= IP_FW_F_IIFNAME_COMPAT;
					strncpy(ifu.fu_via_if_compat.name, ifcmd->name, sizeof(ifu.fu_via_if_compat.name));
					ifu.fu_via_if_compat.unit = ifcmd->p.unit;
				} else {
					ifu.fu_via_ip = ifcmd->p.ip;
				}
				compat_rule->fw_flg |= IF_FW_F_VIAHACK_COMPAT;
				compat_rule->fw_out_if = compat_rule->fw_in_if = ifu;
				
				break;
			}

			case O_FRAG:
				compat_rule->fw_flg |= IP_FW_F_FRAG_COMPAT;
				break;
			
			case O_IPOPT:
				/* IP options */
				compat_rule->fw_ipopt = (cmd->arg1 & 0xff);
				compat_rule->fw_ipnopt = ((cmd->arg1 >> 8) & 0xff);
				break;
				
			case O_TCPFLAGS:
				/* check for "setup" */
				if ((cmd->arg1 & 0xff) == TH_SYN &&
					((cmd->arg1 >> 8) & 0xff) == TH_ACK) {
					compat_rule->fw_tcpf = IP_FW_TCPF_SYN_COMPAT;
					compat_rule->fw_tcpnf = IP_FW_TCPF_ACK_COMPAT;
				}
				else {
					compat_rule->fw_tcpf = fill_compat_tcpflags(cmd->arg1 & 0xff);
					compat_rule->fw_tcpnf = fill_compat_tcpflags((cmd->arg1 >> 8) & 0xff);
				}
				break;
				
			case O_TCPOPTS:
				/* TCP options */
				compat_rule->fw_tcpopt = (cmd->arg1 & 0xff);
				compat_rule->fw_tcpnopt = ((cmd->arg1 >> 8) & 0xff);
				break;
			
			case O_ESTAB:
				compat_rule->fw_ipflg |= IP_FW_IF_TCPEST_COMPAT;
				break;
			
			case O_ICMPTYPE:
			{
				/* ICMP */
				/* XXX: check this */
				int	i, type;
				
				compat_rule->fw_flg |= IP_FW_F_ICMPBIT_COMPAT;
				for (i = 0; i < sizeof(uint32_t) ; i++) {
					type = cmd32->d[0] & i;
					
					compat_rule->fw_uar_compat.fw_icmptypes[type / (sizeof(unsigned) * 8)] |= 
						1 << (type % (sizeof(unsigned) * 8));
				}
				break;
			}
			default:
				break;
		} /* switch */
	} /* for */
}

static void
ipfw_map_from_actions(struct ip_fw *curr_rule, struct ip_fw_compat *compat_rule)
{
	int l;
	ipfw_insn	*cmd;
	
	for (l = curr_rule->cmd_len - curr_rule->act_ofs, cmd = ACTION_PTR(curr_rule);
			l > 0 ; 
			l -= F_LEN(cmd), cmd += F_LEN(cmd)) {
		switch (cmd->opcode) {
			case O_ACCEPT:
				compat_rule->fw_flg |= IP_FW_F_ACCEPT_COMPAT;
				break;
			case O_COUNT:
				compat_rule->fw_flg |= IP_FW_F_COUNT_COMPAT;
				break;
			case O_PIPE:
				compat_rule->fw_flg |= IP_FW_F_PIPE_COMPAT;
				compat_rule->fw_divert_port_compat = cmd->arg1;
				break;
			case O_QUEUE:
				compat_rule->fw_flg |= IP_FW_F_QUEUE_COMPAT;
				compat_rule->fw_divert_port_compat = cmd->arg1;
				break;
			case O_SKIPTO:
				compat_rule->fw_flg |= IP_FW_F_SKIPTO_COMPAT;
				compat_rule->fw_skipto_rule_compat = cmd->arg1;
				break;
			case O_DIVERT:
				compat_rule->fw_flg |= IP_FW_F_DIVERT_COMPAT;
				compat_rule->fw_divert_port_compat = cmd->arg1;
				break;
			case O_TEE:
				compat_rule->fw_flg |= IP_FW_F_TEE_COMPAT;
				compat_rule->fw_divert_port_compat = cmd->arg1;
				break;
			case O_FORWARD_IP:
			{
				ipfw_insn_sa	*p = (ipfw_insn_sa *)cmd;
				
				compat_rule->fw_flg |= IP_FW_F_FWD_COMPAT;
				compat_rule->fw_fwd_ip_compat.sin_len = p->sa.sin_len;
				compat_rule->fw_fwd_ip_compat.sin_family = p->sa.sin_family;
				compat_rule->fw_fwd_ip_compat.sin_port = p->sa.sin_port;
				compat_rule->fw_fwd_ip_compat.sin_addr = p->sa.sin_addr;

				break;
			}
			case O_DENY:
				compat_rule->fw_flg |= IP_FW_F_DENY_COMPAT;
				break;
			case O_REJECT:
				compat_rule->fw_flg |= IP_FW_F_REJECT_COMPAT;
				compat_rule->fw_reject_code_compat = cmd->arg1;
				break;
			case O_CHECK_STATE:
				compat_rule->fw_flg |= IP_FW_F_CHECK_S_COMPAT;
				break;
			default:
				break;
		}
	}
}

static void
ipfw_version_latest_to_one(struct ip_fw *curr_rule, struct ip_fw_compat *rule_vers1)
{
	if (!rule_vers1)
		return;
		
	bzero(rule_vers1, sizeof(struct ip_fw_compat));
	
	rule_vers1->version = IP_FW_VERSION_1;
	rule_vers1->context = curr_rule->context;
	rule_vers1->fw_number = curr_rule->rulenum;
	rule_vers1->fw_pcnt = curr_rule->pcnt;
	rule_vers1->fw_bcnt = curr_rule->bcnt;
	rule_vers1->timestamp = curr_rule->timestamp;
	
	/* convert actions */
	ipfw_map_from_actions(curr_rule, rule_vers1);

	/* convert commands */
	ipfw_map_from_cmds(curr_rule, rule_vers1);
	
#if FW2_DEBUG_VERBOSE
	ipfw_print_vers1_struct(rule_vers1);
#endif
}

/* first convert to version one then to version zero */
static void
ipfw_version_latest_to_zero(struct ip_fw *curr_rule, struct ip_old_fw *rule_vers0)
{
	struct ip_fw_compat	rule_vers1;
	
	ipfw_version_latest_to_one(curr_rule, &rule_vers1);

	bzero(rule_vers0, sizeof(struct ip_old_fw));
	bcopy(&rule_vers1.fw_uar_compat, &rule_vers0->fw_uar, sizeof(rule_vers1.fw_uar_compat));
	bcopy(&rule_vers1.fw_in_if, &rule_vers0->fw_in_if, sizeof(rule_vers1.fw_in_if));
	bcopy(&rule_vers1.fw_out_if, &rule_vers0->fw_out_if, sizeof(rule_vers1.fw_out_if));
	bcopy(&rule_vers1.fw_un_compat, &rule_vers0->fw_un, sizeof(rule_vers1.fw_un_compat));

	rule_vers0->fw_pcnt       = rule_vers1.fw_pcnt;
	rule_vers0->fw_bcnt       = rule_vers1.fw_bcnt;
	rule_vers0->fw_src        = rule_vers1.fw_src;
	rule_vers0->fw_dst        = rule_vers1.fw_dst;
	rule_vers0->fw_smsk       = rule_vers1.fw_smsk;
	rule_vers0->fw_dmsk       = rule_vers1.fw_dmsk;
	rule_vers0->fw_number     = rule_vers1.fw_number;
	rule_vers0->fw_flg        = rule_vers1.fw_flg;
	rule_vers0->fw_ipopt      = rule_vers1.fw_ipopt;
	rule_vers0->fw_ipnopt     = rule_vers1.fw_ipnopt;
	rule_vers0->fw_tcpf       = rule_vers1.fw_tcpf;
	rule_vers0->fw_tcpnf      = rule_vers1.fw_tcpnf;
	rule_vers0->timestamp     = rule_vers1.timestamp;
	rule_vers0->fw_prot       = rule_vers1.fw_prot;
	rule_vers0->fw_nports     = rule_vers1.fw_nports;
	rule_vers0->pipe_ptr      = rule_vers1.pipe_ptr;
	rule_vers0->next_rule_ptr = rule_vers1.next_rule_ptr;

	if (rule_vers1.fw_ipflg && IP_FW_IF_TCPEST_COMPAT) rule_vers0->fw_tcpf |= IP_OLD_FW_TCPF_ESTAB;
}

void
ipfw_convert_from_latest(struct ip_fw *curr_rule, void *old_rule, u_int32_t api_version)
{
	switch (api_version) {
		case IP_FW_VERSION_0:
		{
			struct ip_old_fw	*rule_vers0 = old_rule;
			
			ipfw_version_latest_to_zero(curr_rule, rule_vers0);
			break;
		}
		case IP_FW_VERSION_1:
		{
			struct ip_fw_compat	*rule_vers1 = old_rule;
			
			ipfw_version_latest_to_one(curr_rule, rule_vers1);
			break;
		}
		case IP_FW_CURRENT_API_VERSION:
			/* ipfw2 for now, don't need to do anything */
			break;
		
		default:
			/* unknown version */
			break;
	}
}


/* ********************************************
 * *********** Convert to Latest **************
 * ********************************************/

/* from ip_fw.c */
static int
ipfw_check_vers1_struct(struct ip_fw_compat *frwl)
{
	/* Check for invalid flag bits */
	if ((frwl->fw_flg & ~IP_FW_F_MASK_COMPAT) != 0) {
		/* 
		printf(("%s undefined flag bits set (flags=%x)\n",
		    err_prefix, frwl->fw_flg));
		*/
		return (EINVAL);
	}
	if (frwl->fw_flg == IP_FW_F_CHECK_S_COMPAT) {
		/* check-state */
		return 0 ;
	}
	/* Must apply to incoming or outgoing (or both) */
	if (!(frwl->fw_flg & (IP_FW_F_IN_COMPAT | IP_FW_F_OUT_COMPAT))) {
		/*
		printf(("%s neither in nor out\n", err_prefix));
		*/
		return (EINVAL);
	}
	/* Empty interface name is no good */
	if (((frwl->fw_flg & IP_FW_F_IIFNAME_COMPAT)
	      && !*frwl->fw_in_if.fu_via_if_compat.name)
	    || ((frwl->fw_flg & IP_FW_F_OIFNAME_COMPAT)
	      && !*frwl->fw_out_if.fu_via_if_compat.name)) {
		/*
		printf(("%s empty interface name\n", err_prefix));
		*/
		return (EINVAL);
	}
	/* Sanity check interface matching */
	if ((frwl->fw_flg & IF_FW_F_VIAHACK_COMPAT) == IF_FW_F_VIAHACK_COMPAT) {
		;		/* allow "via" backwards compatibility */
	} else if ((frwl->fw_flg & IP_FW_F_IN_COMPAT)
	    && (frwl->fw_flg & IP_FW_F_OIFACE_COMPAT)) {
		/*
		printf(("%s outgoing interface check on incoming\n",
		    err_prefix));
		*/
		return (EINVAL);
	}
	/* Sanity check port ranges */
	if ((frwl->fw_flg & IP_FW_F_SRNG_COMPAT) && IP_FW_GETNSRCP_COMPAT(frwl) < 2) {
		/*
		printf(("%s src range set but n_src_p=%d\n",
		    err_prefix, IP_FW_GETNSRCP_COMPAT(frwl)));
		*/
		return (EINVAL);
	}
	if ((frwl->fw_flg & IP_FW_F_DRNG_COMPAT) && IP_FW_GETNDSTP_COMPAT(frwl) < 2) {
		/*
		printf(("%s dst range set but n_dst_p=%d\n",
		    err_prefix, IP_FW_GETNDSTP_COMPAT(frwl)));
		*/
		return (EINVAL);
	}
	if (IP_FW_GETNSRCP_COMPAT(frwl) + IP_FW_GETNDSTP_COMPAT(frwl) > IP_FW_MAX_PORTS_COMPAT) {
		/*
		printf(("%s too many ports (%d+%d)\n",
		    err_prefix, IP_FW_GETNSRCP_COMPAT(frwl), IP_FW_GETNDSTP_COMPAT(frwl)));
		*/
		return (EINVAL);
	}
	/*
	 *	Protocols other than TCP/UDP don't use port range
	 */
	if ((frwl->fw_prot != IPPROTO_TCP) &&
	    (frwl->fw_prot != IPPROTO_UDP) &&
	    (IP_FW_GETNSRCP_COMPAT(frwl) || IP_FW_GETNDSTP_COMPAT(frwl))) {
		/*
		printf(("%s port(s) specified for non TCP/UDP rule\n",
		    err_prefix));
		*/
		return (EINVAL);
	}

	/*
	 *	Rather than modify the entry to make such entries work, 
	 *	we reject this rule and require user level utilities
	 *	to enforce whatever policy they deem appropriate.
	 */
	if ((frwl->fw_src.s_addr & (~frwl->fw_smsk.s_addr)) || 
		(frwl->fw_dst.s_addr & (~frwl->fw_dmsk.s_addr))) {
		/*
		printf(("%s rule never matches\n", err_prefix));
		*/
		return (EINVAL);
	}

	if ((frwl->fw_flg & IP_FW_F_FRAG_COMPAT) &&
		(frwl->fw_prot == IPPROTO_UDP || frwl->fw_prot == IPPROTO_TCP)) {
		if (frwl->fw_nports) {
		/*
			printf(("%s cannot mix 'frag' and ports\n", err_prefix));
		*/
			return (EINVAL);
		}
		if (frwl->fw_prot == IPPROTO_TCP &&
			frwl->fw_tcpf != frwl->fw_tcpnf) {
		/*
			printf(("%s cannot mix 'frag' and TCP flags\n", err_prefix));
		*/
			return (EINVAL);
		}
	}

	/* Check command specific stuff */
	switch (frwl->fw_flg & IP_FW_F_COMMAND_COMPAT)
	{
	case IP_FW_F_REJECT_COMPAT:
		if (frwl->fw_reject_code_compat >= 0x100
		    && !(frwl->fw_prot == IPPROTO_TCP
		      && frwl->fw_reject_code_compat == IP_FW_REJECT_RST_COMPAT)) {
		/*
			printf(("%s unknown reject code\n", err_prefix));
		*/
			return (EINVAL);
		}
		break;
	case IP_FW_F_DIVERT_COMPAT:		/* Diverting to port zero is invalid */
	case IP_FW_F_TEE_COMPAT:
	case IP_FW_F_PIPE_COMPAT:              /* piping through 0 is invalid */
	case IP_FW_F_QUEUE_COMPAT:             /* piping through 0 is invalid */
		if (frwl->fw_divert_port_compat == 0) {
		/*
			printf(("%s can't divert to port 0\n", err_prefix));
		*/
			return (EINVAL);
		}
		break;
	case IP_FW_F_DENY_COMPAT:
	case IP_FW_F_ACCEPT_COMPAT:
	case IP_FW_F_COUNT_COMPAT:
	case IP_FW_F_SKIPTO_COMPAT:
	case IP_FW_F_FWD_COMPAT:
	case IP_FW_F_UID_COMPAT:
		break;
	default:
		/*
		printf(("%s invalid command\n", err_prefix));
		*/
		return (EINVAL);
	}

	return 0;
}

static void
ipfw_convert_to_cmds(struct ip_fw *curr_rule, struct ip_fw_compat *compat_rule)
{
	int			k;	
	uint32_t	actbuf[255], cmdbuf[255];
	ipfw_insn	*action, *cmd, *src, *dst;
	ipfw_insn	*have_state = NULL,	/* track check-state or keep-state */
				*end_action = NULL, 
				*end_cmd = NULL;
	
	if (!compat_rule || !curr_rule || !(curr_rule->cmd)) {
		return;
	}

	/* preemptively check the old ip_fw rule to
	 * make sure it's valid before starting to copy stuff
	 */
	if (ipfw_check_vers1_struct(compat_rule)) {
		/* bad rule */
		return;
	}
	
	bzero(actbuf, sizeof(actbuf));		/* actions go here */
	bzero(cmdbuf, sizeof(cmdbuf));

	/* fill in action */
	action = (ipfw_insn *)actbuf;
	{
	u_int	flag = compat_rule->fw_flg;
	
	action->len = 1;	/* default */
	
	if (flag & IP_FW_F_CHECK_S_COMPAT) {
		have_state = action;
		action->opcode = O_CHECK_STATE;
	} 
	else {
		switch (flag & IP_FW_F_COMMAND_COMPAT) {
			case IP_FW_F_ACCEPT_COMPAT:
				action->opcode = O_ACCEPT;
				break;
			case IP_FW_F_COUNT_COMPAT:
				action->opcode = O_COUNT;
				break;
			case IP_FW_F_PIPE_COMPAT:
				action->opcode = O_PIPE;
				action->len = F_INSN_SIZE(ipfw_insn_pipe);
				action->arg1 = compat_rule->fw_divert_port_compat;
				break;
			case IP_FW_F_QUEUE_COMPAT:
				action->opcode = O_QUEUE;
				action->len = F_INSN_SIZE(ipfw_insn_pipe);
				action->arg1 = compat_rule->fw_divert_port_compat;
				break;
			case IP_FW_F_SKIPTO_COMPAT:
				action->opcode = O_SKIPTO;
				action->arg1 = compat_rule->fw_skipto_rule_compat;
				break;
			case IP_FW_F_DIVERT_COMPAT:
				action->opcode = O_DIVERT;
				action->arg1 = compat_rule->fw_divert_port_compat;
				break;
			case IP_FW_F_TEE_COMPAT:
				action->opcode = O_TEE;
				action->arg1 = compat_rule->fw_divert_port_compat;
				break;
			case IP_FW_F_FWD_COMPAT:
			{
				ipfw_insn_sa *p = (ipfw_insn_sa *)action;
				
				action->opcode = O_FORWARD_IP;
				action->len = F_INSN_SIZE(ipfw_insn_sa);
				
				p->sa.sin_len = compat_rule->fw_fwd_ip_compat.sin_len;
				p->sa.sin_family = compat_rule->fw_fwd_ip_compat.sin_family;
				p->sa.sin_port = compat_rule->fw_fwd_ip_compat.sin_port;
				p->sa.sin_addr = compat_rule->fw_fwd_ip_compat.sin_addr;
				
				break;
			}
			case IP_FW_F_DENY_COMPAT:
				action->opcode = O_DENY;
				action->arg1 = 0;
				break;
			case IP_FW_F_REJECT_COMPAT:
				action->opcode = O_REJECT;
				action->arg1 = compat_rule->fw_reject_code_compat;
				break;
			default:
				action->opcode = O_NOP;
				break;
		}
	}
	
	/* action is mandatory */
	if (action->opcode == O_NOP) {
			return;
	}
	
	action = next_cmd(action);
	} /* end actions */
	
	cmd = (ipfw_insn *)cmdbuf;

	/* this is O_CHECK_STATE, we're done */
	if (have_state) {
			goto done;
	}

	{
	ipfw_insn		*prev = NULL;
	u_int			flag = compat_rule->fw_flg;
	
	/* logging */
	if (flag & IP_FW_F_PRN_COMPAT) {
		ipfw_insn_log *c = (ipfw_insn_log *)cmd;
		
		cmd->opcode = O_LOG;
		cmd->len |= F_INSN_SIZE(ipfw_insn_log);
		c->max_log = compat_rule->fw_logamount;

		prev = cmd;
		cmd = next_cmd(cmd);
	}

	/* protocol */
	if (compat_rule->fw_prot != 0) {
		fill_cmd(cmd, O_PROTO, compat_rule->fw_prot);
		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	/* source */
	if (flag & IP_FW_F_SME_COMPAT) {
		cmd->opcode = O_IP_SRC_ME;
		cmd->len |= F_INSN_SIZE(ipfw_insn);
		if (flag & IP_FW_F_INVSRC_COMPAT) {
			cmd->len ^= F_NOT; /* toggle F_NOT */			
		}
		
		prev = cmd;
		cmd = next_cmd(cmd);
	} else {
		if (compat_rule->fw_smsk.s_addr != 0) {
			/* addr/mask */
			ipfw_insn_ip	*ip = (ipfw_insn_ip *)cmd;
			
			ip->addr = compat_rule->fw_src;
			ip->mask = compat_rule->fw_smsk;
			cmd->opcode = O_IP_SRC_MASK;
			cmd->len |= F_INSN_SIZE(ipfw_insn_ip); /* double check this */
		} else {
			/* one IP */
			ipfw_insn_u32	*cmd32 = (ipfw_insn_u32 *)cmd;	/* alias for cmd */
			
			if (compat_rule->fw_src.s_addr == 0) {
				/* any */
				cmd32->o.len &= ~F_LEN_MASK;	/* zero len */
			} else {
				cmd32->d[0] = compat_rule->fw_src.s_addr;
				cmd32->o.opcode = O_IP_SRC;
				cmd32->o.len |= F_INSN_SIZE(ipfw_insn_u32);
			}
		}
		
		if (flag & IP_FW_F_INVSRC_COMPAT) {
			cmd->len ^= F_NOT; /* toggle F_NOT */			
		}

		if (F_LEN(cmd) != 0) { /* !any */
			prev = cmd;
			cmd = next_cmd(cmd);
		}
	}
	
	/* source ports */
	{
		ipfw_insn_u16	*ports = (ipfw_insn_u16 *)cmd;
		uint16_t		*p = ports->ports;
		int				i, j = 0, 
						nports = IP_FW_GETNSRCP_COMPAT(compat_rule),
						have_range = 0;
		
		cmd->opcode = O_IP_SRCPORT;
		for (i = 0; i < nports; i++) {
			if (((flag & IP_FW_F_SRNG_COMPAT) ||
				(flag & IP_FW_F_SMSK_COMPAT)) && !have_range) {
				p[0] = compat_rule->fw_uar_compat.fw_pts[i++];
				p[1] = compat_rule->fw_uar_compat.fw_pts[i];
				have_range = 1;
			} else {
				p[0] = p[1] = compat_rule->fw_uar_compat.fw_pts[i];
			}
			p += 2;
			j++;
		}
		
		if (j > 0) {
			ports->o.len |= j+1; /* leave F_NOT and F_OR untouched */
		}
		
		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	/* destination */
	if (flag & IP_FW_F_DME_COMPAT) {
		cmd->opcode = O_IP_DST_ME;
		cmd->len |= F_INSN_SIZE(ipfw_insn);
		if (flag & IP_FW_F_INVDST_COMPAT) {
			cmd->len ^= F_NOT; /* toggle F_NOT */			
		}

		prev = cmd;
		cmd = next_cmd(cmd);
	} else {
		if (compat_rule->fw_dmsk.s_addr != 0) {
			/* addr/mask */
			ipfw_insn_ip	*ip = (ipfw_insn_ip *)cmd;
			
			ip->addr = compat_rule->fw_dst;
			ip->mask = compat_rule->fw_dmsk;
			cmd->opcode = O_IP_DST_MASK;
			cmd->len |= F_INSN_SIZE(ipfw_insn_ip); /* double check this */
		} else {
			/* one IP */
			ipfw_insn_u32	*cmd32 = (ipfw_insn_u32 *)cmd;	/* alias for cmd */
			
			if (compat_rule->fw_dst.s_addr == 0) {
				/* any */
				cmd32->o.len &= ~F_LEN_MASK;	/* zero len */
			} else {
				cmd32->d[0] = compat_rule->fw_dst.s_addr;
				cmd32->o.opcode = O_IP_DST;
				cmd32->o.len |= F_INSN_SIZE(ipfw_insn_u32);
			}
		}
		
		if (flag & IP_FW_F_INVDST_COMPAT) {
			cmd->len ^= F_NOT; /* toggle F_NOT */			
		}

		if (F_LEN(cmd) != 0) { /* !any */
			prev = cmd;
			cmd = next_cmd(cmd);
		}
	}
	
	/* dest. ports */
	{
		ipfw_insn_u16	*ports = (ipfw_insn_u16 *)cmd;
		uint16_t		*p = ports->ports;
		int				i = IP_FW_GETNSRCP_COMPAT(compat_rule), 
						j = 0, 
						nports = (IP_FW_GETNDSTP_COMPAT(compat_rule) + i),
						have_range = 0;
		
		cmd->opcode = O_IP_DSTPORT;
		for (; i < nports; i++, p += 2) {
			if (((flag & IP_FW_F_DRNG_COMPAT) ||
				(flag & IP_FW_F_DMSK_COMPAT)) && !have_range) {
				/* range */
				p[0] = compat_rule->fw_uar_compat.fw_pts[i++];
				p[1] = compat_rule->fw_uar_compat.fw_pts[i];
				have_range = 1;
			} else {
				p[0] = p[1] = compat_rule->fw_uar_compat.fw_pts[i];
			}
			j++;
		}
		
		if (j > 0) {
			ports->o.len |= j+1; /* leave F_NOT and F_OR untouched */
		}
		
		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	if (flag & IP_FW_F_UID_COMPAT) {
		ipfw_insn_u32	*cmd32 = (ipfw_insn_u32 *)cmd;	/* alias for cmd */
			
		cmd32->o.opcode = O_UID;
		cmd32->o.len |= F_INSN_SIZE(ipfw_insn_u32);
		cmd32->d[0] = compat_rule->fw_uid;

		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	if (flag & IP_FW_F_KEEP_S_COMPAT) {
		have_state = cmd;
		fill_cmd(cmd, O_KEEP_STATE, 0);

		prev = cmd;
		cmd = next_cmd(cmd);
	}
	if (flag & IP_FW_BRIDGED_COMPAT) {
		fill_cmd(cmd, O_LAYER2, 0);

		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	if ((flag & IF_FW_F_VIAHACK_COMPAT) == IF_FW_F_VIAHACK_COMPAT) {
		/* via */
		ipfw_insn_if			*ifcmd = (ipfw_insn_if *)cmd;
		union ip_fw_if_compat	ifu = compat_rule->fw_in_if;
		
		cmd->opcode = O_VIA;
		ifcmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
		
		if (ifu.fu_via_ip.s_addr == 0) {
			/* "any" */
			ifcmd->name[0] = '\0';
			ifcmd->o.len = 0;
		}
		else if (compat_rule->fw_flg & IP_FW_F_IIFNAME_COMPAT) {
			/* by name */
			strncpy(ifcmd->name, ifu.fu_via_if_compat.name, sizeof(ifcmd->name));
			ifcmd->p.unit = ifu.fu_via_if_compat.unit;
		} else {
			/* by addr */
			ifcmd->p.ip = ifu.fu_via_ip;
		}

		prev = cmd;
		cmd = next_cmd(cmd);
	} else {
		if (flag & IP_FW_F_IN_COMPAT) {
			fill_cmd(cmd, O_IN, 0);
	
			prev = cmd;
			cmd = next_cmd(cmd);
		}
		if (flag & IP_FW_F_OUT_COMPAT) {
			/* if the previous command was O_IN, and this
			 * is being set as well, it's equivalent to not
			 * having either command, so let's back up prev 
			 * to the cmd before it and move cmd to prev.
			 */
			if (prev->opcode == O_IN) {
				cmd = prev;
				bzero(cmd, sizeof(*cmd));
			} else {
				cmd->len ^= F_NOT; /* toggle F_NOT */
				fill_cmd(cmd, O_IN, 0);
		
				prev = cmd;
				cmd = next_cmd(cmd);
			}
		}
		if (flag & IP_FW_F_OIFACE_COMPAT) {
			/* xmit */
			ipfw_insn_if	*ifcmd = (ipfw_insn_if *)cmd;
			union ip_fw_if_compat	ifu = compat_rule->fw_out_if;
			
			cmd->opcode = O_XMIT;
			ifcmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
	
			if (ifu.fu_via_ip.s_addr == 0) {
				/* "any" */
				ifcmd->name[0] = '\0';
				ifcmd->o.len = 0;
			}
			else if (flag & IP_FW_F_OIFNAME_COMPAT) {
				/* by name */
				strncpy(ifcmd->name, ifu.fu_via_if_compat.name, sizeof(ifcmd->name));
				ifcmd->p.unit = ifu.fu_via_if_compat.unit;
			} else {
				/* by addr */
				ifcmd->p.ip = ifu.fu_via_ip;
			}
	
			prev = cmd;
			cmd = next_cmd(cmd);
		} 
		else if (flag & IP_FW_F_IIFACE_COMPAT) {
			/* recv */
			ipfw_insn_if	*ifcmd = (ipfw_insn_if *)cmd;
			union ip_fw_if_compat	ifu = compat_rule->fw_in_if;
			
			cmd->opcode = O_RECV;
			ifcmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
	
			if (ifu.fu_via_ip.s_addr == 0) {
				/* "any" */
				ifcmd->name[0] = '\0';
				ifcmd->o.len = 0;
			}
			else if (flag & IP_FW_F_IIFNAME_COMPAT) {
				/* by name */
				strncpy(ifcmd->name, ifu.fu_via_if_compat.name, sizeof(ifcmd->name));
				ifcmd->p.unit = ifu.fu_via_if_compat.unit;
			} else {
				/* by addr */
				ifcmd->p.ip = ifu.fu_via_ip;
			}
	
			prev = cmd;
			cmd = next_cmd(cmd);
		}
	}
	
	if (flag & IP_FW_F_FRAG_COMPAT) {
		fill_cmd(cmd, O_FRAG, 0);

		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	/* IP options */
	if (compat_rule->fw_ipopt != 0 || compat_rule->fw_ipnopt != 0) {
		fill_cmd(cmd, O_IPOPT, (compat_rule->fw_ipopt & 0xff) |
								(compat_rule->fw_ipnopt & 0xff) << 8);
		
		prev = cmd;
		cmd = next_cmd(cmd);
	}
	
	if (compat_rule->fw_prot == IPPROTO_TCP) {
		if (compat_rule->fw_ipflg & IP_FW_IF_TCPEST_COMPAT) {
			fill_cmd(cmd, O_ESTAB, 0);
	
			prev = cmd;
			cmd = next_cmd(cmd);
		}
	
		/* TCP options and flags */
		if (compat_rule->fw_tcpf != 0 || compat_rule->fw_tcpnf != 0) {
			if ((compat_rule->fw_tcpf & IP_FW_TCPF_SYN_COMPAT) &&
				compat_rule->fw_tcpnf & IP_FW_TCPF_ACK_COMPAT) {
				fill_cmd(cmd, O_TCPFLAGS, (TH_SYN) | ( (TH_ACK) & 0xff) <<8);
				
				prev = cmd;
				cmd = next_cmd(cmd);
			}
			else {
				fill_cmd(cmd, O_TCPFLAGS, (compat_rule->fw_tcpf & 0xff) |
											(compat_rule->fw_tcpnf & 0xff) << 8);
				
				prev = cmd;
				cmd = next_cmd(cmd);
			}
		}
		if (compat_rule->fw_tcpopt != 0 || compat_rule->fw_tcpnopt != 0) {
			fill_cmd(cmd, O_TCPOPTS, (compat_rule->fw_tcpopt & 0xff) |
										(compat_rule->fw_tcpnopt & 0xff) << 8);
			
			prev = cmd;
			cmd = next_cmd(cmd);
		}
	}
	
	/* ICMP */
	/* XXX: check this */
	if (flag & IP_FW_F_ICMPBIT_COMPAT) {
		int	i;
		ipfw_insn_u32	*cmd32 = (ipfw_insn_u32 *)cmd;	/* alias for cmd */
		
		cmd32->o.opcode = O_ICMPTYPE;
		cmd32->o.len |= F_INSN_SIZE(ipfw_insn_u32);
		
		for (i = 0; i < IP_FW_ICMPTYPES_DIM_COMPAT; i++) {
			cmd32->d[0] |= compat_rule->fw_uar_compat.fw_icmptypes[i];
		}

		prev = cmd;
		cmd = next_cmd(cmd);
	}
	} /* end commands */
	
done:
	/* finally, copy everything into the current 
	 * rule buffer in the right order.
	 */
	dst = curr_rule->cmd;
	
	/* first, do match probability */
	if (compat_rule->fw_flg & IP_FW_F_RND_MATCH_COMPAT) {
		dst->opcode = O_PROB;
		dst->len = 2;
		*((int32_t *)(dst+1)) = compat_rule->pipe_ptr;
		dst += dst->len;
	}
	
	/* generate O_PROBE_STATE if necessary */
	if (have_state && have_state->opcode != O_CHECK_STATE) {
		fill_cmd(dst, O_PROBE_STATE, 0);
		dst = next_cmd(dst);
	}
	
	/*
	 * copy all commands but O_LOG, O_KEEP_STATE
	 */
	for (src = (ipfw_insn *)cmdbuf; src != cmd; src += k) {
		k = F_LEN(src);

		switch (src->opcode) {
		case O_LOG:
		case O_KEEP_STATE:
			break;
		default:
			bcopy(src, dst, k * sizeof(uint32_t));
			dst += k;
		}
	}

	/*
	 * put back the have_state command as last opcode
	 */
	if (have_state && have_state->opcode != O_CHECK_STATE) {
		k = F_LEN(have_state);
		bcopy(have_state, dst, k * sizeof(uint32_t));
		dst += k;
	}
	
	/*
	 * start action section
	 */
	curr_rule->act_ofs = dst - curr_rule->cmd;

	/*
	 * put back O_LOG if necessary
	 */
	src = (ipfw_insn *)cmdbuf;
	if (src->opcode == O_LOG) {
		k = F_LEN(src);
		bcopy(src, dst, k * sizeof(uint32_t));
		dst += k;
	}
	
	/*
	 * copy all other actions
	 */
	for (src = (ipfw_insn *)actbuf; src != action; src += k) {
		k = F_LEN(src);
		bcopy(src, dst, k * sizeof(uint32_t));
		dst += k;
	}

	curr_rule->cmd_len = (uint32_t *)dst - (uint32_t *)(curr_rule->cmd);
	
	return;
}

static int
ipfw_version_one_to_version_two(struct sockopt *sopt, struct ip_fw *curr_rule, 
								struct ip_fw_compat *rule_vers1)
{
	int	err = EINVAL;
	struct ip_fw_compat	*rule_ptr;
	struct ip_fw_compat	rule;
	
	if (rule_vers1) {
		rule_ptr = rule_vers1;
		err = 0;
	} else {
		/* do some basic size checking here, more extensive checking later */
		if (!sopt->sopt_val || sopt->sopt_valsize < sizeof(struct ip_fw_compat))
			return err;
	
		if ((err = sooptcopyin(sopt, &rule, sizeof(struct ip_fw_compat), 
							sizeof(struct ip_fw_compat)))) {
			return err;
		}
		
		rule_ptr = &rule;
	}

	/* deal with commands */
	ipfw_convert_to_cmds(curr_rule, rule_ptr);

	curr_rule->version = IP_FW_CURRENT_API_VERSION;
	curr_rule->context = rule_ptr->context;
	curr_rule->rulenum = rule_ptr->fw_number;
	curr_rule->pcnt = rule_ptr->fw_pcnt;
	curr_rule->bcnt = rule_ptr->fw_bcnt;
	curr_rule->timestamp = rule_ptr->timestamp;

	
#if FW2_DEBUG_VERBOSE
	ipfw_print_vers2_struct(curr_rule);
#endif /* FW2_DEBUG_VERBOSE */
	
	return err;
}

/* This converts to whatever the latest version is. Currently the 
 * latest version of the firewall is ipfw2.
 */
static int
ipfw_version_one_to_latest(struct sockopt *sopt, struct ip_fw *curr_rule, struct ip_fw_compat *rule_vers1)
{
	int err;
	
	/* if rule_vers1 is not null then this is coming from
	 * ipfw_version_zero_to_latest(), so pass that along;
	 * otherwise let ipfw_version_one_to_version_two()
	 * get the rule from sopt.
	 */
	err = ipfw_version_one_to_version_two(sopt, curr_rule, rule_vers1);
	
	return err;
}

static void
ipfw_version_zero_to_one(struct ip_old_fw *rule_vers0, struct ip_fw_compat *rule_vers1)
{
	bzero(rule_vers1, sizeof(struct ip_fw_compat));
	bcopy(&rule_vers0->fw_uar, &rule_vers1->fw_uar_compat, sizeof(rule_vers0->fw_uar));
	bcopy(&rule_vers0->fw_in_if, &rule_vers1->fw_in_if, sizeof(rule_vers0->fw_in_if));
	bcopy(&rule_vers0->fw_out_if, &rule_vers1->fw_out_if, sizeof(rule_vers0->fw_out_if));
	bcopy(&rule_vers0->fw_un, &rule_vers1->fw_un_compat, sizeof(rule_vers0->fw_un));

	rule_vers1->version       = 10;
	rule_vers1->fw_pcnt       = rule_vers0->fw_pcnt;
	rule_vers1->fw_bcnt       = rule_vers0->fw_bcnt;
	rule_vers1->fw_src        = rule_vers0->fw_src;
	rule_vers1->fw_dst        = rule_vers0->fw_dst;
	rule_vers1->fw_smsk       = rule_vers0->fw_smsk;
	rule_vers1->fw_dmsk       = rule_vers0->fw_dmsk;
	rule_vers1->fw_number     = rule_vers0->fw_number;
	rule_vers1->fw_flg        = rule_vers0->fw_flg;
	rule_vers1->fw_ipopt      = rule_vers0->fw_ipopt;
	rule_vers1->fw_ipnopt     = rule_vers0->fw_ipnopt;
	rule_vers1->fw_tcpf       = rule_vers0->fw_tcpf & ~IP_OLD_FW_TCPF_ESTAB;
	rule_vers1->fw_tcpnf      = rule_vers0->fw_tcpnf;
	rule_vers1->timestamp     = rule_vers0->timestamp;
	rule_vers1->fw_prot       = rule_vers0->fw_prot;
	rule_vers1->fw_nports     = rule_vers0->fw_nports;
	rule_vers1->pipe_ptr      = rule_vers0->pipe_ptr;
	rule_vers1->next_rule_ptr = rule_vers0->next_rule_ptr;
	rule_vers1->fw_ipflg      = (rule_vers0->fw_tcpf & IP_OLD_FW_TCPF_ESTAB) ? IP_FW_IF_TCPEST_COMPAT : 0;
}

/* first convert to version one, then to version two */
static int
ipfw_version_zero_to_latest(struct sockopt *sopt, struct ip_fw *curr_rule)
{
	int		err;
	struct ip_old_fw	rule_vers0;
	struct ip_fw_compat	rule_vers1;

	if (sopt->sopt_name == IP_OLD_FW_GET || 
		sopt->sopt_name == IP_OLD_FW_FLUSH || 
		sopt->sopt_val == NULL) {
		/* In the old-style API, it was legal to not pass in a rule 
		 * structure for certain firewall operations (e.g. flush, 
		 * reset log).  If that's the situation, we pretend we received 
		 * a blank structure. */
		bzero(curr_rule, sizeof(struct ip_fw));
		curr_rule->version = 10;
	}
	else {
		if (!sopt->sopt_val || sopt->sopt_valsize < sizeof(struct ip_old_fw)) {
			return EINVAL;
		}

		err = sooptcopyin(sopt, &rule_vers0, sizeof(struct ip_old_fw), 
					sizeof(struct ip_old_fw));
		if (err) {
			return err;
		}
		
		ipfw_version_zero_to_one(&rule_vers0, &rule_vers1);
	}
	
	return (ipfw_version_one_to_latest(sopt, curr_rule, &rule_vers1));
}

/* rule is a u_int32_t buffer[255] into which the converted 
 * (if necessary) rules go.
 */
int
ipfw_convert_to_latest(struct sockopt *sopt, struct ip_fw *curr_rule, int api_version)
{
	int	err = 0;
	
	/* the following functions copy the rules passed in and
	 * convert to latest structures based on version
	 */
	switch (api_version) {
		case IP_FW_VERSION_0:
			/* this is the oldest version we support */
			err = ipfw_version_zero_to_latest(sopt, curr_rule);
			break;
		
		case IP_FW_VERSION_1:
			/* this is the version supported in Panther */
			err = ipfw_version_one_to_latest(sopt, curr_rule, NULL);
			break;
		
		case IP_FW_CURRENT_API_VERSION:
			/* IPFW2 for now */
			/* do nothing here... */
			break;
		
		default:
			/* unrecognized/unsupported version */
			err = EINVAL;
			break;
	}
	
	return err;
}

int
ipfw_get_command_and_version(struct sockopt *sopt, int *command, u_int32_t *api_version)
{
	int cmd;
	int err = 0;
	u_int32_t	vers = IP_FW_VERSION_NONE;
	
	/* first deal with the oldest version */
	if (sopt->sopt_name == IP_OLD_FW_GET) { 
		vers = IP_FW_VERSION_0;
		cmd = IP_FW_GET;
	}
	else if (sopt->sopt_name == IP_OLD_FW_FLUSH) {
		vers = IP_FW_VERSION_0;
		cmd = IP_FW_FLUSH;
	}
	else if (sopt->sopt_name == IP_OLD_FW_ZERO) { 
		vers = IP_FW_VERSION_0;
		cmd = IP_FW_ZERO;
	}
	else if (sopt->sopt_name == IP_OLD_FW_ADD) { 
		vers = IP_FW_VERSION_0;
		cmd = IP_FW_ADD;
	}
	else if (sopt->sopt_name == IP_OLD_FW_DEL) { 
		vers = IP_FW_VERSION_0;
		cmd = IP_FW_DEL;
	}
	else if (sopt->sopt_name == IP_OLD_FW_RESETLOG) { 
		vers = IP_FW_VERSION_0;
		cmd = IP_FW_RESETLOG;
	}
	else { 
		cmd = sopt->sopt_name;
	}
	
	if (vers == IP_FW_VERSION_NONE) {
		/* working off the fact that the offset
		 * is the same in both structs.
		 */
		struct ip_fw	rule;
		
		if (!sopt->sopt_val || sopt->sopt_valsize < sizeof(struct ip_fw))
			return EINVAL;
	
		if ((err = sooptcopyin(sopt, &rule, sizeof(struct ip_fw), 
							sizeof(struct ip_fw)))) {
			return err;
		}
		
		vers = rule.version;
	}

	if (command) {
		*command = cmd;
	}
	
	if (api_version) {
		*api_version = vers;
	}
	
	return err;
}

