/*	$FreeBSD$	*/

/*
 * Copyright (C) 2012 by Darren Reed.
 *
 * See the IPFILTER.LICENCE file for details on licencing.
 *
 * $Id$
 */

#include "ipf.h"


/*
 * print the filter structure in a useful way
 */
void
printfr( struct frentry *fp, ioctlfunc_t iocfunc)
{
	struct protoent	*p;
	u_short	sec[2];
	u_32_t type;
	int pr, af;
	char *s;
	int hash;

	pr = -2;
	type = fp->fr_type & ~FR_T_BUILTIN;

	if ((fp->fr_type & FR_T_BUILTIN) != 0)
		PRINTF("# Builtin: ");

	if (fp->fr_collect != 0)
		PRINTF("%u ", fp->fr_collect);

	if (fp->fr_type == FR_T_CALLFUNC) {
		;
	} else if (fp->fr_func != NULL) {
		PRINTF("call");
		if ((fp->fr_flags & FR_CALLNOW) != 0)
			PRINTF(" now");
		s = kvatoname(fp->fr_func, iocfunc);
		PRINTF(" %s/%u", s ? s : "?", fp->fr_arg);
	} else if (FR_ISPASS(fp->fr_flags))
		PRINTF("pass");
	else if (FR_ISBLOCK(fp->fr_flags)) {
		PRINTF("block");
	} else if ((fp->fr_flags & FR_LOGMASK) == FR_LOG) {
		printlog(fp);
	} else if (FR_ISACCOUNT(fp->fr_flags))
		PRINTF("count");
	else if (FR_ISAUTH(fp->fr_flags))
		PRINTF("auth");
	else if (FR_ISPREAUTH(fp->fr_flags))
		PRINTF("preauth");
	else if (FR_ISNOMATCH(fp->fr_flags))
		PRINTF("nomatch");
	else if (FR_ISDECAPS(fp->fr_flags))
		PRINTF("decapsulate");
	else if (FR_ISSKIP(fp->fr_flags))
		PRINTF("skip %u", fp->fr_arg);
	else {
		PRINTF("%x", fp->fr_flags);
	}
	if (fp->fr_flags & FR_RETICMP) {
		if ((fp->fr_flags & FR_RETMASK) == FR_FAKEICMP)
			PRINTF(" return-icmp-as-dest");
		else if ((fp->fr_flags & FR_RETMASK) == FR_RETICMP)
			PRINTF(" return-icmp");
		if (fp->fr_icode) {
			if (fp->fr_icode <= MAX_ICMPCODE)
				PRINTF("(%s)",
					icmpcodes[(int)fp->fr_icode]);
			else
				PRINTF("(%d)", fp->fr_icode);
		}
	} else if ((fp->fr_flags & FR_RETMASK) == FR_RETRST)
		PRINTF(" return-rst");

	if (fp->fr_flags & FR_OUTQUE)
		PRINTF(" out ");
	else if (fp->fr_flags & FR_INQUE)
		PRINTF(" in ");

	if (((fp->fr_flags & FR_LOGB) == FR_LOGB) ||
	    ((fp->fr_flags & FR_LOGP) == FR_LOGP)) {
		printlog(fp);
		putchar(' ');
	}

	if (fp->fr_flags & FR_QUICK)
		PRINTF("quick ");

	if (fp->fr_ifnames[0] != -1) {
		printifname("on ", fp->fr_names + fp->fr_ifnames[0],
			    fp->fr_ifa);
		if (fp->fr_ifnames[1] != -1 &&
		    strcmp(fp->fr_names + fp->fr_ifnames[1], "*"))
			printifname(",", fp->fr_names + fp->fr_ifnames[1],
				    fp->fr_ifas[1]);
		putchar(' ');
	}

	if (fp->fr_tif.fd_name != -1)
		print_toif(fp->fr_family, "to", fp->fr_names, &fp->fr_tif);
	if (fp->fr_dif.fd_name != -1)
		print_toif(fp->fr_family, "dup-to", fp->fr_names,
			   &fp->fr_dif);
	if (fp->fr_rif.fd_name != -1)
		print_toif(fp->fr_family, "reply-to", fp->fr_names,
			   &fp->fr_rif);
	if (fp->fr_flags & FR_FASTROUTE)
		PRINTF("fastroute ");

	if ((fp->fr_ifnames[2] != -1 &&
	     strcmp(fp->fr_names + fp->fr_ifnames[2], "*")) ||
	    (fp->fr_ifnames[3] != -1 &&
		 strcmp(fp->fr_names + fp->fr_ifnames[3], "*"))) {
		if (fp->fr_flags & FR_OUTQUE)
			PRINTF("in-via ");
		else
			PRINTF("out-via ");

		if (fp->fr_ifnames[2] != -1) {
			printifname("", fp->fr_names + fp->fr_ifnames[2],
				    fp->fr_ifas[2]);
			if (fp->fr_ifnames[3] != -1) {
				printifname(",",
					    fp->fr_names + fp->fr_ifnames[3],
					    fp->fr_ifas[3]);
			}
			putchar(' ');
		}
	}

	if (fp->fr_family == AF_INET) {
		PRINTF("inet ");
		af = AF_INET;
#ifdef USE_INET6
	} else if (fp->fr_family == AF_INET6) {
		PRINTF("inet6 ");
		af = AF_INET6;
#endif
	} else {
		af = -1;
	}

	if (type == FR_T_IPF) {
		if (fp->fr_mip.fi_tos)
			PRINTF("tos %#x ", fp->fr_tos);
		if (fp->fr_mip.fi_ttl)
			PRINTF("ttl %d ", fp->fr_ttl);
		if (fp->fr_flx & FI_TCPUDP) {
			PRINTF("proto tcp/udp ");
			pr = -1;
		} else if (fp->fr_mip.fi_p) {
			pr = fp->fr_ip.fi_p;
			p = getprotobynumber(pr);
			PRINTF("proto ");
			printproto(p, pr, NULL);
			putchar(' ');
		}
	}

	switch (type)
	{
	case FR_T_NONE :
		PRINTF("all");
		break;

	case FR_T_IPF :
		PRINTF("from %s", fp->fr_flags & FR_NOTSRCIP ? "!" : "");
		printaddr(af, fp->fr_satype, fp->fr_names, fp->fr_ifnames[0],
			  &fp->fr_src.s_addr, &fp->fr_smsk.s_addr);
		if (fp->fr_scmp)
			printportcmp(pr, &fp->fr_tuc.ftu_src);

		PRINTF(" to %s", fp->fr_flags & FR_NOTDSTIP ? "!" : "");
		printaddr(af, fp->fr_datype, fp->fr_names, fp->fr_ifnames[0],
			  &fp->fr_dst.s_addr, &fp->fr_dmsk.s_addr);
		if (fp->fr_dcmp)
			printportcmp(pr, &fp->fr_tuc.ftu_dst);

		if (((fp->fr_proto == IPPROTO_ICMP) ||
		     (fp->fr_proto == IPPROTO_ICMPV6)) && fp->fr_icmpm) {
			int	type = fp->fr_icmp, code;
			char	*name;

			type = ntohs(fp->fr_icmp);
			code = type & 0xff;
			type /= 256;
			name = icmptypename(fp->fr_family, type);
			if (name == NULL)
				PRINTF(" icmp-type %d", type);
			else
				PRINTF(" icmp-type %s", name);
			if (ntohs(fp->fr_icmpm) & 0xff)
				PRINTF(" code %d", code);
		}
		if ((fp->fr_proto == IPPROTO_TCP) &&
		    (fp->fr_tcpf || fp->fr_tcpfm)) {
			PRINTF(" flags ");
			printtcpflags(fp->fr_tcpf, fp->fr_tcpfm);
		}
		break;

	case FR_T_BPFOPC :
	    {
		fakebpf_t *fb;
		int i;

		PRINTF("bpf-v%d { \"", fp->fr_family);
		i = fp->fr_dsize / sizeof(*fb);

		for (fb = fp->fr_data, s = ""; i; i--, fb++, s = " ")
			PRINTF("%s%#x %#x %#x %#x", s, fb->fb_c, fb->fb_t,
			       fb->fb_f, fb->fb_k);

		PRINTF("\" }");
		break;
	    }

	case FR_T_COMPIPF :
		break;

	case FR_T_CALLFUNC :
		PRINTF("call function at %p", fp->fr_data);
		break;

	case FR_T_IPFEXPR :
		PRINTF("exp { \"");
		printipfexpr(fp->fr_data);
		PRINTF("\" } ");
		break;

	default :
		PRINTF("[unknown filter type %#x]", fp->fr_type);
		break;
	}

	if ((type == FR_T_IPF) &&
	    ((fp->fr_flx & FI_WITH) || (fp->fr_mflx & FI_WITH) ||
	     fp->fr_optbits || fp->fr_optmask ||
	     fp->fr_secbits || fp->fr_secmask)) {
		char *comma = " ";

		PRINTF(" with");
		if (fp->fr_optbits || fp->fr_optmask ||
		    fp->fr_secbits || fp->fr_secmask) {
			sec[0] = fp->fr_secmask;
			sec[1] = fp->fr_secbits;
			if (fp->fr_family == AF_INET)
				optprint(sec, fp->fr_optmask, fp->fr_optbits);
#ifdef	USE_INET6
			else
				optprintv6(sec, fp->fr_optmask,
					   fp->fr_optbits);
#endif
		} else if (fp->fr_mflx & FI_OPTIONS) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_OPTIONS))
				PRINTF("not ");
			PRINTF("ipopts");
			comma = ",";
		}
		if (fp->fr_mflx & FI_SHORT) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_SHORT))
				PRINTF("not ");
			PRINTF("short");
			comma = ",";
		}
		if (fp->fr_mflx & FI_FRAG) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_FRAG))
				PRINTF("not ");
			PRINTF("frag");
			comma = ",";
		}
		if (fp->fr_mflx & FI_FRAGBODY) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_FRAGBODY))
				PRINTF("not ");
			PRINTF("frag-body");
			comma = ",";
		}
		if (fp->fr_mflx & FI_NATED) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_NATED))
				PRINTF("not ");
			PRINTF("nat");
			comma = ",";
		}
		if (fp->fr_mflx & FI_LOWTTL) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_LOWTTL))
				PRINTF("not ");
			PRINTF("lowttl");
			comma = ",";
		}
		if (fp->fr_mflx & FI_BAD) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_BAD))
				PRINTF("not ");
			PRINTF("bad");
			comma = ",";
		}
		if (fp->fr_mflx & FI_BADSRC) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_BADSRC))
				PRINTF("not ");
			PRINTF("bad-src");
			comma = ",";
		}
		if (fp->fr_mflx & FI_BADNAT) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_BADNAT))
				PRINTF("not ");
			PRINTF("bad-nat");
			comma = ",";
		}
		if (fp->fr_mflx & FI_OOW) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_OOW))
				PRINTF("not ");
			PRINTF("oow");
			comma = ",";
		}
		if (fp->fr_mflx & FI_MBCAST) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_MBCAST))
				PRINTF("not ");
			PRINTF("mbcast");
			comma = ",";
		}
		if (fp->fr_mflx & FI_BROADCAST) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_BROADCAST))
				PRINTF("not ");
			PRINTF("bcast");
			comma = ",";
		}
		if (fp->fr_mflx & FI_MULTICAST) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_MULTICAST))
				PRINTF("not ");
			PRINTF("mcast");
			comma = ",";
		}
		if (fp->fr_mflx & FI_STATE) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_STATE))
				PRINTF("not ");
			PRINTF("state");
			comma = ",";
		}
		if (fp->fr_mflx & FI_V6EXTHDR) {
			fputs(comma, stdout);
			if (!(fp->fr_flx & FI_V6EXTHDR))
				PRINTF("not ");
			PRINTF("v6hdrs");
			comma = ",";
		}
	}

	if (fp->fr_flags & FR_KEEPSTATE) {
		host_track_t *src = &fp->fr_srctrack;
		PRINTF(" keep state");
		if ((fp->fr_flags & (FR_STSTRICT|FR_NEWISN|
				     FR_NOICMPERR|FR_STATESYNC)) ||
		    (fp->fr_statemax != 0) || (fp->fr_age[0] != 0) ||
		    (src->ht_max_nodes != 0)) {
			char *comma = "";
			PRINTF(" (");
			if (fp->fr_statemax != 0) {
				PRINTF("limit %u", fp->fr_statemax);
				comma = ",";
			}
			if (src->ht_max_nodes != 0) {
				PRINTF("%smax-nodes %d", comma,
				       src->ht_max_nodes);
				if (src->ht_max_per_node)
					PRINTF(", max-per-src %d/%d",
					       src->ht_max_per_node,
					       src->ht_netmask);
				comma = ",";
			}
			if (fp->fr_flags & FR_STSTRICT) {
				PRINTF("%sstrict", comma);
				comma = ",";
			}
			if (fp->fr_flags & FR_STLOOSE) {
				PRINTF("%sloose", comma);
				comma = ",";
			}
			if (fp->fr_flags & FR_NEWISN) {
				PRINTF("%snewisn", comma);
				comma = ",";
			}
			if (fp->fr_flags & FR_NOICMPERR) {
				PRINTF("%sno-icmp-err", comma);
				comma = ",";
			}
			if (fp->fr_flags & FR_STATESYNC) {
				PRINTF("%ssync", comma);
				comma = ",";
			}
			if (fp->fr_age[0] || fp->fr_age[1])
				PRINTF("%sage %d/%d", comma, fp->fr_age[0],
				       fp->fr_age[1]);
			PRINTF(")");
		}
	}
	if (fp->fr_flags & FR_KEEPFRAG) {
		PRINTF(" keep frags");
		if (fp->fr_flags & (FR_FRSTRICT)) {
			PRINTF(" (");
			if (fp->fr_flags & FR_FRSTRICT)
				PRINTF("strict");
			PRINTF(")");

		}
	}
	if (fp->fr_isc != (struct ipscan *)-1) {
		if (fp->fr_isctag != -1)
			PRINTF(" scan %s", fp->fr_isctag + fp->fr_names);
		else
			PRINTF(" scan *");
	}
	if (fp->fr_grhead != -1)
		PRINTF(" head %s", fp->fr_names + fp->fr_grhead);
	if (fp->fr_group != -1)
		PRINTF(" group %s", fp->fr_names + fp->fr_group);
	if (fp->fr_logtag != FR_NOLOGTAG || *fp->fr_nattag.ipt_tag) {
		char *s = "";

		PRINTF(" set-tag(");
		if (fp->fr_logtag != FR_NOLOGTAG) {
			PRINTF("log=%u", fp->fr_logtag);
			s = ", ";
		}
		if (*fp->fr_nattag.ipt_tag) {
			PRINTF("%snat=%-.*s", s, IPFTAG_LEN,
				fp->fr_nattag.ipt_tag);
		}
		PRINTF(")");
	}

	if (fp->fr_pps)
		PRINTF(" pps %d", fp->fr_pps);

	if (fp->fr_comment != -1)
		PRINTF(" comment \"%s\"", fp->fr_names + fp->fr_comment);

	hash = 0;
	if ((fp->fr_flags & FR_KEEPSTATE) && (opts & OPT_VERBOSE)) {
		PRINTF(" # count %d", fp->fr_statecnt);
		if (fp->fr_die != 0)
			PRINTF(" rule-ttl %u", fp->fr_die);
		hash = 1;
	} else if (fp->fr_die != 0) {
		PRINTF(" # rule-ttl %u", fp->fr_die);
		hash = 1;
	}
	if (opts & OPT_DEBUG) {
		if (hash == 0)
			putchar('#');
		PRINTF(" ref %d", fp->fr_ref);
	}
	(void)putchar('\n');
}