forked from extern/egroupware
modifications for new iSchedule draft:
- ischedule-relaxed header cannonisation - error xml response - modified capabilities with serial and iSchedule-Capabilities header in every response - using urls with mailto: schema for Originator and Recipient headers
This commit is contained in:
parent
2fadcab928
commit
d97da6d309
@ -66,7 +66,7 @@ class ischedule_client
|
||||
public function __construct($recipients, $url=null)
|
||||
{
|
||||
$this->recipients = (array)$recipients;
|
||||
$this->originator = $GLOBALS['egw_info']['user']['account_email'];
|
||||
$this->originator = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
|
||||
|
||||
if (is_null($url))
|
||||
{
|
||||
@ -122,7 +122,7 @@ class ischedule_client
|
||||
{
|
||||
throw new Exception("Invalid orginator '$originator'!");
|
||||
}
|
||||
$this->originator = $originator;
|
||||
$this->originator = 'mailto:'.$originator;
|
||||
|
||||
if (!is_null($dkim_private_key))
|
||||
{
|
||||
@ -221,15 +221,17 @@ class ischedule_client
|
||||
//'follow_location' => 1, // default 1=follow, but only for GET, not POST!
|
||||
//'timeout' => $timeout, // max timeout in seconds (float)
|
||||
'content' => $content,
|
||||
'ignore_errors' => true, // return response, even for http-status != 2xx
|
||||
)
|
||||
);
|
||||
|
||||
if ($debug) echo "POST $this->url HTTP/1.1\n$header_string\n$content\n";
|
||||
|
||||
// need to suppress warning, if http-status not 2xx
|
||||
if (($response = @file_get_contents($this->url, false, stream_context_create($opts))) === false)
|
||||
$response = @file_get_contents($this->url, false, stream_context_create($opts));
|
||||
list(, $code, $message) = explode(' ', $http_response_header[0], 3);
|
||||
if ($code[0] !== '2')
|
||||
{
|
||||
list(, $code, $message) = explode(' ', $http_response_header[0], 3);
|
||||
if ($max_redirect && $code[0] === '3')
|
||||
{
|
||||
foreach($http_response_header as $header)
|
||||
@ -248,6 +250,10 @@ class ischedule_client
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($debug) echo implode("\r\n", $http_response_header)."\r\n\r\n".$response;
|
||||
|
||||
if (preg_match('|<response-description>(.*)</response-description>|', $response, $matches)) $message .= ': '.$matches[1];
|
||||
|
||||
throw new Exception($message, $code);
|
||||
}
|
||||
return $response;
|
||||
@ -278,12 +284,10 @@ class ischedule_client
|
||||
$header_names[] = $header;
|
||||
}
|
||||
}
|
||||
error_log(__METHOD__."(".array2string($headers).", \$body, '$selector', '$sign_headers') header_names=".array2string($header_names).', header_values='.array2string($header_values));
|
||||
include_once EGW_API_INC.'/php-mail-domain-signer/lib/class.mailDomainSigner.php';
|
||||
list(,$domain) = explode('@', $this->originator);
|
||||
$mds = new mailDomainSigner($this->dkim_private_key, $domain, $selector);
|
||||
// generate DKIM signature according to iSchedule spec
|
||||
$dkim = $mds->getDKIM(implode(':', $header_names), $header_values, $body, 'relaxed/simple', 'rsa-sha256',
|
||||
$dkim = $mds->getDKIM(implode(':', $header_names), $header_values, $body, 'ischedule-relaxed/simple', 'rsa-sha256',
|
||||
"DKIM-Signature: ".
|
||||
"v=1; ". // DKIM Version
|
||||
"a=\$a; ". // The algorithm used to generate the signature "rsa-sha1"
|
||||
|
@ -63,7 +63,6 @@ class ischedule_server extends groupdav
|
||||
// get raw request body
|
||||
$this->request = file_get_contents('php://input');
|
||||
|
||||
|
||||
switch($_SERVER['REQUEST_METHOD'])
|
||||
{
|
||||
case 'GET':
|
||||
@ -124,7 +123,19 @@ class ischedule_server extends groupdav
|
||||
if (($missing = array_diff(explode(':', strtolower(self::REQUIRED_DKIM_HEADERS.':DKIM-Signature')), array_keys($headers))))
|
||||
{
|
||||
//error_log('headers='.array2string(array_keys($headers)).', required='.self::REQUIRED_DKIM_HEADERS.', missing='.array($missing));
|
||||
throw new Exception ('Bad Request: missing required headers: '.implode(', ', $missing), 400);
|
||||
if (in_array('originator', $missing))
|
||||
{
|
||||
$error = 'originator-missing';
|
||||
}
|
||||
elseif(in_array('recipient', $missing))
|
||||
{
|
||||
$error = 'recipient-missing';
|
||||
}
|
||||
else
|
||||
{
|
||||
$error = 'invalid-scheduling-message';
|
||||
}
|
||||
throw new Exception ("Bad Request: $error: missing required headers: ".implode(', ', $missing), 400);
|
||||
}
|
||||
|
||||
// validate dkim signature
|
||||
@ -133,13 +144,17 @@ class ischedule_server extends groupdav
|
||||
// It will fail if multiple recipients in a single header are also ", " separated (just comma works fine)
|
||||
if (!self::dkim_validate($headers, $this->request, $error))
|
||||
{
|
||||
throw new Exception('Bad Request: DKIM signature invalid: '.$error, 400);
|
||||
throw new Exception('Bad Request: verification-failed: DKIM signature invalid: '.$error, 400);
|
||||
}
|
||||
// check if recipient is a user
|
||||
// ToDo: multiple recipients
|
||||
if (!($account_id = $GLOBALS['egw']->accounts->name2id($headers['recipient'], 'account_email')))
|
||||
// todo: multiple recipients, currently we use last recipient for EGroupware enviroment
|
||||
foreach(preg_split('/, */', $headers['recipient']) as $recipient)
|
||||
{
|
||||
throw new Exception('Bad Request: unknown recipient', 400);
|
||||
if (!stripos($recipient, 'mailto:') === 0 ||
|
||||
!($account_id = $GLOBALS['egw']->accounts->name2id(substr($recipient, 7), 'account_email')))
|
||||
{
|
||||
throw new Exception("Bad Request: recipient-missing: unknown recipient '$recipient'", 400);
|
||||
}
|
||||
}
|
||||
// create enviroment for recipient user, as we act on his behalf
|
||||
$GLOBALS['egw']->session->account_id = $account_id;
|
||||
@ -162,7 +177,7 @@ class ischedule_server extends groupdav
|
||||
if (!preg_match('/component=([^;]+)/i', $headers['content-type'], $matches) ||
|
||||
(!in_array($component=strtoupper($matches[1]), self::$supported_components)))
|
||||
{
|
||||
throw new Exception ('Bad Request: missing or unsupported component in Content-Type header', 400);
|
||||
throw new Exception ('Bad Request: invalid-calendar-data-type: missing or unsupported component in Content-Type header', 400);
|
||||
}
|
||||
if (!preg_match('/method=([^;]+)/i', $headers['content-type'], $matches) ||
|
||||
(!isset(self::$supported_method2origin_requirement[$method=strtoupper($matches[1])])) ||
|
||||
@ -196,12 +211,15 @@ class ischedule_server extends groupdav
|
||||
$matches = false;
|
||||
foreach($originator_requirement as $requirement)
|
||||
{
|
||||
$originator = $headers['originator'];
|
||||
if (stripos($originator, 'mailto:') === 0) $originator = substr($originator, 7);
|
||||
|
||||
if ($requirement == 'ORGANIZER' &&
|
||||
($event['organizer'] == $headers['originator'] || strpos($event['organizer'], '<'.$headers['originator'].'>') !== false) ||
|
||||
($event['organizer'] == $originator || strpos($event['organizer'], '<'.$originator.'>') !== false) ||
|
||||
$requirement == 'ATTENDEE' &&
|
||||
(in_array('e'.$headers['originator'], $event['participants']) ||
|
||||
(in_array('e'.$originator, $event['participants']) ||
|
||||
// ToDO: Participant could have CN, need to check that too
|
||||
$originator_account_id = $GLOBALS['egw']->accounts->name2id($headers['originator'], 'account_email') &&
|
||||
$originator_account_id = $GLOBALS['egw']->accounts->name2id($originator, 'account_email') &&
|
||||
in_array($originator_account_id, $event['participants'])))
|
||||
{
|
||||
$matches = true;
|
||||
@ -210,7 +228,7 @@ class ischedule_server extends groupdav
|
||||
}
|
||||
if (!$matches)
|
||||
{
|
||||
throw new Exception('Bad Request: originator invalid for given '.$component.'!', 400);
|
||||
throw new Exception("Bad Request: originator-invalid: originator '$originator' invalid for given $component component!", 400);
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,6 +257,7 @@ class ischedule_server extends groupdav
|
||||
|
||||
header('Content-Type: text/xml; charset=UTF-8');
|
||||
header('iSchedule-Version: '.self::VERSION);
|
||||
header('iSchedule-Capabilities: '.self::SERIAL);
|
||||
|
||||
echo $xml->outputMemory();
|
||||
}
|
||||
@ -337,11 +356,10 @@ class ischedule_server extends groupdav
|
||||
* @param array $headers header-name in lowercase(!) as key
|
||||
* @param string $body
|
||||
* @param string &$error=null error if false returned
|
||||
* @param boolean $split_recipients=null true=split recpients in multiple headers, false dont, default null try both
|
||||
* @return boolean true if signature could be validated, false otherwise
|
||||
* @todo other dkim q= methods: http/well-known bzw private-exchange
|
||||
*/
|
||||
public static function dkim_validate(array $headers, $body, &$error=null, $split_recipients=null)
|
||||
public static function dkim_validate(array $headers, $body, &$error=null)
|
||||
{
|
||||
// parse dkim signature
|
||||
if (!isset($headers['dkim-signature']) ||
|
||||
@ -377,14 +395,6 @@ class ischedule_server extends groupdav
|
||||
$value = $check[$header];
|
||||
unset($check[$header]);
|
||||
|
||||
// special handling of multivalued recipient header
|
||||
if ($header == 'recipient' && (!isset($split_recipients) || $split_recipients))
|
||||
{
|
||||
if (!is_array($value)) $value = explode(', ', $value);
|
||||
$v = array_pop($value); // dkim uses reverse order!
|
||||
if ($value) $check[$header] = $value;
|
||||
$value = $v;
|
||||
}
|
||||
$dkim_headers[] = $header.': '.$value;
|
||||
}
|
||||
// dkim signature is obvious without content of signature, but must not necessarly be last tag
|
||||
@ -392,7 +402,6 @@ class ischedule_server extends groupdav
|
||||
|
||||
// c defaults to 'simple/simple', check on valid canonicalization methods is performed further down
|
||||
list($header_canon, $body_canon) = explode('/', isset($dkim['c']) ? $dkim['c'] : 'simple/simple');
|
||||
require_once EGW_API_INC.'/php-mail-domain-signer/lib/class.mailDomainSigner.php';
|
||||
|
||||
// Canonicalization for Body
|
||||
switch($body_canon)
|
||||
@ -436,6 +445,10 @@ class ischedule_server extends groupdav
|
||||
$_unsigned = mailDomainSigner::headRelaxCanon(implode("\r\n", $dkim_headers). "\r\n".$dkim_unsigned);
|
||||
break;
|
||||
|
||||
case 'ischedule-relaxed':
|
||||
$_unsigned = mailDomainSigner::headIScheduleRelaxCanon(implode("\r\n", $dkim_headers). "\r\n".$dkim_unsigned);
|
||||
break;
|
||||
|
||||
case 'simple':
|
||||
$_unsigned = mailDomainSigner::headSimpleCanon(implode("\r\n", $dkim_headers). "\r\n".$dkim_unsigned);
|
||||
break;
|
||||
@ -477,7 +490,7 @@ class ischedule_server extends groupdav
|
||||
return false;
|
||||
}
|
||||
$ok = openssl_verify($_unsigned, base64_decode($dkim['b']), $public_key, $hash_algo);
|
||||
error_log(__METHOD__."() openssl_verify('$_unsigned', ..., '$public_key', '$hash_algo') returned ".array2string($ok));
|
||||
if ($ok != 1) error_log(__METHOD__."() openssl_verify('$_unsigned', ..., '$public_key', '$hash_algo') returned ".array2string($ok));
|
||||
|
||||
switch($ok)
|
||||
{
|
||||
@ -487,11 +500,6 @@ class ischedule_server extends groupdav
|
||||
|
||||
case 0:
|
||||
$error = 'DKIM signature does NOT verify';
|
||||
// if dkim did not validate, try not splitting Recipient header
|
||||
if (!isset($split_recipients))
|
||||
{
|
||||
return self::dkim_validate($headers, $body, $error, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -526,6 +534,14 @@ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiawhLuTSVhnl1zz5pXs1A748y
|
||||
N3aNE181dni8nsYqIQB1h4H32J4dZurEiAnP9nflQRjCmmg1NTvFcNz11Bem4zo1
|
||||
K4r4mcfbjlheorK2Mwoh445HR3fo/pP7uV6CcXTNboBJLTxs6ZHswmQjxyuKBKmx
|
||||
yXUKsIQVi3qPyPdB3QIDAQAB
|
||||
-----END PUBLIC KEY-----',
|
||||
),
|
||||
'outdoor-training.de' => array(
|
||||
'calendar' => '-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiawhLuTSVhnl1zz5pXs1A748y
|
||||
N3aNE181dni8nsYqIQB1h4H32J4dZurEiAnP9nflQRjCmmg1NTvFcNz11Bem4zo1
|
||||
K4r4mcfbjlheorK2Mwoh445HR3fo/pP7uV6CcXTNboBJLTxs6ZHswmQjxyuKBKmx
|
||||
yXUKsIQVi3qPyPdB3QIDAQAB
|
||||
-----END PUBLIC KEY-----',
|
||||
),
|
||||
);
|
||||
@ -745,8 +761,29 @@ yXUKsIQVi3qPyPdB3QIDAQAB
|
||||
// exception handler sending message back to the client as http status
|
||||
$code = $e->getCode();
|
||||
$msg = $e->getMessage();
|
||||
list($http_status, $error, $description) = explode(': ', $msg, 3);
|
||||
// check if we have a valid iSchedule error element, if not we use invalid-scheduling-message
|
||||
if (!empty($error) && strpos($error, ' ') !== false)
|
||||
{
|
||||
$description = $error.($description ? ': '.$description : '');
|
||||
$error = 'invalid-scheduling-message';
|
||||
}
|
||||
if (!in_array($code, array(400, 403, 407, 503))) $code = 500;
|
||||
header('HTTP/1.1 '.$code.' '.$msg, true, $code);
|
||||
|
||||
header('HTTP/1.1 '.$code.' '.$http_status, true, $code);
|
||||
header('Content-Type: text/xml; charset=UTF-8');
|
||||
header('iSchedule-Version: '.self::VERSION);
|
||||
header('iSchedule-Capabilities: '.self::SERIAL);
|
||||
|
||||
if ($error)
|
||||
{
|
||||
echo '<?xml version="1.0" encoding="utf-8" ?>
|
||||
<error xmlns="urn:ietf:params:xml:ns:ischedule">
|
||||
<'.$error.' />
|
||||
<response-description>'.htmlspecialchars($description).'</response-description>
|
||||
</error>
|
||||
';
|
||||
}
|
||||
|
||||
// if our groupdav logging is active, log the request plus a trace, if enabled in server-config
|
||||
if (groupdav::$request_starttime && isset(self::$instance))
|
||||
|
@ -17,7 +17,7 @@ if (isset($_SERVER['HTTP_HOST'])) die("This is a commandline ONLY tool!\n");
|
||||
/**
|
||||
* iSchedule command line client, primary for testing and development purpose
|
||||
*
|
||||
* @link https://tools.ietf.org/html/draft-desruisseaux-ischedule-01 iSchedule draft from 2010
|
||||
* @link https://tools.ietf.org/html/draft-desruisseaux-ischedule-03 iSchedule draft from 2013-01-22
|
||||
*/
|
||||
function usage($err=null)
|
||||
{
|
||||
@ -40,6 +40,9 @@ $GLOBALS['egw_info'] = array(
|
||||
'currentapp' => 'login',
|
||||
)
|
||||
);
|
||||
// set a domain for mserver
|
||||
$_REQUEST['domain'] = 'ralfsmacbook.local';
|
||||
|
||||
// if you move this file somewhere else, you need to adapt the path to the header!
|
||||
$egw_dir = dirname(dirname(__FILE__));
|
||||
include($egw_dir.'/header.inc.php');
|
||||
@ -122,5 +125,5 @@ try {
|
||||
}
|
||||
}
|
||||
catch(Exception $e) {
|
||||
echo "\n".($e->getCode() ? $e->getCode().' ' : '').$e->getMessage()."\n\n";
|
||||
if (!$verbose) echo "\n".($e->getCode() ? $e->getCode().' ' : '').$e->getMessage()."\n\n";
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
/**
|
||||
* iSchedule server: serverside of iSchedule
|
||||
*
|
||||
* @link https://tools.ietf.org/html/draft-desruisseaux-ischedule-01 iSchedule draft from 2010
|
||||
* @link https://tools.ietf.org/html/draft-desruisseaux-ischedule-03 iSchedule draft from 2013-01-22
|
||||
*/
|
||||
|
||||
$GLOBALS['egw_info'] = array(
|
||||
|
Loading…
Reference in New Issue
Block a user