diff --git a/lib/Kolab/FreeBusy/Utils.php b/lib/Kolab/FreeBusy/Utils.php
index c045963..15dde17 100644
--- a/lib/Kolab/FreeBusy/Utils.php
+++ b/lib/Kolab/FreeBusy/Utils.php
@@ -1,274 +1,280 @@
 <?php
 
 /**
  * This file is part of the Kolab Server Free/Busy Service
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2013-2014, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Kolab\FreeBusy;
 
 /**
  * Static calss providing utility functions for the Free/Busy service
  */
 class Utils
 {
 	const PRODID = '-//kolab.org//NONSGML Kolab Free-Busy Service 3.2//EN';
 
 	/**
 	 * Resolve the given directory to a real path ending with $append
 	 *
 	 * @param string Arbitrary directory directory path
 	 * @param string Make path end with this string/character
 	 * @return string Absolute file system path
 	 */
 	public static function abspath($dirname, $append = '')
 	{
 		if ($dirname[0] != '/')
 			$dirname = realpath(KOLAB_FREEBUSY_ROOT . '/' . $dirname);
 
 		return rtrim($dirname, '/') . $append;
 	}
 
 	/**
 	 * Returns remote IP address and forwarded addresses if found
 	 *
 	 * @return string Remote IP address(es)
 	 */
 	public static function remoteIP()
 	{
 		$address = $_SERVER['REMOTE_ADDR'];
 
 		// use the NGINX X-Real-IP header, if set
 		if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
 			$address = $_SERVER['HTTP_X_REAL_IP'];
 		}
 		// use the X-Forwarded-For header, if set
 		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
 			$address = $_SERVER['HTTP_X_FORWARDED_FOR'];
 		}
 
 		return $address;
 	}
 
 
 	/**
 	 * Checks if the given IP address is in one of the provided ranges
 	 *
 	 * @param string   IP address
 	 * @param array	List of IP ranges/subnets to check against
 	 * @return boolean True if in range, False if not
 	 */
 	public static function checkIPRange($ip, $ranges)
 	{
 		$ipv6 = strpos($ip, ':') !== false;
 		$ipbin = $ipv6 ? self::ip6net2bits($ip) : ip2long($ip);
 
 		foreach ((array)$ranges as $range) {
 			// don't compare IPv4 and IPv6 addresses/ranges
 			$rangev6 = strpos($range, ':') !== false;
 			if ($ipv6 != $rangev6) {
 				continue;
 			}
 
+			// special entries that allow all IPs
+			if ($range === '*' || $range === 'all' || $range === '0/0'
+				|| $range === '0.0.0.0/0' || $range === '0.0.0.0/0.0.0.0') {
+				return true;
+			}
+
 			// quick substring check (e.g. 192.168.0.)
 			if (( $ipv6 && strpos($ipbin, self::ip6net2bits($range)) === 0) ||
 				(!$ipv6 && strpos($ip, rtrim($range, '*')) === 0)) {
 				return true;
 			}
 
 			// range from-to specified (IPv4 only)
 			list($lower, $upper) = explode('-', $range);
 			if (strlen($upper) && !$ipv6) {
 				if ($ipbin >= ip2long(trim($lower)) && $ipbin <= ip2long(trim($upper))) {
 					return true;
 				}
 			}
 
 			// subnet/length is given
 			list($subnet, $bits) = explode('/', $range);
 
 			// IPv6 subnet
 			if (strlen($bits) && $ipv6) {
 				$subnetbin = self::ip6net2bits($subnet);
 				if (substr($ipbin, 0, $bits) === substr($subnetbin, 0, $bits)) {
 					return true;
 				}
 			}
 			// IPv4 subnet
 			else if (strlen($bits)) {
 				$subnet = ip2long($subnet);
 				$mask = -1 << $bits;
 				$subnet &= $mask;  // just in case the supplied subnet wasn't correctly aligned
 				if (($ipbin & $mask) == $subnet) {
 					return true;
 				}
 			}
 		}
 
 		return false;
 	}
 
 	/**
 	 * Convert the given IPv6 address to a binary string representation.
 	 * (from http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet)
 	 */
 	public static function ip6net2bits($inet)
 	{
 		$binaryip = '';
 		$unpacked = @unpack('A16', inet_pton($inet));
 		foreach (str_split($unpacked[1]) as $char) {
 			$binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
 		}
 		return $binaryip;
 	}
 
 	/**
 	 * Returns number of seconds for a specified offset string.
 	 *
 	 * @param string String representation of the offset (e.g. 20min, 5h, 2days, 1week)
 	 * @return int   Number of seconds
 	 */
 	public static function getOffsetSec($str)
 	{
 		if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) {
 			$amount = (int) $regs[1];
 			$unit   = strtolower($regs[2]);
 		}
 		else {
 			$amount = (int) $str;
 			$unit   = 's';
 		}
 
 		switch ($unit) {
 		case 'w':
 			$amount *= 7;
 		case 'd':
 			$amount *= 24;
 		case 'h':
 			$amount *= 60;
 		case 'm':
 			$amount *= 60;
 		}
 
 		return $amount;
 	}
 
 	/**
 	 * Getter for the free/busy period start time
 	 *
 	 * @return int Unix timestamp
 	 */
 	public static function periodStart()
 	{
 		// use date from HTTP query
 		if (!empty($_GET['dtstart'])) {
 			return self::periodStartDT()->format('U');
 		}
 
 		// Should probably be a setting. For now, do 8 weeks in the past
 		return time() - (60 * 60 * 24 * 7 * 8);
 	}
 
 	/**
 	 * Getter for the free/busy period start time
 	 *
 	 * @return object DateTime instance
 	 */
 	public static function periodStartDT()
 	{
 		// use date from HTTP query
 		if (!empty($_GET['dtstart']) &&
 				($dtstart = filter_input(INPUT_GET, 'dtstart', FILTER_SANITIZE_STRING))
 		) {
 			try {
 				return new \DateTime($dtstart, new \DateTimezone('UTC'));
 			}
 			catch (Exception $e) {
 				// ignore
 			}
 		}
 
 		// Should probably be a setting. For now, do 8 weeks in the past
 		return new \DateTime('now - 8 weeks 00:00:00', new \DateTimezone('UTC'));
 	}
 
 	/**
 	 * Getter for the free/busy period end time
 	 *
 	 * @return int Unix timestamp
 	 */
 	public static function periodEnd()
 	{
 		// use date from HTTP query
 		if (!empty($_GET['dtend'])) {
 			return self::periodEndDT()->format('U');
 		}
 
 		// Should probably be a setting. For now, do 16 weeks into the future
 		return time() + (60 * 60 * 24 * 7 * 16);
 	}
 
 	/**
 	 * Getter for the free/busy period end time
 	 *
 	 * @return object DateTime instance
 	 */
 	public static function periodEndDT()
 	{
 		// use date from HTTP query
 		if (!empty($_GET['dtend']) &&
 			($dtend = filter_input(INPUT_GET, 'dtend', FILTER_SANITIZE_STRING))
 		) {
 			try {
 				return new \DateTime($dtend, new \DateTimezone('UTC'));
 			}
 			catch (Exception $e) {
 				// ignore
 			}
 		}
 
 		// Should probably be a setting. For now, do 8 weeks in the past
 		return new \DateTime('now + 16 weeks 00:00:00', new \DateTimezone('UTC'));
 	}
 
 	/**
 	 * Returns an apparent empty Free/Busy list for the given user
 	 */
 	public static function dummyVFreebusy($user)
 	{
 		$now = time();
 		$dtformat = 'Ymd\THis\Z';
 
 		$dummy  = "BEGIN:VCALENDAR\n";
 		$dummy .= "VERSION:2.0\n";
 		$dummy .= "PRODID:" . self::PRODID . "\n";
 		$dummy .= "METHOD:PUBLISH\n";
 		$dummy .= "BEGIN:VFREEBUSY\n";
 		$dummy .= "ORGANIZER:MAILTO:" . $user . "\n";
 		$dummy .= "DTSTAMP:" . gmdate($dtformat) . "\n";
 		$dummy .= "DTSTART:" . gmdate($dtformat, self::periodStart()) . "\n";
 		$dummy .= "DTEND:"   . gmdate($dtformat, self::periodEnd()) . "\n";
 		$dummy .= "COMMENT:This is a dummy vfreebusy that indicates an empty calendar\n";
 		$dummy .= "FREEBUSY:19700101T000000Z/19700101T000000Z\n";
 		$dummy .= "END:VFREEBUSY\n";
 		$dummy .= "END:VCALENDAR\n";
 
 		return $dummy;
 	}
 }