commit e8c2c6f2ac6de2cc3987b8b2ea153953a7cfa5d0
Author: Viktor G <viktor@netgate.com>
Date:   Sat Sep 12 09:16:07 2020 +0300

    OpenVPN+RADIUS+Cisco-AVPair IPv6 ACL. Issue #10454

diff --git a/src/etc/inc/openvpn.attributes.php b/src/etc/inc/openvpn.attributes.php
index 94788df1e1..66732062ff 100644
--- a/src/etc/inc/openvpn.attributes.php
+++ b/src/etc/inc/openvpn.attributes.php
@@ -51,16 +51,16 @@ function cisco_extract_index($prule) {
 	return -1;;
 }
 
-function parse_cisco_acl_rule($rule, $devname, $dir) {
+function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
 	$rule_orig = $rule;
 	$rule = explode(" ", $rule);
 	$tmprule = "";
 	$index = 0;
 
 	if ($rule[$index] == "permit") {
-		$tmprule = "pass {$dir} quick on {$devname} ";
+		$startrule = "pass {$dir} quick on {$devname} ";
 	} else if ($rule[$index] == "deny") {
-		$tmprule = "block {$dir} quick on {$devname} ";
+		$startrule = "block {$dir} quick on {$devname} ";
 	} else {
 		return;
 	}
@@ -69,9 +69,11 @@ function parse_cisco_acl_rule($rule, $devname, $dir) {
 
 	switch ($rule[$index]) {
 		case "ip":
-			$tmprule .= "inet ";
 			break;
 		case "icmp":
+			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
+			$tmprule .= "proto {$icmp} ";
+			break;
 		case "tcp":
 		case "udp":
 			$tmprule .= "proto {$rule[$index]} ";
@@ -85,29 +87,37 @@ function parse_cisco_acl_rule($rule, $devname, $dir) {
 	/* Source */
 	if (trim($rule[$index]) == "host") {
 		$index++;
+		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
+		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
+			$tmprule .= "from {$rule[$index]} ";
+			$index++;
+		} else {
+			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
+			return;
+		}
+	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
 		$tmprule .= "from {$rule[$index]} ";
 		$index++;
-	} else if (trim($rule[$index]) == "any") {
+	} elseif (trim($rule[$index]) == "any") {
 		$tmprule .= "from any ";
 		$index++;
 	} else {
 		$network = $rule[$index];
 		$netmask = $rule[++$index];
 
-
-		if(!is_ipaddr($network)) {
+		if (is_ipaddrv4($network) && ($proto == "inet")) {
+			try {
+				$netmask = cisco_to_cidr($netmask);
+			} catch(Exception $e) {
+				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
+				return;
+			}
+			$tmprule .= "from {$network}/{$netmask}";
+		} else {
 			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
 			return;
 		}
 
-		try {
-			$netmask = cisco_to_cidr($netmask);
-		} catch(Exception $e) {
-			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
-			return;
-		}
-		$tmprule .= "from {$network}/{$netmask} ";
-
 		$index++;
 	}
 
@@ -152,29 +162,37 @@ function parse_cisco_acl_rule($rule, $devname, $dir) {
 	/* Destination */
 	if (trim($rule[$index]) == "host") {
 		$index++;
+		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
+		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
+			$tmprule .= "to {$rule[$index]} ";
+			$index++;
+		} else {
+			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
+			return;
+		}
+	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
 		$tmprule .= "to {$rule[$index]} ";
 		$index++;
-	} else if (trim($rule[$index]) == "any") {
+	} elseif (trim($rule[$index]) == "any") {
 		$tmprule .= "to any ";
 		$index++;
 	} else {
 		$network = $rule[$index];
 		$netmask = $rule[++$index];
 
-
-		if(!is_ipaddr($network)) {
+		if (is_ipaddrv4($network) && ($proto == "inet")) {
+			try {
+				$netmask = cisco_to_cidr($netmask);
+			} catch(Exception $e) {
+				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
+				return;
+			}
+			$tmprule .= "to {$network}/{$netmask}";
+		} else {
 			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
 			return;
 		}
 
-		try {
-			$netmask = cisco_to_cidr($netmask);
-		} catch(Exception $e) {
-			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination netmask '$netmask'.");
-			return;
-		}
-		$tmprule .= "to {$network}/{$netmask} ";
-
 		$index++;
 	}
 
@@ -216,6 +234,7 @@ function parse_cisco_acl_rule($rule, $devname, $dir) {
 		$index++;
 	}
 
+	$tmprule = $startrule . $proto . " " . $tmprule;
 	return $tmprule;
 }
 
@@ -226,8 +245,8 @@ function parse_cisco_acl($attribs) {
 	}
 	$finalrules = "";
 	if (is_array($attribs['ciscoavpair'])) {
-		$inrules = array();
-		$outrules = array();
+		$inrules = array('inet' => array(), 'inet6' => array());
+		$outrules = array('inet' => array(), 'inet6' => array());
 		foreach ($attribs['ciscoavpair'] as $avrules) {
 			$rule = explode("=", $avrules);
 			$dir = "";
@@ -250,28 +269,36 @@ function parse_cisco_acl($attribs) {
 				continue;
 			}
 
-			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir);
+			if (strstr($rule[0], "ipv6")) {
+				$proto = "inet6";
+			} else {
+				$proto = "inet";
+			}
+
+			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
 
 			if ($dir == "in") {
-				$inrules[$rindex] = $tmprule;
+				$inrules[$proto][$rindex] = $tmprule;
 			} else if ($dir == "out") {
-				$outrules[$rindex] = $tmprule;
+				$outrules[$proto][$rindex] = $tmprule;
 			}
 		}
 
 
 		$state = "";
-		if (!empty($outrules)) {
-			$state = "no state";
-		}
-		ksort($inrules, SORT_NUMERIC);
-		foreach ($inrules as $inrule) {
-			$finalrules .= "{$inrule} {$state}\n";
-		}
-		if (!empty($outrules)) {
-			ksort($outrules, SORT_NUMERIC);
-			foreach ($outrules as $outrule) {
-				$finalrules .= "{$outrule} {$state}\n";
+		foreach (array('inet', 'inet6') as $ip) {
+			if (!empty($outrules[$ip])) {
+				$state = "no state";
+			}
+			ksort($inrules[$ip], SORT_NUMERIC);
+			foreach ($inrules[$ip] as $inrule) {
+				$finalrules .= "{$inrule} {$state}\n";
+			}
+			if (!empty($outrules[$ip])) {
+				ksort($outrules[$ip], SORT_NUMERIC);
+				foreach ($outrules[$ip] as $outrule) {
+					$finalrules .= "{$outrule} {$state}\n";
+				}
 			}
 		}
 	}
