diff --git a/phpgwapi/inc/class.ischedule_client.inc.php b/phpgwapi/inc/class.ischedule_client.inc.php
index 6d2a2dd064..fd279c2312 100644
--- a/phpgwapi/inc/class.ischedule_client.inc.php
+++ b/phpgwapi/inc/class.ischedule_client.inc.php
@@ -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, $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"
diff --git a/phpgwapi/inc/class.ischedule_server.inc.php b/phpgwapi/inc/class.ischedule_server.inc.php
index c14f51cd37..131bcfaf9b 100644
--- a/phpgwapi/inc/class.ischedule_server.inc.php
+++ b/phpgwapi/inc/class.ischedule_server.inc.php
@@ -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 '
+
+ <'.$error.' />
+ '.htmlspecialchars($description).'
+
+';
+ }
// if our groupdav logging is active, log the request plus a trace, if enabled in server-config
if (groupdav::$request_starttime && isset(self::$instance))
diff --git a/phpgwapi/ischedule-cli.php b/phpgwapi/ischedule-cli.php
index 5de39a9802..01d2f89b56 100755
--- a/phpgwapi/ischedule-cli.php
+++ b/phpgwapi/ischedule-cli.php
@@ -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";
}
\ No newline at end of file
diff --git a/phpgwapi/ischedule.php b/phpgwapi/ischedule.php
index 27f7352e39..442320a91e 100644
--- a/phpgwapi/ischedule.php
+++ b/phpgwapi/ischedule.php
@@ -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(