[BUGFIX] t3lib_div::cmpFQDN() lacks support for IPv6 and hostnames
authorStefan Neufeind <typo3.neufeind@speedpartner.de>
Tue, 12 Jul 2011 08:16:01 +0000 (10:16 +0200)
committerXavier Perseguers <xavier@typo3.org>
Mon, 29 Aug 2011 15:41:49 +0000 (17:41 +0200)
Add checks for IPv4 and IPv6. Otherwise take input as hostname.
Implement correct (recursive) wildcard-checks.
Add unit tests.

Change-Id: I15328839d28746c68d7ef108b783c1ffe40d99cd
Resolves: #27217
Releases: 4.5, 4.6
Reviewed-on: http://review.typo3.org/3924
Reviewed-by: Xavier Perseguers
Tested-by: Xavier Perseguers
t3lib/class.t3lib_div.php
tests/t3lib/t3lib_divTest.php

index 914a8e1..7870115 100644 (file)
@@ -949,28 +949,69 @@ final class t3lib_div {
        /**
         * Match fully qualified domain name with list of strings with wildcard
         *
-        * @param       string          The current remote IP address for instance, typ. REMOTE_ADDR
-        * @param       string          A comma-list of domain names to match with. *-wildcard allowed but cannot be part of a string, so it must match the full host name (eg. myhost.*.com => correct, myhost.*domain.com => wrong)
-        * @return      boolean         True if a domain name mask from $list matches $baseIP
+        * @param string $baseIP A hostname or an IPv4/IPv6-address (will by reverse-resolved; typically REMOTE_ADDR)
+        * @param string $list A comma-list of domain names to match with. *-wildcard allowed but cannot be part of a string, so it must match the full host name (eg. myhost.*.com => correct, myhost.*domain.com => wrong)
+        * @return boolean TRUE if a domain name mask from $list matches $baseIP
         */
-       public static function cmpFQDN($baseIP, $list) {
-               if (count(explode('.', $baseIP)) == 4) {
-                       $resolvedHostName = explode('.', gethostbyaddr($baseIP));
-                       $values = self::trimExplode(',', $list, 1);
+       public static function cmpFQDN($baseHost, $list) {
+               $baseHost = trim($baseHost);
+               if (empty($baseHost)) {
+                       return FALSE;
+               }
+               if (self::validIPv4($baseHost) || self::validIPv6($baseHost)) {
+                               // resolve hostname
+                               // note: this is reverse-lookup and can be randomly set as soon as somebody is able to set
+                               // the reverse-DNS for his IP (security when for example used with REMOTE_ADDR)
+                       $baseHostName = gethostbyaddr($baseHost);
+                       if ($baseHostName === $baseHost) {
+                                       // unable to resolve hostname
+                               return FALSE;
+                       }
+               } else {
+                       $baseHostName = $baseHost;
+               }
+               $baseHostNameParts = explode('.', $baseHostName);
 
-                       foreach ($values as $test) {
-                               $hostNameParts = explode('.', $test);
-                               $yes = 1;
+               $values = self::trimExplode(',', $list, 1);
+
+               foreach ($values as $test) {
+                       $hostNameParts = explode('.', $test);
 
-                               foreach ($hostNameParts as $index => $val) {
-                                       $val = trim($val);
-                                       if (strcmp($val, '*') && strcmp($resolvedHostName[$index], $val)) {
-                                               $yes = 0;
+                               // to match hostNameParts can only be shorter (in case of wildcards) or equal
+                       if (count($hostNameParts) > count($baseHostNameParts)) {
+                               continue;
+                       }
+
+                       $yes = TRUE;
+                       foreach ($hostNameParts as $index => $val) {
+                               $val = trim($val);
+                               if ($val === '*') {
+                                               // wildcard valid for one or more hostname-parts
+
+                                       $wildcardStart = $index + 1;
+                                               // wildcard as last/only part always matches, otherwise perform recursive checks
+                                       if ($wildcardStart < count($hostNameParts)) {
+                                               $wildcardMatched = FALSE;
+                                               $tempHostName = implode('.', array_slice($hostNameParts, $index + 1));
+                                               while (($wildcardStart < count($baseHostNameParts)) && (!$wildcardMatched)) {
+                                                       $tempBaseHostName = implode('.', array_slice($baseHostNameParts, $wildcardStart));
+                                                       $wildcardMatched = self::cmpFQDN($tempBaseHostName, $tempHostName);
+                                                       $wildcardStart++;
+                                               }
+                                               if ($wildcardMatched) {
+                                                               // match found by recursive compare
+                                                       return TRUE;
+                                               } else {
+                                                       $yes = FALSE;
+                                               }
                                        }
+                               } elseif ($baseHostNameParts[$index] !== $val) {
+                                               // in case of no match
+                                       $yes = FALSE;
                                }
-                               if ($yes) {
-                                       return TRUE;
-                               }
+                       }
+                       if ($yes) {
+                               return TRUE;
                        }
                }
                return FALSE;
index 228e328..1836e62 100644 (file)
@@ -347,6 +347,58 @@ class t3lib_divTest extends tx_phpunit_testcase {
                $this->assertFalse(t3lib_div::validIP($ip));
        }
 
+       ///////////////////////////////
+       // Tests concerning cmpFQDN
+       ///////////////////////////////
+
+       /**
+        * Data provider for cmpFqdnReturnsTrue
+        *
+        * @return array Data sets
+        */
+       public static function cmpFqdnValidDataProvider() {
+               return array(
+                       'localhost should usually resolve, IPv4' => array('127.0.0.1', '*'),
+                       'localhost should usually resolve, IPv6' => array('::1', '*'),
+                               // other testcases with resolving not possible since it would
+                               // require a working IPv4/IPv6-connectivity
+                       'aaa.bbb.ccc.ddd.eee, full' => array('aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.ddd.eee'),
+                       'aaa.bbb.ccc.ddd.eee, wildcard first' => array('aaa.bbb.ccc.ddd.eee', '*.ccc.ddd.eee'),
+                       'aaa.bbb.ccc.ddd.eee, wildcard last' => array('aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.*'),
+                       'aaa.bbb.ccc.ddd.eee, wildcard middle' => array('aaa.bbb.ccc.ddd.eee', 'aaa.*.eee'),
+                       'list-matches, 1' => array('aaa.bbb.ccc.ddd.eee', 'xxx, yyy, zzz, aaa.*.eee'),
+                       'list-matches, 2' => array('aaa.bbb.ccc.ddd.eee', '127:0:0:1,,aaa.*.eee,::1'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider cmpFqdnValidDataProvider
+        */
+       public function cmpFqdnReturnsTrue($baseHost, $list) {
+               $this->assertTrue(t3lib_div::cmpFQDN($baseHost, $list));
+       }
+
+       /**
+        * Data provider for cmpFqdnReturnsFalse
+        *
+        * @return array Data sets
+        */
+       public static function cmpFqdnInvalidDataProvider() {
+               return array(
+                       'num-parts of hostname to check can only be less or equal than hostname, 1' => array('aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.ddd.eee.fff'),
+                       'num-parts of hostname to check can only be less or equal than hostname, 2' => array('aaa.bbb.ccc.ddd.eee', 'aaa.*.bbb.ccc.ddd.eee'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider cmpFqdnInvalidDataProvider
+        */
+       public function cmpFqdnReturnsFalse($baseHost, $list) {
+               $this->assertFalse(t3lib_div::cmpFQDN($baseHost, $list));
+       }
+
 
        ///////////////////////////////
        // Tests concerning testInt