diff --git a/lib/Kolab/FreeBusy/HTTPAuth.php b/lib/Kolab/FreeBusy/HTTPAuth.php index 9d10927..1cbbce3 100644 --- a/lib/Kolab/FreeBusy/HTTPAuth.php +++ b/lib/Kolab/FreeBusy/HTTPAuth.php @@ -1,185 +1,186 @@ * * Copyright (C) 2013, Kolab Systems AG * * 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 . */ namespace Kolab\FreeBusy; use \Kolab\Config; use \Net_LDAP3; use \Monolog\Logger as Monolog; /** * Static class to process HTTP authentication to this service */ class HTTPAuth { private static $logger; /** * Validate HTTP basic auth against the configured backend */ public static function check($config) { $logger = Logger::get('httpauth'); // First try token authentication if enabled and user/token detected in the URL if (!empty($_SERVER['FREEBUSY_URI']) && Config::boolean($config['allow_token']) - && preg_match('|([^@/]+@[^@/]+)/([a-f0-9]{32})/[^/]+$|', $_SERVER['FREEBUSY_URI'], $matches) + && preg_match('|([^@/]+@[^@/]+)/([a-f0-9]{32})$|', $_SERVER['FREEBUSY_URI'], $matches) && self::checkToken($config, $matches[1], $matches[2]) ) { return true; } // no http auth submitted, abort! if (empty($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) { $logger->addDebug('No HTTP auth submitted'); return false; } switch ($config['type']) { case 'static': return self::checkStatic($config, $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); case 'ldap': return self::checkLDAP($config, $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); default: $logger->addWarning('Unsupported auth type ' . $config['type']); break; } return false; } /** * Validate static user credentials from config */ private static function checkStatic($config, $user, $pass) { $valid = $user == $config['username'] && $pass == $config['password']; Logger::get('httpauth')->addInfo("Static: authenticating user '$user': " . ($valid ? 'SUCCESS' : 'FAILURE')); return $valid; } /** * Validate user credentials against the configured LDAP backend */ private static function checkLDAP($config, $user, $pass) { self::$logger = Logger::get('httpauth', intval($config['loglevel'])); list($u, $d) = explode('@', $user); $replaces = array('%dc' => 'dc=' . str_replace('.', ',dc=', $d), '%u' => $u); $config['base_dn'] = strtr($config['base_dn'], $replaces); $config['filter'] = strtr($config['filter'], $replaces); $host = parse_url($config['host']); $ldap_config = array( 'hosts' => array($config['host']), 'port' => $host['port'] ?: 389, 'use_tls' => $host['scheme'] == 'tls', 'root_dn' => $config['base_dn'], 'filter' => $config['filter'], 'service_bind_dn' => $config['bind_dn'], 'service_bind_pw' => $config['bind_pw'], 'log_hook' => 'Kolab\FreeBusy\HTTPAuth::ldapLog', ); // instantiate Net_LDAP3 and connect with logger $ldap = new Net_LDAP3($ldap_config); // connect + bind to LDAP server if ($ldap->connect()) { self::$logger->addDebug("LDAP: connected to $config[host] with '$config[bind_dn]'"); // extract domain part from base_dn $dn_domain = ldap_explode_dn($config['base_dn'], 1); unset($dn_domain['count']); $domain = join('.', $dn_domain); $valid = (bool)$ldap->login($user, $pass, $domain); } else { self::$logger->addWarning("LDAP: connectiion to $config[host] with '$config[bind_dn]' failed!"); } self::$logger->addInfo("LDAP: authenticating user '$user': " . ($valid ? 'SUCCESS' : 'FAILURE')); return $valid; } /** * Validate user token and credentials from freebusy_auth cache */ private static function checkToken($config, $user, $token) { // See 'ready' hook handler in kolab_auth plugin // for details on how the token auth (cache) entries are created // load the Roundcube framework with its autoloader require_once KOLAB_FREEBUSY_ROOT . '/lib/Roundcube/bootstrap.php'; $rcube = \rcube::get_instance(\rcube::INIT_WITH_DB | \rcube::INIT_WITH_PLUGINS); $ip = \rcube_utils::remote_addr(); $key = md5("$token:$ip:$user"); $valid = false; $rcube->config->set('freebusy_auth_cache', 'db'); $cache = $rcube->get_cache_shared('freebusy_auth', false); if ($cache && ($deadline = $cache->get($key))) { $now = new \DateTime('now', new \DateTimeZone('UTC')); $deadline = new \DateTime($deadline); if ($deadline >= $now) { $valid = true; } } + $_SERVER['FREEBUSY_USER'] = $user; $status = $valid ? 'SUCCESS' : 'FAILURE'; Logger::get('httpauth')->addInfo("Token: authenticating user $user/$token/$ip: $status"); return $valid; } /** * Callback for Net_LDAP3 logging */ public static function ldapLog($level, $msg) { // map PHP log levels to Monolog levels static $loglevels = array( LOG_DEBUG => Monolog::DEBUG, LOG_NOTICE => Monolog::NOTICE, LOG_INFO => Monolog::INFO, LOG_WARNING => Monolog::WARNING, LOG_ERR => Monolog::ERROR, LOG_CRIT => Monolog::CRITICAL, LOG_ALERT => Monolog::ALERT, LOG_EMERG => Monolog::EMERGENCY, ); $msg = is_array($msg) ? join('; ', $msg) : strval($msg); self::$logger->addRecord($loglevels[$level], $msg); } } \ No newline at end of file diff --git a/public_html/index.php b/public_html/index.php index eb74d5e..603b9a2 100644 --- a/public_html/index.php +++ b/public_html/index.php @@ -1,124 +1,128 @@ * * Copyright (C) 2014, Kolab Systems AG * * 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 . */ define('KOLAB_FREEBUSY_ROOT', realpath('../')); // configure env for Roundcube framework define('RCUBE_INSTALL_PATH', KOLAB_FREEBUSY_ROOT . '/'); define('RCUBE_CONFIG_DIR', KOLAB_FREEBUSY_ROOT . '/config/'); define('RCUBE_PLUGINS_DIR', KOLAB_FREEBUSY_ROOT . '/lib/plugins/'); // suppress error notices ini_set('error_reporting', E_ALL &~ E_NOTICE); // use composer's autoloader for both dependencies and local lib $loader = require_once(KOLAB_FREEBUSY_ROOT . '/vendor/autoload.php'); $loader->set('Kolab', array(KOLAB_FREEBUSY_ROOT . '/lib')); // register Kolab namespace $loader->setUseIncludePath(true); // enable searching the include_path (e.g. for PEAR packages) use Kolab\Config; use Kolab\FreeBusy\Utils; use Kolab\FreeBusy\Logger; use Kolab\FreeBusy\Directory; use Kolab\FreeBusy\HTTPAuth; // load config $config = Config::get_instance(KOLAB_FREEBUSY_ROOT . '/config'); if ($config->valid()) { // check for trusted IP first $remote_ip = Utils::remoteIP(); $trusted_ip = $config->trustednetworks ? Utils::checkIPRange($remote_ip, $config->get('trustednetworks.allow', array(), Config::ARR)) : false; $log = Logger::get('web'); $uri = $_SERVER['REDIRECT_URL']; // we're not always redirected here if (empty($uri)) { $uri = $_SERVER['REQUEST_URI']; $log->addDebug('Request (direct): ' . $uri, array('ip' => $remote_ip, 'trusted' => $trusted_ip)); } else { $log->addDebug('Request (redirect): ' . $uri, array('ip' => $remote_ip, 'trusted' => $trusted_ip)); } list($uri, $args) = explode('?', $uri); // check HTTP authentication if (!$trusted_ip && $config->httpauth) { $_SERVER['FREEBUSY_URI'] = urldecode(rtrim($uri, '/')); if (!HTTPAuth::check($config->httpauth)) { $log->addDebug("Abort with 401 Unauthorized"); header('WWW-Authenticate: Basic realm="Kolab Free/Busy Service"'); header($_SERVER['SERVER_PROTOCOL'] . " 401 Unauthorized", true); exit; } } #header('Content-type: text/calendar; charset=utf-8', true); header('Content-type: text/plain; charset=utf-8', true); // analyse request - $url = array_filter(explode('/', $uri)); - $user = strtolower(array_pop($url)); - $extended = false; - - // remove file extension - if (preg_match('/^(.+)\.([ipx]fb)$/i', $user, $m)) { - $user = urldecode($m[1]); - $extended = $m[2] == 'xfb'; + $user = $_SERVER['FREEBUSY_USER']; + $extended = !empty($_SERVER['FREEBUSY_EXTENDED']); + + if (!$user) { + $url = array_filter(explode('/', $uri)); + $user = strtolower(array_pop($url)); + + // remove file extension + if (preg_match('/^(.+)\.([ipx]fb)$/i', $user, $m)) { + $user = urldecode($m[1]); + $extended = $m[2] == 'xfb'; + } } // iterate over directories foreach ($config->directory as $key => $dirconfig) { $log->addDebug("Trying directory $key", $dirconfig); $directory = Directory::factory($dirconfig); if ($directory && ($fbdata = $directory->getFreeBusyData($user, $extended))) { $log->addInfo("Found valid data for user $user in directory $key"); echo $fbdata; exit; } } // return 404 if request was sent from a trusted IP if ($trusted_ip) { $log->addDebug("Returning '404 Not Found' for user $user"); header($_SERVER['SERVER_PROTOCOL'] . " 404 Not found", true); } else { $log->addInfo("Returning empty Free/Busy list for user $user"); // Return an apparent empty Free/Busy list. print Utils::dummyVFreebusy($user); } } // exit with error # header($_SERVER['SERVER_PROTOCOL'] . " 500 Internal Server Error", true);