Description: Implement IPv6 support.
 Functional IPv6 support depends on an extended grammar,
 an extended parser, and also reading of "/proc/net/tcp6".
 .
 Using a union large enough to accomodate "struct in_addr"
 and "struct in6_addr" a unified evaluation of the two
 address family is possible.
 .
 The implementation of CIDR netmasks for IPv4 is conducted.
 .
 Finally, the manual pages are expanded.
Author: Mats Erik Andersson <debian@gisladisker.se>
Reviewed-by: Joao Eriberto Mota Filho <eriberto@debian.org>
Forwarded: no
Last-Update: 2014-10-20

diff -Naur tcpspy-1.7d.debian/rule.c tcpspy-1.7d/rule.c
--- tcpspy-1.7d.debian/rule.c	2002-01-25 02:00:50.000000000 +0100
+++ tcpspy-1.7d/rule.c	2011-03-04 20:05:17.000000000 +0100
@@ -70,12 +70,16 @@
 void rule_gen_rport (u_int16_t low, u_int16_t high);
 void rule_gen_laddr (u_int32_t addr, u_int32_t mask);
 void rule_gen_raddr (u_int32_t addr, u_int32_t mask);
+void rule_gen_laddr6 (u_int32_t addr[4], u_int32_t mask[4]);
+void rule_gen_raddr6 (u_int32_t addr[4], u_int32_t mask[4]);
+void rule_gen_ip (void);
+void rule_gen_ip6 (void);
 void rule_gen_exe (unsigned long estrid);
 void rule_gen_or (void);
 void rule_gen_and (void);
 void rule_gen_not (void);
-int rule_eval (uid_t r_uid, u_int32_t laddr, u_int16_t lport, 
-		u_int32_t raddr, u_int16_t rport, const char *mexe);
+int rule_eval (int maf, uid_t r_uid, u_int32_t *laddr, u_int16_t lport, 
+		u_int32_t *raddr, u_int16_t rport, const char *mexe);
 void rule_parse (const char *r);
 unsigned long st_store (const char *s);
 
@@ -117,6 +121,10 @@
 #define BC_LADDR	4
 #define BC_RADDR	5
 #define BC_EXE		6
+#define BC_LADDR6	7
+#define BC_RADDR6	8
+#define BC_IP		9
+#define BC_IP6		10
 
 #define BC_OR		64
 #define BC_AND		65
@@ -186,6 +194,62 @@
 }
 
 /*
+ * rule_gen_laddr6 ()
+ *
+ * Generate a local IPv6 address comparison.
+ */
+void rule_gen_laddr6 (u_int32_t addr[4], u_int32_t mask[4])
+{
+	add_code (BC_LADDR6);
+	add_code ((bytecode_t) addr[0]);
+	add_code ((bytecode_t) addr[1]);
+	add_code ((bytecode_t) addr[2]);
+	add_code ((bytecode_t) addr[3]);
+	add_code ((bytecode_t) mask[0]);
+	add_code ((bytecode_t) mask[1]);
+	add_code ((bytecode_t) mask[2]);
+	add_code ((bytecode_t) mask[3]);
+}
+
+/*
+ * rule_gen_raddr6 ()
+ *
+ * Generate a remote IPv6 address comparison.
+ */
+void rule_gen_raddr6 (u_int32_t addr[4], u_int32_t mask[4])
+{
+	add_code (BC_RADDR6);
+	add_code ((bytecode_t) addr[0]);
+	add_code ((bytecode_t) addr[1]);
+	add_code ((bytecode_t) addr[2]);
+	add_code ((bytecode_t) addr[3]);
+	add_code ((bytecode_t) mask[0]);
+	add_code ((bytecode_t) mask[1]);
+	add_code ((bytecode_t) mask[2]);
+	add_code ((bytecode_t) mask[3]);
+}
+
+/*
+ * rule_gen_ip ()
+ *
+ * Generate an IPv4 address check.
+ */
+void rule_gen_ip (void)
+{
+	add_code (BC_IP);
+}
+
+/*
+ * rule_gen_ip ()
+ *
+ * Generate an IPv6 address check.
+ */
+void rule_gen_ip6 (void)
+{
+	add_code (BC_IP6);
+}
+
+/*
  * rule_gen_exe ()
  *
  * Generate an executable filename comparison.
@@ -284,8 +348,8 @@
  *
  * Returns nonzero if the connection matches the rule, zero otherwise.
  */
-int rule_eval (uid_t muid, u_int32_t mladdr, u_int16_t mlport, 
-		u_int32_t mraddr, u_int16_t mrport, const char *mexe)
+int rule_eval (int maf, uid_t muid, u_int32_t *mladdr, u_int16_t mlport, 
+		u_int32_t *mraddr, u_int16_t mrport, const char *mexe)
 {
 	size_t ip = 0;
 	unsigned int c;
@@ -293,6 +357,8 @@
 	static size_t stack_size = 0, stack_ptr = 0;
 
 	stack_ptr = 0;
+	PUSH(0);	/* Put a single FALSE on the stack. This protects
+			 * against a segfault from a simple rule. */
 
 	for (ip = 0; ip < code_length; ) {
 		c = NEXTCODE;
@@ -360,7 +426,9 @@
 
 				SHORTCIRCUIT;
 
-				PUSH (((mladdr & mask) == addr) ? 1 : 0);
+				PUSH (( (maf == AF_INET) &&
+					((*mladdr & mask) == addr) )
+						? 1 : 0);
 				}
 				break;	
 			case BC_RADDR:
@@ -375,10 +443,77 @@
 
 				SHORTCIRCUIT;
 
-				PUSH (((mraddr & mask) == addr) ? 1 : 0);
+				PUSH (( (maf == AF_INET) &&
+					((*mraddr & mask) == addr) )
+						? 1 : 0);
 				}
 				break;	
+			case BC_LADDR6:
+				/*
+				 * Local IPv6 address comparison.
+				 */
+				{
+				u_int32_t addr[4], mask[4], patch[4];
+				int j;
+
+				for (j = 0; j < 4; ++j)
+					addr[j] = (u_int32_t) NEXTCODE;
+				for (j = 0; j < 4; ++j)
+					mask[j] = (u_int32_t) NEXTCODE;
 
+				SHORTCIRCUIT;
+
+				for (j = 0; j < 4; ++j)
+					patch[j] = mladdr[j] & mask[j];
+
+				PUSH (( (maf == AF_INET6) &&
+					(memcmp (patch, addr, sizeof (patch)) == 0) )
+						? 1 : 0);
+				}
+				break;	
+			case BC_RADDR6:
+				/*
+				 * Remote IPv6 address comparison.
+				 */
+				{
+				u_int32_t addr[4], mask[4], patch[4];
+				int j;
+
+				for (j = 0; j < 4; ++j)
+					addr[j] = (u_int32_t) NEXTCODE;
+				for (j = 0; j < 4; ++j)
+					mask[j] = (u_int32_t) NEXTCODE;
+
+				SHORTCIRCUIT;
+
+				for (j = 0; j < 4; ++j)
+					patch[j] = mraddr[j] & mask[j];
+
+				PUSH (( (maf == AF_INET6) &&
+					(memcmp (patch, addr, sizeof (patch)) == 0) )
+						? 1 : 0);
+				}
+				break;	
+			case BC_IP:
+				/*
+				 * Verify that the address is IPv4.
+				 */
+				{
+				SHORTCIRCUIT;
+
+				PUSH ( (maf == AF_INET) ? 1 : 0);
+				}
+				break;
+			case BC_IP6:
+				/*
+				 * Verify that the address is IPv6.
+				 */
+				{
+				SHORTCIRCUIT;
+
+				PUSH ( (maf == AF_INET6) ? 1 : 0);
+				}
+				break;
 			case BC_EXE:
 				/*
 				 * Executable filename comparison.
diff -Naur tcpspy-1.7d.debian/rule_grammar.y tcpspy-1.7d/rule_grammar.y
--- tcpspy-1.7d.debian/rule_grammar.y	2002-01-25 02:01:02.000000000 +0100
+++ tcpspy-1.7d/rule_grammar.y	2011-03-04 03:19:47.000000000 +0100
@@ -57,6 +57,9 @@
 	struct {
 		u_int32_t addr, mask;
 	} addr;
+	struct {
+		u_int32_t addr[4], mask[4];
+	} addr6;
 	unsigned long exe;
 }
 
@@ -68,6 +71,8 @@
 %token <port> PORT_SPEC
 %token LADDR RADDR
 %token <addr> ADDR_SPEC
+%token <addr6> ADDR6_SPEC
+%token IP IP6
 %token EXE
 %token <exe> EXE_SPEC
 
@@ -99,6 +104,10 @@
 	|	RPORT PORT_SPEC		{ rule_gen_rport ($2.low, $2.high); }
 	|	LADDR ADDR_SPEC		{ rule_gen_laddr ($2.addr, $2.mask); }
 	|	RADDR ADDR_SPEC		{ rule_gen_raddr ($2.addr, $2.mask); }
+	|	LADDR ADDR6_SPEC	{ rule_gen_laddr6 ($2.addr, $2.mask); }
+	|	RADDR ADDR6_SPEC	{ rule_gen_raddr6 ($2.addr, $2.mask); }
+	|	IP			{ rule_gen_ip (); }
+	|	IP6			{ rule_gen_ip6 (); }
 	|	EXE EXE_SPEC		{ rule_gen_exe ($2); }
 	;
 
diff -Naur tcpspy-1.7d.debian/rule.h tcpspy-1.7d/rule.h
--- tcpspy-1.7d.debian/rule.h	2002-01-25 02:00:50.000000000 +0100
+++ tcpspy-1.7d/rule.h	2011-03-04 03:22:38.000000000 +0100
@@ -44,12 +44,16 @@
 void rule_gen_rport (u_int16_t low, u_int16_t high);
 void rule_gen_laddr (u_int32_t addr, u_int32_t mask);
 void rule_gen_raddr (u_int32_t addr, u_int32_t mask);
+void rule_gen_laddr6 (u_int32_t addr6[4], u_int32_t mask6[4]);
+void rule_gen_raddr6 (u_int32_t addr6[4], u_int32_t mask6[4]);
+void rule_gen_ip (void);
+void rule_gen_ip6 (void);
 void rule_gen_exe (unsigned long estrid);
 void rule_gen_or (void);
 void rule_gen_and (void);
 void rule_gen_not (void);
-int rule_eval (uid_t muid, u_int32_t mladdr, u_int16_t mlport,
-		u_int32_t mraddr, u_int16_t mrport, const char *mexe);
+int rule_eval (int maf, uid_t muid, u_int32_t *mladdr, u_int16_t mlport,
+		u_int32_t *mraddr, u_int16_t mrport, const char *mexe);
 void rule_parse (const char *r);
 void rule_parse_file (FILE *fp);
 
diff -Naur tcpspy-1.7d.debian/rule_lexer.l tcpspy-1.7d/rule_lexer.l
--- tcpspy-1.7d.debian/rule_lexer.l	2002-01-25 02:01:02.000000000 +0100
+++ tcpspy-1.7d/rule_lexer.l	2011-03-04 18:36:53.000000000 +0100
@@ -66,6 +66,10 @@
 NUMBER			{DIGIT}+
 QSTRING			\"[^"]*\"
 IPADDR			{NUMBER}\.{NUMBER}\.{NUMBER}\.{NUMBER}
+HEXGROUP		([[:xdigit:]]{1,4})
+HEXBLOCK		({HEXGROUP}(:{HEXGROUP}){0,7})
+HEXPART			((::)?{HEXBLOCK}|({HEXBLOCK}::({HEXBLOCK}?)))
+IP6ADDR			({HEXPART}(:{IPADDR})?)
 
 %%
 
@@ -74,6 +78,8 @@
 "or"			return OR;
 "and"			return AND;
 "not"			return NOT;
+"ip"			return IP;
+"ip6"			return IP6;
 
 <INITIAL>user		{
 			BEGIN (WANT_USER);
@@ -152,25 +158,70 @@
 			BEGIN (WANT_ADDR);
 			return RADDR;
 			}
-<WANT_ADDR>{IPADDR}(\/{IPADDR})? {
+<WANT_ADDR>{IPADDR}(\/{IPADDR}|\/{NUMBER})? {
 			struct in_addr in;
+			int pfx;
 			char *s;
 			BEGIN (INITIAL);
 
 			yytext[yyleng] = '\0';
 			if ((s = strchr (yytext, '/')) != NULL) {
 				*s++ = '\0'; 
-				if (inet_aton (s, &in) == 0)
-					YY_FATAL_ERROR ("bad net mask");
-				rulelval.addr.mask = in.s_addr;
+				if (strchr (s, '.')) {
+					if (inet_aton (s, &in) == 0)
+						YY_FATAL_ERROR ("bad net mask");
+					rulelval.addr.mask = in.s_addr;
+				} else {
+					pfx = atoi (s);
+					if ((pfx < 1) || (pfx > 32))
+						YY_FATAL_ERROR ("bad net mask");
+					rulelval.addr.mask = htonl (0xFFFFFFFF << (32 - pfx));
+				}
 			} else rulelval.addr.mask = 0xFFFFFFFF;
+
 			if (inet_aton (yytext, &in) == 0)
 				YY_FATAL_ERROR ("bad IP address");
-			rulelval.addr.addr = in.s_addr;
+			rulelval.addr.addr = in.s_addr & rulelval.addr.mask;
 
 			return ADDR_SPEC;
 			}
 
+<WANT_ADDR>{IP6ADDR}(\/{NUMBER})? {
+			struct in6_addr in6;
+			int pfx, j, grp, rem;
+			char *s;
+			uint8_t *bmask;
+
+			BEGIN (INITIAL);
+
+			yytext[yyleng] = '\0';
+			for (j = 0; j < 4; ++j)
+				rulelval.addr6.mask[j] = 0xFFFFFFFF;
+			if ((s = strchr (yytext, '/')) != NULL) {
+				*s++ = '\0';
+				pfx = atoi (s);
+				if ((pfx < 1) || (pfx > 128))
+					YY_FATAL_ERROR ("bad net mask");
+				grp = pfx / 8;
+				rem = pfx % 8;
+				if (grp < 16) {
+					/* Non-trivial netmask needs calculation.
+					 * Cast to access network byte ordering. */
+					bmask = (uint8_t *) rulelval.addr6.mask;
+					bmask[grp] <<= (8 - rem);
+					for (j = grp + 1; j < 16; ++j)
+						bmask[j] = 0;
+				}
+			}
+
+			if (inet_pton (AF_INET6, yytext, &in6) < 1)
+				YY_FATAL_ERROR ("bad IP address");
+			for (j = 0; j < 4; ++j)
+				rulelval.addr6.addr[j] = in6.s6_addr32[j] & rulelval.addr6.mask[j];
+
+			return ADDR6_SPEC;
+			}
+
 <INITIAL>exe		{
 			BEGIN (WANT_EXE);
 			return EXE;
diff -Naur tcpspy-1.7d.debian/tcpspy.8 tcpspy-1.7d/tcpspy.8
--- tcpspy-1.7d.debian/tcpspy.8	2002-01-25 02:00:50.000000000 +0100
+++ tcpspy-1.7d/tcpspy.8	2011-03-08 16:18:30.000000000 +0100
@@ -133,6 +133,12 @@
 .BI user " \N'34'username\N'34'"
 Same as above, but using a username instead of a user id.
 .TP
+.BI ip
+True if the connection is IPv4.
+.TP
+.BI ip6
+True if the connection is IPv6.
+.TP
 .BI lport " port"
 True if the local end of the connection has port number
 .IR port .
@@ -162,9 +168,16 @@
 but compares the port number of the remote end of the connection.
 .TP
 .BI laddr " n.n.n.n[/m.m.m.m]"
+.TP
+.BI laddr " n.n.n.n/m"
+.TP
+.BI laddr " ip6-addr[/m]"
 Interpreted as a "net/mask" expression; true if "net" is equal to the bitwise
 AND of the local address of the connection and "mask". If no mask is specified,
-a default mask with all bits set (255.255.255.255) is used.
+a default mask with all bits set (255.255.255.255) is used. The CIDR type netmask
+is also possible. With IPv6 only a prefix length netmask is allowed, and the
+length defaults to 128. Depending on the address family, these rules contain
+an implicit match condition "ip" or "ip6", respectively.
 .TP
 .B raddr
 Same as
diff -Naur tcpspy-1.7d.debian/tcpspy.c tcpspy-1.7d/tcpspy.c
--- tcpspy-1.7d.debian/tcpspy.c	2002-01-25 02:01:02.000000000 +0100
+++ tcpspy-1.7d/tcpspy.c	2011-03-08 16:07:13.000000000 +0100
@@ -31,6 +31,7 @@
  * $Id: tcpspy.c,v 1.39.1.3 2001/07/02 11:55:36 tim Stab $
  */
 
+#define __USE_GNU	/* s6_addr32 */
 #include <arpa/inet.h>
 #include <assert.h>
 #include <ctype.h>
@@ -83,9 +84,16 @@
  * easier.
  */
 typedef struct conn {
-	unsigned long lcl;
+	int af;
+	union {
+		unsigned long lcl;
+		unsigned long lcl6[4];
+	};
 	unsigned long lclp;
-	unsigned long rmt;
+	union {
+		unsigned long rmt;
+		unsigned long rmt6[4];
+	};
 	unsigned long rmtp;
 	unsigned long uid;
 	unsigned long ino;
@@ -149,7 +157,9 @@
 
 	assert (c != NULL);
 
-	h = c->lcl ^ c->lclp ^ c->rmt ^ c->rmtp ^ c->uid ^ c->ino;
+	h = c->lcl6[0] ^ c->lcl6[1] ^ c->lcl6[2] ^ c->lcl6[3];
+	h = h ^ c->rmt6[0] ^ c->rmt6[1] ^ c->rmt6[2] ^ c->rmt6[3];
+	h = h ^ c->lclp ^ c->rmtp ^ c->uid ^ c->ino;
 
 	return h % CONNTABLE_BUCKETS;
 }
@@ -194,8 +204,10 @@
 
 	bucket = ct->buckets[ct_hash (c)];
 	while (bucket != NULL) {
-		if ((c->lcl == bucket->lcl) && (c->lclp == bucket->lclp) && 
-			(c->rmt == bucket->rmt) && (c->rmtp == bucket->rmtp) &&
+		if ((c->af == bucket->af) &&
+			(memcmp(c->lcl6, bucket->lcl6, sizeof (c->lcl6)) == 0) &&
+			(memcmp(c->rmt6, bucket->rmt6, sizeof (c->rmt6)) == 0) &&
+			(c->lclp == bucket->lclp) && (c->rmtp == bucket->rmtp) &&
 			(c->uid == bucket->uid) && (c->ino == bucket->ino)) {
 			return 1;
 		}
@@ -213,7 +225,7 @@
  */
 static int ct_read (conntable_t *ct)
 {
-	static FILE *fp = NULL;
+	static FILE *fp = NULL, *fp6 = NULL;
 	char buf[1024];
 	conn_t c;
 	
@@ -225,12 +237,24 @@
 	}
 	rewind (fp);
 
+	if (fp6 == NULL) {
+		fp6 = fopen ("/proc/net/tcp6", "r");
+#ifndef RELAX_PROC_IP6
+		if (fp6 == NULL) panic ("/proc/net/tcp6: %s", strerror (errno));
+#endif
+	}
+	if (fp6)
+		rewind (fp6);
+
 	if (fgets (buf, sizeof (buf), fp) == NULL)
 		panic ("/proc/net/tcp: missing header");
 	
 	while (fgets (buf, sizeof (buf), fp) != NULL) {
 		unsigned long st;
 
+		memset (c.lcl6, 0, sizeof (c.lcl6));
+		memset (c.rmt6, 0, sizeof (c.rmt6));
+		c.af = AF_INET;
 		if (sscanf (buf, "%*d: %lx:%lx %lx:%lx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu", &c.lcl, &c.lclp, &c.rmt, &c.rmtp, &st, &c.uid, &c.ino) != 7) {
 			logmsg ("/proc/net/tcp: warning: incomplete line");
 			continue;
@@ -243,6 +267,33 @@
 			return 0;
 	}
 
+	/* Some systems do not use IPv6. */
+	if (fp6) {
+		if (fgets (buf, sizeof (buf), fp6) == NULL)
+			panic ("/proc/net/tcp6: missing header");
+
+		while (fgets (buf, sizeof (buf), fp6) != NULL) {
+			unsigned long st;
+
+			memset (c.lcl6, 0, sizeof (c.lcl6));
+			memset (c.rmt6, 0, sizeof (c.rmt6));
+			c.af = AF_INET6;
+			if (sscanf (buf, "%*d: %8lx%8lx%8lx%8lx:%lx %8lx%8lx%8lx%8lx:%lx %lx %*x:%*x %*x:%*x %*x %lu %*d %lu",
+					&c.lcl6[0], &c.lcl6[1], &c.lcl6[2], &c.lcl6[3], &c.lclp,
+					&c.rmt6[0], &c.rmt6[1], &c.rmt6[2], &c.rmt6[3], &c.rmtp,
+					&st, &c.uid, &c.ino) != 13) {
+				logmsg ("/proc/net/tcp6: warning: incomplete line");
+				continue;
+			}
+			if ((c.ino == 0) || (st != TCP_ESTABLISHED)) continue;
+			if (showprocs != 0)
+				huntinode ((ino_t) c.ino, c.exe, sizeof (c.exe));
+
+			if (ct_add (ct, &c) == 0)
+				return 0;
+		}
+	}
+
 	return 1;
 }
 
@@ -323,7 +374,7 @@
 			if (len < 0)
 				continue;
 			lnktgt[len] = '\0';
-			if (sscanf (lnktgt, "socket:[%lu]", &this_ino) != 1)
+			if (sscanf (lnktgt, "socket:[%lu]", (long unsigned int *) &this_ino) != 1)
 				continue;
 			if (this_ino != i)
 				continue;
@@ -351,9 +402,7 @@
  */
 static void logconn (conn_t *c, const char *action)
 {
-	struct in_addr in;
-	char laddr[16], raddr[16]; /* assume IPv4 nnn.nnn.nnn.nnn\0 */
-	char *n;
+	char laddr[INET6_ADDRSTRLEN], raddr[INET6_ADDRSTRLEN]; /* space reserved for numeric IPv6 */
 	struct passwd *pw;
 	char uidbuf[32];
 	char *user;
@@ -363,16 +412,16 @@
 	assert (c != NULL);
 	assert (action != NULL);
 
-	if ((gotrule != 0) && (rule_eval (c->uid, c->lcl, c->lclp, c->rmt, c->rmtp, (showprocs != 0) ? c->exe : NULL) == 0))
+	if ((gotrule != 0) &&
+		(rule_eval (c->af, c->uid, (u_int32_t *) c->lcl6, c->lclp,
+				(u_int32_t *) c->rmt6, c->rmtp, (showprocs != 0) ? c->exe : NULL) == 0))
 		return;
 
-	in.s_addr = c->lcl;
-	n = inet_ntoa (in);
-	strncpy (laddr, n, sizeof (laddr));
+	if ((inet_ntop (c->af, c->lcl6, laddr, sizeof (laddr)) == NULL) && (errno == ENOSPC))
+		laddr[0] = '\0';
 	laddr[sizeof (laddr) - 1] = '\0';
-	in.s_addr = c->rmt;
-	n = inet_ntoa (in);
-	strncpy (raddr, n, sizeof (raddr));
+	if ((inet_ntop (c->af, c->rmt6, raddr, sizeof (raddr)) == NULL) && (errno == ENOSPC))
+		raddr[0] = '\0';
 	raddr[sizeof (raddr) - 1] = '\0';
 
 	snprintf (lport, sizeof (lport), "%lu", c->lclp);
