156 $this->failed =
true;
157 if ( isset ( $this->parsed [
's' ] ) )
159 if ( ! preg_match (
'/(\*|calendar)/', $this->parsed [
's' ] ) ) {
160 dbg_error_log(
'ischedule',
'validateKey ERROR: bad selector' );
164 if ( isset ( $this->parsed [
'k' ] ) && $this->parsed [
'k' ] !=
'rsa' ) {
165 dbg_error_log(
'ischedule',
'validateKey ERROR: bad key algorythm, algo was:' . $this->parsed [
'k' ] );
169 if (isset($this->parsed[
't'])) {
170 if ( ! preg_match (
'/^[y:s]+$/', $this->parsed [
't' ] ) ) {
171 dbg_error_log(
'ischedule',
'validateKey ERROR: type mismatch' );
174 if ( preg_match (
'/y/', $this->parsed [
't' ] ) )
175 $this->failOnError =
false;
176 if ( preg_match (
'/s/', $this->parsed [
't' ] ) )
177 $this->subdomainsOK =
false;
180 if ( isset ( $this->parsed [
'g' ] ) )
181 $this->remote_user_rule = $this->parsed [
'g' ];
183 $this->remote_user_rule =
'*';
184 if ( isset ( $this->parsed [
'p' ] ) )
186 if ( preg_match (
'/[^A-Za-z0-9_=+\/]/', $this->parsed [
'p' ] ) )
188 $data =
"-----BEGIN PUBLIC KEY-----\n" . implode (
"\n",str_split ( $this->parsed [
'p' ], 64 )) .
"\n-----END PUBLIC KEY-----";
189 if ( $data ===
false )
191 $this->remote_public_key = $data;
194 dbg_error_log(
'ischedule',
'validateKey ERROR: no key in dns record' );
197 $this->failed =
false;
207 if ( isset($icfg) && isset($icfg[$this->domain]) )
209 $this->remote_server = $icfg [ $this->domain ] [
'server' ];
210 $this->remote_port = $icfg [ $this->domain ] [
'port' ];
211 $this->remote_ssl = $icfg [ $this->domain ] [
'ssl' ];
214 $this->remote_ssl =
false;
215 $parts = explode (
'.', $this->domain );
216 $tld = $parts [ count ( $parts ) - 1 ];
218 if ( strlen ( $tld ) == 2 && in_array ( $tld, Array (
'uk',
'nz' ) ) )
220 if ( $this->domain ==
'mycaldav' || $this->domain ==
'altcaldav' )
223 while ( count ( $parts ) >= $len )
225 $r = dns_get_record (
'_ischedules._tcp.' . implode (
'.', $parts ) , DNS_SRV );
227 if (is_array($r) && count($r) > 0) {
228 $remote_server = $r [ 0 ] [
'target' ];
229 $remote_port = $r [ 0 ] [
'port' ];
230 $this->remote_ssl =
true;
234 if ( ! isset ( $remote_server ) ) {
235 $r = dns_get_record (
'_ischedule._tcp.' . implode (
'.', $parts ) , DNS_SRV );
237 if (is_array($r) && count($r) > 0) {
238 $remote_server = $r [ 0 ] [
'target' ];
239 $remote_port = $r [ 0 ] [
'port' ];
243 array_shift ( $parts );
246 if ( ! isset ( $remote_server ) )
248 if ( $this->try_anyway ==
true )
250 if ( ! isset ( $remote_server ) )
251 $remote_server = $this->domain;
252 if ( ! isset ( $remote_port ) )
256 dbg_error_log(
'ischedule',
'Domain %s did not have srv records for iSchedule', $this->domain );
260 dbg_error_log(
'ischedule', $this->domain .
' found srv records for ' . $remote_server .
':' . $remote_port );
261 $this->remote_server = $remote_server;
262 $this->remote_port = $remote_port;
271 if ( $domain !=
null && $this->domain != $domain )
272 $this->domain = $domain;
273 if ( ! isset ( $this->remote_server ) && isset ( $this->domain ) && ! $this->
getServer ( ) )
275 $this->remote_url =
'http'. ( $this->remote_ssl ?
's' :
'' ) .
'://' .
276 $this->remote_server .
':' . $this->remote_port .
'/.well-known/ischedule';
277 $remote_capabilities = file_get_contents ( $this->remote_url .
'?query=capabilities' );
278 if ( $remote_capabilities ===
false )
280 $xml_parser = xml_parser_create_ns(
'UTF-8');
281 $this->xml_tags = array();
282 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
283 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
284 $rc = xml_parse_into_struct( $xml_parser, $remote_capabilities, $this->xml_tags );
285 if ( $rc ==
false ) {
286 dbg_error_log(
'ERROR',
'XML parsing error: %s at line %d, column %d',
287 xml_error_string(xml_get_error_code($xml_parser)),
288 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
289 dbg_error_log(
'ischedule', $this->domain .
' iSchedule error parsing remote xml' );
292 xml_parser_free($xml_parser);
293 $xmltree = BuildXMLTree( $this->xml_tags );
294 if ( !is_object($xmltree) ) {
295 dbg_error_log(
'ischedule', $this->domain .
' iSchedule error in remote xml' );
296 $request->DoResponse( 406, translate(
"REPORT body is not valid XML data!") );
299 dbg_error_log(
'ischedule', $this->domain .
' got capabilites' );
300 $this->capabilities_xml = $xmltree;
309 if ( ! isset ( $this->capabilities_xml ) )
311 dbg_error_log(
'ischedule', $this->domain .
' capabilities not set, quering for capability:' . $capability );
312 if ( $domain ==
null )
314 if ( $this->domain != $domain )
315 $this->domain = $domain;
319 switch ( $capability )
324 $comp = $this->capabilities_xml->GetPath (
'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
325 foreach ( $comp as $c )
327 if ( $c->GetAttribute (
'name' ) == $capability )
331 case 'VFREEBUSY/REQUEST':
333 case 'VTODO/REQUEST':
337 case 'VEVENT/REQUEST':
339 case 'VEVENT/CANCEL':
340 case 'VEVENT/PUBLISH':
341 case 'VEVENT/COUNTER':
342 case 'VEVENT/DECLINECOUNTER':
343 dbg_error_log(
'ischedule', $this->domain .
' xml query' );
344 $comp = $this->capabilities_xml->GetPath (
'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
345 list ( $component, $method ) = explode (
'/', $capability );
346 dbg_error_log(
'ischedule', $this->domain .
' quering for capability:' . count ( $comp ) .
' ' . $component );
347 foreach ( $comp as $c )
349 dbg_error_log(
'ischedule', $this->domain .
' quering for capability:' . $c->GetAttribute (
'name' ) .
' == ' . $component );
350 if ( $c->GetAttribute (
'name' ) == $component )
352 $methods = $c->GetElements (
'urn:ietf:params:xml:ns:ischedule:method' );
353 if ( count ( $methods ) == 0 )
355 foreach ( $methods as $m )
357 if ( $m->GetAttribute (
'name' ) == $method )
376 if ( $this->scheduling_dkim_domain ==
null )
379 if ( is_array ( $headers ) !==
true )
381 foreach ( $headers as $key => $value )
383 $b .= $key .
': ' . $value .
"\r\n";
386 $dk[
'a'] =
'rsa-' . $this->scheduling_dkim_algo;
387 $dk[
's'] = $this->selector;
388 $dk[
'd'] = $this->scheduling_dkim_domain;
389 $dk[
'c'] =
'simple-http';
390 if ( isset ( $_SERVER[
'SERVER_NAME'] ) && strstr ( $_SERVER[
'SERVER_NAME'], $this->domain ) !==
false )
391 $dk[
'i'] =
'@' . $_SERVER[
'SERVER_NAME'];
392 $dk[
'q'] =
'dns/txt';
393 $dk[
'l'] = strlen ( $body );
395 if ( isset ( $this->valid_time ) )
396 $dk[
'x'] = $this->valid_time;
397 $dk[
'h'] = implode (
':', array_keys ( $headers ) );
398 $dk[
'bh'] = base64_encode ( hash (
'sha256', $body ,
true ) );
400 foreach ( $dk as $key => $val )
401 $value .=
"$key=$val; ";
403 $tosign = $b .
'DKIM-Signature: ' . $value;
404 openssl_sign ( $tosign, $sig, $this->schedule_private_key, $this->scheduling_dkim_algo );
405 $this->tosign = $tosign;
406 $value .= base64_encode ( $sig );
419 if ( empty($this->scheduling_dkim_domain) )
421 if ( is_array ( $address ) )
422 list ( $user, $domain ) = explode (
'@', $address[0] );
424 list ( $user, $domain ) = explode (
'@', $address );
427 dbg_error_log(
'ischedule', $domain .
' did not have iSchedule capabilities for ' . $type );
430 dbg_error_log(
'ischedule', $domain .
' trying with iSchedule capabilities for ' . $type );
433 dbg_error_log(
'ischedule', $domain .
' trying with iSchedule capabilities for ' . $type .
' OK');
434 list ( $component, $method ) = explode (
'/', $type );
435 $headers = array ( );
436 $headers[
'iSchedule-Version'] =
'1.0';
437 $headers[
'Originator'] =
'mailto:' . $session->email;
438 if ( is_array ( $address ) )
439 $headers[
'Recipient'] = implode (
', ' , $address );
441 $headers[
'Recipient'] = $address;
442 $headers[
'Content-Type'] =
'text/calendar; component=' . $component ;
444 $headers[
'Content-Type'] .=
'; method=' . $method;
445 $headers[
'DKIM-Signature'] = $this->
signDKIM ( $headers, $data );
446 if ( $headers[
'DKIM-Signature'] ==
false )
448 $request_headers = array ( );
449 foreach ( $headers as $k => $v )
450 $request_headers[] = $k .
': ' . $v;
451 $curl = curl_init ( $this->remote_url );
452 curl_setopt ( $curl, CURLOPT_RETURNTRANSFER,
true );
453 curl_setopt ( $curl, CURLOPT_HTTPHEADER, array() );
454 curl_setopt ( $curl, CURLOPT_HTTPHEADER, $request_headers );
455 curl_setopt ( $curl, CURLOPT_SSL_VERIFYPEER,
false);
456 curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST,
false);
457 curl_setopt ( $curl, CURLOPT_POST, 1);
458 curl_setopt ( $curl, CURLOPT_POSTFIELDS, $data);
459 curl_setopt ( $curl, CURLOPT_CUSTOMREQUEST,
'POST' );
460 $xmlresponse = curl_exec ( $curl );
461 $info = curl_getinfo ( $curl );
462 curl_close ( $curl );
463 if ( $info[
'http_code'] >= 400 )
465 dbg_error_log (
'ischedule',
'remote server returned error (%s)', $info[
'http_code'] );
469 error_log (
'remote response '. $xmlresponse . print_r ( $info,
true ) );
470 $xml_parser = xml_parser_create_ns(
'UTF-8');
472 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
473 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
474 $rc = xml_parse_into_struct( $xml_parser, $xmlresponse, $xml_tags );
475 if ( $rc ==
false ) {
476 dbg_error_log(
'ERROR',
'XML parsing error: %s at line %d, column %d',
477 xml_error_string(xml_get_error_code($xml_parser)),
478 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
481 $xmltree = BuildXMLTree( $xml_tags );
482 xml_parser_free($xml_parser);
483 if ( !is_object($xmltree) ) {
484 dbg_error_log(
'ERROR',
'iSchedule RESPONSE body is not valid XML data!' );
487 $resp = $xmltree->GetPath (
'/*/urn:ietf:params:xml:ns:ischedule:response' );
489 foreach ( $resp as $r )
491 $recipient = $r->GetElements (
'urn:ietf:params:xml:ns:ischedule:recipient' );
492 $status = $r->GetElements (
'urn:ietf:params:xml:ns:ischedule:request-status' );
493 $calendardata = $r->GetElements (
'urn:ietf:params:xml:ns:ischedule:calendar-data' );
494 if ( count ( $recipient ) < 1 )
496 if ( count ( $calendardata ) > 0 )
498 $result [ $recipient[0]->GetContent() ] = $calendardata[0]->GetContent();
502 $result [ $recipient[0]->GetContent() ] = $status[0]->GetContent();
505 if ( count ( $result ) < 1 )
522 $this->failed =
true;
523 $tags = preg_split (
'/;[\s\t]/', $sig );
525 foreach ( $tags as $v )
527 list($key,$value) = preg_split (
'/=/', $v, 2 );
528 $dkim[$key] = $value;
535 if ( ! preg_match (
'{(simple|simple-http|relaxed)(/(simple|simple-http|relaxed))?}', $dkim[
'c'], $matches ) )
536 return 'bad canonicalization:' . $dkim[
'c'] ;
538 if ( count ( $matches ) > 2 )
539 $this->body_cannon = $matches[2];
541 $this->body_cannon = $matches[1];
543 $this->header_cannon = $matches[1];
546 if ( $dkim[
'a'] !=
'rsa-sha1' && $dkim[
'a'] !=
'rsa-sha256' )
547 return 'bad signing algorithm:' . $dkim[
'a'] ;
550 if ( $dkim[
'q'] !=
'dns/txt' )
551 return 'bad query method';
554 if ( ! isset ( $dkim[
'd'] ) )
555 return 'missing signing domain';
557 $this->remote_server = $dkim[
'd'];
559 if ( isset ( $dkim[
'i'] ) )
562 if ( ! stristr ( $dkim[
'i'], $dkim[
'd'] ) )
563 return 'signing domain mismatch';
565 if ( strstr ( $dkim [
'i' ],
'@' ) )
566 $this->remote_user = substr ( $dkim [
'i' ], 0, strpos ( $dkim [
'i' ],
'@' ) - 1 );
570 if ( ! isset ( $dkim[
's'] ) )
571 return 'missing selector';
572 $this->remote_selector = $dkim[
's'];
575 if ( ! isset ( $dkim[
'h'] ) )
576 return 'missing list of signed headers';
577 $this->signed_headers = preg_split (
'/:/', $dkim[
'h'] );
580 foreach ( $this->signed_headers as $h )
582 $sh[] = strtolower ( $h );
583 if ( in_array ( strtolower ( $h ), $this->disallowed_headers ) )
584 return "$h is NOT allowed in signed header fields per RFC4871 or iSchedule";
587 foreach ( $this->required_headers as $h )
588 if ( ! in_array ( strtolower ( $h ), $sh ) )
589 return "$h is REQUIRED but missing in signed header fields per iSchedule";
592 if ( ! isset ( $dkim[
'bh'] ) )
593 return 'missing body signature';
596 if ( ! isset ( $dkim[
'b'] ) )
597 return 'missing signature in b field';
600 if ( isset ( $dkim[
'l'] ) )
601 $this->signed_length = $dkim[
'l'];
603 $this->failed =
false;
604 $this->DKSig = $dkim;
630 $this->failed =
true;
632 foreach ( $this->signed_headers as $h )
633 if ( isset ( $_SERVER[
'HTTP_' . strtoupper ( strtr ( $h,
'-',
'_' ) ) ] ) )
634 $signed .=
"$h: " . $_SERVER[
'HTTP_' . strtoupper ( strtr ( $h,
'-',
'_' ) ) ] .
"\r\n";
636 $signed .=
"$h: " . $_SERVER[ strtoupper ( strtr ( $h,
'-',
'_' ) ) ] .
"\r\n";
637 if ( ! isset ( $_SERVER[
'HTTP_ORIGINATOR'] ) || stripos ( $signed,
'Originator' ) ===
false )
638 return "missing Originator";
639 if ( ! isset ( $_SERVER[
'HTTP_RECIPIENT'] ) || stripos ( $signed,
'Recipient' ) ===
false )
640 return "missing Recipient";
641 if ( ! isset ( $_SERVER[
'HTTP_ISCHEDULE_VERSION'] ) || $_SERVER[
'HTTP_ISCHEDULE_VERSION'] !=
'1' )
642 return "missing or mismatch ischedule-version header";
643 $body = $request->raw_post;
644 if ( ! isset ( $this->signed_length ) )
645 $this->signed_length = strlen ( $body );
647 $body = substr ( $body, 0, $this->signed_length );
648 if ( isset ( $this->remote_user_rule ) )
649 if ( $this->remote_user_rule !=
'*' && ! stristr ( $this->remote_user, $this->remote_user_rule ) )
650 return "remote user rule failure";
651 $hash_algo = preg_replace (
'/^.*(sha1|sha256).*/',
'$1', $this->DKSig[
'a'] );
652 $body_hash = base64_encode ( hash ( $hash_algo, $body ,
true ) );
653 if ( $this->DKSig[
'bh'] != $body_hash )
654 return "body hash mismatch";
655 $sig = $_SERVER[
'HTTP_DKIM_SIGNATURE'];
656 $sig = preg_replace (
'/ b=[^;\s\r\n\t]+/',
' b=', $sig );
657 $signed .=
'DKIM-Signature: ' . $sig;
658 $verify = openssl_verify ( $signed, base64_decode ( $this->DKSig[
'b'] ), $this->remote_public_key, $hash_algo );
661 openssl_sign ( $signed, $sigb, $this->schedule_private_key, $hash_algo );
662 $sigc = base64_encode ( $sigb );
663 $verify1 = openssl_verify ( $signed, $sigc, $this->remote_public_key, $hash_algo );
664 return "signature verification failed " . $this->remote_public_key .
" \n\n". $sig .
" \n" . $hash_algo .
"\n". print_r ($verify,1) .
" XX " . $verify1 .
"\n";
666 $this->failed =
false;
676 if ( isset ( $_SERVER[
'HTTP_DKIM_SIGNATURE'] ) )
677 $sig = $_SERVER[
'HTTP_DKIM_SIGNATURE'];
680 $request->DoResponse( 403, translate(
'DKIM signature missing') );
683 if ( isset ( $_SERVER[
'HTTP_ORGANIZER'] ) )
684 $request->DoResponse( 403, translate(
'Organizer Missing') );
686 dbg_error_log (
'ischedule',
'beginning validation');
688 if ( $err !==
true || $this->failed )
689 $request->DoResponse( 412,
'DKIM signature invalid ' .
"\n" . $err .
"\n" );
690 if ( ! $this->
getTxt () || $this->failed )
691 $request->DoResponse( 400, translate(
'DKIM signature validation failed(DNS ERROR)') );
692 if ( ! $this->
parseTxt () || $this->failed )
693 $request->DoResponse( 400, translate(
'DKIM signature validation failed(KEY Parse ERROR)') );
695 $request->DoResponse( 400, translate(
'DKIM signature validation failed(KEY Validation ERROR)') );
697 if ( $err !==
true || $this->failed )
698 $request->DoResponse( 412, translate(
'DKIM signature validation failed(Signature verification ERROR)') .
'\n' . $err );
699 dbg_error_log (
'ischedule',
'signature ok');