Stefan Liebl 79f4015906 roundcube
2020-07-09 15:41:32 +02:00

398 lines
12 KiB
PHP

<?php
/*
RCM CardDAV Plugin
Copyright (C) 2011-2016 Benjamin Schieder <rcmcarddav@wegwerf.anderdonau.de>,
Michael Stilkerich <ms@mike2k.de>
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 2 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
if (file_exists(__DIR__ . '/vendor/autoload.php'))
require_once __DIR__ . '/vendor/autoload.php';
\Httpful\Bootstrap::init();
class carddav_common
{
const DEBUG = false; // set to true for basic debugging
const DEBUG_HTTP = false; // set to true for debugging raw http stream
const NSDAV = 'DAV:';
const NSCARDDAV = 'urn:ietf:params:xml:ns:carddav';
// admin settings from config.inc.php
private static $admin_settings;
// encryption scheme
public static $pwstore_scheme = 'base64';
private $module_prefix = '';
public function __construct($module_prefix = '')
{{{
$this->module_prefix = $module_prefix;
}}}
public static function concaturl($str, $cat)
{{{
preg_match(";(^https?://[^/]+)(.*);", $str, $match);
$hostpart = $match[1];
$urlpart = $match[2];
// is $cat already a full URL?
if(strpos($cat, '://') !== FALSE) {
return $cat;
}
// is $cat a simple filename?
// then attach it to the URL
if (substr($cat, 0, 1) != "/"){
$urlpart .= "/$cat";
// $cat is a full path, the append it to the
// hostpart only
} else {
$urlpart = $cat;
}
// remove // in the path
$urlpart = preg_replace(';//+;','/',$urlpart);
return $hostpart.$urlpart;
}}}
// log helpers
private function getCaller()
{{{
// determine calling function for debug output
if (version_compare(PHP_VERSION, "5.4", ">=")){
$caller=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,3);
} else {
$caller=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
$caller=$caller[2]['function'];
return $caller;
}}}
public function warn()
{{{
$caller=self::getCaller();
rcmail::write_log("carddav.warn", $this->module_prefix . "($caller) " . implode(' ', func_get_args()));
}}}
public function debug()
{{{
if(self::DEBUG) {
$caller=self::getCaller();
rcmail::write_log("carddav", $this->module_prefix . "($caller) " . implode(' ', func_get_args()));
}
}}}
public function debug_http()
{{{
if(self::DEBUG_HTTP) {
$caller=self::getCaller();
rcmail::write_log("carddav", $this->module_prefix . "($caller) " . implode(' ', func_get_args()));
}
}}}
// XML helpers
public function checkAndParseXML($reply) {
if(!is_array($reply))
return false;
if(!self::check_contenttype($reply['headers']['content-type'], ';(text|application)/xml;'))
return false;
$xml = new SimpleXMLElement($reply['body']);
$this->registerNamespaces($xml);
return $xml;
}
public function registerNamespaces($xml) {
// Use slightly complex prefixes to avoid conflicts
$xml->registerXPathNamespace('RCMCC', self::NSCARDDAV);
$xml->registerXPathNamespace('RCMCD', self::NSDAV);
}
// HTTP helpers
/**
* @param $url: url of the requested resource
*
* @param $http_opts: Options for the HTTP request, keys:
* - method: request method (GET, PROPFIND, etc.)
* - content: request body
* - header: array of HTTP request headers as simple strings
*
* @param $carddav: config array containing at least the keys
* - url: base url, used if $url is a relative url
* - username
* - password: password (encoded/encrypted form as stored in DB)
*/
public function cdfopen($url, $http_opts, $carddav)
{{{
$redirect_limit = 5;
$rcmail = rcmail::get_instance();
$username=$carddav['username'];
$password = self::decrypt_password($carddav['password']);
$baseurl=$carddav['url'];
// determine calling function for debug output
$caller=self::getCaller();
$local = $rcmail->user->get_username('local');
$domain = $rcmail->user->get_username('domain');
// Substitute Placeholders
$username = str_replace( '%u', $_SESSION['username'], $username);
$username = str_replace( '%V' ,str_replace('@','_', str_replace('.','_',$_SESSION['username'])), $username);
$username = str_replace( '%l', $local, $username);
$username = str_replace( '%d', $domain, $username);
if($password == '%p')
$password = $rcmail->decrypt($_SESSION['password']);
$baseurl = str_replace("%u", $username, $carddav['url']);
$url = str_replace("%u", $username, $url);
$baseurl = str_replace("%l", $local, $baseurl);
$url = str_replace("%l", $local, $url);
$baseurl = str_replace("%d", $domain, $baseurl);
$url = str_replace("%d", $domain, $url);
// if $url is relative, prepend the base url
$url = self::concaturl($baseurl, $url);
do {
$isRedirect = false;
if (self::DEBUG){ $this->debug("$caller requesting $url as user $username [RL $redirect_limit]"); }
$httpful = \Httpful\Request::init();
$scheme = strtolower($carddav['authentication_scheme']);
if ($scheme != "basic" && $scheme != "digest" && $scheme != "negotiate"){
/* figure out authentication */
$httpful->addHeader("User-Agent", "RCM CardDAV plugin/3.0.3");
$httpful->uri($url);
$httpful->method($http_opts['method']);
$error = $httpful->send();
$httpful = \Httpful\Request::init();
$scheme = "unknown";
// Using raw_headers since there might be multiple www-authenticate headers
if (preg_match("/^(.*\n)*WWW-Authenticate:\s+Negotiate\b/i", $error->raw_headers) && !empty($_SERVER['KRB5CCNAME'])){
$httpful->negotiateAuth($username, $password);
$scheme = "negotiate";
} else if (preg_match("/\bDigest\b/i", $error->headers["www-authenticate"])){
$httpful->digestAuth($username, $password);
$scheme = "digest";
} else if (preg_match("/\bBasic\b/i", $error->headers["www-authenticate"])){
$httpful->basicAuth($username, $password);
$scheme = "basic";
}
if ($scheme != "unknown")
carddav_backend::update_addressbook($carddav['abookid'], array("authentication_scheme"), array($scheme));
} else {
// if we have KRB5CCNAME, use negotiate even if current scheme is basic
if ((strtolower($scheme) == "negotiate" || strtolower($scheme) == "basic") && !empty($_SERVER['KRB5CCNAME'])) {
$httpful->negotiateAuth($username, $password);
} else if (strtolower($scheme) == "digest"){
$httpful->digestAuth($username, $password);
// if we don't have KRB5CCNAME, use basic even if current scheme is negotiate
} else if (strtolower($scheme) == "negotiate" || strtolower($scheme) == "basic"){
$httpful->basicAuth($username, $password);
}
}
$httpful->addHeader("User-Agent", "RCM CardDAV plugin/3.0.3");
$httpful->uri($url);
$httpful->method($http_opts['method']);
if (array_key_exists('content',$http_opts) && strlen($http_opts['content'])>0 && $http_opts['method'] != "GET"){
$httpful->body($http_opts['content']);
}
if(array_key_exists('header',$http_opts)) {
foreach ($http_opts['header'] as $header){
$h = explode(": ", $header);
if (strlen($h[0]) > 0 && strlen($h[1]) > 0){
// Only append headers with key AND value
$httpful->addHeader($h[0], $h[1]);
}
}
}
$reply = $httpful->send();
$scode = $reply->code;
if (self::DEBUG){ $this->debug("Code: $scode"); }
$isRedirect = ($scode>300 && $scode<304) || $scode==307;
if($isRedirect && strlen($reply->headers['location'])>0) {
$url = self::concaturl($baseurl, $reply->headers['location']);
} else {
$retVal["status"] = $scode;
$retVal["headers"] = $reply->headers;
$retVal["body"] = $reply->raw_body;
if (self::DEBUG_HTTP){ $this->debug_http("success: ".var_export($retVal, true)); }
return $retVal;
}
} while($redirect_limit-->0 && $isRedirect);
return $reply->code;
}}}
public function check_contenttype($ctheader, $expectedct)
{{{
if(!is_array($ctheader)) {
$ctheader = array($ctheader);
}
foreach($ctheader as $ct) {
if(preg_match($expectedct, $ct))
return true;
}
return false;
}}}
// password helpers
private function carddav_des_key()
{{{
$rcmail = rcmail::get_instance();
$imap_password = $rcmail->decrypt($_SESSION['password']);
while(strlen($imap_password)<24) {
$imap_password .= $imap_password;
}
return substr($imap_password, 0, 24);
}}}
public function encrypt_password($clear)
{{{
if(strcasecmp(self::$pwstore_scheme, 'plain')===0)
return $clear;
if(strcasecmp(self::$pwstore_scheme, 'encrypted')===0) {
// return {IGNORE} scheme if session password is empty (krb_authentication plugin)
if(empty($_SESSION['password'])) return '{IGNORE}';
// encrypted with IMAP password
$rcmail = rcmail::get_instance();
$imap_password = self::carddav_des_key();
$deskey_backup = $rcmail->config->set('carddav_des_key', $imap_password);
$crypted = $rcmail->encrypt($clear, 'carddav_des_key');
// there seems to be no way to unset a preference
$deskey_backup = $rcmail->config->set('carddav_des_key', '');
return '{ENCRYPTED}'.$crypted;
}
if(strcasecmp(self::$pwstore_scheme, 'des_key')===0) {
// encrypted with global des_key
$rcmail = rcmail::get_instance();
$crypted = $rcmail->encrypt($clear);
return '{DES_KEY}'.$crypted;
}
// default: base64-coded password
return '{BASE64}'.base64_encode($clear);
}}}
public function password_scheme($crypt)
{{{
if(strpos($crypt, '{IGNORE}') === 0)
return 'ignore';
if(strpos($crypt, '{ENCRYPTED}') === 0)
return 'encrypted';
if(strpos($crypt, '{DES_KEY}') === 0)
return 'des_key';
if(strpos($crypt, '{BASE64}') === 0)
return 'base64';
// unknown scheme, assume cleartext
return 'plain';
}}}
public function decrypt_password($crypt)
{{{
if(strpos($crypt, '{ENCRYPTED}') === 0) {
// return {IGNORE} scheme if session password is empty (krb_authentication plugin)
if (empty($_SESSION['password'])) return '{IGNORE}';
$crypt = substr($crypt, strlen('{ENCRYPTED}'));
$rcmail = rcmail::get_instance();
$imap_password = self::carddav_des_key();
$deskey_backup = $rcmail->config->set('carddav_des_key', $imap_password);
$clear = $rcmail->decrypt($crypt, 'carddav_des_key');
// there seems to be no way to unset a preference
$deskey_backup = $rcmail->config->set('carddav_des_key', '');
return $clear;
}
if(strpos($crypt, '{DES_KEY}') === 0) {
$crypt = substr($crypt, strlen('{DES_KEY}'));
$rcmail = rcmail::get_instance();
return $rcmail->decrypt($crypt);
}
if(strpos($crypt, '{BASE64}') === 0) {
$crypt = substr($crypt, strlen('{BASE64}'));
return base64_decode($crypt);
}
// unknown scheme, assume cleartext
return $crypt;
}}}
// admin settings from config.inc.php
public static function get_adminsettings()
{{{
if(is_array(self::$admin_settings))
return self::$admin_settings;
$rcmail = rcmail::get_instance();
$prefs = array();
$configfile = dirname(__FILE__)."/config.inc.php";
if (file_exists($configfile)){
require("$configfile");
}
self::$admin_settings = $prefs;
if(is_array($prefs['_GLOBAL'])) {
$scheme = $prefs['_GLOBAL']['pwstore_scheme'];
if(preg_match("/^(plain|base64|encrypted|des_key)$/", $scheme))
self::$pwstore_scheme = $scheme;
}
return $prefs;
}}}
// short form for deprecated Q helper function
public function Q($str, $mode='strict', $newlines=true)
{{{
return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
}}}
}
?>