82 if ( $icfg [ $this->remote_selector .
'._domainkey.' . $this->remote_server ] )
84 $this->dk = $icfg [ $this->remote_selector .
'._domainkey.' . $this->remote_server ];
88 $dkim = dns_get_record ( $this->remote_selector .
'._domainkey.' . $this->remote_server , DNS_TXT );
89 if ( count ( $dkim ) > 0 )
91 $this->dk = $dkim [ 0 ] [
'txt' ];
92 if ( $dkim [ 0 ] [
'entries' ] )
95 foreach ( $dkim [ 0 ] [
'entries' ] as $v )
97 $this->dk .= trim ( $v );
100 dbg_error_log(
'ischedule',
'getTxt '. $this->dk .
' XX');
104 dbg_error_log(
'ischedule',
'getTxt FAILED '. print_r ( $dkim ) );
105 $this->failed =
true;
146 $this->failed =
true;
147 if ( isset ( $this->parsed [
's' ] ) )
149 if ( ! preg_match (
'/(\*|calendar)/', $this->parsed [
's' ] ) ) {
150 dbg_error_log(
'ischedule',
'validateKey ERROR: bad selector' );
154 if ( isset ( $this->parsed [
'k' ] ) && $this->parsed [
'k' ] !=
'rsa' ) {
155 dbg_error_log(
'ischedule',
'validateKey ERROR: bad key algorythm, algo was:' . $this->parsed [
'k' ] );
158 if ( isset ( $this->parsed [
't' ] ) && ! preg_match (
'/^[y:s]+$/', $this->parsed [
't' ] ) ) {
159 dbg_error_log(
'ischedule',
'validateKey ERROR: type mismatch' );
164 if ( preg_match (
'/y/', $this->parsed [
't' ] ) )
165 $this->failOnError =
false;
166 if ( preg_match (
'/s/', $this->parsed [
't' ] ) )
167 $this->subdomainsOK =
false;
169 if ( isset ( $this->parsed [
'g' ] ) )
170 $this->remote_user_rule = $this->parsed [
'g' ];
172 $this->remote_user_rule =
'*';
173 if ( isset ( $this->parsed [
'p' ] ) )
175 if ( preg_match (
'/[^A-Za-z0-9_=+\/]/', $this->parsed [
'p' ] ) )
177 $data =
"-----BEGIN PUBLIC KEY-----\n" . implode (
"\n",str_split ( $this->parsed [
'p' ], 64 )) .
"\n-----END PUBLIC KEY-----";
178 if ( $data ===
false )
180 $this->remote_public_key = $data;
183 dbg_error_log(
'ischedule',
'validateKey ERROR: no key in dns record' . $this->parsed [
'p' ] );
186 $this->failed =
false;
196 if ( isset($icfg) && $icfg [ $this->domain ] )
198 $this->remote_server = $icfg [ $this->domain ] [
'server' ];
199 $this->remote_port = $icfg [ $this->domain ] [
'port' ];
200 $this->remote_ssl = $icfg [ $this->domain ] [
'ssl' ];
203 $this->remote_ssl =
false;
204 $parts = explode (
'.', $this->domain );
205 $tld = $parts [ count ( $parts ) - 1 ];
207 if ( strlen ( $tld ) == 2 && in_array ( $tld, Array (
'uk',
'nz' ) ) )
209 if ( $this->domain ==
'mycaldav' || $this->domain ==
'altcaldav' )
211 while ( count ( $parts ) >= $len )
213 $r = dns_get_record (
'_ischedules._tcp.' . implode (
'.', $parts ) , DNS_SRV );
218 $remote_server = $r [ 0 ] [
'target' ];
219 $remote_port = $r [ 0 ] [
'port' ];
220 $this->remote_ssl =
true;
223 if ( ! isset ( $remote_server ) )
225 $r = dns_get_record (
'_ischedule._tcp.' . implode (
'.', $parts ) , DNS_SRV );
229 if ( 0 < count ( $r ) )
231 $remote_server = $r [ 0 ] [
'target' ];
232 $remote_port = $r [ 0 ] [
'port' ];
236 array_shift ( $parts );
238 if ( ! isset ( $remote_server ) )
240 if ( $this->try_anyway ==
true )
242 if ( ! isset ( $remote_server ) )
243 $remote_server = $this->domain;
244 if ( ! isset ( $remote_port ) )
248 dbg_error_log(
'ischedule',
'Domain %s did not have srv records for iSchedule', $this->domain );
252 dbg_error_log(
'ischedule', $this->domain .
' found srv records for ' . $remote_server .
':' . $remote_port );
253 $this->remote_server = $remote_server;
254 $this->remote_port = $remote_port;
263 if ( $domain !=
null && $this->domain != $domain )
264 $this->domain = $domain;
265 if ( ! isset ( $this->remote_server ) && isset ( $this->domain ) && ! $this->
getServer ( ) )
267 $this->remote_url =
'http'. ( $this->remote_ssl ?
's' :
'' ) .
'://' .
268 $this->remote_server .
':' . $this->remote_port .
'/.well-known/ischedule';
269 $remote_capabilities = file_get_contents ( $this->remote_url .
'?query=capabilities' );
270 if ( $remote_capabilities ===
false )
272 $xml_parser = xml_parser_create_ns(
'UTF-8');
273 $this->xml_tags = array();
274 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
275 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
276 $rc = xml_parse_into_struct( $xml_parser, $remote_capabilities, $this->xml_tags );
277 if ( $rc ==
false ) {
278 dbg_error_log(
'ERROR',
'XML parsing error: %s at line %d, column %d',
279 xml_error_string(xml_get_error_code($xml_parser)),
280 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
281 dbg_error_log(
'ischedule', $this->domain .
' iSchedule error parsing remote xml' );
284 xml_parser_free($xml_parser);
285 $xmltree = BuildXMLTree( $this->xml_tags );
286 if ( !is_object($xmltree) ) {
287 dbg_error_log(
'ischedule', $this->domain .
' iSchedule error in remote xml' );
288 $request->DoResponse( 406, translate(
"REPORT body is not valid XML data!") );
291 dbg_error_log(
'ischedule', $this->domain .
' got capabilites' );
292 $this->capabilities_xml = $xmltree;
301 if ( ! isset ( $this->capabilities_xml ) )
303 dbg_error_log(
'ischedule', $this->domain .
' capabilities not set, quering for capability:' . $capability );
304 if ( $domain ==
null )
306 if ( $this->domain != $domain )
307 $this->domain = $domain;
311 switch ( $capability )
316 $comp = $this->capabilities_xml->GetPath (
'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
317 foreach ( $comp as $c )
319 if ( $c->GetAttribute (
'name' ) == $capability )
323 case 'VFREEBUSY/REQUEST':
325 case 'VTODO/REQUEST':
329 case 'VEVENT/REQUEST':
331 case 'VEVENT/CANCEL':
332 case 'VEVENT/PUBLISH':
333 case 'VEVENT/COUNTER':
334 case 'VEVENT/DECLINECOUNTER':
335 dbg_error_log(
'ischedule', $this->domain .
' xml query' );
336 $comp = $this->capabilities_xml->GetPath (
'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
337 list ( $component, $method ) = explode (
'/', $capability );
338 dbg_error_log(
'ischedule', $this->domain .
' quering for capability:' . count ( $comp ) .
' ' . $component );
339 foreach ( $comp as $c )
341 dbg_error_log(
'ischedule', $this->domain .
' quering for capability:' . $c->GetAttribute (
'name' ) .
' == ' . $component );
342 if ( $c->GetAttribute (
'name' ) == $component )
344 $methods = $c->GetElements (
'urn:ietf:params:xml:ns:ischedule:method' );
345 if ( count ( $methods ) == 0 )
347 foreach ( $methods as $m )
349 if ( $m->GetAttribute (
'name' ) == $method )
368 if ( $this->scheduling_dkim_domain ==
null )
371 if ( is_array ( $headers ) !==
true )
373 foreach ( $headers as $key => $value )
375 $b .= $key .
': ' . $value .
"\r\n";
378 $dk[
'a'] =
'rsa-' . $this->scheduling_dkim_algo;
379 $dk[
's'] = $this->selector;
380 $dk[
'd'] = $this->scheduling_dkim_domain;
381 $dk[
'c'] =
'simple-http';
382 if ( isset ( $_SERVER[
'SERVER_NAME'] ) && strstr ( $_SERVER[
'SERVER_NAME'], $this->domain ) !==
false )
383 $dk[
'i'] =
'@' . $_SERVER[
'SERVER_NAME'];
384 $dk[
'q'] =
'dns/txt';
385 $dk[
'l'] = strlen ( $body );
387 if ( isset ( $this->valid_time ) )
388 $dk[
'x'] = $this->valid_time;
389 $dk[
'h'] = implode (
':', array_keys ( $headers ) );
390 $dk[
'bh'] = base64_encode ( hash (
'sha256', $body ,
true ) );
392 foreach ( $dk as $key => $val )
393 $value .=
"$key=$val; ";
395 $tosign = $b .
'DKIM-Signature: ' . $value;
396 openssl_sign ( $tosign, $sig, $this->schedule_private_key, $this->scheduling_dkim_algo );
397 $this->tosign = $tosign;
398 $value .= base64_encode ( $sig );
411 if ( empty($this->scheduling_dkim_domain) )
413 if ( is_array ( $address ) )
414 list ( $user, $domain ) = explode (
'@', $address[0] );
416 list ( $user, $domain ) = explode (
'@', $address );
419 dbg_error_log(
'ischedule', $domain .
' did not have iSchedule capabilities for ' . $type );
422 dbg_error_log(
'ischedule', $domain .
' trying with iSchedule capabilities for ' . $type );
425 dbg_error_log(
'ischedule', $domain .
' trying with iSchedule capabilities for ' . $type .
' OK');
426 list ( $component, $method ) = explode (
'/', $type );
427 $headers = array ( );
428 $headers[
'iSchedule-Version'] =
'1.0';
429 $headers[
'Originator'] =
'mailto:' . $session->email;
430 if ( is_array ( $address ) )
431 $headers[
'Recipient'] = implode (
', ' , $address );
433 $headers[
'Recipient'] = $address;
434 $headers[
'Content-Type'] =
'text/calendar; component=' . $component ;
436 $headers[
'Content-Type'] .=
'; method=' . $method;
437 $headers[
'DKIM-Signature'] = $this->
signDKIM ( $headers, $data );
438 if ( $headers[
'DKIM-Signature'] ==
false )
440 $request_headers = array ( );
441 foreach ( $headers as $k => $v )
442 $request_headers[] = $k .
': ' . $v;
443 $curl = curl_init ( $this->remote_url );
444 curl_setopt ( $curl, CURLOPT_RETURNTRANSFER,
true );
445 curl_setopt ( $curl, CURLOPT_HTTPHEADER, array() );
446 curl_setopt ( $curl, CURLOPT_HTTPHEADER, $request_headers );
447 curl_setopt ( $curl, CURLOPT_SSL_VERIFYPEER,
false);
448 curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST,
false);
449 curl_setopt ( $curl, CURLOPT_POST, 1);
450 curl_setopt ( $curl, CURLOPT_POSTFIELDS, $data);
451 curl_setopt ( $curl, CURLOPT_CUSTOMREQUEST,
'POST' );
452 $xmlresponse = curl_exec ( $curl );
453 $info = curl_getinfo ( $curl );
454 curl_close ( $curl );
455 if ( $info[
'http_code'] >= 400 )
457 dbg_error_log (
'ischedule',
'remote server returned error (%s)', $info[
'http_code'] );
461 error_log (
'remote response '. $xmlresponse . print_r ( $info,
true ) );
462 $xml_parser = xml_parser_create_ns(
'UTF-8');
464 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
465 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
466 $rc = xml_parse_into_struct( $xml_parser, $xmlresponse, $xml_tags );
467 if ( $rc ==
false ) {
468 dbg_error_log(
'ERROR',
'XML parsing error: %s at line %d, column %d',
469 xml_error_string(xml_get_error_code($xml_parser)),
470 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
473 $xmltree = BuildXMLTree( $xml_tags );
474 xml_parser_free($xml_parser);
475 if ( !is_object($xmltree) ) {
476 dbg_error_log(
'ERROR',
'iSchedule RESPONSE body is not valid XML data!' );
479 $resp = $xmltree->GetPath (
'/*/urn:ietf:params:xml:ns:ischedule:response' );
481 foreach ( $resp as $r )
483 $recipient = $r->GetElements (
'urn:ietf:params:xml:ns:ischedule:recipient' );
484 $status = $r->GetElements (
'urn:ietf:params:xml:ns:ischedule:request-status' );
485 $calendardata = $r->GetElements (
'urn:ietf:params:xml:ns:ischedule:calendar-data' );
486 if ( count ( $recipient ) < 1 )
488 if ( count ( $calendardata ) > 0 )
490 $result [ $recipient[0]->GetContent() ] = $calendardata[0]->GetContent();
494 $result [ $recipient[0]->GetContent() ] = $status[0]->GetContent();
497 if ( count ( $result ) < 1 )
514 $this->failed =
true;
515 $tags = preg_split (
'/;[\s\t]/', $sig );
516 foreach ( $tags as $v )
518 list($key,$value) = preg_split (
'/=/', $v, 2 );
519 $dkim[$key] = $value;
525 if ( ! preg_match (
'{(simple|simple-http|relaxed)(/(simple|simple-http|relaxed))?}', $dkim[
'c'], $matches ) )
526 return 'bad canonicalization:' . $dkim[
'c'] ;
527 if ( count ( $matches ) > 2 )
528 $this->body_cannon = $matches[2];
530 $this->body_cannon = $matches[1];
531 $this->header_cannon = $matches[1];
533 if ( $dkim[
'a'] !=
'rsa-sha1' && $dkim[
'a'] !=
'rsa-sha256' )
534 return 'bad signing algorythm:' . $dkim[
'a'] ;
536 if ( $dkim[
'q'] !=
'dns/txt' )
537 return 'bad query method';
539 if ( ! isset ( $dkim[
'd'] ) )
540 return 'missing signing domain';
541 $this->remote_server = $dkim[
'd'];
543 if ( isset ( $dkim[
'i'] ) )
546 if ( ! stristr ( $dkim[
'i'], $dkim[
'd'] ) )
547 return 'signing domain mismatch';
549 if ( strstr ( $dkim [
'i' ],
'@' ) )
550 $this->remote_user = substr ( $dkim [
'i' ], 0, strpos ( $dkim [
'i' ],
'@' ) - 1 );
553 if ( ! isset ( $dkim[
's'] ) )
554 return 'missing selector';
555 $this->remote_selector = $dkim[
's'];
557 if ( ! isset ( $dkim[
'h'] ) )
558 return 'missing list of signed headers';
559 $this->signed_headers = preg_split (
'/:/', $dkim[
'h'] );
562 foreach ( $this->signed_headers as $h )
564 $sh[] = strtolower ( $h );
565 if ( in_array ( strtolower ( $h ), $this->disallowed_headers ) )
566 return "$h is NOT allowed in signed header fields per RFC4871 or iSchedule";
568 foreach ( $this->required_headers as $h )
569 if ( ! in_array ( strtolower ( $h ), $sh ) )
570 return "$h is REQUIRED but missing in signed header fields per iSchedule";
572 if ( ! isset ( $dkim[
'bh'] ) )
573 return 'missing body signature';
575 if ( ! isset ( $dkim[
'b'] ) )
576 return 'missing signature in b field';
578 if ( isset ( $dkim[
'l'] ) )
579 $this->signed_length = $dkim[
'l'];
580 $this->failed =
false;
581 $this->DKSig = $dkim;
607 $this->failed =
true;
609 foreach ( $this->signed_headers as $h )
610 if ( isset ( $_SERVER[
'HTTP_' . strtoupper ( strtr ( $h,
'-',
'_' ) ) ] ) )
611 $signed .=
"$h: " . $_SERVER[
'HTTP_' . strtoupper ( strtr ( $h,
'-',
'_' ) ) ] .
"\r\n";
613 $signed .=
"$h: " . $_SERVER[ strtoupper ( strtr ( $h,
'-',
'_' ) ) ] .
"\r\n";
614 if ( ! isset ( $_SERVER[
'HTTP_ORIGINATOR'] ) || stripos ( $signed,
'Originator' ) ===
false )
615 return "missing Originator";
616 if ( ! isset ( $_SERVER[
'HTTP_RECIPIENT'] ) || stripos ( $signed,
'Recipient' ) ===
false )
617 return "missing Recipient";
618 if ( ! isset ( $_SERVER[
'HTTP_ISCHEDULE_VERSION'] ) || $_SERVER[
'HTTP_ISCHEDULE_VERSION'] !=
'1' )
619 return "missing or mismatch ischedule-version header";
620 $body = $request->raw_post;
621 if ( ! isset ( $this->signed_length ) )
622 $this->signed_length = strlen ( $body );
624 $body = substr ( $body, 0, $this->signed_length );
625 if ( isset ( $this->remote_user_rule ) )
626 if ( $this->remote_user_rule !=
'*' && ! stristr ( $this->remote_user, $this->remote_user_rule ) )
627 return "remote user rule failure";
628 $hash_algo = preg_replace (
'/^.*(sha1|sha256).*/',
'$1', $this->DKSig[
'a'] );
629 $body_hash = base64_encode ( hash ( $hash_algo, $body ,
true ) );
630 if ( $this->DKSig[
'bh'] != $body_hash )
631 return "body hash mismatch";
632 $sig = $_SERVER[
'HTTP_DKIM_SIGNATURE'];
633 $sig = preg_replace (
'/ b=[^;\s\r\n\t]+/',
' b=', $sig );
634 $signed .=
'DKIM-Signature: ' . $sig;
635 $verify = openssl_verify ( $signed, base64_decode ( $this->DKSig[
'b'] ), $this->remote_public_key, $hash_algo );
638 openssl_sign ( $signed, $sigb, $this->schedule_private_key, $hash_algo );
639 $sigc = base64_encode ( $sigb );
640 $verify1 = openssl_verify ( $signed, $sigc, $this->remote_public_key, $hash_algo );
641 return "signature verification failed " . $this->remote_public_key .
" \n\n". $sig .
" \n" . $hash_algo .
"\n". print_r ($verify,1) .
" XX " . $verify1 .
"\n";
643 $this->failed =
false;
653 if ( isset ( $_SERVER[
'HTTP_DKIM_SIGNATURE'] ) )
654 $sig = $_SERVER[
'HTTP_DKIM_SIGNATURE'];
657 $request->DoResponse( 403, translate(
'DKIM signature missing') );
660 if ( isset ( $_SERVER[
'HTTP_ORGANIZER'] ) )
661 $request->DoResponse( 403, translate(
'Organizer Missing') );
663 dbg_error_log (
'ischedule',
'beginning validation');
665 if ( $err !==
true || $this->failed )
666 $request->DoResponse( 412,
'DKIM signature invalid ' .
"\n" . $err .
"\n" );
667 if ( ! $this->
getTxt () || $this->failed )
668 $request->DoResponse( 400, translate(
'DKIM signature validation failed(DNS ERROR)') );
669 if ( ! $this->
parseTxt () || $this->failed )
670 $request->DoResponse( 400, translate(
'DKIM signature validation failed(KEY Parse ERROR)') );
672 $request->DoResponse( 400, translate(
'DKIM signature validation failed(KEY Validation ERROR)') );
674 if ( $err !==
true || $this->failed )
675 $request->DoResponse( 412, translate(
'DKIM signature validation failed(Signature verification ERROR)') .
'\n' . $err );
676 dbg_error_log (
'ischedule',
'signature ok');