diff --git a/lib/Autodiscover.php b/lib/Autodiscover.php
index 33cda73..53cc909 100644
--- a/lib/Autodiscover.php
+++ b/lib/Autodiscover.php
@@ -1,342 +1,392 @@
 <?php
 
 /**
  +--------------------------------------------------------------------------+
  | Kolab Autodiscover Service                                               |
  |                                                                          |
  | Copyright (C) 2011-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 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 General Public License for more details.                             |
  |                                                                          |
  | You should have received a copy of the GNU General Public License        |
  | along with this program. If not, see http://www.gnu.org/licenses/.       |
  +--------------------------------------------------------------------------+
  | Author: Aleksander Machniak <machniak@kolabsys.com>                      |
  +--------------------------------------------------------------------------+
 */
 
 /**
  * Main application class
  */
 class Autodiscover
 {
     const CHARSET = 'UTF-8';
 
     protected $conf;
     protected $config = array();
 
     /**
      * Autodiscover main execution path
      */
     public static function run()
     {
         $uris = array($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']);
         $type = '';
 
         // Detect request type
         foreach ($uris as $uri) {
             // Outlook/Activesync
             if (stripos($uri, 'autodiscover.xml') !== false) {
                 $type = 'Microsoft';
                 break;
             }
+            // Microsoft Autodiscover V2
+            elseif (stripos($uri, 'autodiscover.json') !== false) {
+                $type = 'Json';
+                break;
+            }
             // Mozilla Thunderbird (Kmail/Kontact/Evolution)
             else if (strpos($uri, 'config-v1.1.xml') !== false) {
                 $type = 'Mozilla';
                 break;
             }
         }
 
         if (!$type) {
             header("HTTP/1.0 404 Not Found");
             exit;
         }
 
         $class = "Autodiscover$type";
 
         require_once __DIR__ . '/' . $class . '.php';
 
         $engine = new $class;
         $engine->handle();
 
         // fallback to 404
         header("HTTP/1.0 404 Not Found");
         exit;
     }
 
     /**
      * Initialization of class instance
      */
     public function __construct()
     {
         require_once __DIR__ . '/Conf.php';
         require_once __DIR__ . '/Log.php';
 
         $this->conf = Conf::get_instance();
     }
 
     /**
      * Handle request
      */
     public function handle()
     {
         // read request parameters
         $this->handle_request();
 
         // validate requested email address
         if (empty($this->email)) {
             $this->error("Email address not provided");
         }
 
         if (!strpos($this->email, '@')) {
             $this->error("Invalid email address");
         }
 
         // find/set services parameters
         $this->configure();
 
         // send response
         $this->handle_response();
     }
 
     /**
      * Send error to the client and exit
      */
     protected function error($msg)
     {
         header("HTTP/1.0 500 $msg");
         exit;
     }
 
+    /**
+     * Send 401 Unauthorized to the client end exit
+     */
+    protected function unauthorized($basicauth = true)
+    {
+        if ($basicauth) {
+            header('WWW-Authenticate: Basic realm="'.$_SERVER['HTTP_HOST'].'"');
+        }
+        header('HTTP/1.0 401 Unauthorized');
+        exit;
+    }
+
     /**
      * Get services configuration
      */
     protected function configure()
     {
         $pos = strrpos($this->email, '@');
 
         $this->config = array(
             'email'            => $this->email,
             'domain'           => strtolower(substr($this->email, $pos + 1)),
             'displayName'      => $this->conf->get('autodiscover', 'service_name'),
             'displayShortName' => $this->conf->get('autodiscover', 'service_short'),
         );
 
         // get user form LDAP, set domain/login/user in $this->config
         $user = $this->get_user($this->email, $this->config['domain']);
 
         $proto_map = array('tls' => 'STARTTLS', 'ssl' => 'SSL');
 
         foreach (array('imap', 'pop3', 'smtp') as $type) {
             if ($value = $this->conf->get('autodiscover', $type)) {
                 $params = explode(';', $value);
 
                 $pass_secure = in_array($params[1], array('CRAM-MD5', 'DIGEST-MD5'));
 
                 $host = $params[0];
                 $host = str_replace('%d', $this->config['domain'], $host);
                 $url  = parse_url($host);
 
                 $this->config[$type] = array(
                     'hostname'        => $url['host'],
                     'port'            => $url['port'],
                     'socketType'      => $proto_map[$url['scheme']] ?: 'plain',
                     'username'        => $this->config['login'] ?: $this->config['email'],
                     'authentication'  => 'password-' . ($pass_secure ? 'encrypted' : 'cleartext'),
                 );
             }
         }
 
         if ($host = $this->conf->get('autodiscover', 'activesync')) {
             $host = str_replace('%d', $this->config['domain'], $host);
             $this->config['activesync'] = $host;
         }
 
         // Log::debug(print_r($this->config, true));
     }
 
     /**
      * Get user record from LDAP
      */
     protected function get_user($email, $domain)
     {
         // initialize LDAP connection
         $result = $this->init_ldap();
 
         if (!$result) {
             $this->config = array_merge(
                 $this->config,
                 Array('mail' => $email, 'domain' => $domain)
             );
 
             return;
         }
 
         // find domain
         if (!$this->ldap->find_domain($domain)) {
             $this->error("Unknown domain");
         }
 
         // find user
         $user = $this->find_user($email, $domain);
 
         // update config
         $this->config = array_merge($this->config, (array)$user, array('domain' => $domain));
     }
 
     /**
      * Initialize LDAP connection
      */
     protected function init_ldap()
     {
         $ldap_uri = $this->conf->get('ldap_uri', false);
 
         if (!$ldap_uri) {
             return false;
         }
 
         $uri = parse_url($ldap_uri);
 
         $this->_ldap_server  = ($uri['scheme'] === 'ldaps' ? 'ldaps://' : '') . $uri['host'];
         $this->_ldap_port    = $uri['port'];
         $this->_ldap_scheme  = $uri['scheme'];
         $this->_ldap_bind_dn = $this->conf->get('ldap', 'service_bind_dn');
         $this->_ldap_bind_pw = $this->conf->get('ldap', 'service_bind_pw');
 
         // Catch cases in which the ldap server port has not been explicitely defined
         if (!$this->_ldap_port) {
             $this->_ldap_port = $this->_ldap_scheme == 'ldaps' ? 636 : 389;
         }
 
         require_once 'Net/LDAP3.php';
 
         $this->ldap = new Net_LDAP3(array(
             'debug'           => in_array(strtolower($this->conf->get('autodiscover', 'debug_mode')), array('trace', 'debug')),
             'log_hook'        => array($this, 'ldap_log'),
             'vlv'             => $this->conf->get('ldap', 'vlv', Conf::AUTO),
             'config_root_dn'  => "cn=config",
             'hosts'           => array($this->_ldap_server),
             'port'            => $this->_ldap_port,
             'use_tls'         => $this->_ldap_scheme == 'tls',
             'domain_base_dn'  => $this->conf->get('ldap', 'domain_base_dn'),
             'domain_filter'   => $this->conf->get('ldap', 'domain_filter'),
             'domain_name_attribute' => $this->conf->get('ldap', 'domain_name_attribute'),
         ));
 
         $this->_ldap_domain = $this->conf->get('primary_domain');
 
         // connect to LDAP
         if (!$this->ldap->connect()) {
             $this->error("Storage connection failed");
             return false;
         }
 
         // bind as the service user
         if (!$this->ldap->bind($this->_ldap_bind_dn, $this->_ldap_bind_pw)) {
             $this->error("Storage connection failed");
             return false;
         }
 
         return true;
     }
 
     /**
      * Find user in LDAP
      */
     private function find_user($email, $domain)
     {
         $filter = $this->conf->get('login_filter');
 
         if (empty($filter)) {
             $filter = $this->conf->get('filter');
         }
 
         if (empty($filter)) {
             $filter = "(&(|(mail=%s)(mail=%U@%d)(alias=%s)(alias=%U@%d)(uid=%s))(objectclass=inetorgperson))";
         }
 
         $_parts           = explode('@', $email);
         $localpart        = $_parts[0];
         $replace_patterns = array(
             '/%s/' => $email,
             '/%d/' => $domain,
             '/%U/' => $localpart,
             '/%r/' => $domain,
         );
         $attributes = array(
             'login'    => $this->conf->get('autodiscover', 'login_attribute') ?: 'mail',
             'username' => $this->conf->get('autodiscover', 'name_attribute') ?: 'cn',
         );
 
         $filter  = preg_replace(array_keys($replace_patterns), array_values($replace_patterns), $filter);
         $base_dn = $this->ldap->domain_root_dn($domain);
 
         $result = $this->ldap->search($base_dn, $filter, 'sub', array_values($attributes));
 
         if (!$result) {
             Log::debug("Could not search $base_dn with $filter");
             return;
         }
 
         if ($result->count() > 1) {
             Log::debug("Multiple entries found.");
             return;
         }
         else if ($result->count() < 1) {
             Log::debug("No entries found.");
             return;
         }
 
         // parse result
         $entries = $result->entries(true);
         $dn      = key($entries);
         $entry   = $entries[$dn];
-        $result  = array();
+        $result  = array('dn' => $dn);
 
         foreach ($attributes as $idx => $attr) {
             $result[$idx] = is_array($entry[$attr]) ? current($entry[$attr]) : $entry[$attr];
         }
 
         return $result;
     }
 
+    /**
+     * authenticate a user by his given dn and password
+     */
+    protected function authenticate($dn, $password)
+    {
+        if (empty($this->_ldap_server)) {
+            return false;
+        }
+
+        $ldap = new Net_LDAP3(array(
+            'debug'           => in_array(strtolower($this->conf->get('autodiscover', 'debug_mode')), array('trace', 'debug')),
+            'log_hook'        => array($this, 'ldap_log'),
+            'hosts'           => array($this->_ldap_server),
+            'port'            => $this->_ldap_port,
+            'use_tls'         => $this->_ldap_scheme == 'tls'
+        ));
+
+        // connect to LDAP
+        if (!$ldap->connect()) {
+            $this->error("Storage connection failed");
+            return false;
+        }
+
+        // bind as given userdn
+        if (!$ldap->bind($dn, $password)) {
+            $this->unauthorized();
+            return false;
+        }
+
+        $ldap->close();
+        return true;
+    }
+
     /**
      * LDAP logging handler
      */
     public function ldap_log($level, $msg)
     {
         if (is_array($msg)) {
             $msg = implode("\n", $msg);
         }
 
         switch ($level) {
             case LOG_DEBUG:
                 Log::debug($str . $msg);
                 break;
             case LOG_ERR:
                 Log::error($str . $msg);
                 break;
             case LOG_INFO:
                 Log::info($str . $msg);
                 break;
             case LOG_WARNING:
                 Log::warning($str . $msg);
                 break;
             case LOG_ALERT:
             case LOG_CRIT:
             case LOG_EMERG:
             case LOG_NOTICE:
             default:
                 Log::trace($str . $msg);
                 break;
         }
     }
 }
diff --git a/lib/AutodiscoverJson.php b/lib/AutodiscoverJson.php
new file mode 100644
index 0000000..75a7065
--- /dev/null
+++ b/lib/AutodiscoverJson.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ +--------------------------------------------------------------------------+
+ | Kolab Autodiscover Service                                               |
+ |                                                                          |
+ | Copyright (C) 2011-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 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 General Public License for more details.                             |
+ |                                                                          |
+ | You should have received a copy of the GNU General Public License        |
+ | along with this program. If not, see http://www.gnu.org/licenses/.       |
+ +--------------------------------------------------------------------------+
+ | Author: Daniel Hoffend <dh@dotlan.net>                                   |
+ +--------------------------------------------------------------------------+
+*/
+
+/**
+ * Autodiscover Service class for Microsoft Autodiscover V2
+ */
+class AutodiscoverJson extends Autodiscover
+{
+
+    public function handle_request()
+    {
+        if (preg_match('|autodiscover.json/v1.0/([^\?]+)|', $_SERVER['REQUEST_URI'], $regs)) {
+            $this->email = $regs[1];
+        }
+
+        Log::debug('Request [json]: ' . $_SERVER['REQUEST_URI']);
+    }
+
+    /**
+     * Generates JSON response
+     */
+    protected function handle_response()
+    {
+        if (strtolower($_GET['Protocol']) == 'activesync'
+            && !empty($this->config['activesync'])
+        ) {
+            if (!preg_match('/^https?:/i', $this->config['activesync'])) {
+                $this->config['activesync'] = 'https://' . $this->config['activesync'] . '/Microsoft-Server-ActiveSync';
+            }
+            $json = array(
+                'Protocol' => 'ActiveSync',
+                'Url' => $this->config['activesync']
+            );
+        }
+        elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
+            $json = array(
+                'Protocol' => 'ActiveSync',
+                'Url' => 'https://' . $_SERVER['HTTP_HOST'] . '/Autodiscover/Autodiscover.xml'
+            );
+        }
+        else {
+            http_response_code(400);
+            $json = array(
+                'ErrorCore' => 'InvalidProtocol',
+                'ErrorMessage' => 'The given protocol value \u0027'
+                    . $_GET['Protocol']
+                    . '\u0027 is invalid. Supported values are \u0027'
+                    . (!empty($this->config['activesync']) ? 'ActiveSync,' : '')
+                    . 'AutodiscoverV1\u0027'
+            );
+        }
+
+        $response = json_encode($json, JSON_PRETTY_PRINT);
+        Log::debug('Response [json]: ' . $response);
+
+        header('Content-Type: application/json; charset=' . Autodiscover::CHARSET);
+        echo $response;
+        exit;
+    }
+}
diff --git a/lib/AutodiscoverMicrosoft.php b/lib/AutodiscoverMicrosoft.php
index 4a3e2db..9459098 100644
--- a/lib/AutodiscoverMicrosoft.php
+++ b/lib/AutodiscoverMicrosoft.php
@@ -1,246 +1,263 @@
 <?php
 
 /**
  +--------------------------------------------------------------------------+
  | Kolab Autodiscover Service                                               |
  |                                                                          |
  | Copyright (C) 2011-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 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 General Public License for more details.                             |
  |                                                                          |
  | You should have received a copy of the GNU General Public License        |
  | along with this program. If not, see http://www.gnu.org/licenses/.       |
  +--------------------------------------------------------------------------+
  | Author: Aleksander Machniak <machniak@kolabsys.com>                      |
  +--------------------------------------------------------------------------+
 */
 
 /**
  * Autodiscover Service class for Microsoft Outlook and Activesync devices
  */
 class AutodiscoverMicrosoft extends Autodiscover
 {
     const NS            = "http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006";
     const RESPONSE_NS   = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
     const MOBILESYNC_NS = "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006";
 
     private $type = 'outlook';
+    private $password;
 
     /**
      * Handle request parameters (find email address)
      */
     protected function handle_request()
     {
         $post = $_SERVER['REQUEST_METHOD'] == 'POST' ? file_get_contents('php://input') : null;
 
-        Log::debug('Request [microsoft]: ' . $post);
+        // check for basic authentication
+        Log::debug('Request [microsoft]: Basic Auth Username: ' . ($_SERVER['PHP_AUTH_USER'] ?: 'none'));
+        if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) {
+            $this->unauthorized();
+        }
+        $this->password = $_SERVER['PHP_AUTH_PW'];
 
+        // check for request object
+        Log::debug('Request [microsoft]: ' . $post);
         if (empty($post)) {
             $this->error("Invalid input");
         }
 
         // parse XML
         try {
             $xml = new SimpleXMLElement($post);
             $ns  = $xml->getDocNamespaces();
 
             if (empty($ns) || empty($ns[''])) {
                 $this->error("Invalid input. Missing XML request schema");
             }
 
             $xml->registerXPathNamespace('request', $ns['']);
 
             if ($email = $xml->xpath('//request:EMailAddress')) {
                 $this->email = (string) array_shift($email);
             }
 
             if ($schema = $xml->xpath('//request:AcceptableResponseSchema')) {
                 $schema = (string) array_shift($schema);
 
                 if (strpos($schema, 'mobilesync')) {
                     $this->type = 'mobilesync';
                 }
             }
         }
         catch (Exception $e) {
             $this->error("Invalid input");
         }
+
+        // basic auth username must match with given email address
+        if ($_SERVER['PHP_AUTH_USER'] != $this->email) {
+            $this->unauthorized();
+        }
     }
 
     /**
      * Handle response
      */
     public function handle_response()
     {
-        $method = $this->type . '_response';
+        // authenticate the user found during configure() against ldap
+        if (empty($this->config['dn']) || !$this->authenticate($this->config['dn'], $this->password)) {
+            $this->unauthorized();
+        }
 
+        $method = $this->type . '_response';
         $xml = $this->$method();
         $xml->formatOutput = true;
 
         $response = $xml->saveXML();
 
         Log::debug('Response [microsoft]: ' . $response);
 
         header('Content-type: text/xml; charset=' . Autodiscover::CHARSET);
         echo $response;
         exit;
     }
 
     /**
      * Generates XML response for Activesync
      */
     protected function mobilesync_response()
     {
         if (empty($this->config['activesync'])) {
             $this->error("Activesync not supported");
         }
 
         if (!preg_match('/^https?:/i', $this->config['activesync'])) {
             $this->config['activesync'] = 'https://' . $this->config['activesync'] . '/Microsoft-Server-ActiveSync';
         }
 
         $xml = new DOMDocument('1.0', Autodiscover::CHARSET);
 
         // create main elements (tree)
         $doc = $xml->createElementNS(self::NS, 'Autodiscover');
         $doc = $xml->appendChild($doc);
 
         $response = $xml->createElementNS(self::MOBILESYNC_NS, 'Response');
         $response = $doc->appendChild($response);
 
         $user = $xml->createElement('User');
         $user = $response->appendChild($user);
 
         $action = $xml->createElement('Action');
         $action = $response->appendChild($action);
 
         $settings = $xml->createElement('Settings');
         $settings = $action->appendChild($settings);
 
         $server = $xml->createElement('Server');
         $server = $settings->appendChild($server);
 
         // configuration
 
         $dispname = $xml->createElement('DisplayName');
         $dispname = $user->appendChild($dispname);
         $dispname->appendChild($xml->createTextNode($this->config['username']));
 
         $email = $xml->createElement('EMailAddress');
         $email = $user->appendChild($email);
         $email->appendChild($xml->createTextNode($this->config['login'] ?: $this->config['email']));
 
         $element = $xml->createElement('Type');
         $element = $server->appendChild($element);
         $element->appendChild($xml->createTextNode('MobileSync'));
 
         $element = $xml->createElement('Url');
         $element = $server->appendChild($element);
         $element->appendChild($xml->createTextNode($this->config['activesync']));
 
         $element = $xml->createElement('Name');
         $element = $server->appendChild($element);
         $element->appendChild($xml->createTextNode($this->config['activesync']));
 
         return $xml;
     }
 
     /**
      * Generates XML response for Outlook
      */
     protected function outlook_response()
     {
         $xml = new DOMDocument('1.0', Autodiscover::CHARSET);
 
         // create main elements (tree)
         $doc = $xml->createElementNS(self::NS, 'Autodiscover');
         $doc = $xml->appendChild($doc);
 
         $response = $xml->createElementNS(self::RESPONSE_NS, 'Response');
         $response = $doc->appendChild($response);
 
         $user = $xml->createElement('User');
         $user = $response->appendChild($user);
 
         $account = $xml->createElement('Account');
         $account = $response->appendChild($account);
 
         $accountType = $xml->createElement('AccountType');
         $accountType = $account->appendChild($accountType);
         $accountType->appendChild($xml->createTextNode('email'));
 
         $action = $xml->createElement('Action');
         $action = $account->appendChild($action);
         $action->appendChild($xml->createTextNode('settings'));
 
         // configuration
 
         $dispname = $xml->createElement('DisplayName');
         $dispname = $user->appendChild($dispname);
         $dispname->appendChild($xml->createTextNode($this->config['username']));
 
         $email = $xml->createElement('AutoDiscoverSMTPAddress');
         $email = $user->appendChild($email);
         $email->appendChild($xml->createTextNode($this->config['email']));
 
         // @TODO: Microsoft supports also DAV protocol here
         foreach (array('imap', 'pop3', 'smtp') as $type) {
             if (!empty($this->config[$type])) {
                 $protocol = $this->add_protocol_element($xml, $type, $this->config[$type]);
                 $account->appendChild($protocol);
             }
         }
 
         return $xml;
     }
 
     /**
      * Creates Protocol element for XML response
      */
     private function add_protocol_element($xml, $type, $config)
     {
         $protocol = $xml->createElement('Protocol');
 
         $element = $xml->createElement('Type');
         $element = $protocol->appendChild($element);
         $element->appendChild($xml->createTextNode(strtoupper($type)));
 
         // @TODO: TTL/ExpirationDate tags
 
         // server attributes map
         $server_attributes = array(
             'Server'    => 'hostname',
             'Port'      => 'port',
             'LoginName' => 'username',
         );
 
         foreach ($server_attributes as $tag_name => $conf_name) {
             $value = $this->config[$type][$conf_name];
             if (!empty($value)) {
                 $element = $xml->createElement($tag_name);
                 $element->appendChild($xml->createTextNode($value));
                 $protocol->appendChild($element);
             }
         }
 
         $spa     = $this->config[$type]['authentication'] == 'password-encrypted' ? 'on' : 'off';
         $element = $xml->createElement('SPA');
         $element->appendChild($xml->createTextNode($spa));
         $protocol->appendChild($element);
 
         $map     = array('STARTTLS' => 'TLS', 'SSL' => 'SSL', 'plain' => 'None');
         $element = $xml->createElement('Encryption');
         $element->appendChild($xml->createTextNode($map[$this->config[$type]['socketType']] ?: 'Auto'));
         $protocol->appendChild($element);
 
         return $protocol;
     }
 }