verify required headers are signed
[davical.git] / inc / iSchedule.php
blob365f7845bc77473aa639dd7bc3af84688a2f50ad
1 <?php
2 /**
3 * Functions that are needed for iScheduling requests
5 * - verifying Domain Key signatures
6 * - delivering remote scheduling requests to local users inboxes
7 * - Utility functions which we can use to decide whether this
8 * is a permitted activity for this user.
10 * @package davical
11 * @subpackage iSchedule
12 * @author Rob Ostensen <rob@boxacle.net>
13 * @copyright Rob Ostensen
14 * @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
17 require_once("XMLDocument.php");
19 /**
20 * A class for handling iScheduling requests.
22 * @package davical
23 * @subpackage iSchedule
25 class iSchedule
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 $required_headers = Array ( 'host', // draft 01 section 7.1 required headers
38 'originator',
39 'recipient',
40 'content-type' );
41 private $disallowed_headers = Array ( 'connection', // draft 01 section 7.1 disallowed headers
42 'keep-alive',
43 'dkim-signature',
44 'proxy-authenticate',
45 'proxy-authorization',
46 'te',
47 'trailers',
48 'transfer-encoding',
49 'upgrade' );
51 function __construct ( )
53 global $c;
54 $this->selector = 'cal';
55 if ( is_object ( $c ) && isset ( $c->scheduling_dkim_selector ) )
57 $this->scheduling_dkim_domain = $c->scheduling_dkim_domain ;
58 $this->scheduling_dkim_selector = $c->scheduling_dkim_selector ;
59 $this->schedule_private_key = $c->schedule_private_key ;
60 if ( ! preg_match ( '/BEGIN RSA PRIVATE KEY/', $this->schedule_private_key ) )
62 $key = file_get_contents ( $this->schedule_private_key );
63 if ( $key !== false )
64 $this->schedule_private_key = $key;
66 if ( isset ( $c->scheduling_dkim_algo ) )
67 $this->scheduling_dkim_algo = $c->scheduling_dkim_algo;
68 else
69 $this->scheduling_dkim_algo = 'sha256';
70 if ( isset ( $c->scheduling_dkim_valid_time ) )
71 $this->valid_time = $c->scheduling_dkim_valid_time;
75 /**
76 * gets the domainkey TXT record from DNS
77 */
78 function getTxt ()
80 // TODO handle parents of subdomains and procuration records
82 $dkim = dns_get_record ( $this->remote_selector . '._domainkey.' . $this->remote_server , DNS_TXT );
83 if ( count ( $dkim ) > 0 )
85 $this->dk = $dkim [ 0 ] [ 'txt' ];
86 if ( $dkim [ 0 ] [ 'entries' ] )
88 $this->dk = '';
89 foreach ( $dkim [ 0 ] [ 'entries' ] as $v )
91 $this->dk .= trim ( $v );
94 dbg_error_log( 'ischedule', 'getTxt '. $this->dk . ' XX');
96 else
98 dbg_error_log( 'ischedule', 'getTxt FAILED '. print_r ( $dkim ) );
99 $this->failed = true;
100 return false;
102 return true;
106 * strictly for testing purposes
108 function setTxt ( $dk )
110 $this->dk = $dk;
114 * parses DNS TXT record from domainkey lookup
116 function parseTxt ( )
118 if ( $this->failed == true )
119 return false;
120 $clean = preg_replace ( '/\s?([;=])\s?/', '$1', $this->dk );
121 $pairs = preg_split ( '/;/', $clean );
122 $this->parsed = array();
123 foreach ( $pairs as $v )
125 list($key,$value) = preg_split ( '/=/', $v, 2 );
126 $value = trim ( $value, '\\' );
127 if ( preg_match ( '/(g|k|n|p|s|t|v)/', $key ) )
128 $this->parsed [ $key ] = $value;
129 else
130 $this->parsed_ignored [ $key ] = $value;
132 return true;
136 * validates that domainkey is acceptable for the current request
138 function validateKey ( )
140 $this->failed = true;
141 if ( isset ( $this->parsed [ 's' ] ) )
143 if ( ! preg_match ( '/(\*|calendar)/', $this->parsed [ 's' ] ) ) {
144 dbg_error_log( 'ischedule', 'validateKey ERROR: bad selector' );
145 return false; // not a wildcard or calendar key
148 if ( isset ( $this->parsed [ 'k' ] ) && $this->parsed [ 'k' ] != 'rsa' ) {
149 dbg_error_log( 'ischedule', 'validateKey ERROR: bad key algorythm, algo was:' . $this->parsed [ 'k' ] );
150 return false; // we only speak rsa for now
152 if ( isset ( $this->parsed [ 't' ] ) && ! preg_match ( '/^[y:s]+$/', $this->parsed [ 't' ] ) ) {
153 dbg_error_log( 'ischedule', 'validateKey ERROR: type mismatch' );
154 return false;
156 else
158 if ( preg_match ( '/y/', $this->parsed [ 't' ] ) )
159 $this->failOnError = false;
160 if ( preg_match ( '/s/', $this->parsed [ 't' ] ) )
161 $this->subdomainsOK = false;
163 if ( isset ( $this->parsed [ 'g' ] ) )
164 $this->remote_user_rule = $this->parsed [ 'g' ];
165 else
166 $this->remote_user_rule = '*';
167 if ( isset ( $this->parsed [ 'p' ] ) )
169 if ( preg_match ( '/[^A-Za-z0-9_=+\/]/', $this->parsed [ 'p' ] ) )
170 return false;
171 $data = "-----BEGIN PUBLIC KEY-----\n" . implode ("\n",str_split ( $this->parsed [ 'p' ], 64 )) . "\n-----END PUBLIC KEY-----";
172 if ( $data === false )
173 return false;
174 $this->remote_public_key = $data;
176 else {
177 dbg_error_log( 'ischedule', 'validateKey ERROR: no key in dns record' . $this->parsed [ 'p' ] );
178 return false;
180 $this->failed = false;
181 return true;
185 * finds a remote calendar server via DNS SRV records
187 function getServer ( )
189 $this->remote_ssl = false;
190 $parts = explode ( '.', $this->domain );
191 $tld = $parts [ count ( $parts ) - 1 ];
192 $len = 2;
193 if ( strlen ( $tld ) == 2 && in_array ( $tld, Array ( 'uk', 'nz' ) ) )
194 $len = 3; // some country code tlds should have 3 components
195 if ( $this->domain == 'mycaldav' || $this->domain == 'altcaldav' )
196 $len = 1;
197 while ( count ( $parts ) >= $len )
199 $r = dns_get_record ( '_ischedules._tcp.' . implode ( '.', $parts ) , DNS_SRV );
200 if ( 0 < count ( $r ) )
202 $remote_server = $r [ 0 ] [ 'target' ];
203 $remote_port = $r [ 0 ] [ 'port' ];
204 $this->remote_ssl = true;
205 break;
207 if ( ! isset ( $remote_server ) )
209 $r = dns_get_record ( '_ischedule._tcp.' . implode ( '.', $parts ) , DNS_SRV );
210 if ( 0 < count ( $r ) )
212 $remote_server = $r [ 0 ] [ 'target' ];
213 $remote_port = $r [ 0 ] [ 'port' ];
214 break;
217 array_shift ( $parts );
219 if ( ! isset ( $remote_server ) )
221 if ( $this->try_anyway == true )
223 if ( ! isset ( $remote_server ) )
224 $remote_server = $this->domain;
225 if ( ! isset ( $remote_port ) )
226 $remote_port = 80;
228 else {
229 dbg_error_log('ischedule', $domain . ' did not have srv records for iSchedule' );
230 return false;
233 dbg_error_log('ischedule', $this->domain . ' found srv records for ' . $remote_server . ':' . $remote_port );
234 $this->remote_server = $remote_server;
235 $this->remote_port = $remote_port;
236 return true;
240 * get capabilities from remote server
242 function getCapabilities ( $domain = null )
244 if ( $domain != null && $this->domain != $domain )
245 $this->domain = $domain;
246 if ( ! isset ( $this->remote_server ) && isset ( $this->domain ) && ! $this->getServer ( ) )
247 return false;
248 $this->remote_url = 'http'. ( $this->remote_ssl ? 's' : '' ) . '://' .
249 $this->remote_server . ':' . $this->remote_port . '/.well-known/ischedule';
250 $remote_capabilities = file_get_contents ( $this->remote_url . '?query=capabilities' );
251 if ( $remote_capabilities === false )
252 return false;
253 $xml_parser = xml_parser_create_ns('UTF-8');
254 $this->xml_tags = array();
255 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
256 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
257 $rc = xml_parse_into_struct( $xml_parser, $remote_capabilities, $this->xml_tags );
258 if ( $rc == false ) {
259 dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d',
260 xml_error_string(xml_get_error_code($xml_parser)),
261 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
262 dbg_error_log('ischedule', $this->domain . ' iSchedule error parsing remote xml' );
263 return false;
265 xml_parser_free($xml_parser);
266 $xmltree = BuildXMLTree( $this->xml_tags );
267 if ( !is_object($xmltree) ) {
268 dbg_error_log('ischedule', $this->domain . ' iSchedule error in remote xml' );
269 $request->DoResponse( 406, translate("REPORT body is not valid XML data!") );
270 return false;
272 dbg_error_log('ischedule', $this->domain . ' got capabilites' );
273 $this->capabilities_xml = $xmltree;
274 return true;
278 * query capabilities retrieved from server
280 function queryCapabilities ( $capability, $domain = null )
282 if ( ! isset ( $this->capabilities_xml ) )
284 dbg_error_log('ischedule', $this->domain . ' capabilities not set, quering for capability:' . $capability );
285 if ( $domain == null )
286 return false;
287 if ( $this->domain != $domain )
288 $this->domain = $domain;
289 if ( ! $this->getCapabilities ( ) )
290 return false;
292 switch ( $capability )
294 case 'VEVENT':
295 case 'VFREEBUSY':
296 case 'VTODO':
297 $comp = $this->capabilities_xml->GetPath ( 'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
298 foreach ( $comp as $c )
300 if ( $c->GetAttribute ( 'name' ) == $capability )
301 return true;
303 return false;
304 case 'VFREEBUSY/REQUEST':
305 case 'VTODO/ADD':
306 case 'VTODO/REQUEST':
307 case 'VTODO/REPLY':
308 case 'VTODO/CANCEL':
309 case 'VEVENT/ADD':
310 case 'VEVENT/REQUEST':
311 case 'VEVENT/REPLY':
312 case 'VEVENT/CANCEL':
313 case 'VEVENT/PUBLISH':
314 case 'VEVENT/COUNTER':
315 case 'VEVENT/DECLINECOUNTER':
316 dbg_error_log('ischedule', $this->domain . ' xml query' );
317 $comp = $this->capabilities_xml->GetPath ( 'urn:ietf:params:xml:ns:ischedule:supported-scheduling-message-set/urn:ietf:params:xml:ns:ischedule:comp' );
318 list ( $component, $method ) = explode ( '/', $capability );
319 dbg_error_log('ischedule', $this->domain . ' quering for capability:' . count ( $comp ) . ' ' . $component );
320 foreach ( $comp as $c )
322 dbg_error_log('ischedule', $this->domain . ' quering for capability:' . $c->GetAttribute ( 'name' ) . ' == ' . $component );
323 if ( $c->GetAttribute ( 'name' ) == $component )
325 $methods = $c->GetElements ( 'urn:ietf:params:xml:ns:ischedule:method' );
326 if ( count ( $methods ) == 0 )
327 return true; // seems like we should accept everything if there are no children
328 foreach ( $methods as $m )
330 if ( $m->GetAttribute ( 'name' ) == $method )
331 return true;
335 return false;
336 default:
337 return false;
342 * signs a POST body and headers
344 * @param string $body the body of the POST
345 * @param array $headers the headers to sign as passed to header ();
347 function signDKIM ( $headers, $body )
349 if ( $this->scheduling_dkim_domain == null )
350 return false;
351 $b = '';
352 if ( is_array ( $headers ) !== true )
353 return false;
354 foreach ( $headers as $key => $value )
356 $b .= $key . ': ' . $value . "\r\n";
358 $dk['v'] = '1';
359 $dk['a'] = 'rsa-' . $this->scheduling_dkim_algo;
360 $dk['s'] = $this->selector;
361 $dk['d'] = $this->scheduling_dkim_domain;
362 $dk['c'] = 'simple-http'; // implied canonicalization of simple-http/simple from rfc4871 Section-3.5
363 if ( isset ( $_SERVER['SERVER_NAME'] ) && strstr ( $_SERVER['SERVER_NAME'], $this->domain ) !== false ) // don't use when testing
364 $dk['i'] = '@' . $_SERVER['SERVER_NAME']; //optional
365 $dk['q'] = 'dns/txt'; // optional, dns/txt is the default if missing
366 $dk['l'] = strlen ( $body ); //optional
367 $dk['t'] = time ( ); // timestamp of signature, optional
368 if ( isset ( $this->valid_time ) )
369 $dk['x'] = $this->valid_time; // unix timestamp expiriation of signature, optional
370 $dk['h'] = implode ( ':', array_keys ( $headers ) );
371 $dk['bh'] = base64_encode ( hash ( 'sha256', $body , true ) );
372 $value = '';
373 foreach ( $dk as $key => $val )
374 $value .= "$key=$val; ";
375 $value .= 'b=';
376 $tosign = $b . 'DKIM-Signature: ' . $value;
377 openssl_sign ( $tosign, $sig, $this->schedule_private_key, $this->scheduling_dkim_algo );
378 $this->tosign = $tosign;
379 $value .= base64_encode ( $sig );
380 return $value;
384 * send request to remote server
385 * $address should be an email address or an array of email addresses all with the same domain
386 * $type should be in the format COMPONENT/METHOD eg (VFREEBUSY, VEVENT/REQUEST, VEVENT/REPLY, etc. )
387 * $data is the vcalendar data N.B. must already be rendered into text format
389 function sendRequest ( $address, $type, $data )
391 global $session;
392 if ( empty($this->scheduling_dkim_domain) )
393 return false;
394 if ( is_array ( $address ) )
395 list ( $user, $domain ) = explode ( '@', $address[0] );
396 else
397 list ( $user, $domain ) = explode ( '@', $address );
398 if ( ! $this->getCapabilities ( $domain ) )
400 dbg_error_log('ischedule', $domain . ' did not have iSchedule capabilities for ' . $type );
401 return false;
403 dbg_error_log('ischedule', $domain . ' trying with iSchedule capabilities for ' . $type );
404 if ( $this->queryCapabilities ( $type ) )
406 dbg_error_log('ischedule', $domain . ' trying with iSchedule capabilities for ' . $type . ' OK');
407 list ( $component, $method ) = explode ( '/', $type );
408 $headers = array ( );
409 $headers['iSchedule-Version'] = '1.0';
410 $headers['Originator'] = 'mailto:' . $session->email;
411 if ( is_array ( $address ) )
412 $headers['Recipient'] = implode ( ', ' , $address );
413 else
414 $headers['Recipient'] = $address;
415 $headers['Content-Type'] = 'text/calendar; component=' . $component ;
416 if ( $method )
417 $headers['Content-Type'] .= '; method=' . $method;
418 $headers['DKIM-Signature'] = $this->signDKIM ( $headers, $body );
419 if ( $headers['DKIM-Signature'] == false )
420 return false;
421 $request_headers = array ( );
422 foreach ( $headers as $k => $v )
423 $request_headers[] = $k . ': ' . $v;
424 $curl = curl_init ( $this->remote_url );
425 curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, true );
426 curl_setopt ( $curl, CURLOPT_HTTPHEADER, array() ); // start with no headers set
427 curl_setopt ( $curl, CURLOPT_HTTPHEADER, $request_headers );
428 curl_setopt ( $curl, CURLOPT_SSL_VERIFYPEER, false);
429 curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST, false);
430 curl_setopt ( $curl, CURLOPT_POST, 1);
431 curl_setopt ( $curl, CURLOPT_POSTFIELDS, $data);
432 curl_setopt ( $curl, CURLOPT_CUSTOMREQUEST, 'POST' );
433 $xmlresponse = curl_exec ( $curl );
434 $info = curl_getinfo ( $curl );
435 curl_close ( $curl );
436 if ( $info['http_code'] >= 400 )
438 dbg_error_log ( 'ischedule', 'remote server returned error (%s)', $info['http_code'] );
439 return false;
442 error_log ( 'remote response '. $xmlresponse . print_r ( $info, true ) );
443 $xml_parser = xml_parser_create_ns('UTF-8');
444 $xml_tags = array();
445 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
446 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
447 $rc = xml_parse_into_struct( $xml_parser, $xmlresponse, $xml_tags );
448 if ( $rc == false ) {
449 dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d',
450 xml_error_string(xml_get_error_code($xml_parser)),
451 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
452 return false;
454 $xmltree = BuildXMLTree( $xml_tags );
455 xml_parser_free($xml_parser);
456 if ( !is_object($xmltree) ) {
457 dbg_error_log( 'ERROR', 'iSchedule RESPONSE body is not valid XML data!' );
458 return false;
460 $resp = $xmltree->GetPath ( '/*/urn:ietf:params:xml:ns:ischedule:response' );
461 $result = array();
462 foreach ( $resp as $r )
464 $recipient = $r->GetElements ( 'urn:ietf:params:xml:ns:ischedule:recipient' );
465 $status = $r->GetElements ( 'urn:ietf:params:xml:ns:ischedule:request-status' );
466 $calendardata = $r->GetElements ( 'urn:ietf:params:xml:ns:ischedule:calendar-data' );
467 if ( count ( $recipient ) < 1 )
468 continue; // this should be an error
469 if ( count ( $calendardata ) > 0 )
471 $result [ $recipient[0]->GetContent() ] = $calendardata[0]->GetContent();
473 else
475 $result [ $recipient[0]->GetContent() ] = $status[0]->GetContent();
478 if ( count ( $result ) < 1 )
479 return false;
480 else
481 return $result;
483 else
484 return false;
488 * parses and validates DK header
490 * @param string $sig the value of the DKIM-Signature header
492 function parseDKIM ( $sig )
495 $this->failed = true;
496 $tags = preg_split ( '/;[\s\t]/', $sig );
497 foreach ( $tags as $v )
499 list($key,$value) = preg_split ( '/=/', $v, 2 );
500 $dkim[$key] = $value;
502 // the canonicalization method is currently undefined as of draft-01 of the iSchedule spec
503 // but it does define the value, it should be simple-http. RFC4871 also defines two methods
504 // simple and relaxed, simple is probably the same as simple http
505 // relaxed allows for header case folding and whitespace folding, see section 3.4.4 of RFC4871
506 if ( ! preg_match ( '{(simple|simple-http|relaxed)(/(simple|simple-http|relaxed))?}', $dkim['c'], $matches ) ) // canonicalization method
507 return 'bad canonicalization:' . $dkim['c'] ;
508 if ( count ( $matches ) > 2 )
509 $this->body_cannon = $matches[2];
510 else
511 $this->body_cannon = $matches[1];
512 $this->header_cannon = $matches[1];
513 // signing algorythm REQUIRED
514 if ( $dkim['a'] != 'rsa-sha1' && $dkim['a'] != 'rsa-sha256' ) // we only support the minimum required
515 return 'bad signing algorythm:' . $dkim['a'] ;
516 // query method to retrieve public key, could/should we add https to the spec? REQUIRED
517 if ( $dkim['q'] != 'dns/txt' )
518 return 'bad query method';
519 // domain of the signing entity REQUIRED
520 if ( ! isset ( $dkim['d'] ) )
521 return 'missing signing domain';
522 $this->remote_server = $dkim['d'];
523 // identity of signing AGENT, OPTIONAL
524 if ( isset ( $dkim['i'] ) )
526 // if present, domain of the signing agent must be a match or a subdomain of the signing domain
527 if ( ! stristr ( $dkim['i'], $dkim['d'] ) ) // RFC4871 does not specify a case match requirement
528 return 'signing domain mismatch';
529 // grab the local part of the signing agent if it's an email address
530 if ( strstr ( $dkim [ 'i' ], '@' ) )
531 $this->remote_user = substr ( $dkim [ 'i' ], 0, strpos ( $dkim [ 'i' ], '@' ) - 1 );
533 // selector used to retrieve public key REQUIRED
534 if ( ! isset ( $dkim['s'] ) )
535 return 'missing selector';
536 $this->remote_selector = $dkim['s'];
537 // signed header fields, colon seperated REQUIRED
538 if ( ! isset ( $dkim['h'] ) )
539 return 'missing list of signed headers';
540 $this->signed_headers = preg_split ( '/:/', $dkim['h'] );
542 $sh = Array ();
543 foreach ( $this->signed_headers as $h )
545 $sh[] = strtolower ( $h );
546 if ( in_array ( strtolower ( $h ), $this->disallowed_headers ) )
547 return "$h is NOT allowed in signed header fields per RFC4871 or iSchedule";
549 foreach ( $this->required_headers as $h )
550 if ( ! in_array ( strtolower ( $h ), $sh ) )
551 return "$h is REQUIRED but missing in signed header fields per iSchedule";
552 // body hash REQUIRED
553 if ( ! isset ( $dkim['bh'] ) )
554 return 'missing body signature';
555 // signed header hash REQUIRED
556 if ( ! isset ( $dkim['b'] ) )
557 return 'missing signature in b field';
558 // length of body used for signing
559 if ( isset ( $dkim['l'] ) )
560 $this->signed_length = $dkim['l'];
561 $this->failed = false;
562 $this->DKSig = $dkim;
563 return true;
567 * split up a mailto uri into domain and user components
568 * TODO handle other uri types (eg http)
570 function parseURI ( $uri )
572 if ( preg_match ( '/^mailto:([^@]+)@([^\s\t\n]+)/', $uri, $matches ) )
574 $this->remote_user = $matches[1];
575 $this->domain = $matches[2];
577 else
578 return false;
582 * verifies parsed DKIM header is valid for current message with a signature from the public key in DNS
583 * TODO handle multiple headers of the same name
585 function verifySignature ( )
587 global $request,$c;
588 $this->failed = true;
589 $signed = '';
590 foreach ( $this->signed_headers as $h )
591 if ( isset ( $_SERVER['HTTP_' . strtoupper ( strtr ( $h, '-', '_' ) ) ] ) )
592 $signed .= "$h: " . $_SERVER['HTTP_' . strtoupper ( strtr ( $h, '-', '_' ) ) ] . "\r\n";
593 else
594 $signed .= "$h: " . $_SERVER[ strtoupper ( strtr ( $h, '-', '_' ) ) ] . "\r\n";
595 if ( ! isset ( $_SERVER['HTTP_ORIGINATOR'] ) || stripos ( $signed, 'Originator' ) === false ) //required header, must be signed
596 return "missing Originator";
597 if ( ! isset ( $_SERVER['HTTP_RECIPIENT'] ) || stripos ( $signed, 'Recipient' ) === false ) //required header, must be signed
598 return "missing Recipient";
599 if ( ! isset ( $_SERVER['HTTP_ISCHEDULE_VERSION'] ) || $_SERVER['HTTP_ISCHEDULE_VERSION'] != '1' ) //required header and we only speak version 1 for now
600 return "missing or mismatch ischedule-version header";
601 $body = $request->raw_post;
602 if ( ! isset ( $this->signed_length ) ) // Should we use the Content-Length header if the signed length is missing?
603 $this->signed_length = strlen ( $body );
604 else
605 $body = substr ( $body, 0, $this->signed_length );
606 if ( isset ( $this->remote_user_rule ) )
607 if ( $this->remote_user_rule != '*' && ! stristr ( $this->remote_user, $this->remote_user_rule ) )
608 return "remote user rule failure";
609 $hash_algo = preg_replace ( '/^.*(sha1|sha256).*/','$1', $this->DKSig['a'] );
610 $body_hash = base64_encode ( hash ( $hash_algo, $body , true ) );
611 if ( $this->DKSig['bh'] != $body_hash )
612 return "body hash mismatch";
613 $sig = $_SERVER['HTTP_DKIM_SIGNATURE'];
614 $sig = preg_replace ( '/ b=[^;\s\r\n\t]+/', ' b=', $sig );
615 $signed .= 'DKIM-Signature: ' . $sig;
616 $verify = openssl_verify ( $signed, base64_decode ( $this->DKSig['b'] ), $this->remote_public_key, $hash_algo );
617 if ( $verify != 1 )
619 openssl_sign ( $signed, $sigb, $this->schedule_private_key, $hash_algo );
620 $sigc = base64_encode ( $sigb );
621 $verify1 = openssl_verify ( $signed, $sigc, $this->remote_public_key, $hash_algo );
622 return "signature verification failed " . $this->remote_public_key . " \n\n". $sig . " \n" . $hash_algo . "\n". print_r ($verify,1) . " XX " . $verify1 . "\n";
624 $this->failed = false;
625 return true;
629 * checks that current request has a valid DKIM signature signed by a currently valid key from DNS
631 function validateRequest ( )
633 global $request;
634 if ( isset ( $_SERVER['HTTP_DKIM_SIGNATURE'] ) )
635 $sig = $_SERVER['HTTP_DKIM_SIGNATURE'];
636 else
638 $request->DoResponse( 403, translate('DKIM signature missing') );
639 return false;
641 if ( isset ( $_SERVER['HTTP_ORGANIZER'] ) )
642 $request->DoResponse( 403, translate('Organizer Missing') );
644 dbg_error_log ('ischedule','beginning validation');
645 $err = $this->parseDKIM ( $sig );
646 if ( $err !== true || $this->failed )
647 $request->DoResponse( 412, 'DKIM signature invalid ' . "\n" . $err . "\n" );
648 if ( ! $this->getTxt () || $this->failed ) // this could also be a 424 failed dependency response
649 $request->DoResponse( 400, translate('DKIM signature validation failed(DNS ERROR)') );
650 if ( ! $this->parseTxt () || $this->failed )
651 $request->DoResponse( 400, translate('DKIM signature validation failed(KEY Parse ERROR)') );
652 if ( ! $this->validateKey () || $this->failed )
653 $request->DoResponse( 400, translate('DKIM signature validation failed(KEY Validation ERROR)') );
654 $err = $this->verifySignature ();
655 if ( $err !== true || $this->failed )
656 $request->DoResponse( 412, translate('DKIM signature validation failed(Signature verification ERROR)') . '\n' . $err );
657 dbg_error_log ('ischedule','signature ok');
658 return true;