--- ip_login.module.orig	2011-09-07 15:54:02.000000000 +0200
+++ ip_login.module	2011-09-07 15:54:35.000000000 +0200
@@ -115,17 +115,14 @@
     break;
   case 'validate' :
     if ($category == 'account' && isset($edit['ip_login_match'])) {
-      //TODO: replace with regexp ideally
-      // validate: replace all non-numeric but legal IP range chars with '|'
-      $ip_login_addresses = strtr($edit['ip_login_match'], ' ,.-', '||||');
-      foreach (explode('|', $ip_login_addresses) as $quad) {
-        if (strlen($quad) && !is_numeric($quad)) {
+      $allowed_ips = explode(',', $edit['ip_login_match']);
+      $filter = new IPFilter();
+      if (!$filter->validate($allowed_ips)) {
           // bad entry, warn & bail
           form_set_error(
             'ip_login_match',
-            t('Only numbers, spaces, commas, dots and hyphens allowed in IP ranges.'));
+            t('Invalid IP mask.<br />Valid masks are: single address, address using * wildcard, address range in the form of IP1 - IP2, and CIDR address in the form of address/prefix_size.'));
           break;
-        }
       }
     }
     break;
@@ -257,20 +254,10 @@
   $partial_matches = db_query("SELECT uid, ip_match FROM {ip_login_user} WHERE ip_match LIKE ('%s') ORDER BY LENGTH(ip_match) DESC", '%' . $addr[0] . '.%');
   $uid_matched = 0;
   while (($row = db_fetch_object($partial_matches))) {
-    // multiple values are separated with commas so try each in turn
-    $user_ip_ranges = explode(",", $row->ip_match);
-    foreach ($user_ip_ranges as $ip_range) {
-      // clear any whitespace, break into quads, then compare
-      $ip_range = explode('.', trim($ip_range));
-      foreach ($ip_range as $index => $quad) {
-        $matches = ip_login_match_quad($addr[$index], $quad);
-        if (!$matches) break; // no match, escape this foreach and move on to next IP range
-      }
-      // if it matches, stop here and do login
-      if ($matches) {
-        $uid_matched = $row->uid;
-        break 2; // escape the foreach (ranges) and while (db_result)
-      }
+    $allowed_ips = explode(',', $row->ip_match);
+    $filter = new IPFilter($allowed_ips);
+    if ($filter->check($ip)) {
+       $uid_matched = $row->uid;
     }
   }
 
@@ -434,35 +421,6 @@
   drupal_goto(variable_get('ip_login_destination', 'user'));
 }
 
-/**
- * Compares a single IP quadrant to a matching quadrant.
- *
- * The matching quad can contain wildcards (*), ranges (10-12) or exact numbers
- * @param $find_value
- *    A string containing the quadrant value being looked for
- * @param $in_range
- *    String with a quadrant value, range or wildcard to compare to
- * @return TRUE
- *    If $find_value matches an IP address $in_range
- *
- */
-function ip_login_match_quad($find_value, $in_range) {
-  // if we've got a wildcard just return TRUE
-  if ($in_range == '*') return TRUE;
-
-  // check if this quad contains the range character '-'
-  $range = explode('-', $in_range);
-  if (isset($range[1])) {
-    // we've got a range, test upper and lower bounds
-    if (($find_value >= $range[0]) && ($find_value <= $range[1])) return TRUE;
-  }
-  else {
-    // no range, just do normal match
-    return ($range[0] == $find_value);
-  }
-  return FALSE;
-}
-
 /*
  * Returns TRUE if a user has permission to log out and back in as another user
  */
@@ -529,3 +487,123 @@
   }
   return FALSE;
 }
+
+/*
+ * IPFilter class
+ * original version: http://www.php.net/manual/en/function.ip2long.php#102898
+ */
+class IPFilter {
+    private static $_IP_TYPE_SINGLE = 'single';
+    private static $_IP_TYPE_WILDCARD = 'wildcard';
+    private static $_IP_TYPE_MASK = 'mask';
+    private static $_IP_TYPE_SECTION = 'section';
+    private $_allowed_ips = array();
+
+    public function __construct($allowed_ips) {
+        $this -> _allowed_ips = $allowed_ips;
+    }
+
+    // Validate rules
+    public function validate($allowed_ips = NULL) {
+        $allowed_ips = $allowed_ips ? $allowed_ips : $this->_allowed_ips;
+
+        foreach($allowed_ips as $allowed_ip) {
+            $type = $this -> _judge_ip_type($allowed_ip);
+            $sub_rst = call_user_func(array($this,'_sub_validate_' . $type), trim($allowed_ip));
+
+            if (!$sub_rst) {
+                return FALSE;
+            }
+        }
+
+        return TRUE;
+    }
+
+    public function check($ip, $allowed_ips = null) {
+        $allowed_ips = $allowed_ips ? $allowed_ips : $this->_allowed_ips;
+
+        foreach($allowed_ips as $allowed_ip) {
+            $type = $this -> _judge_ip_type($allowed_ip);
+            $sub_rst = call_user_func(array($this,'_sub_checker_' . $type), $allowed_ip, $ip);
+
+            if ($sub_rst) {
+                return TRUE;
+            }
+        }
+
+        return FALSE;
+    }
+
+    private function _judge_ip_type($ip) {
+        if (strpos($ip, '*')) {
+            return self :: $_IP_TYPE_WILDCARD;
+        }
+
+        if (strpos($ip, '/')) {
+            return self :: $_IP_TYPE_MASK;
+        }
+
+        if (strpos($ip, '-')) {
+            return self :: $_IP_TYPE_SECTION;
+        }
+
+        if (ip2long($ip)) {
+            return self :: $_IP_TYPE_SINGLE;
+        }
+
+        return FALSE;
+    }
+
+    private function _sub_checker_single($allowed_ip, $ip) {
+        return (ip2long($allowed_ip) == ip2long($ip));
+    }
+
+    private function _sub_validate_single($allowed_ip) {
+        return (ip2long($allowed_ip));
+    }
+
+    private function _sub_checker_wildcard($allowed_ip, $ip) {
+        $allowed_ip_arr = explode('.', $allowed_ip);
+        $ip_arr = explode('.', $ip);
+        for($i = 0;$i < count($allowed_ip_arr);$i++) {
+            if ($allowed_ip_arr[$i] == '*') {
+                return TRUE;
+            }
+            elseif (FALSE == ($allowed_ip_arr[$i] == $ip_arr[$i])) {
+                return FALSE;
+            }
+        }
+    }
+
+    private function _sub_validate_wildcard($allowed_ip) {
+       return $this->_sub_validate_single(str_replace('*','0', $allowed_ip));
+    }
+
+
+// this part is based on http://www.php.net/manual/en/function.ip2long.php#82397
+    private function _sub_checker_mask($cidr, $ip) {
+       list ($net, $mask) = explode ('/', $cidr);
+       return ( ip2long ($ip) & ~((1 << (32 - $mask)) - 1) ) == ip2long ($net);
+    }
+
+    private function _sub_validate_mask($cidr) {
+       list ($net, $mask) = explode ('/', $cidr);
+       if (!is_numeric($mask)) return FALSE;
+       if ($mask<0 OR $mask>32) return FALSE;
+       return $this->_sub_validate_single($net);
+    }
+
+    private function _sub_checker_section($allowed_ip, $ip) {
+        list($begin, $end) = explode('-', $allowed_ip);
+        $begin = ip2long($begin);
+        $end = ip2long($end);
+        $ip = ip2long($ip);
+        return ($ip >= $begin && $ip <= $end);
+    }
+
+    private function _sub_validate_section($allowed_ip, $ip) {
+        list($begin, $end) = explode('-', $allowed_ip);
+        return ($this->_sub_validate_single($begin) & $this->_sub_validate_single($end));
+    }
+}
+?>
