DAViCal
Loading...
Searching...
No Matches
iSchedule.php
1<?php
16
17require_once("XMLDocument.php");
18
26{
27 public $parsed;
28 public $selector;
29 public $domain;
30 private $dk;
31 private $DKSig;
32 private $try_anyway = false;
33 private $failed = false;
34 private $failOnError = true;
35 private $subdomainsOK = true;
36 private $remote_public_key;
37 private $remote_ssl;
38 private $body_cannon;
39 private $header_cannon;
40 private $remote_server;
41 private $remote_selector;
42 private $signed_headers;
43 private $signed_length;
44 private $remote_user_rule;
45
46 private $required_headers = Array ( 'host', // draft 01 section 7.1 required headers
47 'originator',
48 'recipient',
49 'content-type' );
50 private $disallowed_headers = Array ( 'connection', // draft 01 section 7.1 disallowed headers
51 'keep-alive',
52 'dkim-signature',
53 'proxy-authenticate',
54 'proxy-authorization',
55 'te',
56 'trailers',
57 'transfer-encoding',
58 'upgrade' );
59
60 function __construct ( )
61 {
62 global $c;
63 $this->selector = 'cal';
64 if ( is_object ( $c ) && isset ( $c->scheduling_dkim_selector ) )
65 {
66 $this->scheduling_dkim_domain = $c->scheduling_dkim_domain ;
67 $this->scheduling_dkim_selector = $c->scheduling_dkim_selector ;
68 $this->schedule_private_key = $c->schedule_private_key ;
69 if ( ! preg_match ( '/BEGIN RSA PRIVATE KEY/', $this->schedule_private_key ) )
70 {
71 $key = file_get_contents ( $this->schedule_private_key );
72 if ( $key !== false )
73 $this->schedule_private_key = $key;
74 }
75 if ( isset ( $c->scheduling_dkim_algo ) )
76 $this->scheduling_dkim_algo = $c->scheduling_dkim_algo;
77 else
78 $this->scheduling_dkim_algo = 'sha256';
79 if ( isset ( $c->scheduling_dkim_valid_time ) )
80 $this->valid_time = $c->scheduling_dkim_valid_time;
81 }
82 }
83
87 function getTxt ()
88 {
89 global $icfg;
90 // TODO handle parents of subdomains and procuration records
91 $dns_str = $this->remote_selector . '._domainkey.' . $this->remote_server;
92
93 if ( isset($icfg) && isset($icfg[$dns_str]) ) {
94 $this->dk = $icfg[$dns_str];
95 return true;
96 }
97
98 $dkim = dns_get_record($dns_str, DNS_TXT);
99 if ( count ( $dkim ) > 0 )
100 {
101 $this->dk = $dkim [ 0 ] [ 'txt' ];
102 if ( $dkim [ 0 ] [ 'entries' ] )
103 {
104 $this->dk = '';
105 foreach ( $dkim [ 0 ] [ 'entries' ] as $v )
106 {
107 $this->dk .= trim ( $v );
108 }
109 }
110 dbg_error_log( 'ischedule', 'getTxt '. $this->dk . ' XX');
111 }
112 else
113 {
114 dbg_error_log( 'ischedule', 'getTxt FAILED '. print_r ( $dkim ) );
115 $this->failed = true;
116 return false;
117 }
118 return true;
119 }
120
124 function setTxt ( $dk )
125 {
126 $this->dk = $dk;
127 }
128
132 function parseTxt ( )
133 {
134 if ( $this->failed == true )
135 return false;
136 $clean = preg_replace ( '/\s?([;=])\s?/', '$1', $this->dk );
137 $pairs = preg_split ( '/;/', $clean );
138 $this->parsed = array();
139 foreach ( $pairs as $v )
140 {
141 list($key,$value) = preg_split ( '/=/', $v, 2 );
142 $value = trim ( $value, '\\' );
143 if ( preg_match ( '/(g|k|n|p|s|t|v)/', $key ) )
144 $this->parsed [ $key ] = $value;
145 else
146 $this->parsed_ignored [ $key ] = $value;
147 }
148 return true;
149 }
150
154 function validateKey ( )
155 {
156 $this->failed = true;
157 if ( isset ( $this->parsed [ 's' ] ) )
158 {
159 if ( ! preg_match ( '/(\*|calendar)/', $this->parsed [ 's' ] ) ) {
160 dbg_error_log( 'ischedule', 'validateKey ERROR: bad selector' );
161 return false; // not a wildcard or calendar key
162 }
163 }
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' ] );
166 return false; // we only speak rsa for now
167 }
168
169 if (isset($this->parsed['t'])) {
170 if ( ! preg_match ( '/^[y:s]+$/', $this->parsed [ 't' ] ) ) {
171 dbg_error_log( 'ischedule', 'validateKey ERROR: type mismatch' );
172 return false;
173 }
174 if ( preg_match ( '/y/', $this->parsed [ 't' ] ) )
175 $this->failOnError = false;
176 if ( preg_match ( '/s/', $this->parsed [ 't' ] ) )
177 $this->subdomainsOK = false;
178 }
179
180 if ( isset ( $this->parsed [ 'g' ] ) )
181 $this->remote_user_rule = $this->parsed [ 'g' ];
182 else
183 $this->remote_user_rule = '*';
184 if ( isset ( $this->parsed [ 'p' ] ) )
185 {
186 if ( preg_match ( '/[^A-Za-z0-9_=+\/]/', $this->parsed [ 'p' ] ) )
187 return false;
188 $data = "-----BEGIN PUBLIC KEY-----\n" . implode ("\n",str_split ( $this->parsed [ 'p' ], 64 )) . "\n-----END PUBLIC KEY-----";
189 if ( $data === false )
190 return false;
191 $this->remote_public_key = $data;
192 }
193 else {
194 dbg_error_log( 'ischedule', 'validateKey ERROR: no key in dns record' );
195 return false;
196 }
197 $this->failed = false;
198 return true;
199 }
200
204 function getServer ( )
205 {
206 global $icfg;
207 if ( isset($icfg) && isset($icfg[$this->domain]) )
208 {
209 $this->remote_server = $icfg [ $this->domain ] [ 'server' ];
210 $this->remote_port = $icfg [ $this->domain ] [ 'port' ];
211 $this->remote_ssl = $icfg [ $this->domain ] [ 'ssl' ];
212 return true;
213 }
214 $this->remote_ssl = false;
215 $parts = explode ( '.', $this->domain );
216 $tld = $parts [ count ( $parts ) - 1 ];
217 $len = 2;
218 if ( strlen ( $tld ) == 2 && in_array ( $tld, Array ( 'uk', 'nz' ) ) )
219 $len = 3; // some country code tlds should have 3 components
220 if ( $this->domain == 'mycaldav' || $this->domain == 'altcaldav' )
221 $len = 1;
222
223 while ( count ( $parts ) >= $len )
224 {
225 $r = dns_get_record ( '_ischedules._tcp.' . implode ( '.', $parts ) , DNS_SRV );
226
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;
231 break;
232 }
233
234 if ( ! isset ( $remote_server ) ) {
235 $r = dns_get_record ( '_ischedule._tcp.' . implode ( '.', $parts ) , DNS_SRV );
236
237 if (is_array($r) && count($r) > 0) {
238 $remote_server = $r [ 0 ] [ 'target' ];
239 $remote_port = $r [ 0 ] [ 'port' ];
240 break;
241 }
242 }
243 array_shift ( $parts );
244 }
245
246 if ( ! isset ( $remote_server ) )
247 {
248 if ( $this->try_anyway == true )
249 {
250 if ( ! isset ( $remote_server ) )
251 $remote_server = $this->domain;
252 if ( ! isset ( $remote_port ) )
253 $remote_port = 80;
254 }
255 else {
256 dbg_error_log('ischedule', 'Domain %s did not have srv records for iSchedule', $this->domain );
257 return false;
258 }
259 }
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;
263 return true;
264 }
265
269 function getCapabilities ( $domain = null )
270 {
271 if ( $domain != null && $this->domain != $domain )
272 $this->domain = $domain;
273 if ( ! isset ( $this->remote_server ) && isset ( $this->domain ) && ! $this->getServer ( ) )
274 return false;
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 )
279 return 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' );
290 return false;
291 }
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!") );
297 return false;
298 }
299 dbg_error_log('ischedule', $this->domain . ' got capabilites' );
300 $this->capabilities_xml = $xmltree;
301 return true;
302 }
303
307 function queryCapabilities ( $capability, $domain = null )
308 {
309 if ( ! isset ( $this->capabilities_xml ) )
310 {
311 dbg_error_log('ischedule', $this->domain . ' capabilities not set, quering for capability:' . $capability );
312 if ( $domain == null )
313 return false;
314 if ( $this->domain != $domain )
315 $this->domain = $domain;
316 if ( ! $this->getCapabilities ( ) )
317 return false;
318 }
319 switch ( $capability )
320 {
321 case 'VEVENT':
322 case 'VFREEBUSY':
323 case 'VTODO':
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 )
326 {
327 if ( $c->GetAttribute ( 'name' ) == $capability )
328 return true;
329 }
330 return false;
331 case 'VFREEBUSY/REQUEST':
332 case 'VTODO/ADD':
333 case 'VTODO/REQUEST':
334 case 'VTODO/REPLY':
335 case 'VTODO/CANCEL':
336 case 'VEVENT/ADD':
337 case 'VEVENT/REQUEST':
338 case 'VEVENT/REPLY':
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 )
348 {
349 dbg_error_log('ischedule', $this->domain . ' quering for capability:' . $c->GetAttribute ( 'name' ) . ' == ' . $component );
350 if ( $c->GetAttribute ( 'name' ) == $component )
351 {
352 $methods = $c->GetElements ( 'urn:ietf:params:xml:ns:ischedule:method' );
353 if ( count ( $methods ) == 0 )
354 return true; // seems like we should accept everything if there are no children
355 foreach ( $methods as $m )
356 {
357 if ( $m->GetAttribute ( 'name' ) == $method )
358 return true;
359 }
360 }
361 }
362 return false;
363 default:
364 return false;
365 }
366 }
367
374 function signDKIM ( $headers, $body )
375 {
376 if ( $this->scheduling_dkim_domain == null )
377 return false;
378 $b = '';
379 if ( is_array ( $headers ) !== true )
380 return false;
381 foreach ( $headers as $key => $value )
382 {
383 $b .= $key . ': ' . $value . "\r\n";
384 }
385 $dk['v'] = '1';
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'; // implied canonicalization of simple-http/simple from rfc4871 Section-3.5
390 if ( isset ( $_SERVER['SERVER_NAME'] ) && strstr ( $_SERVER['SERVER_NAME'], $this->domain ) !== false ) // don't use when testing
391 $dk['i'] = '@' . $_SERVER['SERVER_NAME']; //optional
392 $dk['q'] = 'dns/txt'; // optional, dns/txt is the default if missing
393 $dk['l'] = strlen ( $body ); //optional
394 $dk['t'] = time ( ); // timestamp of signature, optional
395 if ( isset ( $this->valid_time ) )
396 $dk['x'] = $this->valid_time; // unix timestamp expiriation of signature, optional
397 $dk['h'] = implode ( ':', array_keys ( $headers ) );
398 $dk['bh'] = base64_encode ( hash ( 'sha256', $body , true ) );
399 $value = '';
400 foreach ( $dk as $key => $val )
401 $value .= "$key=$val; ";
402 $value .= 'b=';
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 );
407 return $value;
408 }
409
416 function sendRequest ( $address, $type, $data )
417 {
418 global $session;
419 if ( empty($this->scheduling_dkim_domain) )
420 return false;
421 if ( is_array ( $address ) )
422 list ( $user, $domain ) = explode ( '@', $address[0] );
423 else
424 list ( $user, $domain ) = explode ( '@', $address );
425 if ( ! $this->getCapabilities ( $domain ) )
426 {
427 dbg_error_log('ischedule', $domain . ' did not have iSchedule capabilities for ' . $type );
428 return false;
429 }
430 dbg_error_log('ischedule', $domain . ' trying with iSchedule capabilities for ' . $type );
431 if ( $this->queryCapabilities ( $type ) )
432 {
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 );
440 else
441 $headers['Recipient'] = $address;
442 $headers['Content-Type'] = 'text/calendar; component=' . $component ;
443 if ( $method )
444 $headers['Content-Type'] .= '; method=' . $method;
445 $headers['DKIM-Signature'] = $this->signDKIM ( $headers, $data );
446 if ( $headers['DKIM-Signature'] == false )
447 return 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() ); // start with no headers set
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 )
464 {
465 dbg_error_log ( 'ischedule', 'remote server returned error (%s)', $info['http_code'] );
466 return false;
467 }
468
469 error_log ( 'remote response '. $xmlresponse . print_r ( $info, true ) );
470 $xml_parser = xml_parser_create_ns('UTF-8');
471 $xml_tags = array();
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) );
479 return false;
480 }
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!' );
485 return false;
486 }
487 $resp = $xmltree->GetPath ( '/*/urn:ietf:params:xml:ns:ischedule:response' );
488 $result = array();
489 foreach ( $resp as $r )
490 {
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 )
495 continue; // this should be an error
496 if ( count ( $calendardata ) > 0 )
497 {
498 $result [ $recipient[0]->GetContent() ] = $calendardata[0]->GetContent();
499 }
500 else
501 {
502 $result [ $recipient[0]->GetContent() ] = $status[0]->GetContent();
503 }
504 }
505 if ( count ( $result ) < 1 )
506 return false;
507 else
508 return $result;
509 }
510 else
511 return false;
512 }
513
519 function parseDKIM ( $sig )
520 {
521
522 $this->failed = true;
523 $tags = preg_split ( '/;[\s\t]/', $sig );
524
525 foreach ( $tags as $v )
526 {
527 list($key,$value) = preg_split ( '/=/', $v, 2 );
528 $dkim[$key] = $value;
529 }
530
531 // the canonicalization method is currently undefined as of draft-01 of the iSchedule spec
532 // but it does define the value, it should be simple-http. RFC4871 also defines two methods
533 // simple and relaxed, simple is probably the same as simple http
534 // relaxed allows for header case folding and whitespace folding, see section 3.4.4 of RFC4871
535 if ( ! preg_match ( '{(simple|simple-http|relaxed)(/(simple|simple-http|relaxed))?}', $dkim['c'], $matches ) ) // canonicalization method
536 return 'bad canonicalization:' . $dkim['c'] ;
537
538 if ( count ( $matches ) > 2 )
539 $this->body_cannon = $matches[2];
540 else
541 $this->body_cannon = $matches[1];
542
543 $this->header_cannon = $matches[1];
544
545 // signing algorithm REQUIRED
546 if ( $dkim['a'] != 'rsa-sha1' && $dkim['a'] != 'rsa-sha256' ) // we only support the minimum required
547 return 'bad signing algorithm:' . $dkim['a'] ;
548
549 // query method to retrieve public key, could/should we add https to the spec? REQUIRED
550 if ( $dkim['q'] != 'dns/txt' )
551 return 'bad query method';
552
553 // domain of the signing entity REQUIRED
554 if ( ! isset ( $dkim['d'] ) )
555 return 'missing signing domain';
556
557 $this->remote_server = $dkim['d'];
558 // identity of signing AGENT, OPTIONAL
559 if ( isset ( $dkim['i'] ) )
560 {
561 // if present, domain of the signing agent must be a match or a subdomain of the signing domain
562 if ( ! stristr ( $dkim['i'], $dkim['d'] ) ) // RFC4871 does not specify a case match requirement
563 return 'signing domain mismatch';
564 // grab the local part of the signing agent if it's an email address
565 if ( strstr ( $dkim [ 'i' ], '@' ) )
566 $this->remote_user = substr ( $dkim [ 'i' ], 0, strpos ( $dkim [ 'i' ], '@' ) - 1 );
567 }
568
569 // selector used to retrieve public key REQUIRED
570 if ( ! isset ( $dkim['s'] ) )
571 return 'missing selector';
572 $this->remote_selector = $dkim['s'];
573
574 // signed header fields, colon seperated REQUIRED
575 if ( ! isset ( $dkim['h'] ) )
576 return 'missing list of signed headers';
577 $this->signed_headers = preg_split ( '/:/', $dkim['h'] );
578
579 $sh = Array ();
580 foreach ( $this->signed_headers as $h )
581 {
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";
585 }
586
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";
590
591 // body hash REQUIRED
592 if ( ! isset ( $dkim['bh'] ) )
593 return 'missing body signature';
594
595 // signed header hash REQUIRED
596 if ( ! isset ( $dkim['b'] ) )
597 return 'missing signature in b field';
598
599 // length of body used for signing
600 if ( isset ( $dkim['l'] ) )
601 $this->signed_length = $dkim['l'];
602
603 $this->failed = false;
604 $this->DKSig = $dkim;
605 return true;
606 }
607
612 function parseURI ( $uri )
613 {
614 if ( preg_match ( '/^mailto:([^@]+)@([^\s\t\n]+)/', $uri, $matches ) )
615 {
616 $this->remote_user = $matches[1];
617 $this->domain = $matches[2];
618 }
619 else
620 return false;
621 }
622
627 function verifySignature ( )
628 {
629 global $request,$c;
630 $this->failed = true;
631 $signed = '';
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";
635 else
636 $signed .= "$h: " . $_SERVER[ strtoupper ( strtr ( $h, '-', '_' ) ) ] . "\r\n";
637 if ( ! isset ( $_SERVER['HTTP_ORIGINATOR'] ) || stripos ( $signed, 'Originator' ) === false ) //required header, must be signed
638 return "missing Originator";
639 if ( ! isset ( $_SERVER['HTTP_RECIPIENT'] ) || stripos ( $signed, 'Recipient' ) === false ) //required header, must be signed
640 return "missing Recipient";
641 if ( ! isset ( $_SERVER['HTTP_ISCHEDULE_VERSION'] ) || $_SERVER['HTTP_ISCHEDULE_VERSION'] != '1' ) //required header and we only speak version 1 for now
642 return "missing or mismatch ischedule-version header";
643 $body = $request->raw_post;
644 if ( ! isset ( $this->signed_length ) ) // Should we use the Content-Length header if the signed length is missing?
645 $this->signed_length = strlen ( $body );
646 else
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 );
659 if ( $verify != 1 )
660 {
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";
665 }
666 $this->failed = false;
667 return true;
668 }
669
673 function validateRequest ( )
674 {
675 global $request;
676 if ( isset ( $_SERVER['HTTP_DKIM_SIGNATURE'] ) )
677 $sig = $_SERVER['HTTP_DKIM_SIGNATURE'];
678 else
679 {
680 $request->DoResponse( 403, translate('DKIM signature missing') );
681 return false;
682 }
683 if ( isset ( $_SERVER['HTTP_ORGANIZER'] ) )
684 $request->DoResponse( 403, translate('Organizer Missing') );
685
686 dbg_error_log ('ischedule','beginning validation');
687 $err = $this->parseDKIM ( $sig );
688 if ( $err !== true || $this->failed )
689 $request->DoResponse( 412, 'DKIM signature invalid ' . "\n" . $err . "\n" );
690 if ( ! $this->getTxt () || $this->failed ) // this could also be a 424 failed dependency response
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)') );
694 if ( ! $this->validateKey () || $this->failed )
695 $request->DoResponse( 400, translate('DKIM signature validation failed(KEY Validation ERROR)') );
696 $err = $this->verifySignature ();
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');
700 return true;
701 }
702}
703
parseURI( $uri)
getCapabilities( $domain=null)
validateRequest()
setTxt( $dk)
sendRequest( $address, $type, $data)
queryCapabilities( $capability, $domain=null)
parseDKIM( $sig)
signDKIM( $headers, $body)
verifySignature()