iptables: add -C to check for existing rules

It is often useful to check whether a specific rule is already present
in a chain without actually modifying the iptables config.

Services like fail2ban usually employ techniques like grepping through
the output of "iptables -L" which is quite error prone.

This patch adds a new operation -C to the iptables command which
mostly works like -D; it can detect and indicate the existence of the
specified rule by modifying the exit code. The new operation
TC_CHECK_ENTRY uses the same code as the -D operation, whose functions
got a dry-run parameter appended.

Signed-off-by: Stefan Tomanek <stefan.tomanek@wertarbyte.de>
Signed-off-by: Jan Engelhardt <jengelh@medozas.de>
diff --git a/libiptc/libip4tc.c b/libiptc/libip4tc.c
index c1d78e2..e2d2a5e 100644
--- a/libiptc/libip4tc.c
+++ b/libiptc/libip4tc.c
@@ -76,6 +76,7 @@
 #define TC_INSERT_ENTRY		iptc_insert_entry
 #define TC_REPLACE_ENTRY	iptc_replace_entry
 #define TC_APPEND_ENTRY		iptc_append_entry
+#define TC_CHECK_ENTRY		iptc_check_entry
 #define TC_DELETE_ENTRY		iptc_delete_entry
 #define TC_DELETE_NUM_ENTRY	iptc_delete_num_entry
 #define TC_FLUSH_ENTRIES	iptc_flush_entries
diff --git a/libiptc/libip6tc.c b/libiptc/libip6tc.c
index 27fe4c4..c1508cd 100644
--- a/libiptc/libip6tc.c
+++ b/libiptc/libip6tc.c
@@ -71,6 +71,7 @@
 #define TC_INSERT_ENTRY		ip6tc_insert_entry
 #define TC_REPLACE_ENTRY	ip6tc_replace_entry
 #define TC_APPEND_ENTRY		ip6tc_append_entry
+#define TC_CHECK_ENTRY		ip6tc_check_entry
 #define TC_DELETE_ENTRY		ip6tc_delete_entry
 #define TC_DELETE_NUM_ENTRY	ip6tc_delete_num_entry
 #define TC_FLUSH_ENTRIES	ip6tc_flush_entries
diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c
index 7a9c742..d3b1c51 100644
--- a/libiptc/libiptc.c
+++ b/libiptc/libiptc.c
@@ -31,6 +31,7 @@
  */
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <stdbool.h>
 #include <xtables.h>
 
 #include "linux_list.h"
@@ -1956,12 +1957,11 @@
 	const STRUCT_ENTRY *b,
 	unsigned char *matchmask);
 
-/* Delete the first rule in `chain' which matches `fw'. */
-int
-TC_DELETE_ENTRY(const IPT_CHAINLABEL chain,
-		const STRUCT_ENTRY *origfw,
-		unsigned char *matchmask,
-		struct xtc_handle *handle)
+
+/* find the first rule in `chain' which matches `fw' and remove it unless dry_run is set */
+static int delete_entry(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *origfw,
+			unsigned char *matchmask, struct xtc_handle *handle,
+			bool dry_run)
 {
 	struct chain_head *c;
 	struct rule_head *r, *i;
@@ -2005,6 +2005,10 @@
 		if (!target_same(r, i, mask))
 			continue;
 
+		/* if we are just doing a dry run, we simply skip the rest */
+		if (dry_run)
+			return 1;
+
 		/* If we are about to delete the rule that is the
 		 * current iterator, move rule iterator back.  next
 		 * pointer will then point to real next node */
@@ -2027,6 +2031,20 @@
 	return 0;
 }
 
+/* check whether a specified rule is present */
+int TC_CHECK_ENTRY(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *origfw,
+		   unsigned char *matchmask, struct xtc_handle *handle)
+{
+	/* do a dry-run delete to find out whether a matching rule exists */
+	return delete_entry(chain, origfw, matchmask, handle, true);
+}
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int TC_DELETE_ENTRY(const IPT_CHAINLABEL chain,	const STRUCT_ENTRY *origfw,
+		    unsigned char *matchmask, struct xtc_handle *handle)
+{
+	return delete_entry(chain, origfw, matchmask, handle, false);
+}
 
 /* Delete the rule in position `rulenum' in `chain'. */
 int